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,
|
dev@129
|
82 channel: 0,
|
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
|
dev@129
|
90 super('entity',
|
dev@129
|
91 new SpectrogramEntity(
|
dev@129
|
92 buffer.getChannelData(mergedOptions.channel),
|
dev@129
|
93 mergedOptions
|
dev@129
|
94 ),
|
dev@129
|
95 mergedOptions
|
dev@129
|
96 );
|
dev@129
|
97
|
dev@129
|
98 this.configureShape(Waves.shapes.Matrix, {}, mergedOptions);
|
dev@129
|
99 }
|
dev@129
|
100 }
|