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 }