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