annotate src/app/spectrogram/Spectrogram.ts @ 184:7643e60dc1dd

Half height.
author Lucas Thompson <dev@lucas.im>
date Wed, 22 Mar 2017 11:22:48 +0000
parents 8c882ee9d097
children fafba3b9ba6b
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;
dev@129 12 private framing: Framing;
dev@129 13 private fft: RealFft;
dev@129 14 private real: Float32Array;
dev@129 15 private nCols: number;
cannam@130 16 private columnHeight: number;
dev@129 17 private window: Float32Array;
dev@129 18
dev@129 19 constructor(samples: Float32Array, options: Framing & Object) {
dev@129 20 super();
dev@129 21 this.samples = samples;
dev@129 22 this.framing = options;
dev@129 23 this.real = new Float32Array(this.framing.blockSize);
dev@129 24 this.nCols = Math.floor(this.samples.length / this.framing.stepSize); //!!! not correct
cannam@130 25 this.columnHeight = Math.round(this.framing.blockSize / 2) + 1;
dev@129 26 this.fft = new KissRealFft(this.framing.blockSize);
dev@129 27 this.window = hann(this.framing.blockSize);
dev@129 28 }
dev@129 29
cannam@131 30 dispose(): void {
cannam@131 31 this.fft.dispose();
cannam@131 32 }
cannam@131 33
dev@129 34 getColumnCount(): number {
dev@129 35 return this.nCols;
dev@129 36 }
dev@129 37
dev@129 38 getColumnHeight(): number {
cannam@130 39 return this.columnHeight;
dev@129 40 }
dev@129 41
dev@129 42 getColumn(n: number): Float32Array {
dev@129 43
dev@129 44 const startSample = n * this.framing.stepSize;
dev@129 45 const sz = this.framing.blockSize;
dev@129 46
dev@129 47 this.real.fill(0);
dev@129 48
dev@129 49 let available = sz;
dev@129 50 if (startSample + sz >= this.samples.length) {
dev@129 51 available = this.samples.length - startSample;
dev@129 52 }
dev@129 53
dev@129 54 for (let i = 0; i < available; ++i) {
dev@129 55 this.real[i] = this.samples[startSample + i] * this.window[i];
dev@129 56 }
dev@129 57
dev@129 58 const complex = this.fft.forward(this.real);
dev@129 59
dev@129 60 const h = this.getColumnHeight();
dev@129 61 const col = new Float32Array(h);
dev@129 62
cannam@130 63 const scale = 1.0 / Math.sqrt(sz);
dev@129 64 for (let i = 0; i < h; ++i) {
cannam@130 65 const re : number = complex[i*2] * scale;
cannam@130 66 const im : number = complex[i*2+1] * scale;
cannam@130 67 const mag = Math.sqrt(re * re + im * im);
cannam@130 68 col[i] = mag;
dev@129 69 }
dev@129 70
dev@129 71 return col;
dev@129 72 }
dev@129 73 }
dev@129 74
dev@129 75 export class WavesSpectrogramLayer extends Waves.core.Layer {
dev@129 76 constructor(buffer: AudioBuffer,
dev@129 77 options: Framing & Object) {
dev@129 78
dev@129 79 const defaults = {
dev@129 80 normalise: 'hybrid',
dev@129 81 gain: 40.0,
cannam@162 82 channel: -1,
dev@129 83 stepSize: 512,
dev@129 84 blockSize: 1024
dev@129 85 };
dev@129 86
dev@129 87 const mergedOptions: Framing & Object & {channel: number} =
dev@129 88 Object.assign({}, defaults, options);
dev@129 89
cannam@162 90 const getSamples = ((buffer, channel) => {
cannam@162 91 const nch = buffer.numberOfChannels;
cannam@162 92 if (channel >= 0 || nch == 1) {
cannam@162 93 return buffer.getChannelData(channel);
cannam@162 94 } else {
cannam@163 95 const before = performance.now();
cannam@162 96 console.log("mixing down " + nch + " channels for spectrogram...");
cannam@162 97 const mixed = Float32Array.from(buffer.getChannelData(0));
cannam@162 98 const n = mixed.length;
cannam@162 99 for (let ch = 1; ch < nch; ++ch) {
cannam@162 100 const buf = buffer.getChannelData(ch);
cannam@162 101 for (let i = 0; i < n; ++i) mixed[i] += buf[i];
cannam@162 102 }
cannam@162 103 const scale = 1.0 / nch;
cannam@162 104 for (let i = 0; i < n; ++i) mixed[i] *= scale;
cannam@163 105 console.log("done in " + (performance.now() - before) + "ms");
cannam@162 106 return mixed;
cannam@162 107 }
cannam@162 108 });
cannam@162 109
dev@129 110 super('entity',
cannam@162 111 new SpectrogramEntity(getSamples(buffer, mergedOptions.channel),
cannam@162 112 mergedOptions),
cannam@162 113 mergedOptions);
dev@129 114
dev@129 115 this.configureShape(Waves.shapes.Matrix, {}, mergedOptions);
dev@129 116 }
dev@129 117 }