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