comparison src/app/waveform/waveform.component.ts @ 110:9890436bcc9a

Bodge in okay-ish pan and zoom, with a lot of dupe from CenteredZoomState and state flying about everywhere.
author Lucas Thompson <dev@lucas.im>
date Fri, 10 Mar 2017 18:24:52 +0000
parents 68fe21cfda2a
children 689c1bfe8e68
comparison
equal deleted inserted replaced
109:68fe21cfda2a 110:9890436bcc9a
50 50
51 private featureExtractionSubscription: Subscription; 51 private featureExtractionSubscription: Subscription;
52 private playingStateSubscription: Subscription; 52 private playingStateSubscription: Subscription;
53 private seekedSubscription: Subscription; 53 private seekedSubscription: Subscription;
54 private isPlaying: boolean; 54 private isPlaying: boolean;
55 private offsetAtPanStart: number;
56 private initialZoom: number;
57 private initialDistance: number;
55 58
56 constructor(private audioService: AudioPlayerService, 59 constructor(private audioService: AudioPlayerService,
57 private piperService: FeatureExtractionService, 60 private piperService: FeatureExtractionService,
58 public ngZone: NgZone) { 61 public ngZone: NgZone) {
59 this.colouredLayers = new Map(); 62 this.colouredLayers = new Map();
179 Math.round(c0[1] * prop0 + c1[1] * prop1), 182 Math.round(c0[1] * prop0 + c1[1] * prop1),
180 Math.round(c0[2] * prop0 + c1[2] * prop1), 183 Math.round(c0[2] * prop0 + c1[2] * prop1),
181 255 ]; 184 255 ];
182 }); 185 });
183 } 186 }
184 187
185 iceMapper() { 188 iceMapper() {
186 let hexColours = [ 189 let hexColours = [
187 // Based on ColorBrewer ylGnBu 190 // Based on ColorBrewer ylGnBu
188 "ffffff", "ffff00", "f7fcf0", "e0f3db", "ccebc5", "a8ddb5", 191 "ffffff", "ffff00", "f7fcf0", "e0f3db", "ccebc5", "a8ddb5",
189 "7bccc4", "4eb3d3", "2b8cbe", "0868ac", "084081", "042040" 192 "7bccc4", "4eb3d3", "2b8cbe", "0868ac", "084081", "042040"
190 ]; 193 ];
191 hexColours.reverse(); 194 hexColours.reverse();
192 return this.interpolatingMapper(hexColours); 195 return this.interpolatingMapper(hexColours);
193 } 196 }
194 197
195 renderWaveform(buffer: AudioBuffer): void { 198 renderWaveform(buffer: AudioBuffer): void {
196 const height: number = this.trackDiv.nativeElement.getBoundingClientRect().height; 199 const height: number = this.trackDiv.nativeElement.getBoundingClientRect().height;
197 const mainTrack = this.timeline.getTrackById('main'); 200 const mainTrack = this.timeline.getTrackById('main');
198 if (this.timeline) { 201 if (this.timeline) {
199 // resize 202 // resize
240 height: height * 0.9, 243 height: height * 0.9,
241 stepSize: 512, 244 stepSize: 512,
242 fftSize: 1024 245 fftSize: 1024
243 }); 246 });
244 this.addLayer(spectrogramLayer, mainTrack, this.timeline.timeContext); 247 this.addLayer(spectrogramLayer, mainTrack, this.timeline.timeContext);
245 */ 248 */
246 this.cursorLayer = new wavesUI.helpers.CursorLayer({ 249 this.cursorLayer = new wavesUI.helpers.CursorLayer({
247 height: height 250 height: height
248 }); 251 });
249 this.addLayer(this.cursorLayer, mainTrack, this.timeline.timeContext); 252 this.addLayer(this.cursorLayer, mainTrack, this.timeline.timeContext);
250 this.timeline.state = new wavesUI.states.CenteredZoomState(this.timeline); 253 this.timeline.state = new wavesUI.states.CenteredZoomState(this.timeline);
251 mainTrack.render(); 254 mainTrack.render();
252 mainTrack.update(); 255 mainTrack.update();
253 256
254 257
255 if ('ontouchstart' in window) { 258 if ('ontouchstart' in window) {
259 interface Point {
260 x: number;
261 y: number;
262 }
263
264 const pixelToExponent: Function = wavesUI.utils.scales.linear()
265 .domain([0, 100]) // 100px => factor 2
266 .range([0, 1]);
267
268 const calculateDistance: (p1: Point, p2: Point) => number = (p1, p2) => {
269 return Math.pow(
270 Math.pow(p2.x - p1.x, 2) +
271 Math.pow(p2.y - p1.y, 2), 0.5);
272 };
273
256 const hammertime = new Hammer(this.trackDiv.nativeElement); 274 const hammertime = new Hammer(this.trackDiv.nativeElement);
257 const scroll = (ev) => { 275 const scroll = (ev) => {
258 const sign = ev.direction === Hammer.DIRECTION_LEFT ? -1 : 1; 276 this.timeline.timeContext.offset = this.offsetAtPanStart +
259 let delta = this.timeline.timeContext.timeToPixel.invert(sign * ev.distance); 277 this.timeline.timeContext.timeToPixel.invert(ev.deltaX);
260 const speed: number = Math.abs(ev.velocityX);
261 delta *= (speed > 0.075 ? 0.075 : speed); // this is completely made up to limit the max speed, TODO something sensible
262 this.timeline.timeContext.offset += delta;
263 this.timeline.tracks.update(); 278 this.timeline.tracks.update();
264 }; 279 };
265 280
266 const zoom = (ev) => { 281 const zoom = (ev) => {
267 const minZoom = this.timeline.state.minZoom; 282 const minZoom = this.timeline.state.minZoom;
268 const maxZoom = this.timeline.state.maxZoom; 283 const maxZoom = this.timeline.state.maxZoom;
269 const initialZoom = this.timeline.timeContext.zoom; 284 const distance = calculateDistance({
270 const targetZoom = initialZoom * ev.scale; 285 x: ev.pointers[0].clientX,
271 const lastCenterTime = this.timeline.timeContext.timeToPixel.invert(ev.center.x); 286 y: ev.pointers[0].clientY
272 this.timeline.timeContext.zoom = Math.min(Math.max(targetZoom, minZoom), maxZoom); 287 }, {
273 const newCenterTime = this.timeline.timeContext.timeToPixel.invert(ev.center.x); 288 x: ev.pointers[1].clientX,
289 y: ev.pointers[1].clientY
290 });
291
292 const lastCenterTime =
293 this.timeline.timeContext.timeToPixel.invert(ev.center.x);
294
295 const exponent = pixelToExponent(distance - this.initialDistance);
296 const targetZoom = this.initialZoom * Math.pow(2, exponent);
297
298 this.timeline.timeContext.zoom =
299 Math.min(Math.max(targetZoom, minZoom), maxZoom);
300
301 const newCenterTime =
302 this.timeline.timeContext.timeToPixel.invert(ev.center.x);
303
274 this.timeline.timeContext.offset += newCenterTime - lastCenterTime; 304 this.timeline.timeContext.offset += newCenterTime - lastCenterTime;
275 this.timeline.tracks.update(); 305 this.timeline.tracks.update();
276 }; 306 };
277 const seek = (ev) => { 307 const seek = (ev) => {
278 this.audioService.seekTo( 308 this.audioService.seekTo(
279 this.timeline.timeContext.timeToPixel.invert(ev.center.x) - this.timeline.timeContext.offset 309 this.timeline.timeContext.timeToPixel.invert(ev.center.x) - this.timeline.timeContext.offset
280 ); 310 );
281 }; 311 };
282 hammertime.get('pinch').set({ enable: true }); 312 hammertime.get('pinch').set({ enable: true });
313 hammertime.on('panstart', () => {
314 this.offsetAtPanStart = this.timeline.timeContext.offset;
315 });
283 hammertime.on('panleft', scroll); 316 hammertime.on('panleft', scroll);
284 hammertime.on('panright', scroll); 317 hammertime.on('panright', scroll);
318 hammertime.on('pinchstart', (e) => {
319 this.initialZoom = this.timeline.timeContext.zoom;
320
321 this.initialDistance = calculateDistance({
322 x: e.pointers[0].clientX,
323 y: e.pointers[0].clientY
324 }, {
325 x: e.pointers[1].clientX,
326 y: e.pointers[1].clientY
327 });
328 });
285 hammertime.on('pinch', zoom); 329 hammertime.on('pinch', zoom);
286 hammertime.on('tap', seek); 330 hammertime.on('tap', seek);
287 } 331 }
288 332
289 this.animate(); 333 this.animate();