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 }
|