dev@129: /** dev@129: * Created by lucast on 16/03/2017. dev@129: */ dev@236: import {RealFft, KissRealFft} from 'piper/fft/RealFft'; dev@236: import {hann} from 'piper/FftUtilities'; dev@236: import {Framing} from 'piper'; dev@289: import Waves from 'waves-ui-piper'; dev@129: dev@129: class SpectrogramEntity extends Waves.utils.MatrixEntity { dev@129: dev@129: private samples: Float32Array; cannam@168: private sampleRate: number; dev@129: private framing: Framing; dev@129: private fft: RealFft; dev@129: private real: Float32Array; dev@129: private nCols: number; cannam@130: private columnHeight: number; dev@129: private window: Float32Array; dev@129: cannam@168: constructor(samples: Float32Array, options: Framing & Object, sampleRate: number) { dev@129: super(); dev@129: this.samples = samples; cannam@168: this.sampleRate = sampleRate; dev@129: this.framing = options; dev@129: this.real = new Float32Array(this.framing.blockSize); dev@236: this.nCols = Math.floor(this.samples.length / this.framing.stepSize); // !!! not correct cannam@130: this.columnHeight = Math.round(this.framing.blockSize / 2) + 1; dev@129: this.fft = new KissRealFft(this.framing.blockSize); dev@129: this.window = hann(this.framing.blockSize); dev@129: } dev@129: cannam@131: dispose(): void { cannam@131: this.fft.dispose(); cannam@131: } cannam@131: dev@129: getColumnCount(): number { dev@129: return this.nCols; dev@129: } dev@129: dev@129: getColumnHeight(): number { cannam@130: return this.columnHeight; dev@129: } dev@129: cannam@168: getStepDuration(): number { cannam@168: return this.framing.stepSize / this.sampleRate; cannam@168: } dev@236: dev@129: getColumn(n: number): Float32Array { dev@129: dev@129: const startSample = n * this.framing.stepSize; dev@129: const sz = this.framing.blockSize; dev@129: dev@129: this.real.fill(0); dev@129: dev@129: let available = sz; dev@129: if (startSample + sz >= this.samples.length) { dev@129: available = this.samples.length - startSample; dev@129: } dev@129: dev@129: for (let i = 0; i < available; ++i) { dev@129: this.real[i] = this.samples[startSample + i] * this.window[i]; dev@129: } dev@129: dev@129: const complex = this.fft.forward(this.real); dev@129: dev@129: const h = this.getColumnHeight(); dev@129: const col = new Float32Array(h); dev@129: cannam@130: const scale = 1.0 / Math.sqrt(sz); dev@129: for (let i = 0; i < h; ++i) { dev@236: const re: number = complex[i * 2] * scale; dev@236: const im: number = complex[i * 2 + 1] * scale; dev@236: col[i] = Math.sqrt(re * re + im * im); dev@129: } dev@129: dev@129: return col; dev@129: } dev@129: } dev@129: dev@129: export class WavesSpectrogramLayer extends Waves.core.Layer { dev@236: constructor(bufferIn: AudioBuffer, dev@129: options: Framing & Object) { dev@129: dev@129: const defaults = { dev@129: normalise: 'hybrid', dev@129: gain: 40.0, cannam@162: channel: -1, dev@129: stepSize: 512, dev@129: blockSize: 1024 dev@129: }; dev@129: dev@129: const mergedOptions: Framing & Object & {channel: number} = dev@129: Object.assign({}, defaults, options); dev@129: cannam@162: const getSamples = ((buffer, channel) => { cannam@162: const nch = buffer.numberOfChannels; dev@236: if (channel >= 0 || nch === 1) { dev@236: if (channel < 0) { dev@236: channel = 0; dev@236: } dev@236: return buffer.getChannelData(channel); cannam@162: } else { cannam@163: const before = performance.now(); dev@236: console.log('mixing down ' + nch + ' channels for spectrogram...'); dev@236: const mixed = Float32Array.from(buffer.getChannelData(0)); dev@236: const n = mixed.length; dev@236: for (let ch = 1; ch < nch; ++ch) { dev@236: const buf = buffer.getChannelData(ch); dev@236: for (let i = 0; i < n; ++i) { dev@236: mixed[i] += buf[i]; dev@236: } dev@236: } dev@236: const scale = 1.0 / nch; dev@236: for (let i = 0; i < n; ++i) { dev@236: mixed[i] *= scale; dev@236: } dev@236: console.log('done in ' + (performance.now() - before) + 'ms'); dev@236: return mixed; cannam@162: } cannam@162: }); dev@236: dev@236: super( dev@236: 'entity', dev@236: new SpectrogramEntity(getSamples(bufferIn, mergedOptions.channel), dev@236: mergedOptions, dev@236: bufferIn.sampleRate), dev@236: mergedOptions dev@236: ); dev@129: dev@129: this.configureShape(Waves.shapes.Matrix, {}, mergedOptions); dev@129: } dev@129: }