Skip to content

Commit 5954687

Browse files
committed
(1) New PV version that passes output by reference instead of creating new vectors in each function. (2) Let's see how the jsfft library performs.
1 parent 37cc620 commit 5954687

File tree

2 files changed

+486
-0
lines changed

2 files changed

+486
-0
lines changed

Diff for: PV_fast.js

+384
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,384 @@
1+
function PhaseVocoder(winSize, sampleRate) {
2+
3+
var _sampleRate = sampleRate; var _RS = 0; var _RA = 0; var _omega;
4+
5+
var _previousInputPhase; var _previousOutputPhase; var _framingWindow;
6+
7+
var _squaredFramingWindow; var _winSize = winSize;
8+
9+
var _overlapBuffers; var _owOverlapBuffers;
10+
11+
var _first = true;
12+
13+
14+
15+
this.overlap_and_slide = function(RS, frame, overlapBuffer, windowSize, finishedBytes) {
16+
17+
for (var i=0; i<RS; i++) {
18+
finishedBytes[i] = overlapBuffer.shift();
19+
while(overlapBuffer.length > windowSize - 1 && overlapBuffer.length >= 0)
20+
overlapBuffer.shift();
21+
overlapBuffer.push.apply(overlapBuffer, [0.0]);
22+
}
23+
24+
var outBytes = [].concat(overlapBuffer);
25+
26+
for (var i=0; i<outBytes.length; i++)
27+
outBytes[i] = frame[i] + overlapBuffer[i];
28+
29+
while ((overlapBuffer.length > windowSize - outBytes.length) && overlapBuffer.length >= 0) {
30+
overlapBuffer.shift();
31+
}
32+
33+
overlapBuffer.push.apply(overlapBuffer, outBytes);
34+
35+
return;
36+
}
37+
38+
39+
this.find_peaks = function(magFrame, out) {
40+
var magSpecPad = [0,0].concat(magFrame).concat([0,0]);
41+
out.peaks = [];
42+
43+
for (var i=2, I=0; i<=magSpecPad.length-2; i++, I++) {
44+
x = magSpecPad[i];
45+
if (x > magSpecPad[i-2] && x > magSpecPad[i-1] && x > magSpecPad[i+1] && x > magSpecPad[i+2]) {
46+
out.peaks = out.peaks.concat(I);
47+
}
48+
}
49+
50+
out.inflRegStart = new Array(out.peaks.length);
51+
52+
out.inflRegStart[0] = 0;
53+
for (var i=0; i<out.peaks.length-1; i++) {
54+
out.inflRegStart[i+1] = Math.ceil((out.peaks[i] + out.peaks[i+1])/2);
55+
}
56+
57+
out.inflRegEnd = new Array(out.peaks.length);
58+
for (var i=1; i<out.inflRegStart.length; i++) {
59+
out.inflRegEnd[i-1] = out.inflRegStart[i]-1;
60+
}
61+
out.inflRegEnd[out.inflRegEnd.length] = out.inflRegEnd.length-1;
62+
63+
out.influenceRegions = new Array(out.inflRegStart.length);
64+
for (var i=0; i<out.influenceRegions.length; i++)
65+
out.influenceRegions[i] = Math.max(0, out.inflRegEnd[i] - out.inflRegStart[i] + 1);
66+
67+
return;
68+
}
69+
70+
71+
this.get_phase_advances = function(currentInputPhase, previousInputPhase, omega, RA, RS, instPhaseAdvHop) {
72+
var twoPI = 2 * Math.PI;
73+
74+
for (var i=0; i<omega.length; i++) {
75+
var expectedPhaseAdv = omega[i] * RA;
76+
77+
var auxheterodynedPhaseIncr = (currentInputPhase[i] - previousInputPhase[i]) - expectedPhaseAdv;
78+
var heterodynedPhaseIncr = auxheterodynedPhaseIncr - twoPI * Math.round(auxheterodynedPhaseIncr/twoPI);
79+
80+
var instPhaseAdvPerSampleHop = omega[i] + heterodynedPhaseIncr / RA;
81+
82+
instPhaseAdvHop[i] = instPhaseAdvPerSampleHop * RS;
83+
}
84+
85+
return;
86+
}
87+
88+
89+
this.get_phasor_theta_v2 = function(currentInputPhase, previousOutputPhase, instPhaseAdv, frequencyBins, influenceRegions, theta) {
90+
// Get the peaks in the spectrum together with their regions of influence.
91+
92+
var theta_idx = 0;
93+
for (var i=0; i<frequencyBins.length; i++) {
94+
var bin = frequencyBins[i];
95+
for (var j=0; j<influenceRegions[i]; j++, theta_idx++) {
96+
theta[theta_idx] = previousOutputPhase[bin] + instPhaseAdv[bin] - currentInputPhase[bin];
97+
}
98+
}
99+
100+
var remaining_length = theta.length - theta_idx;
101+
for (var i=0; i<remaining_length; i++, theta_idx++)
102+
theta[theta_idx] = 0;
103+
104+
return;
105+
}
106+
107+
108+
this.identity_phase_locking = function(currentInputMagnitude, currentInputPhase, previousOutputPhase, instPhaseAdv, phasor_theta) {
109+
var _ = this; var r = {};
110+
111+
_.find_peaks(currentInputMagnitude, r);
112+
113+
_.get_phasor_theta_v2(currentInputPhase, previousOutputPhase, instPhaseAdv, r.peaks, r.influenceRegions, phasor_theta);
114+
115+
return;
116+
}
117+
118+
this.pv_step_v2 = function(fftObject, previousInputPhase, previousOutputPhase, omega, RA, RS, out) {
119+
120+
var _ = this;
121+
122+
var currentInputPhase = fftObject.phase;
123+
124+
var instPhaseAdv = new Array(omega.length);
125+
_.get_phase_advances(currentInputPhase, previousInputPhase, omega, RA, RS, instPhaseAdv);
126+
127+
var currentInputMag = fftObject.magnitude;
128+
129+
var phasor_theta = new Array(currentInputPhase.length);
130+
_.identity_phase_locking(currentInputMag, currentInputPhase, previousOutputPhase, instPhaseAdv, phasor_theta);
131+
132+
out.real = new Array((phasor_theta.length-1)*2);
133+
out.imag = new Array((phasor_theta.length-1)*2);
134+
out.phase = new Array((phasor_theta.length-1)*2);
135+
out.magnitude = new Array((phasor_theta.length-1)*2);
136+
var doubleSize = (phasor_theta.length-1)*2;
137+
var sqrt = Math.sqrt; var cos = Math.cos;
138+
var sin = Math.sin; var atan2 = Math.atan2;
139+
140+
141+
for (var i=0; i<phasor_theta.length; i++) {
142+
var theta = phasor_theta[i];
143+
144+
var phasor_theta_real = cos(theta);
145+
var phasor_theta_imag = sin(theta);
146+
out.real[i] = phasor_theta_real * fftObject.real[i] - phasor_theta_imag * fftObject.imag[i];
147+
out.imag[i] = phasor_theta_real * fftObject.imag[i] + phasor_theta_imag * fftObject.real[i];
148+
out.phase[i] = atan2(out.imag[i], out.real[i]);
149+
out.magnitude[i] = sqrt(out.imag[i]*out.imag[i] + out.real[i]*out.real[i]);
150+
151+
if (i>0) {
152+
out.real[doubleSize-i] = out.real[i];
153+
out.imag[doubleSize-i] = -out.imag[i];
154+
out.phase[doubleSize-i] = atan2(out.imag[doubleSize-i], out.real[doubleSize-i]);
155+
out.magnitude[doubleSize-i] = sqrt(out.imag[doubleSize-i]*out.imag[doubleSize-i] + out.real[doubleSize-i]*out.real[doubleSize-i]);
156+
}
157+
158+
}
159+
160+
return;
161+
}
162+
163+
164+
this.process = function(inputFrame) {
165+
166+
var _ = this;
167+
168+
var __RS = _RS;
169+
var __RA = _RA;
170+
171+
// -----------------------------
172+
// ----------FFT STEP-----------
173+
// -----------------------------
174+
175+
var fftObject = {};
176+
var out = {};
177+
var processedFrame = [];
178+
179+
if (_first) {
180+
_.STFT(inputFrame, _framingWindow, _winSize, fftObject);
181+
_previousOutputPhase = fftObject.phase;
182+
_previousInputPhase = fftObject.phase;
183+
processedFrame = new Array(fftObject.real.length);
184+
_.ISTFT(fftObject.real, fftObject.imag, _framingWindow, true, processedFrame);
185+
} else {
186+
_.STFT(inputFrame, _framingWindow, Math.round(_winSize/2)+1, fftObject);
187+
_.pv_step_v2(fftObject, _previousInputPhase, _previousOutputPhase, _omega, __RA, __RS, out);
188+
_previousOutputPhase = out.phase;
189+
_previousInputPhase = fftObject.phase;
190+
processedFrame = new Array(out.real);
191+
_.ISTFT(out.real, out.imag, _framingWindow, true, processedFrame);
192+
}
193+
194+
_first = false;
195+
196+
197+
// -----------------------------
198+
// ------OVERLAP AND SLIDE------
199+
// -----------------------------
200+
var outputFrame = [];
201+
_.overlap_and_slide(__RS, processedFrame, _overlapBuffers, _winSize, outputFrame);
202+
var owFrame = [];
203+
_.overlap_and_slide(__RS, _squaredFramingWindow, _owOverlapBuffers, _winSize, owFrame);
204+
205+
for (var i=0; i<outputFrame.length; i++)
206+
outputFrame[i] = outputFrame[i] / ((owFrame[i]<10e-3)? 1 : owFrame[i]);
207+
208+
return outputFrame;
209+
210+
}
211+
212+
213+
214+
this.STFT = function(inputFrame, windowFrame, wantedSize, out) {
215+
var winSize = windowFrame.length;
216+
var _inputFrame = new Array(winSize);
217+
var fftFrame = new Array(2*winSize);
218+
219+
for (var i=0; i<winSize; i++) {
220+
_inputFrame[i] = inputFrame[i] * windowFrame[i];
221+
}
222+
var fft = new FFT.complex(winSize, false);
223+
fft.simple(fftFrame, _inputFrame, 'real');
224+
225+
out.real = new Array(Math.min(winSize,wantedSize));
226+
out.imag = new Array(Math.min(winSize,wantedSize));
227+
out.magnitude = new Array(Math.min(winSize,wantedSize));
228+
out.phase = new Array(Math.min(winSize,wantedSize));
229+
230+
for (var p=0; p<winSize && p<wantedSize; p++) {
231+
var real = out.real; var imag = out.imag;
232+
var phase = out.phase; var magnitude = out.magnitude;
233+
real[p] = fftFrame[2*p];
234+
imag[p] = fftFrame[2*p+1];
235+
magnitude[p] = Math.sqrt(imag[p]*imag[p] + real[p]*real[p]);
236+
phase[p] = Math.atan2(imag[p], real[p]);
237+
}
238+
239+
return;
240+
}
241+
242+
this.ISTFT = function(real, imaginary, windowFrame, restoreEnergy, output2) {
243+
var input = new Array(2 * real.length);
244+
var output1 = new Array(2 * real.length);
245+
246+
for (var i=0; i<real.length; i++) {
247+
input[2*i] = real[i];
248+
input[2*i+1] = imaginary[i];
249+
}
250+
251+
var ifft = new FFT.complex(real.length, true);
252+
253+
ifft.simple(output1, input, 'complex');
254+
255+
if (restoreEnergy) {
256+
var energy1 = 0;
257+
var energy2 = 0;
258+
var eps = 2.2204e-16;
259+
for (var i=0; i<windowFrame.length; i++) {
260+
energy1 += Math.abs(output1[2*i]);
261+
output2[i] = output1[2*i] / windowFrame.length;
262+
output2[i] *= windowFrame[i];
263+
energy2 += Math.abs(output1[2*i]);
264+
output2[i] *= energy1/(energy2+eps);
265+
}
266+
} else if (windowFrame) {
267+
for (var i=0; i<windowFrame.length; i++) {
268+
output2[i] = output1[2*i] / windowFrame.length;
269+
output2[i] *= windowFrame[i];
270+
}
271+
} else {
272+
for (var i=0; i<real.length; i++) {
273+
output2[i] = output1[2*i] / real.length;
274+
}
275+
}
276+
277+
return;
278+
}
279+
280+
281+
this.init = function() {
282+
283+
var _ = this;
284+
285+
_omega = _.create_omega_array(winSize);
286+
287+
_previousInputPhase = _.create_constant_array(winSize/2, 0);
288+
_previousOutputPhase = _.create_constant_array(winSize/2, 0);
289+
_framingWindow = _.create_sin_beta_window_array(winSize, 1);
290+
_squaredFramingWindow = _framingWindow.map(function(x,i){ return x*x; });
291+
292+
_overlapBuffers = _.create_constant_array(winSize, 0);
293+
294+
_owOverlapBuffers = _.create_constant_array(winSize, 0);
295+
296+
_.set_alpha(1);
297+
}
298+
299+
this.create_omega_array = function(size) {
300+
return Array.apply(null, Array(size/2 + 1)).map(function (x, i) {
301+
return 2 * Math.PI * i / size;
302+
});
303+
}
304+
305+
this.create_sin_beta_window_array = function(size, beta) {
306+
return Array.apply(null, Array(size)).map(function(x,i){
307+
return Math.pow(Math.sin(Math.PI*i/size), beta);
308+
});
309+
}
310+
311+
this.create_constant_array = function(size, constant) {
312+
return Array.apply(null, Array(size)).map(function () {
313+
return constant;
314+
});
315+
}
316+
317+
this.reset = function() {
318+
319+
var _ = this;
320+
321+
_previousInputPhase = _.create_constant_array(winSize/2, 0);
322+
_previousOutputPhase = _.create_constant_array(winSize/2, 0);
323+
324+
_overlapBuffers = _.create_constant_array(winSize, 0);
325+
_owOverlapBuffers = _.create_constant_array(winSize, 0);
326+
327+
_first = true;
328+
}
329+
330+
this.reset2 = function() {
331+
332+
var _ = this;
333+
334+
_previousInputPhase = _.create_constant_array(winSize/2, 0);
335+
_previousOutputPhase = _.create_constant_array(winSize/2, 0);
336+
337+
_first = true;
338+
}
339+
340+
341+
this.get_previous_input_phase = function() {
342+
return _previousInputPhase;
343+
}
344+
345+
this.get_previous_output_phase = function() {
346+
return _previousOutputPhase;
347+
}
348+
349+
this.get_analysis_hop = function() {
350+
return _RA;
351+
}
352+
353+
this.get_synthesis_hop = function() {
354+
return _RS;
355+
}
356+
357+
this.get_alpha = function() {
358+
return _RS / _RA;
359+
}
360+
361+
this.get_framing_window = function() {
362+
return _framingWindow;
363+
}
364+
365+
this.get_squared_framing_window = function() {
366+
return _squaredFramingWindow;
367+
}
368+
369+
this.set_alpha = function(newAlpha) {
370+
_RA = _winSize/4;
371+
_RS = Math.round(newAlpha * _RA);
372+
// _RS = Math.round(_winSize/2);
373+
// _RA = Math.round(_RS / newAlpha);
374+
}
375+
376+
this.get_alpha_step = function() {
377+
return 1/_RA;
378+
}
379+
380+
this.set_hops = function(RA, RS) {
381+
_RA = RA;
382+
_RS = RS;
383+
}
384+
}

0 commit comments

Comments
 (0)