Mercurial > hg > ugly-duckling
diff src/app/waveform/waveform.component.ts @ 108:7740f7fd7c3c
Some crazy work to try to get sensible default normalisation and colour map
author | Chris Cannam <cannam@all-day-breakfast.com> |
---|---|
date | Fri, 10 Mar 2017 14:46:18 +0000 |
parents | fe6b0c525a7d |
children | 68fe21cfda2a |
line wrap: on
line diff
--- a/src/app/waveform/waveform.component.ts Thu Mar 09 16:42:44 2017 +0000 +++ b/src/app/waveform/waveform.component.ts Fri Mar 10 14:46:18 2017 +0000 @@ -111,6 +111,77 @@ return timeline; } + estimatePercentile(matrix, percentile) { + // our sample is not evenly distributed across the whole data set: + // it is guaranteed to include at least one sample from every + // column, and could sample some values more than once. But it + // should be good enough in most cases (todo: show this) + if (matrix.length === 0) return 0.0; + const w = matrix.length; + const h = matrix[0].length; + const n = w * h; + const m = (n > 10000 ? 10000 : n); // should base that on the %ile + let m_per = Math.floor(m / w); + if (m_per < 1) m_per = 1; + let sample = []; + for (let x = 0; x < w; ++x) { + for (let i = 0; i < m_per; ++i) { + const y = Math.floor(Math.random() * h); + sample.push(matrix[x][y]); + } + } + 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"); + const estimate = sample[ix]; + console.log("Estimate is: " + estimate + " (where min sampled value = " + + sample[0] + " and max = " + sample[sample.length-1] + ")"); + return estimate; + } + + interpolatingMapper(hexColours) { + const colours = hexColours.map(n => { + const i = parseInt(n, 16); + return [ (i >> 16) & 255, (i >> 8) & 255, i & 255, 255 ]; + }); + const last = colours.length - 1; + return (value => { + // value must be in the range [0,1]. We quantize to 256 levels, + // as the PNG encoder deep inside uses a limited palette for + // simplicity. Should document this for the mapper. Also that + // individual colour values should be integers + value = Math.round(value * 255) / 255; + const m = value * last; + if (m >= last) { + return colours[last]; + } + if (m <= 0) { + return colours[0]; + } + const base = Math.floor(m); + const prop0 = base + 1.0 - m; + const prop1 = m - base; + const c0 = colours[base]; + const c1 = colours[base+1]; + return [ Math.round(c0[0] * prop0 + c1[0] * prop1), + Math.round(c0[1] * prop0 + c1[1] * prop1), + Math.round(c0[2] * prop0 + c1[2] * prop1), + 255 ]; + }); + } + + iceMapper() { + let hexColours = [ + // Based on ColorBrewer ylGnBu + "ffffff", "ffff00", "f7fcf0", "e0f3db", "ccebc5", "a8ddb5", + "7bccc4", "4eb3d3", "2b8cbe", "0868ac", "084081", "042040" + ]; + hexColours.reverse(); + return this.interpolatingMapper(hexColours); + } + renderWaveform(buffer: AudioBuffer): void { const height: number = this.trackDiv.nativeElement.getBoundingClientRect().height; const mainTrack = this.timeline.getTrackById('main'); @@ -343,20 +414,25 @@ break; } case 'matrix': { - const stepDuration = (features as FixedSpacedFeatures).stepDuration; - const matrixData = (features.data as Float32Array[]); - if (matrixData.length === 0) return; - const matrixEntity = new wavesUI.utils.PrefilledMatrixEntity(matrixData); - let matrixLayer = new wavesUI.helpers.MatrixLayer(matrixEntity, { - color: colour, - height: height - }); - this.colouredLayers.set(this.addLayer( - matrixLayer, - mainTrack, - this.timeline.timeContext - ), colour); - break; + const stepDuration = (features as FixedSpacedFeatures).stepDuration; + const matrixData = (features.data as Float32Array[]); + if (matrixData.length === 0) return; + const targetValue = this.estimatePercentile(matrixData, 97); + const gain = (targetValue > 0.0 ? (1.0 / targetValue) : 1.0); + console.log("setting gain to " + gain); + const matrixEntity = new wavesUI.utils.PrefilledMatrixEntity(matrixData); + let matrixLayer = new wavesUI.helpers.MatrixLayer(matrixEntity, { + gain, + height, + normalise: 'hybrid', + mapper: this.iceMapper() + }); + this.colouredLayers.set(this.addLayer( + matrixLayer, + mainTrack, + this.timeline.timeContext + ), colour); + break; } default: console.log("Cannot render an appropriate layer for feature shape '" +