annotate src/app/spectrogram/Spectrogram.ts @ 168:19ca3aaf7807

Give spectrogram a step duration
author Chris Cannam <cannam@all-day-breakfast.com>
date Mon, 27 Mar 2017 18:17:27 +0100
parents fafba3b9ba6b
children 53ea6406d601
rev   line source
dev@129 1 /**
dev@129 2 * Created by lucast on 16/03/2017.
dev@129 3 */
dev@129 4 import {RealFft, KissRealFft} from "piper/fft/RealFft";
dev@129 5 import {hann} from "piper/FftUtilities";
dev@129 6 import {Framing} from "piper";
dev@129 7 import Waves from 'waves-ui';
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@129 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@129 28 this.fft = new KissRealFft(this.framing.blockSize);
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 }
cannam@168 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) {
cannam@130 71 const re : number = complex[i*2] * scale;
cannam@130 72 const im : number = complex[i*2+1] * scale;
cannam@130 73 const mag = Math.sqrt(re * re + im * im);
cannam@130 74 col[i] = mag;
dev@129 75 }
dev@129 76
dev@129 77 return col;
dev@129 78 }
dev@129 79 }
dev@129 80
dev@129 81 export class WavesSpectrogramLayer extends Waves.core.Layer {
dev@129 82 constructor(buffer: AudioBuffer,
dev@129 83 options: Framing & Object) {
dev@129 84
dev@129 85 const defaults = {
dev@129 86 normalise: 'hybrid',
dev@129 87 gain: 40.0,
cannam@162 88 channel: -1,
dev@129 89 stepSize: 512,
dev@129 90 blockSize: 1024
dev@129 91 };
dev@129 92
dev@129 93 const mergedOptions: Framing & Object & {channel: number} =
dev@129 94 Object.assign({}, defaults, options);
dev@129 95
cannam@162 96 const getSamples = ((buffer, channel) => {
cannam@162 97 const nch = buffer.numberOfChannels;
cannam@162 98 if (channel >= 0 || nch == 1) {
cannam@164 99 if (channel < 0) channel = 0;
cannam@162 100 return buffer.getChannelData(channel);
cannam@162 101 } else {
cannam@163 102 const before = performance.now();
cannam@162 103 console.log("mixing down " + nch + " channels for spectrogram...");
cannam@162 104 const mixed = Float32Array.from(buffer.getChannelData(0));
cannam@162 105 const n = mixed.length;
cannam@162 106 for (let ch = 1; ch < nch; ++ch) {
cannam@162 107 const buf = buffer.getChannelData(ch);
cannam@162 108 for (let i = 0; i < n; ++i) mixed[i] += buf[i];
cannam@162 109 }
cannam@162 110 const scale = 1.0 / nch;
cannam@162 111 for (let i = 0; i < n; ++i) mixed[i] *= scale;
cannam@163 112 console.log("done in " + (performance.now() - before) + "ms");
cannam@162 113 return mixed;
cannam@162 114 }
cannam@162 115 });
cannam@162 116
dev@129 117 super('entity',
cannam@162 118 new SpectrogramEntity(getSamples(buffer, mergedOptions.channel),
cannam@168 119 mergedOptions,
cannam@168 120 buffer.sampleRate),
cannam@162 121 mergedOptions);
dev@129 122
dev@129 123 this.configureShape(Waves.shapes.Matrix, {}, mergedOptions);
dev@129 124 }
dev@129 125 }