Mercurial > hg > ugly-duckling
diff src/app/waveform/waveform.component.ts @ 236:53ea6406d601
Generate new project with latest @angular/cli, including Angular 4.
author | Lucas Thompson <dev@lucas.im> |
---|---|
date | Tue, 25 Apr 2017 20:01:09 +0100 |
parents | 0833ddde6a83 |
children | ff71da632fbb |
line wrap: on
line diff
--- a/src/app/waveform/waveform.component.ts Mon Apr 24 17:05:12 2017 +0100 +++ b/src/app/waveform/waveform.component.ts Tue Apr 25 20:01:09 2017 +0100 @@ -1,31 +1,39 @@ import { - Component, OnInit, ViewChild, ElementRef, Input, AfterViewInit, NgZone, - OnDestroy, ChangeDetectorRef + Component, + OnInit, + ViewChild, + ElementRef, + Input, + AfterViewInit, + NgZone, + OnDestroy, + ChangeDetectorRef } from '@angular/core'; import { AudioPlayerService, AudioResource, AudioResourceError -} from "../services/audio-player/audio-player.service"; +} from '../services/audio-player/audio-player.service'; import wavesUI from 'waves-ui'; import { FeatureExtractionService -} from "../services/feature-extraction/feature-extraction.service"; -import {Subscription} from "rxjs"; +} from '../services/feature-extraction/feature-extraction.service'; +import {Subscription} from 'rxjs/Subscription'; import { FeatureCollection, - FixedSpacedFeatures, SimpleResponse -} from "piper/HigherLevelUtilities"; -import {toSeconds} from "piper"; -import {FeatureList, Feature} from "piper/Feature"; + FixedSpacedFeatures, + SimpleResponse +} from 'piper/HigherLevelUtilities'; +import {toSeconds} from 'piper'; +import {FeatureList, Feature} from 'piper/Feature'; import * as Hammer from 'hammerjs'; -import {WavesSpectrogramLayer} from "../spectrogram/Spectrogram"; +import {WavesSpectrogramLayer} from '../spectrogram/Spectrogram'; type Layer = any; type Track = any; type Colour = string; @Component({ - selector: 'app-waveform', + selector: 'ugly-waveform', templateUrl: './waveform.component.html', styleUrls: ['./waveform.component.css'] }) @@ -109,20 +117,22 @@ if (this.seekedSubscription) { return; } - if(this.playingStateSubscription) { + if (this.playingStateSubscription) { return; } this.seekedSubscription = this.audioService.seeked$.subscribe(() => { - if (!this.isPlaying) + if (!this.isPlaying) { this.animate(); + } }); this.playingStateSubscription = this.audioService.playingStateChange$.subscribe( isPlaying => { this.isPlaying = isPlaying; - if (this.isPlaying) + if (this.isPlaying) { this.animate(); + } }); } else { if (this.isPlaying) { @@ -169,6 +179,19 @@ private hasShot: boolean; private isLoading: boolean; + private static changeColour(layer: Layer, colour: string): void { + const butcherShapes = (shape) => { + shape.install({color: () => colour}); + shape.params.color = colour; + shape.update(layer._renderingContext, layer.data); + }; + + layer._$itemCommonShapeMap.forEach(butcherShapes); + layer._$itemShapeMap.forEach(butcherShapes); + layer.render(); + layer.update(); + } + constructor(private audioService: AudioPlayerService, private piperService: FeatureExtractionService, private ngZone: NgZone, @@ -187,7 +210,7 @@ } ngAfterViewInit(): void { - this.trackIdPrefix = this.trackIdPrefix || "default"; + this.trackIdPrefix = this.trackIdPrefix || 'default'; if (this.timeline) { this.renderTimeline(null, true, true); } else { @@ -199,7 +222,7 @@ useExistingDuration: boolean = false, isInitialRender: boolean = false): Timeline { const track: HTMLElement = this.trackDiv.nativeElement; - track.innerHTML = ""; + track.innerHTML = ''; const height: number = track.getBoundingClientRect().height; const width: number = track.getBoundingClientRect().width; const pixelsPerSecond = width / duration; @@ -236,7 +259,7 @@ y: number; } - let zoomGestureJustEnded: boolean = false; + let zoomGestureJustEnded = false; const pixelToExponent: Function = wavesUI.utils.scales.linear() .domain([0, 100]) // 100px => factor 2 @@ -271,10 +294,13 @@ let isZooming; const scroll = (ev) => { - if (ev.center.x - startX === 0) return; + if (ev.center.x - startX === 0) { + return; + } + if (zoomGestureJustEnded) { zoomGestureJustEnded = false; - console.log("Skip this event: likely a single touch dangling from pinch"); + console.log('Skip this event: likely a single touch dangling from pinch'); return; } componentTimeline.timeContext.offset = offsetAtPanStart + @@ -283,7 +309,10 @@ }; const zoom = (ev) => { - if (ev.touches.length < 2) return; + if (ev.touches.length < 2) { + return; + } + ev.preventDefault(); const minZoom = componentTimeline.state.minZoom; const maxZoom = componentTimeline.state.maxZoom; @@ -323,7 +352,10 @@ const element: HTMLElement = this.trackDiv.nativeElement; element.addEventListener('touchstart', (e) => { - if (e.touches.length < 2) return; + if (e.touches.length < 2) { + return; + } + isZooming = true; initialZoom = componentTimeline.timeContext.zoom; @@ -360,8 +392,11 @@ const n = w * h; const m = (n > 50000 ? 50000 : n); // should base that on the %ile let m_per = Math.floor(m / w); - if (m_per < 1) m_per = 1; - let sample = []; + if (m_per < 1) { + m_per = 1; + } + + const sample = []; for (let x = 0; x < w; ++x) { for (let i = 0; i < m_per; ++i) { const y = Math.floor(Math.random() * h); @@ -372,18 +407,18 @@ } } if (sample.length === 0) { - console.log("WARNING: No samples gathered, even though we hoped for " + - (m_per * w) + " of them"); + console.log('WARNING: No samples gathered, even though we hoped for ' + + (m_per * w) + ' of them'); return 0.0; } - sample.sort((a,b) => { return a - b; }); + sample.sort((a, b) => { return a - b; }); const ix = Math.floor((sample.length * percentile) / 100); - console.log("Estimating " + percentile + "-%ile of " + - n + "-sample dataset (" + w + " x " + h + ") as value " + ix + - " of sorted " + sample.length + "-sample subset"); + console.log('Estimating ' + percentile + '-%ile of ' + + n + '-sample dataset (' + w + ' x ' + h + ') as value ' + ix + + ' of sorted ' + sample.length + '-sample subset'); const estimate = sample[ix]; - console.log("Estimate is: " + estimate + " (where min sampled value = " + - sample[0] + " and max = " + sample[sample.length-1] + ")"); + console.log('Estimate is: ' + estimate + ' (where min sampled value = ' + + sample[0] + ' and max = ' + sample[sample.length - 1] + ')'); return estimate; } @@ -407,7 +442,7 @@ const prop0 = base + 1.0 - m; const prop1 = m - base; const c0 = colours[base]; - const c1 = colours[base+1]; + const c1 = colours[base + 1]; return [ c0[0] * prop0 + c1[0] * prop1, c0[1] * prop0 + c1[1] * prop1, c0[2] * prop0 + c1[2] * prop1 ]; @@ -415,10 +450,10 @@ } iceMapper() { - let hexColours = [ + const hexColours = [ // Based on ColorBrewer ylGnBu - "ffffff", "ffff00", "f7fcf0", "e0f3db", "ccebc5", "a8ddb5", - "7bccc4", "4eb3d3", "2b8cbe", "0868ac", "084081", "042040" + 'ffffff', 'ffff00', 'f7fcf0', 'e0f3db', 'ccebc5', 'a8ddb5', + '7bccc4', '4eb3d3', '2b8cbe', '0868ac', '084081', '042040' ]; hexColours.reverse(); return this.interpolatingMapper(hexColours); @@ -432,12 +467,12 @@ const t = v * (1 - (1 - f) * s); let r = 0, g = 0, b = 0; switch (i % 6) { - case 0: r = v, g = t, b = p; break; - case 1: r = q, g = v, b = p; break; - case 2: r = p, g = v, b = t; break; - case 3: r = p, g = q, b = v; break; - case 4: r = t, g = p, b = v; break; - case 5: r = v, g = p, b = q; break; + case 0: r = v; g = t; b = p; break; + case 1: r = q; g = v; b = p; break; + case 2: r = p; g = v; b = t; break; + case 3: r = p; g = q; b = v; break; + case 4: r = t; g = p; b = v; break; + case 5: r = v; g = p; b = q; break; } return [ r, g, b ]; } @@ -455,10 +490,12 @@ sunsetMapper() { return (value => { - let r = (value - 0.24) * 2.38; - let g = (value - 0.64) * 2.777; + const r = (value - 0.24) * 2.38; + const g = (value - 0.64) * 2.777; let b = (3.6 * value); - if (value > 0.277) b = 2.0 - b; + if (value > 0.277) { + b = 2.0 - b; + } return [ r, g, b ]; }); } @@ -466,11 +503,11 @@ clearTimeline(): void { // loop through layers and remove them, waves-ui provides methods for this but it seems to not work properly const timeContextChildren = this.timeline.timeContext._children; - for (let track of this.timeline.tracks) { + for (const track of this.timeline.tracks) { if (track.layers.length === 0) { continue; } const trackLayers = Array.from(track.layers); while (trackLayers.length) { - let layer: Layer = trackLayers.pop(); + const layer: Layer = trackLayers.pop(); if (this.layers.includes(layer)) { track.remove(layer); this.layers.splice(this.layers.indexOf(layer), 1); @@ -498,7 +535,7 @@ this.timeline.pixelsPerSecond = width / buffer.duration; waveTrack.height = height; } else { - this.renderTimeline(buffer.duration) + this.renderTimeline(buffer.duration); } this.timeline.timeContext.offset = 0.5 * this.timeline.timeContext.visibleDuration; @@ -514,12 +551,12 @@ const waveHeight = totalWaveHeight / nchannels; for (let ch = 0; ch < nchannels; ++ch) { - console.log("about to construct a waveform layer for channel " + ch); + console.log('about to construct a waveform layer for channel ' + ch); const waveformLayer = new wavesUI.helpers.WaveformLayer(buffer, { - top: (height - totalWaveHeight)/2 + waveHeight * ch, - height: waveHeight, - color: 'darkblue', - channel: ch + top: (height - totalWaveHeight) / 2 + waveHeight * ch, + height: waveHeight, + color: 'darkblue', + channel: ch }); this.addLayer(waveformLayer, waveTrack, this.timeline.timeContext); } @@ -561,8 +598,14 @@ this.hasShot = true; } - if (!extracted.hasOwnProperty('features') || !extracted.hasOwnProperty('outputDescriptor')) return; - if (!extracted.features.hasOwnProperty('shape') || !extracted.features.hasOwnProperty('data')) return; + if (!extracted.hasOwnProperty('features') + || !extracted.hasOwnProperty('outputDescriptor')) { + return; + } + if (!extracted.features.hasOwnProperty('shape') + || !extracted.features.hasOwnProperty('data')) { + return; + } const features: FeatureCollection = (extracted.features as FeatureCollection); const outputDescriptor = extracted.outputDescriptor; // const height = this.trackDiv.nativeElement.getBoundingClientRect().height / 2; @@ -574,7 +617,9 @@ case 'vector': { const stepDuration = (features as FixedSpacedFeatures).stepDuration; const featureData = (features.data as Float32Array); - if (featureData.length === 0) return; + if (featureData.length === 0) { + return; + } const normalisationFactor = 1.0 / featureData.reduce( (currentMax, feature) => Math.max(currentMax, feature), @@ -588,7 +633,7 @@ }; }); - let lineLayer = new wavesUI.helpers.LineLayer(plotData, { + const lineLayer = new wavesUI.helpers.LineLayer(plotData, { color: colour, height: height }); @@ -601,7 +646,9 @@ } case 'list': { const featureData = (features.data as FeatureList); - if (featureData.length === 0) return; + if (featureData.length === 0) { + return; + } // TODO look at output descriptor instead of directly inspecting features const hasDuration = outputDescriptor.configured.hasDuration; const isMarker = !hasDuration @@ -609,18 +656,16 @@ && featureData[0].featureValues == null; const isRegion = hasDuration && featureData[0].timestamp != null; - console.log("Have list features: length " + featureData.length + - ", isMarker " + isMarker + ", isRegion " + isRegion + - ", hasDuration " + hasDuration); + console.log('Have list features: length ' + featureData.length + + ', isMarker ' + isMarker + ', isRegion ' + isRegion + + ', hasDuration ' + hasDuration); // TODO refactor, this is incomprehensible if (isMarker) { - const plotData = featureData.map(feature => { - return { - time: toSeconds(feature.timestamp), - label: feature.label - } - }); - let featureLayer = new wavesUI.helpers.TickLayer(plotData, { + const plotData = featureData.map(feature => ({ + time: toSeconds(feature.timestamp), + label: feature.label + })); + const featureLayer = new wavesUI.helpers.TickLayer(plotData, { height: height, color: colour, labelPosition: 'bottom', @@ -632,7 +677,7 @@ this.timeline.timeContext ); } else if (isRegion) { - console.log("Output is of region type"); + console.log('Output is of region type'); const binCount = outputDescriptor.configured.binCount || 0; const isBarRegion = featureData[0].featureValues.length >= 1 || binCount >= 1 ; const getSegmentArgs = () => { @@ -676,23 +721,21 @@ }; // TODO avoid copying Float32Array to an array - map is problematic here return bars.concat([...feature.featureValues] - .map(val => Object.assign({}, staticProperties, {y: val}))) + .map(val => Object.assign({}, staticProperties, {y: val}))); }, []), {yDomain: [min, max + barHeight], height: height} as any ]; } else { - return [featureData.map(feature => { - return { - x: toSeconds(feature.timestamp), - width: toSeconds(feature.duration), - color: colour, - opacity: 0.8 - } - }), {height: height}]; + return [featureData.map(feature => ({ + x: toSeconds(feature.timestamp), + width: toSeconds(feature.duration), + color: colour, + opacity: 0.8 + })), {height: height}]; } }; - let segmentLayer = new wavesUI.helpers.SegmentLayer( + const segmentLayer = new wavesUI.helpers.SegmentLayer( ...getSegmentArgs() ); this.addLayer( @@ -705,19 +748,23 @@ } case 'matrix': { const stepDuration = (features as FixedSpacedFeatures).stepDuration; - //!!! + start time + // !!! + start time const matrixData = (features.data as Float32Array[]); - if (matrixData.length === 0) return; - console.log("matrix data length = " + matrixData.length); - console.log("height of first column = " + matrixData[0].length); + + if (matrixData.length === 0) { + return; + } + + console.log('matrix data length = ' + matrixData.length); + console.log('height of first column = ' + matrixData[0].length); const targetValue = this.estimatePercentile(matrixData, 95); const gain = (targetValue > 0.0 ? (1.0 / targetValue) : 1.0); - console.log("setting gain to " + gain); + console.log('setting gain to ' + gain); const matrixEntity = new wavesUI.utils.PrefilledMatrixEntity(matrixData, 0, // startTime stepDuration); - let matrixLayer = new wavesUI.helpers.MatrixLayer(matrixEntity, { + const matrixLayer = new wavesUI.helpers.MatrixLayer(matrixEntity, { gain, top: 0, height: height, @@ -732,8 +779,9 @@ break; } default: - console.log("Cannot render an appropriate layer for feature shape '" + - features.shape + "'"); + console.log( + `Cannot render an appropriate layer for feature shape '${features.shape}'` + ); } this.isLoading = false; @@ -742,7 +790,9 @@ } private animate(): void { - if (!this.isSeeking) return; + if (!this.isSeeking) { + return; + } this.ngZone.runOutsideAngular(() => { // listen for time passing... @@ -778,8 +828,9 @@ this.timeline.tracks.update(); } - if (this.isPlaying) + if (this.isPlaying) { requestAnimationFrame(updateSeekingCursor); + } }; updateSeekingCursor(); }); @@ -800,28 +851,19 @@ } } - private static changeColour(layer: Layer, colour: string): void { - const butcherShapes = (shape) => { - shape.install({color: () => colour}); - shape.params.color = colour; - shape.update(layer._renderingContext, layer.data); - }; - - layer._$itemCommonShapeMap.forEach(butcherShapes); - layer._$itemShapeMap.forEach(butcherShapes); - layer.render(); - layer.update(); - } - ngOnDestroy(): void { - if (this.featureExtractionSubscription) + if (this.featureExtractionSubscription) { this.featureExtractionSubscription.unsubscribe(); - if (this.playingStateSubscription) + } + if (this.playingStateSubscription) { this.playingStateSubscription.unsubscribe(); - if (this.seekedSubscription) + } + if (this.seekedSubscription) { this.seekedSubscription.unsubscribe(); - if (this.onAudioDataSubscription) + } + if (this.onAudioDataSubscription) { this.onAudioDataSubscription.unsubscribe(); + } } seekStart(): void { @@ -844,7 +886,7 @@ const timeContext: any = this.timeline.timeContext; if (this.isSeeking) { this.audioService.seekTo( - timeContext.timeToPixel.invert(x)- timeContext.offset + timeContext.timeToPixel.invert(x) - timeContext.offset ); } }