annotate src/app/spectrogram/Spectrogram.ts @ 509:041468f553e1 tip master

Merge pull request #57 from LucasThompson/fix/session-stack-max-call-stack Fix accidental recursion in PersistentStack
author Lucas Thompson <LucasThompson@users.noreply.github.com>
date Mon, 27 Nov 2017 11:04:30 +0000
parents c39df81c4dae
children
rev   line source
dev@129 1 /**
dev@129 2 * Created by lucast on 16/03/2017.
dev@129 3 */
dev@497 4 import {RealFft, KissRealFft, hann} from 'piper-js/fft';
dev@497 5 import {KissFft} from 'piper-js/fft/KissFftModule';
dev@497 6 import {Framing} from 'piper-js/core';
dev@289 7 import Waves from 'waves-ui-piper';
dev@129 8
dev@129 9 class SpectrogramEntity extends Waves.utils.MatrixEntity {
dev@129 10
dev@129 11 private samples: Float32Array;
cannam@168 12 private sampleRate: number;
dev@129 13 private framing: Framing;
dev@129 14 private fft: RealFft;
dev@129 15 private real: Float32Array;
dev@129 16 private nCols: number;
cannam@130 17 private columnHeight: number;
dev@129 18 private window: Float32Array;
dev@129 19
cannam@168 20 constructor(samples: Float32Array, options: Framing & Object, sampleRate: number) {
dev@129 21 super();
dev@129 22 this.samples = samples;
cannam@168 23 this.sampleRate = sampleRate;
dev@129 24 this.framing = options;
dev@129 25 this.real = new Float32Array(this.framing.blockSize);
dev@236 26 this.nCols = Math.floor(this.samples.length / this.framing.stepSize); // !!! not correct
cannam@130 27 this.columnHeight = Math.round(this.framing.blockSize / 2) + 1;
dev@497 28 this.fft = new KissRealFft(this.framing.blockSize, KissFft);
dev@129 29 this.window = hann(this.framing.blockSize);
dev@129 30 }
dev@129 31
cannam@131 32 dispose(): void {
cannam@131 33 this.fft.dispose();
cannam@131 34 }
cannam@131 35
dev@129 36 getColumnCount(): number {
dev@129 37 return this.nCols;
dev@129 38 }
dev@129 39
dev@129 40 getColumnHeight(): number {
cannam@130 41 return this.columnHeight;
dev@129 42 }
dev@129 43
cannam@168 44 getStepDuration(): number {
cannam@168 45 return this.framing.stepSize / this.sampleRate;
cannam@168 46 }
dev@236 47
dev@129 48 getColumn(n: number): Float32Array {
dev@129 49
dev@129 50 const startSample = n * this.framing.stepSize;
dev@129 51 const sz = this.framing.blockSize;
dev@129 52
dev@129 53 this.real.fill(0);
dev@129 54
dev@129 55 let available = sz;
dev@129 56 if (startSample + sz >= this.samples.length) {
dev@129 57 available = this.samples.length - startSample;
dev@129 58 }
dev@129 59
dev@129 60 for (let i = 0; i < available; ++i) {
dev@129 61 this.real[i] = this.samples[startSample + i] * this.window[i];
dev@129 62 }
dev@129 63
dev@129 64 const complex = this.fft.forward(this.real);
dev@129 65
dev@129 66 const h = this.getColumnHeight();
dev@129 67 const col = new Float32Array(h);
dev@129 68
cannam@130 69 const scale = 1.0 / Math.sqrt(sz);
dev@129 70 for (let i = 0; i < h; ++i) {
dev@236 71 const re: number = complex[i * 2] * scale;
dev@236 72 const im: number = complex[i * 2 + 1] * scale;
dev@236 73 col[i] = Math.sqrt(re * re + im * im);
dev@129 74 }
dev@129 75
dev@129 76 return col;
dev@129 77 }
dev@129 78 }
dev@129 79
dev@129 80 export class WavesSpectrogramLayer extends Waves.core.Layer {
dev@236 81 constructor(bufferIn: AudioBuffer,
dev@129 82 options: Framing & Object) {
dev@129 83
dev@129 84 const defaults = {
dev@129 85 normalise: 'hybrid',
dev@129 86 gain: 40.0,
cannam@162 87 channel: -1,
dev@129 88 stepSize: 512,
dev@129 89 blockSize: 1024
dev@129 90 };
dev@129 91
dev@129 92 const mergedOptions: Framing & Object & {channel: number} =
dev@129 93 Object.assign({}, defaults, options);
dev@129 94
cannam@162 95 const getSamples = ((buffer, channel) => {
cannam@162 96 const nch = buffer.numberOfChannels;
dev@236 97 if (channel >= 0 || nch === 1) {
dev@236 98 if (channel < 0) {
dev@236 99 channel = 0;
dev@236 100 }
dev@236 101 return buffer.getChannelData(channel);
cannam@162 102 } else {
cannam@163 103 const before = performance.now();
dev@236 104 console.log('mixing down ' + nch + ' channels for spectrogram...');
dev@236 105 const mixed = Float32Array.from(buffer.getChannelData(0));
dev@236 106 const n = mixed.length;
dev@236 107 for (let ch = 1; ch < nch; ++ch) {
dev@236 108 const buf = buffer.getChannelData(ch);
dev@236 109 for (let i = 0; i < n; ++i) {
dev@236 110 mixed[i] += buf[i];
dev@236 111 }
dev@236 112 }
dev@236 113 const scale = 1.0 / nch;
dev@236 114 for (let i = 0; i < n; ++i) {
dev@236 115 mixed[i] *= scale;
dev@236 116 }
dev@236 117 console.log('done in ' + (performance.now() - before) + 'ms');
dev@236 118 return mixed;
cannam@162 119 }
cannam@162 120 });
dev@236 121
dev@236 122 super(
dev@236 123 'entity',
dev@236 124 new SpectrogramEntity(getSamples(bufferIn, mergedOptions.channel),
dev@236 125 mergedOptions,
dev@236 126 bufferIn.sampleRate),
dev@236 127 mergedOptions
dev@236 128 );
dev@129 129
dev@129 130 this.configureShape(Waves.shapes.Matrix, {}, mergedOptions);
dev@129 131 }
dev@129 132 }