Mercurial > hg > ugly-duckling
comparison src/app/visualisations/waveform/waveform.component.ts @ 347:82d476b976e0
Move waveform.component and remove all logic not concerned with rendering an audio buffer as a waveform. Wire app in app.component.
author | Lucas Thompson <dev@lucas.im> |
---|---|
date | Thu, 25 May 2017 17:55:29 +0100 |
parents | |
children | a9ce5516c17d |
comparison
equal
deleted
inserted
replaced
346:f87a96ab1e3f | 347:82d476b976e0 |
---|---|
1 import { | |
2 Component, | |
3 ViewChild, | |
4 ElementRef, | |
5 Input, | |
6 ChangeDetectorRef | |
7 } from '@angular/core'; | |
8 import wavesUI from 'waves-ui-piper'; | |
9 import {attachTouchHandlerBodges} from '../WavesJunk'; | |
10 import {OnSeekHandler} from '../../playhead/PlayHeadHelpers'; | |
11 | |
12 type Layer = any; | |
13 type Track = any; | |
14 | |
15 @Component({ | |
16 selector: 'ugly-waveform', | |
17 templateUrl: './waveform.component.html', | |
18 styleUrls: ['./waveform.component.css'] | |
19 }) | |
20 export class WaveformComponent { | |
21 | |
22 @ViewChild('track') trackDiv: ElementRef; | |
23 @Input() set width(width: number) { | |
24 if (this.timeline) { | |
25 requestAnimationFrame(() => { | |
26 this.timeline.timeContext.visibleWidth = width; | |
27 this.timeline.tracks.update(); | |
28 }); | |
29 } | |
30 } | |
31 @Input() timeline: Timeline; | |
32 @Input() trackIdPrefix: string; | |
33 @Input() onSeek: OnSeekHandler; | |
34 | |
35 @Input() set audioBuffer(buffer: AudioBuffer) { | |
36 this._audioBuffer = buffer || undefined; | |
37 if (this.audioBuffer) { | |
38 this.renderWaveform(this.audioBuffer); | |
39 } | |
40 } | |
41 | |
42 get audioBuffer(): AudioBuffer { | |
43 return this._audioBuffer; | |
44 } | |
45 | |
46 private _audioBuffer: AudioBuffer; | |
47 private layers: Layer[]; | |
48 private zoomOnMouseDown: number; | |
49 private offsetOnMouseDown: number; | |
50 private waveTrack: Track; | |
51 | |
52 constructor(private ref: ChangeDetectorRef) { | |
53 this.layers = []; | |
54 } | |
55 | |
56 renderTimeline(duration: number = 1.0): Timeline { | |
57 const track: HTMLElement = this.trackDiv.nativeElement; | |
58 track.innerHTML = ''; | |
59 const height: number = track.getBoundingClientRect().height; | |
60 const width: number = track.getBoundingClientRect().width; | |
61 this.timeline.pixelsPerSecond = width / duration; | |
62 this.timeline.visibleWidth = width; | |
63 this.waveTrack = this.timeline.createTrack( | |
64 track, | |
65 height, | |
66 `wave-${this.trackIdPrefix || 'default'}` | |
67 ); | |
68 | |
69 if ('ontouchstart' in window) { | |
70 attachTouchHandlerBodges(this.trackDiv.nativeElement, this.timeline); | |
71 } | |
72 } | |
73 | |
74 // TODO can likely be removed, or use waves-ui methods | |
75 clearTimeline(): void { | |
76 // loop through layers and remove them, waves-ui provides methods for this but it seems to not work properly | |
77 const timeContextChildren = this.timeline.timeContext._children; | |
78 for (const track of this.timeline.tracks) { | |
79 if (track.layers.length === 0) { continue; } | |
80 const trackLayers = Array.from(track.layers); | |
81 while (trackLayers.length) { | |
82 const layer: Layer = trackLayers.pop(); | |
83 if (this.layers.includes(layer)) { | |
84 track.remove(layer); | |
85 this.layers.splice(this.layers.indexOf(layer), 1); | |
86 const index = timeContextChildren.indexOf(layer.timeContext); | |
87 if (index >= 0) { | |
88 timeContextChildren.splice(index, 1); | |
89 } | |
90 layer.destroy(); | |
91 } | |
92 } | |
93 } | |
94 } | |
95 | |
96 renderWaveform(buffer: AudioBuffer): void { | |
97 const height = this.trackDiv.nativeElement.getBoundingClientRect().height; | |
98 if (this.timeline && this.waveTrack) { | |
99 // resize | |
100 const width = this.trackDiv.nativeElement.getBoundingClientRect().width; | |
101 | |
102 this.clearTimeline(); | |
103 this.timeline.visibleWidth = width; | |
104 this.timeline.pixelsPerSecond = width / buffer.duration; | |
105 this.waveTrack.height = height; | |
106 } else { | |
107 this.renderTimeline(buffer.duration); | |
108 } | |
109 this.timeline.timeContext.offset = 0.5 * this.timeline.timeContext.visibleDuration; | |
110 | |
111 // time axis | |
112 const timeAxis = new wavesUI.helpers.TimeAxisLayer({ | |
113 height: height, | |
114 color: '#b0b0b0' | |
115 }); | |
116 this.addLayer(timeAxis, this.waveTrack, this.timeline.timeContext, true); | |
117 | |
118 const nchannels = buffer.numberOfChannels; | |
119 const totalWaveHeight = height * 0.9; | |
120 const waveHeight = totalWaveHeight / nchannels; | |
121 | |
122 for (let ch = 0; ch < nchannels; ++ch) { | |
123 const waveformLayer = new wavesUI.helpers.WaveformLayer(buffer, { | |
124 top: (height - totalWaveHeight) / 2 + waveHeight * ch, | |
125 height: waveHeight, | |
126 color: '#0868ac', | |
127 channel: ch | |
128 }); | |
129 this.addLayer(waveformLayer, this.waveTrack, this.timeline.timeContext); | |
130 } | |
131 | |
132 this.timeline.state = new wavesUI.states.CenteredZoomState(this.timeline); | |
133 this.waveTrack.render(); | |
134 this.waveTrack.update(); | |
135 this.ref.markForCheck(); | |
136 } | |
137 | |
138 // TODO can likely use methods in waves-ui directly | |
139 private addLayer(layer: Layer, | |
140 track: Track, | |
141 timeContext: any, | |
142 isAxis: boolean = false): void { | |
143 timeContext.zoom = 1.0; | |
144 if (!layer.timeContext) { | |
145 layer.setTimeContext(isAxis ? | |
146 timeContext : new wavesUI.core.LayerTimeContext(timeContext)); | |
147 } | |
148 track.add(layer); | |
149 this.layers.push(layer); | |
150 layer.render(); | |
151 layer.update(); | |
152 } | |
153 | |
154 seekStart(): void { | |
155 this.zoomOnMouseDown = this.timeline.timeContext.zoom; | |
156 this.offsetOnMouseDown = this.timeline.timeContext.offset; | |
157 } | |
158 | |
159 seekEnd(x: number): void { | |
160 const hasSameZoom: boolean = this.zoomOnMouseDown === | |
161 this.timeline.timeContext.zoom; | |
162 const hasSameOffset: boolean = this.offsetOnMouseDown === | |
163 this.timeline.timeContext.offset; | |
164 if (hasSameZoom && hasSameOffset) { | |
165 this.seek(x); | |
166 } | |
167 } | |
168 | |
169 seek(x: number): void { | |
170 if (this.timeline) { | |
171 const timeContext: any = this.timeline.timeContext; | |
172 if (this.onSeek) { | |
173 this.onSeek(timeContext.timeToPixel.invert(x) - timeContext.offset); | |
174 } | |
175 } | |
176 } | |
177 } |