wavesurfer.js 7.0.0-alpha.43 → 7.0.0-alpha.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/dist/base-plugin.d.ts +2 -8
- package/dist/base-plugin.js +7 -4
- package/dist/decoder.d.ts +1 -1
- package/dist/decoder.js +2 -4
- package/dist/plugins/envelope.d.ts +13 -5
- package/dist/plugins/envelope.js +59 -30
- package/dist/plugins/envelope.min.js +1 -1
- package/dist/plugins/minimap.d.ts +3 -2
- package/dist/plugins/minimap.js +8 -8
- package/dist/plugins/minimap.min.js +1 -1
- package/dist/plugins/multitrack.min.js +1 -1
- package/dist/plugins/record.js +5 -4
- package/dist/plugins/record.min.js +1 -1
- package/dist/plugins/regions.d.ts +2 -2
- package/dist/plugins/regions.js +9 -7
- package/dist/plugins/regions.min.js +1 -1
- package/dist/plugins/spectrogram-fft.d.mts +22 -0
- package/dist/plugins/spectrogram-fft.d.ts +9 -0
- package/dist/plugins/spectrogram-fft.js +150 -0
- package/dist/plugins/spectrogram-fft.mjs +175 -0
- package/dist/plugins/spectrogram.d.mts +177 -0
- package/dist/plugins/spectrogram.d.ts +58 -16
- package/dist/plugins/spectrogram.js +312 -140
- package/dist/plugins/spectrogram.min.js +1 -0
- package/dist/plugins/spectrogram.mjs +407 -0
- package/dist/plugins/timeline.d.ts +2 -2
- package/dist/plugins/timeline.js +3 -4
- package/dist/plugins/timeline.min.js +1 -1
- package/dist/renderer.d.ts +1 -1
- package/dist/renderer.js +3 -3
- package/dist/wavesurfer.d.ts +6 -0
- package/dist/wavesurfer.js +10 -6
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +13 -4
- package/dist/legacy-adapter.d.ts +0 -7
- package/dist/legacy-adapter.js +0 -15
|
@@ -1,165 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spectrogram plugin
|
|
3
|
+
*
|
|
4
|
+
* Render a spectrogram visualisation of the audio.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* // ... initialising wavesurfer with the plugin
|
|
8
|
+
* var wavesurfer = WaveSurfer.create({
|
|
9
|
+
* // wavesurfer options ...
|
|
10
|
+
* plugins: [
|
|
11
|
+
* SpectrogramPlugin.create({
|
|
12
|
+
* // plugin options ...
|
|
13
|
+
* })
|
|
14
|
+
* ]
|
|
15
|
+
* });
|
|
16
|
+
*/
|
|
17
|
+
// @ts-nocheck
|
|
1
18
|
import BasePlugin from '../base-plugin.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
recordedUrl = '';
|
|
19
|
+
import FFT from './spectrogram-fft.mjs';
|
|
20
|
+
export default class SpectrogramPlugin extends BasePlugin {
|
|
5
21
|
static create(options) {
|
|
6
22
|
return new SpectrogramPlugin(options || {});
|
|
7
23
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
this.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
for (let i = 0; i < dataArray.length; i++) {
|
|
19
|
-
let sum = 0;
|
|
20
|
-
let count = 0;
|
|
21
|
-
for (let j = i - Math.floor(windowSize / 2); j <= i + Math.floor(windowSize / 2); j++) {
|
|
22
|
-
if (j >= 0 && j < dataArray.length) {
|
|
23
|
-
sum += dataArray[j];
|
|
24
|
-
count++;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
smoothedDataArray[i] = sum / count;
|
|
28
|
-
}
|
|
29
|
-
// Find the first two peaks (formants) above a certain threshold
|
|
30
|
-
const threshold = -70; // Adjust the threshold as needed
|
|
31
|
-
const formants = [];
|
|
32
|
-
let prevValue = smoothedDataArray[0];
|
|
33
|
-
let prevSlope = 0;
|
|
34
|
-
for (let i = 1; i < smoothedDataArray.length - 1; i++) {
|
|
35
|
-
const slope = smoothedDataArray[i] - prevValue;
|
|
36
|
-
if (slope > 0 && prevSlope < 0 && smoothedDataArray[i] > threshold) {
|
|
37
|
-
formants.push(i);
|
|
38
|
-
if (formants.length >= 2) {
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
24
|
+
constructor(options) {
|
|
25
|
+
super(options);
|
|
26
|
+
this.utils = {
|
|
27
|
+
style: (el, styles) => Object.assign(el.style, styles),
|
|
28
|
+
};
|
|
29
|
+
this.drawSpectrogram = (frequenciesData) => {
|
|
30
|
+
if (!isNaN(frequenciesData[0][0])) {
|
|
31
|
+
// data is 1ch [sample, freq] format
|
|
32
|
+
// to [channel, sample, freq] format
|
|
33
|
+
frequenciesData = [frequenciesData];
|
|
41
34
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// Print the formant frequencies
|
|
50
|
-
if (formantFrequencies.length >= 2) {
|
|
51
|
-
return formantFrequencies;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
render(stream) {
|
|
55
|
-
if (!this.container || !this.wavesurfer)
|
|
56
|
-
return () => undefined;
|
|
57
|
-
const canvas = document.createElement('canvas');
|
|
58
|
-
canvas.width = this.container.clientWidth;
|
|
59
|
-
canvas.height = this.container.clientHeight;
|
|
60
|
-
canvas.style.zIndex = '10';
|
|
61
|
-
this.container.appendChild(canvas);
|
|
62
|
-
const canvasCtx = canvas.getContext('2d');
|
|
63
|
-
const audioContext = new AudioContext();
|
|
64
|
-
const source = audioContext.createMediaStreamSource(stream);
|
|
65
|
-
const analyser = audioContext.createAnalyser();
|
|
66
|
-
source.connect(analyser);
|
|
67
|
-
// An array that will store real-time waveform data
|
|
68
|
-
const bufferLength = analyser.frequencyBinCount;
|
|
69
|
-
const dataArray = new Uint8Array(bufferLength);
|
|
70
|
-
// An array that will store FFT data
|
|
71
|
-
const freqArray = new Float32Array(bufferLength);
|
|
72
|
-
let animationId;
|
|
73
|
-
const drawWaveform = () => {
|
|
74
|
-
if (!canvasCtx)
|
|
35
|
+
const spectrCc = this.spectrCc;
|
|
36
|
+
const height = this.height;
|
|
37
|
+
const width = this.width;
|
|
38
|
+
const freqFrom = this.buffer.sampleRate / 2;
|
|
39
|
+
const freqMin = this.frequencyMin;
|
|
40
|
+
const freqMax = this.frequencyMax;
|
|
41
|
+
if (!spectrCc) {
|
|
75
42
|
return;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
canvasCtx.lineTo(x, y);
|
|
43
|
+
}
|
|
44
|
+
for (let c = 0; c < frequenciesData.length; c++) {
|
|
45
|
+
// for each channel
|
|
46
|
+
const pixels = this.resample(frequenciesData[c]);
|
|
47
|
+
const imageData = new ImageData(width, height);
|
|
48
|
+
for (let i = 0; i < pixels.length; i++) {
|
|
49
|
+
for (let j = 0; j < pixels[i].length; j++) {
|
|
50
|
+
const colorMap = this.colorMap[pixels[i][j]];
|
|
51
|
+
const redIndex = ((height - j) * width + i) * 4;
|
|
52
|
+
imageData.data[redIndex] = colorMap[0] * 255;
|
|
53
|
+
imageData.data[redIndex + 1] = colorMap[1] * 255;
|
|
54
|
+
imageData.data[redIndex + 2] = colorMap[2] * 255;
|
|
55
|
+
imageData.data[redIndex + 3] = colorMap[3] * 255;
|
|
56
|
+
}
|
|
93
57
|
}
|
|
94
|
-
|
|
58
|
+
// scale and stack spectrograms
|
|
59
|
+
createImageBitmap(imageData).then((renderer) => {
|
|
60
|
+
spectrCc.drawImage(renderer, 0, height * (1 - freqMax / freqFrom), // source x, y
|
|
61
|
+
width, (height * (freqMax - freqMin)) / freqFrom, // source width, height
|
|
62
|
+
0, height * c, // destination x, y
|
|
63
|
+
width, height);
|
|
64
|
+
});
|
|
95
65
|
}
|
|
96
|
-
|
|
97
|
-
canvasCtx.stroke();
|
|
98
|
-
animationId = requestAnimationFrame(drawWaveform);
|
|
66
|
+
this.emit('ready');
|
|
99
67
|
};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
68
|
+
this.frequenciesDataUrl = options.frequenciesDataUrl;
|
|
69
|
+
this.container =
|
|
70
|
+
'string' == typeof options.container ? document.querySelector(options.container) : options.container;
|
|
71
|
+
if (options.colorMap) {
|
|
72
|
+
if (options.colorMap.length < 256) {
|
|
73
|
+
throw new Error('Colormap must contain 256 elements');
|
|
104
74
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
75
|
+
for (let i = 0; i < options.colorMap.length; i++) {
|
|
76
|
+
const cmEntry = options.colorMap[i];
|
|
77
|
+
if (cmEntry.length !== 4) {
|
|
78
|
+
throw new Error('ColorMap entries must contain 4 values');
|
|
79
|
+
}
|
|
108
80
|
}
|
|
109
|
-
|
|
110
|
-
|
|
81
|
+
this.colorMap = options.colorMap;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
this.colorMap = [];
|
|
85
|
+
for (let i = 0; i < 256; i++) {
|
|
86
|
+
const val = (255 - i) / 256;
|
|
87
|
+
this.colorMap.push([val, val, val, 1]);
|
|
111
88
|
}
|
|
112
|
-
canvas?.remove();
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
cleanUp() {
|
|
116
|
-
this.stopSpectrograming();
|
|
117
|
-
this.wavesurfer?.empty();
|
|
118
|
-
if (this.recordedUrl) {
|
|
119
|
-
URL.revokeObjectURL(this.recordedUrl);
|
|
120
|
-
this.recordedUrl = '';
|
|
121
89
|
}
|
|
90
|
+
this.fftSamples = options.fftSamples || 512;
|
|
91
|
+
this.height = options.height || this.fftSamples / 2;
|
|
92
|
+
this.noverlap = options.noverlap;
|
|
93
|
+
this.windowFunc = options.windowFunc;
|
|
94
|
+
this.alpha = options.alpha;
|
|
95
|
+
this.channels = 1;
|
|
96
|
+
// Getting file's original samplerate is difficult(#1248).
|
|
97
|
+
// So set 12kHz default to render like wavesurfer.js 5.x.
|
|
98
|
+
this.frequencyMin = options.frequencyMin || 0;
|
|
99
|
+
this.frequencyMax = options.frequencyMax || 0;
|
|
100
|
+
this.createWrapper();
|
|
101
|
+
this.createCanvas();
|
|
122
102
|
}
|
|
123
|
-
|
|
124
|
-
this.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
103
|
+
onInit() {
|
|
104
|
+
this.container = this.container || this.wavesurfer.getWrapper();
|
|
105
|
+
this.container.appendChild(this.wrapper);
|
|
106
|
+
if (this.wavesurfer.options.fillParent) {
|
|
107
|
+
this.utils.style(this.wrapper, {
|
|
108
|
+
width: '100%',
|
|
109
|
+
overflowX: 'hidden',
|
|
110
|
+
overflowY: 'hidden',
|
|
111
|
+
});
|
|
128
112
|
}
|
|
129
|
-
|
|
130
|
-
|
|
113
|
+
this.width = this.wavesurfer.getWrapper().offsetWidth;
|
|
114
|
+
this.subscriptions.push(this.wavesurfer.on('redraw', () => this.render()));
|
|
115
|
+
}
|
|
116
|
+
destroy() {
|
|
117
|
+
this.unAll();
|
|
118
|
+
this.wavesurfer.un('ready', this._onReady);
|
|
119
|
+
this.wavesurfer.un('redraw', this._onRender);
|
|
120
|
+
this.wavesurfer = null;
|
|
121
|
+
this.util = null;
|
|
122
|
+
this.options = null;
|
|
123
|
+
if (this.wrapper) {
|
|
124
|
+
this.wrapper.remove();
|
|
125
|
+
this.wrapper = null;
|
|
131
126
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
127
|
+
}
|
|
128
|
+
createWrapper() {
|
|
129
|
+
this.wrapper = document.createElement('div');
|
|
130
|
+
this.utils.style(this.wrapper, {
|
|
131
|
+
display: 'block',
|
|
132
|
+
position: 'relative',
|
|
133
|
+
userSelect: 'none',
|
|
134
|
+
webkitUserSelect: 'none',
|
|
135
|
+
height: `${this.height * this.channels}px`,
|
|
139
136
|
});
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
137
|
+
// if labels are active
|
|
138
|
+
if (this.options.labels) {
|
|
139
|
+
const labelsEl = document.createElement('canvas');
|
|
140
|
+
labelsEl.classList.add('spec-labels');
|
|
141
|
+
this.utils.style(labelsEl, {
|
|
142
|
+
position: 'absolute',
|
|
143
|
+
zIndex: 9,
|
|
144
|
+
width: '55px',
|
|
145
|
+
height: '100%',
|
|
146
|
+
});
|
|
147
|
+
this.wrapper.appendChild(labelsEl);
|
|
148
|
+
this.labelsEl = labelsEl;
|
|
149
|
+
}
|
|
150
|
+
this.wrapper.addEventListener('click', this._onWrapperClick);
|
|
151
|
+
}
|
|
152
|
+
_wrapperClickHandler(event) {
|
|
153
|
+
event.preventDefault();
|
|
154
|
+
const relX = 'offsetX' in event ? event.offsetX : event.layerX;
|
|
155
|
+
this.emit('click', relX / this.width || 0);
|
|
156
|
+
}
|
|
157
|
+
createCanvas() {
|
|
158
|
+
const canvas = document.createElement('canvas');
|
|
159
|
+
this.wrapper.appendChild(canvas);
|
|
160
|
+
this.spectrCc = canvas.getContext('2d');
|
|
161
|
+
this.utils.style(canvas, {
|
|
162
|
+
position: 'absolute',
|
|
163
|
+
left: 0,
|
|
164
|
+
top: 0,
|
|
165
|
+
width: '100%',
|
|
166
|
+
height: '100%',
|
|
167
|
+
zIndex: 4,
|
|
144
168
|
});
|
|
145
|
-
|
|
146
|
-
this.emit('startSpectrograming');
|
|
147
|
-
this.mediaSpectrogramer = mediaSpectrogramer;
|
|
169
|
+
this.canvas = canvas;
|
|
148
170
|
}
|
|
149
|
-
|
|
150
|
-
|
|
171
|
+
render() {
|
|
172
|
+
this.canvas.width = this.width;
|
|
173
|
+
this.canvas.height = this.height;
|
|
174
|
+
if (this.frequenciesDataUrl) {
|
|
175
|
+
this.loadFrequenciesData(this.frequenciesDataUrl);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
this.getFrequencies(this.drawSpectrogram);
|
|
179
|
+
}
|
|
180
|
+
this.loadLabels('rgba(68,68,68,0.5)', '12px', '10px', '', '#fff', '#f7f7f7', 'center', '#specLabels');
|
|
151
181
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
182
|
+
getFrequencies(callback) {
|
|
183
|
+
const fftSamples = this.fftSamples;
|
|
184
|
+
const buffer = this.wavesurfer.getDecodedData();
|
|
185
|
+
const channels = this.channels;
|
|
186
|
+
this.frequencyMax = this.frequencyMax || buffer.sampleRate / 2;
|
|
187
|
+
if (!buffer)
|
|
188
|
+
return;
|
|
189
|
+
this.buffer = buffer;
|
|
190
|
+
// This may differ from file samplerate. Browser resamples audio.
|
|
191
|
+
const sampleRate = buffer.sampleRate;
|
|
192
|
+
const frequencies = [];
|
|
193
|
+
let noverlap = this.noverlap;
|
|
194
|
+
if (!noverlap) {
|
|
195
|
+
const uniqueSamplesPerPx = buffer.length / this.canvas.width;
|
|
196
|
+
noverlap = Math.max(0, Math.round(fftSamples - uniqueSamplesPerPx));
|
|
155
197
|
}
|
|
198
|
+
const fft = new FFT(fftSamples, sampleRate, this.windowFunc, this.alpha);
|
|
199
|
+
for (let c = 0; c < channels; c++) {
|
|
200
|
+
// for each channel
|
|
201
|
+
const channelData = buffer.getChannelData(c);
|
|
202
|
+
const channelFreq = [];
|
|
203
|
+
let currentOffset = 0;
|
|
204
|
+
while (currentOffset + fftSamples < channelData.length) {
|
|
205
|
+
const segment = channelData.slice(currentOffset, currentOffset + fftSamples);
|
|
206
|
+
const spectrum = fft.calculateSpectrum(segment);
|
|
207
|
+
const array = new Uint8Array(fftSamples / 2);
|
|
208
|
+
let j;
|
|
209
|
+
for (j = 0; j < fftSamples / 2; j++) {
|
|
210
|
+
array[j] = Math.max(-255, Math.log10(spectrum[j]) * 45);
|
|
211
|
+
}
|
|
212
|
+
channelFreq.push(array);
|
|
213
|
+
// channelFreq: [sample, freq]
|
|
214
|
+
currentOffset += fftSamples - noverlap;
|
|
215
|
+
}
|
|
216
|
+
frequencies.push(channelFreq);
|
|
217
|
+
// frequencies: [channel, sample, freq]
|
|
218
|
+
}
|
|
219
|
+
callback(frequencies, this);
|
|
156
220
|
}
|
|
157
|
-
|
|
158
|
-
return
|
|
221
|
+
loadFrequenciesData(url) {
|
|
222
|
+
return fetch(url)
|
|
223
|
+
.then((response) => response.json())
|
|
224
|
+
.then(this.drawSpectrogram);
|
|
159
225
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
226
|
+
freqType(freq) {
|
|
227
|
+
return freq >= 1000 ? (freq / 1000).toFixed(1) : Math.round(freq);
|
|
228
|
+
}
|
|
229
|
+
unitType(freq) {
|
|
230
|
+
return freq >= 1000 ? 'KHz' : 'Hz';
|
|
231
|
+
}
|
|
232
|
+
loadLabels(bgFill, fontSizeFreq, fontSizeUnit, fontType, textColorFreq, textColorUnit, textAlign, container) {
|
|
233
|
+
const frequenciesHeight = this.height;
|
|
234
|
+
bgFill = bgFill || 'rgba(68,68,68,0)';
|
|
235
|
+
fontSizeFreq = fontSizeFreq || '12px';
|
|
236
|
+
fontSizeUnit = fontSizeUnit || '10px';
|
|
237
|
+
fontType = fontType || 'Helvetica';
|
|
238
|
+
textColorFreq = textColorFreq || '#fff';
|
|
239
|
+
textColorUnit = textColorUnit || '#fff';
|
|
240
|
+
textAlign = textAlign || 'center';
|
|
241
|
+
container = container || '#specLabels';
|
|
242
|
+
const bgWidth = 55;
|
|
243
|
+
const getMaxY = frequenciesHeight || 512;
|
|
244
|
+
const labelIndex = 5 * (getMaxY / 256);
|
|
245
|
+
const freqStart = this.frequencyMin;
|
|
246
|
+
const step = (this.frequencyMax - freqStart) / labelIndex;
|
|
247
|
+
// prepare canvas element for labels
|
|
248
|
+
const ctx = this.labelsEl.getContext('2d');
|
|
249
|
+
const dispScale = window.devicePixelRatio;
|
|
250
|
+
this.labelsEl.height = this.height * this.channels * dispScale;
|
|
251
|
+
this.labelsEl.width = bgWidth * dispScale;
|
|
252
|
+
ctx.scale(dispScale, dispScale);
|
|
253
|
+
if (!ctx) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
for (let c = 0; c < this.channels; c++) {
|
|
257
|
+
// for each channel
|
|
258
|
+
// fill background
|
|
259
|
+
ctx.fillStyle = bgFill;
|
|
260
|
+
ctx.fillRect(0, c * getMaxY, bgWidth, (1 + c) * getMaxY);
|
|
261
|
+
ctx.fill();
|
|
262
|
+
let i;
|
|
263
|
+
// render labels
|
|
264
|
+
for (i = 0; i <= labelIndex; i++) {
|
|
265
|
+
ctx.textAlign = textAlign;
|
|
266
|
+
ctx.textBaseline = 'middle';
|
|
267
|
+
const freq = freqStart + step * i;
|
|
268
|
+
const label = this.freqType(freq);
|
|
269
|
+
const units = this.unitType(freq);
|
|
270
|
+
const yLabelOffset = 2;
|
|
271
|
+
const x = 16;
|
|
272
|
+
let y;
|
|
273
|
+
if (i == 0) {
|
|
274
|
+
y = (1 + c) * getMaxY + i - 10;
|
|
275
|
+
// unit label
|
|
276
|
+
ctx.fillStyle = textColorUnit;
|
|
277
|
+
ctx.font = fontSizeUnit + ' ' + fontType;
|
|
278
|
+
ctx.fillText(units, x + 24, y);
|
|
279
|
+
// freq label
|
|
280
|
+
ctx.fillStyle = textColorFreq;
|
|
281
|
+
ctx.font = fontSizeFreq + ' ' + fontType;
|
|
282
|
+
ctx.fillText(label, x, y);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
y = (1 + c) * getMaxY - i * 50 + yLabelOffset;
|
|
286
|
+
// unit label
|
|
287
|
+
ctx.fillStyle = textColorUnit;
|
|
288
|
+
ctx.font = fontSizeUnit + ' ' + fontType;
|
|
289
|
+
ctx.fillText(units, x + 24, y);
|
|
290
|
+
// freq label
|
|
291
|
+
ctx.fillStyle = textColorFreq;
|
|
292
|
+
ctx.font = fontSizeFreq + ' ' + fontType;
|
|
293
|
+
ctx.fillText(label, x, y);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
resample(oldMatrix) {
|
|
299
|
+
const columnsNumber = this.width;
|
|
300
|
+
const newMatrix = [];
|
|
301
|
+
const oldPiece = 1 / oldMatrix.length;
|
|
302
|
+
const newPiece = 1 / columnsNumber;
|
|
303
|
+
let i;
|
|
304
|
+
for (i = 0; i < columnsNumber; i++) {
|
|
305
|
+
const column = new Array(oldMatrix[0].length);
|
|
306
|
+
let j;
|
|
307
|
+
for (j = 0; j < oldMatrix.length; j++) {
|
|
308
|
+
const oldStart = j * oldPiece;
|
|
309
|
+
const oldEnd = oldStart + oldPiece;
|
|
310
|
+
const newStart = i * newPiece;
|
|
311
|
+
const newEnd = newStart + newPiece;
|
|
312
|
+
const overlap = oldEnd <= newStart || newEnd <= oldStart
|
|
313
|
+
? 0
|
|
314
|
+
: Math.min(Math.max(oldEnd, newStart), Math.max(newEnd, oldStart)) -
|
|
315
|
+
Math.max(Math.min(oldEnd, newStart), Math.min(newEnd, oldStart));
|
|
316
|
+
let k;
|
|
317
|
+
/* eslint-disable max-depth */
|
|
318
|
+
if (overlap > 0) {
|
|
319
|
+
for (k = 0; k < oldMatrix[0].length; k++) {
|
|
320
|
+
if (column[k] == null) {
|
|
321
|
+
column[k] = 0;
|
|
322
|
+
}
|
|
323
|
+
column[k] += (overlap / newPiece) * oldMatrix[j][k];
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/* eslint-enable max-depth */
|
|
327
|
+
}
|
|
328
|
+
const intColumn = new Uint8Array(oldMatrix[0].length);
|
|
329
|
+
let m;
|
|
330
|
+
for (m = 0; m < oldMatrix[0].length; m++) {
|
|
331
|
+
intColumn[m] = column[m];
|
|
332
|
+
}
|
|
333
|
+
newMatrix.push(intColumn);
|
|
334
|
+
}
|
|
335
|
+
return newMatrix;
|
|
163
336
|
}
|
|
164
337
|
}
|
|
165
|
-
export default SpectrogramPlugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Spectrogram=e():t.Spectrogram=e()}(WaveSurfer,(()=>(()=>{"use strict";var t={d:(e,s)=>{for(var i in s)t.o(s,i)&&!t.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:s[i]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>r});const s=class{constructor(){this.listeners={}}on(t,e){return this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),()=>this.un(t,e)}once(t,e){const s=this.on(t,e),i=this.on(t,(()=>{s(),i()}));return s}un(t,e){this.listeners[t]&&(e?this.listeners[t].delete(e):delete this.listeners[t])}unAll(){this.listeners={}}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)))}},i=class extends s{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.subscriptions.forEach((t=>t()))}};function a(t,e,s,i){switch(this.bufferSize=t,this.sampleRate=e,this.bandwidth=2/t*(e/2),this.sinTable=new Float32Array(t),this.cosTable=new Float32Array(t),this.windowValues=new Float32Array(t),this.reverseTable=new Uint32Array(t),this.peakBand=0,this.peak=0,s){case"bartlett":for(a=0;a<t;a++)this.windowValues[a]=2/(t-1)*((t-1)/2-Math.abs(a-(t-1)/2));break;case"bartlettHann":for(a=0;a<t;a++)this.windowValues[a]=.62-.48*Math.abs(a/(t-1)-.5)-.38*Math.cos(2*Math.PI*a/(t-1));break;case"blackman":for(i=i||.16,a=0;a<t;a++)this.windowValues[a]=(1-i)/2-.5*Math.cos(2*Math.PI*a/(t-1))+i/2*Math.cos(4*Math.PI*a/(t-1));break;case"cosine":for(a=0;a<t;a++)this.windowValues[a]=Math.cos(Math.PI*a/(t-1)-Math.PI/2);break;case"gauss":for(i=i||.25,a=0;a<t;a++)this.windowValues[a]=Math.pow(Math.E,-.5*Math.pow((a-(t-1)/2)/(i*(t-1)/2),2));break;case"hamming":for(a=0;a<t;a++)this.windowValues[a]=.54-.46*Math.cos(2*Math.PI*a/(t-1));break;case"hann":case void 0:for(a=0;a<t;a++)this.windowValues[a]=.5*(1-Math.cos(2*Math.PI*a/(t-1)));break;case"lanczoz":for(a=0;a<t;a++)this.windowValues[a]=Math.sin(Math.PI*(2*a/(t-1)-1))/(Math.PI*(2*a/(t-1)-1));break;case"rectangular":for(a=0;a<t;a++)this.windowValues[a]=1;break;case"triangular":for(a=0;a<t;a++)this.windowValues[a]=2/t*(t/2-Math.abs(a-(t-1)/2));break;default:throw Error("No such window function '"+s+"'")}for(var a,r=1,n=t>>1;r<t;){for(a=0;a<r;a++)this.reverseTable[a+r]=this.reverseTable[a]+n;r<<=1,n>>=1}for(a=0;a<t;a++)this.sinTable[a]=Math.sin(-Math.PI/a),this.cosTable[a]=Math.cos(-Math.PI/a);this.calculateSpectrum=function(t){var e,s,i,a=this.bufferSize,r=this.cosTable,n=this.sinTable,h=this.reverseTable,l=new Float32Array(a),o=new Float32Array(a),c=2/this.bufferSize,f=Math.sqrt,p=new Float32Array(a/2),u=Math.floor(Math.log(a)/Math.LN2);if(Math.pow(2,u)!==a)throw"Invalid buffer size, must be a power of 2.";if(a!==t.length)throw"Supplied buffer is not the same size as defined FFT. FFT Size: "+a+" Buffer Size: "+t.length;for(var d,w,M,b,g,m,y,v,x=1,S=0;S<a;S++)l[S]=t[h[S]]*this.windowValues[h[S]],o[S]=0;for(;x<a;){d=r[x],w=n[x],M=1,b=0;for(var k=0;k<x;k++){for(S=k;S<a;)m=M*l[g=S+x]-b*o[g],y=M*o[g]+b*l[g],l[g]=l[S]-m,o[g]=o[S]-y,l[S]+=m,o[S]+=y,S+=x<<1;M=(v=M)*d-b*w,b=v*w+b*d}x<<=1}S=0;for(var q=a/2;S<q;S++)(i=c*f((e=l[S])*e+(s=o[S])*s))>this.peak&&(this.peakBand=S,this.peak=i),p[S]=i;return p}}class r extends i{static create(t){return new r(t||{})}constructor(t){if(super(t),this.utils={style:(t,e)=>Object.assign(t.style,e)},this.drawSpectrogram=t=>{isNaN(t[0][0])||(t=[t]);const e=this.spectrCc,s=this.height,i=this.width,a=this.buffer.sampleRate/2,r=this.frequencyMin,n=this.frequencyMax;if(e){for(let h=0;h<t.length;h++){const l=this.resample(t[h]),o=new ImageData(i,s);for(let t=0;t<l.length;t++)for(let e=0;e<l[t].length;e++){const a=this.colorMap[l[t][e]],r=4*((s-e)*i+t);o.data[r]=255*a[0],o.data[r+1]=255*a[1],o.data[r+2]=255*a[2],o.data[r+3]=255*a[3]}createImageBitmap(o).then((t=>{e.drawImage(t,0,s*(1-n/a),i,s*(n-r)/a,0,s*h,i,s)}))}this.emit("ready")}},this.frequenciesDataUrl=t.frequenciesDataUrl,this.container="string"==typeof t.container?document.querySelector(t.container):t.container,t.colorMap){if(t.colorMap.length<256)throw new Error("Colormap must contain 256 elements");for(let e=0;e<t.colorMap.length;e++)if(4!==t.colorMap[e].length)throw new Error("ColorMap entries must contain 4 values");this.colorMap=t.colorMap}else{this.colorMap=[];for(let t=0;t<256;t++){const e=(255-t)/256;this.colorMap.push([e,e,e,1])}}this.fftSamples=t.fftSamples||512,this.height=t.height||this.fftSamples/2,this.noverlap=t.noverlap,this.windowFunc=t.windowFunc,this.alpha=t.alpha,this.channels=1,this.frequencyMin=t.frequencyMin||0,this.frequencyMax=t.frequencyMax||0,this.createWrapper(),this.createCanvas()}onInit(){this.container=this.container||this.wavesurfer.getWrapper(),this.container.appendChild(this.wrapper),this.wavesurfer.options.fillParent&&this.utils.style(this.wrapper,{width:"100%",overflowX:"hidden",overflowY:"hidden"}),this.width=this.wavesurfer.getWrapper().offsetWidth,this.subscriptions.push(this.wavesurfer.on("redraw",(()=>this.render())))}destroy(){this.unAll(),this.wavesurfer.un("ready",this._onReady),this.wavesurfer.un("redraw",this._onRender),this.wavesurfer=null,this.util=null,this.options=null,this.wrapper&&(this.wrapper.remove(),this.wrapper=null)}createWrapper(){if(this.wrapper=document.createElement("div"),this.utils.style(this.wrapper,{display:"block",position:"relative",userSelect:"none",webkitUserSelect:"none",height:this.height*this.channels+"px"}),this.options.labels){const t=document.createElement("canvas");t.classList.add("spec-labels"),this.utils.style(t,{position:"absolute",zIndex:9,width:"55px",height:"100%"}),this.wrapper.appendChild(t),this.labelsEl=t}this.wrapper.addEventListener("click",this._onWrapperClick)}_wrapperClickHandler(t){t.preventDefault();const e="offsetX"in t?t.offsetX:t.layerX;this.emit("click",e/this.width||0)}createCanvas(){const t=document.createElement("canvas");this.wrapper.appendChild(t),this.spectrCc=t.getContext("2d"),this.utils.style(t,{position:"absolute",left:0,top:0,width:"100%",height:"100%",zIndex:4}),this.canvas=t}render(){this.canvas.width=this.width,this.canvas.height=this.height,this.frequenciesDataUrl?this.loadFrequenciesData(this.frequenciesDataUrl):this.getFrequencies(this.drawSpectrogram),this.loadLabels("rgba(68,68,68,0.5)","12px","10px","","#fff","#f7f7f7","center","#specLabels")}getFrequencies(t){const e=this.fftSamples,s=this.wavesurfer.getDecodedData(),i=this.channels;if(this.frequencyMax=this.frequencyMax||s.sampleRate/2,!s)return;this.buffer=s;const r=s.sampleRate,n=[];let h=this.noverlap;if(!h){const t=s.length/this.canvas.width;h=Math.max(0,Math.round(e-t))}const l=new a(e,r,this.windowFunc,this.alpha);for(let t=0;t<i;t++){const i=s.getChannelData(t),a=[];let r=0;for(;r+e<i.length;){const t=i.slice(r,r+e),s=l.calculateSpectrum(t),n=new Uint8Array(e/2);let o;for(o=0;o<e/2;o++)n[o]=Math.max(-255,45*Math.log10(s[o]));a.push(n),r+=e-h}n.push(a)}t(n,this)}loadFrequenciesData(t){return fetch(t).then((t=>t.json())).then(this.drawSpectrogram)}freqType(t){return t>=1e3?(t/1e3).toFixed(1):Math.round(t)}unitType(t){return t>=1e3?"KHz":"Hz"}loadLabels(t,e,s,i,a,r,n,h){t=t||"rgba(68,68,68,0)",e=e||"12px",s=s||"10px",i=i||"Helvetica",a=a||"#fff",r=r||"#fff",n=n||"center",h=h||"#specLabels";const l=this.height||512,o=l/256*5,c=this.frequencyMin,f=(this.frequencyMax-c)/o,p=this.labelsEl.getContext("2d"),u=window.devicePixelRatio;if(this.labelsEl.height=this.height*this.channels*u,this.labelsEl.width=55*u,p.scale(u,u),p)for(let h=0;h<this.channels;h++){let u;for(p.fillStyle=t,p.fillRect(0,h*l,55,(1+h)*l),p.fill(),u=0;u<=o;u++){p.textAlign=n,p.textBaseline="middle";const t=c+f*u,o=this.freqType(t),d=this.unitType(t),w=2,M=16;let b;0==u?(b=(1+h)*l+u-10,p.fillStyle=r,p.font=s+" "+i,p.fillText(d,M+24,b),p.fillStyle=a,p.font=e+" "+i,p.fillText(o,M,b)):(b=(1+h)*l-50*u+w,p.fillStyle=r,p.font=s+" "+i,p.fillText(d,M+24,b),p.fillStyle=a,p.font=e+" "+i,p.fillText(o,M,b))}}}resample(t){const e=this.width,s=[],i=1/t.length,a=1/e;let r;for(r=0;r<e;r++){const e=new Array(t[0].length);let n;for(n=0;n<t.length;n++){const s=n*i,h=s+i,l=r*a,o=l+a,c=h<=l||o<=s?0:Math.min(Math.max(h,l),Math.max(o,s))-Math.max(Math.min(h,l),Math.min(o,s));let f;if(c>0)for(f=0;f<t[0].length;f++)null==e[f]&&(e[f]=0),e[f]+=c/a*t[n][f]}const h=new Uint8Array(t[0].length);let l;for(l=0;l<t[0].length;l++)h[l]=e[l];s.push(h)}return s}}return e.default})()));
|