Skip to content

Commit 413e701

Browse files
committed
Add initial Vocoder version
1 parent 26c8d22 commit 413e701

File tree

5 files changed

+293
-0
lines changed

5 files changed

+293
-0
lines changed

Vocoder.jsfx

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
desc:Vocoder (by Geraint Luff)
2+
3+
slider1:window_ms=10<1,50>-window length (ms)
4+
slider2:smoothing_ms=5<0,20,1>-energy smoothing (ms)
5+
slider3:max_gain_db=40<0,100,1>-max band gain (dB)
6+
slider4:switch_order=0<0,1,1{main to aux, aux to main}>-max band gain (dB)
7+
8+
in_pin:main L
9+
in_pin:main R
10+
in_pin:aux L
11+
in_pin:aux R
12+
out_pin:out L
13+
out_pin:out R
14+
15+
import ui-lib.jsfx-inc
16+
filename:0,themes/bitmap-simple/theme-dark-blue.png
17+
// https://unsplash.com/photos/jXd2FSvcRr8
18+
filename:1,themes/backgrounds/umberto-jXd2FSvcRr8-unsplash.png
19+
20+
@init
21+
22+
gfx_ext_retina = 1;
23+
24+
max_gain = 100;
25+
26+
function setup_stft_state(fft_size, freemem) (
27+
bins = fft_size/2;
28+
main_energy_bins = freemem;
29+
aux_energy_bins = freemem + bins;
30+
31+
i = 0;
32+
loop(bins,
33+
main_energy[i] = 0;
34+
aux_energy[i] = 0;
35+
i += 1;
36+
);
37+
//////////////////////// Setup block
38+
// This is called when playback starts, or when the FFT size is changed
39+
0;
40+
////////////////////////
41+
);
42+
43+
function process_stft_segment(fft_buffer_main, fft_buffer_aux, fft_size) local(fft_bin, left_real, left_imag, right_real, right_imag) (
44+
interval_ms = fft_interval*1000/srate;
45+
main_slew = aux_slew = 1/(smoothing_ms/interval_ms + 1);
46+
47+
fft_bin = 0; // FFT bin number
48+
loop(fft_size/2,
49+
fft_bin2 = fft_bin ? (fft_size - fft_bin) : 0;
50+
51+
// Unfold complex spectrum into two real spectra
52+
left_real = fft_buffer_main[2*fft_bin] + fft_buffer_main[2*fft_bin2];
53+
left_imag = fft_buffer_main[2*fft_bin + 1] - fft_buffer_main[2*fft_bin2 + 1];
54+
right_real = fft_buffer_main[2*fft_bin + 1] + fft_buffer_main[2*fft_bin2 + 1];
55+
right_imag = -fft_buffer_main[2*fft_bin] + fft_buffer_main[2*fft_bin2];
56+
aux_left_real = fft_buffer_aux[2*fft_bin] + fft_buffer_aux[2*fft_bin2];
57+
aux_left_imag = fft_buffer_aux[2*fft_bin + 1] - fft_buffer_aux[2*fft_bin2 + 1];
58+
aux_right_real = fft_buffer_aux[2*fft_bin + 1] + fft_buffer_aux[2*fft_bin2 + 1];
59+
aux_right_imag = -fft_buffer_aux[2*fft_bin] + fft_buffer_aux[2*fft_bin2];
60+
61+
//////////////////////// Main STFT block
62+
input_energy = left_real*left_real + left_imag*left_imag + right_real*right_real + right_imag*right_imag;
63+
aux_energy = aux_left_real*aux_left_real + aux_left_imag*aux_left_imag + aux_right_real*aux_right_real + aux_right_imag*aux_right_imag;
64+
65+
main_energy_bins[fft_bin] += (input_energy - main_energy_bins[fft_bin])*main_slew;
66+
input_energy = main_energy_bins[fft_bin];
67+
aux_energy_bins[fft_bin] += (aux_energy - aux_energy_bins[fft_bin])*aux_slew;
68+
aux_energy = aux_energy_bins[fft_bin];
69+
70+
energy_ratio = aux_energy/(aux_energy/max_gain + input_energy + 0.000001);
71+
gain = sqrt(energy_ratio);
72+
73+
left_real *= gain;
74+
left_imag *= gain;
75+
right_real *= gain;
76+
right_imag *= gain;
77+
////////////////////////
78+
79+
// Re-fold back into complex spectrum
80+
fft_buffer_main[2*fft_bin] = (left_real - right_imag)*0.5;
81+
fft_buffer_main[2*fft_bin + 1] = (left_imag + right_real)*0.5;
82+
fft_buffer_main[2*fft_bin2] = (left_real + right_imag)*0.5;
83+
fft_buffer_main[2*fft_bin2 + 1] = (-left_imag + right_real)*0.5;
84+
85+
fft_bin += 1;
86+
);
87+
);
88+
89+
MAX_FFT_SIZE = 32768;
90+
91+
freemem = 0;
92+
freemem = ui_setup(freemem);
93+
freemem = (fft_buffer = freemem) + MAX_FFT_SIZE*2;
94+
freemem = (fft_buffer2 = freemem) + MAX_FFT_SIZE*2;
95+
freemem = (window_buffer = freemem) + MAX_FFT_SIZE;
96+
97+
buffer_length = MAX_FFT_SIZE;
98+
buffer_index = 0;
99+
freemem = (input_buffer = freemem) + buffer_length*2;
100+
freemem = (aux_buffer = freemem) + buffer_length*2;
101+
freemem = (output_buffer = freemem) + buffer_length*2;
102+
103+
function window(r) local(s, s2, gaussian_width, x) (
104+
// When squared, the Hann window adds up perfectly for overlap >= 4, so it's suitable for perfect reconstruction
105+
(0.5 - 0.5*cos(r*2*$pi))/sqrt(0.375);
106+
);
107+
108+
fft_size = 0;
109+
110+
@block
111+
112+
max_gain = pow(10, max_gain_db*0.05);
113+
114+
overlap_factor = 6;
115+
window_samples = min(ceil(window_ms*0.001*srate), MAX_FFT_SIZE);
116+
fft_size = 16;
117+
while (fft_size < window_samples) (
118+
fft_size *= 2;
119+
);
120+
fft_interval = ceil(window_samples/overlap_factor);
121+
window_samples = fft_size - 3;
122+
fft_scaling_factor = 1/window_samples*(fft_interval/fft_size);
123+
124+
window_samples != prev_window_samples ? (
125+
prev_window_samples = window_samples;
126+
// Fill window buffer
127+
i = 0;
128+
while (i < window_samples) (
129+
r = (i + 0.5)/window_samples;
130+
window_buffer[i] = window(r);
131+
i += 1;
132+
);
133+
while (i < fft_size) (
134+
window_buffer[i] = 0;
135+
);
136+
);
137+
138+
fft_size != prev_fft_size ? (
139+
setup_stft_state(fft_size, freemem);
140+
prev_fft_size = fft_size;
141+
);
142+
143+
pdc_delay = window_samples;
144+
pdc_bot_ch = 0;
145+
pdc_top_ch = 2;
146+
147+
@sample
148+
149+
input_buffer[buffer_index*2] = spl0;
150+
input_buffer[buffer_index*2 + 1] = spl1;
151+
aux_buffer[buffer_index*2] = spl2;
152+
aux_buffer[buffer_index*2 + 1] = spl3;
153+
154+
fft_counter += 1;
155+
fft_counter >= fft_interval ? (
156+
fft_counter = 0;
157+
158+
// Copy input to buffer
159+
bi = buffer_index - window_samples;
160+
i = 0;
161+
loop(window_samples,
162+
i2 = bi + i;
163+
i2 < 0 ? i2 += buffer_length;
164+
165+
// main
166+
fft_buffer[2*i] = input_buffer[2*i2]*window_buffer[i];
167+
fft_buffer[2*i + 1] = input_buffer[2*i2 + 1]*window_buffer[i];
168+
// aux
169+
fft_buffer2[2*i] = aux_buffer[2*i2]*window_buffer[i];
170+
fft_buffer2[2*i + 1] = aux_buffer[2*i2 + 1]*window_buffer[i];
171+
172+
i += 1;
173+
);
174+
loop(fft_size - window_samples,
175+
fft_buffer[2*i] = 0;
176+
fft_buffer[2*i + 1] = 0;
177+
fft_buffer2[2*i] = 0;
178+
fft_buffer2[2*i + 1] = 0;
179+
i += 1;
180+
);
181+
182+
// Process buffer
183+
fft(fft_buffer, fft_size);
184+
fft_permute(fft_buffer, fft_size);
185+
fft(fft_buffer2, fft_size);
186+
fft_permute(fft_buffer2, fft_size);
187+
188+
switch_order ? (
189+
out_fft_buffer = fft_buffer2;
190+
process_stft_segment(fft_buffer2, fft_buffer, fft_size);
191+
) : (
192+
out_fft_buffer = fft_buffer;
193+
process_stft_segment(fft_buffer, fft_buffer2, fft_size);
194+
);
195+
fft_ipermute(out_fft_buffer, fft_size);
196+
ifft(out_fft_buffer, fft_size);
197+
198+
// Add to output
199+
i = 0;
200+
loop(window_samples,
201+
i2 = buffer_index + i;
202+
(i2 >= buffer_length) ? i2 -= buffer_length;
203+
204+
output_buffer[2*i2] += out_fft_buffer[2*i]*fft_scaling_factor*window_buffer[i];
205+
output_buffer[2*i2 + 1] += out_fft_buffer[2*i + 1]*fft_scaling_factor*window_buffer[i];
206+
207+
i += 1;
208+
);
209+
);
210+
211+
output_index = buffer_index;
212+
spl0 = output_buffer[output_index*2];
213+
spl1 = output_buffer[output_index*2 + 1];
214+
output_buffer[output_index*2] = 0; // clear the sample we just read
215+
output_buffer[output_index*2 + 1] = 0;
216+
217+
buffer_index = (buffer_index + 1)%buffer_length;
218+
219+
@gfx 420 150
220+
221+
function labelled_dial(value, low, high, bias, default, label, format) (
222+
ui_push_height(50);
223+
value = control_dial(value, low, high, bias, default);
224+
ui_pop();
225+
ui_push_above(50);
226+
ui_text(label);
227+
ui_pop();
228+
ui_push_below(50);
229+
ui_textnumber(value, format);
230+
ui_pop();
231+
value;
232+
);
233+
234+
function labelled_switch(value, label, valueText) (
235+
ui_push_height(35);
236+
ui_push_width(60);
237+
value = control_switch(value);
238+
ui_pop();
239+
ui_pop();
240+
ui_push_above(50);
241+
ui_text(label);
242+
ui_pop();
243+
ui_push_below(50);
244+
ui_text(valueText);
245+
ui_pop();
246+
value;
247+
);
248+
249+
control_start("main", "bitmap-simple", 0);
250+
251+
gfx_a = 0.8;
252+
g_scale = max(gfx_w/450, gfx_h/300);
253+
gfx_x = (gfx_w - 450*g_scale)*0.5;
254+
gfx_y = (gfx_h - 300*g_scale)*0.5;
255+
gfx_mode = 2;
256+
gfx_blit(1, g_scale, 0);
257+
gfx_mode = 0;
258+
259+
ui_screen() == "main" ? (
260+
ui_split_leftratio(1/4);
261+
ui_automate(window_ms, labelled_dial(window_ms, 1, 50, 2, 10, "window", "%i ms"));
262+
ui_split_next();
263+
ui_automate(smoothing_ms, labelled_dial(smoothing_ms, 0, 20, 2, 5, "smoothing", "%i ms"));
264+
ui_split_next();
265+
ui_automate(max_gain_db, labelled_dial(max_gain_db, 0, 100, 2, 40, "max gain", "%i dB"));
266+
ui_split_next();
267+
ui_automate(switch_order, labelled_switch(switch_order, "swap inputs", switch_order ? "on" : "off"));
268+
ui_pop();
269+
) : control_system();

index.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,9 @@ <h1>Effects</h1>
215215
[*][url=https://geraintluff.github.io/jsfx#MIDI%20Gate]MIDI Gate[/url] - a velocity-sensitive MIDI-controlled gate. Audio passes through only when MIDI note is down (or up, in &quot;mute&quot; mode).
216216
[*][url=https://geraintluff.github.io/jsfx#MIDI%20Harmony]MIDI Harmony[/url] - arpeggiator and auto-accompaniment plugin[/list]
217217
218+
[b]Misc[/b]
219+
[list][*][url=https://geraintluff.github.io/jsfx#Vocoder]Vocoder[/url] - Misc effect[/list]
220+
218221
[b]Pitch Correction[/b]
219222
[list][*][url=https://geraintluff.github.io/jsfx#Warble]Warble[/url] - a basic pitch-editing effect[/list]
220223
@@ -260,6 +263,7 @@ <h3>MIDI</h3><ul><li><a href="#Droplets">Droplets</a> - randomly play the notes
260263
</li>
261264
<li><a href="#MIDI Harmony">MIDI Harmony</a> - arpeggiator and auto-accompaniment plugin
262265
</li></ul>
266+
<h3>Misc</h3><ul><li><a href="#Vocoder">Vocoder</a> - Misc effect</li></ul>
263267
<h3>Pitch Correction</h3><ul><li><a href="#Warble">Warble</a> - a basic pitch-editing effect
264268
</li></ul></div>
265269
<!--/reapack:nav-->
@@ -410,6 +414,7 @@ <h3>Pitch Correction</h3><ul><li><a href="#Warble">Warble</a> - a basic pitch-ed
410414
<p>The &quot;Lookahead&quot; dial controls how much latency is added. With this at 0, sometimes the start of a note can sometimes be momentarily detuned because of ongoing vibrato. To prevent this completely, turn the dial to its maximum value (which scales according to the LFO Rate value).</p>
411415
<p><em>(background by Tom Barrett on <a href="https://unsplash.com/photos/-bSucp2nUdQ">Unsplash</a>)</em></p>
412416
</div>
417+
<div class="reapack-package" id="Vocoder"><h1>Vocoder</h1></div>
413418
<div class="reapack-package reapack-package-with-links" id="Warble"><div class="reapack-links"><audio controls preload="none" src="audio-demos/warble-formant-mod.mp3"></audio><audio controls preload="none" src="audio-demos/warble-original.mp3"></audio><audio controls preload="none" src="audio-demos/warble-shifted.mp3"></audio><audio controls preload="none" src="audio-demos/warble-subtle.mp3"></audio></div><h1 id="warble">Warble</h1>
414419
<p>Warble is a relatively basic pitch-editing plugin. It analyses incoming audio, stores and displays it on a zoomable graph (middle mouse and scroll wheel). (demo: <a href="audio-demos/warble-original.mp3">original</a>, <a href="audio-demos/warble-subtle.mp3">minor correction</a>, <a href="audio-demos/warble-shifted.mp3">major alterations</a>, <a href="audio-demos/warble-formant-mod.mp3">formant LFO</a>)</p>
415420
<p>There are three tools: nudge, erase and smooth. You use these tools with the mouse (left/right buttons do different things) to define how much it should be shifted. The current change amount is displayed in red.</p>

index.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1965,5 +1965,9 @@
19651965
<metadata>
19661966
</metadata>
19671967
</reapack>
1968+
<reapack name="Vocoder" type="effect">
1969+
<metadata>
1970+
</metadata>
1971+
</reapack>
19681972
</category>
19691973
</index>

reapack.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,21 @@
625625
}
626626
},
627627
"version": "1.3.4"
628+
},
629+
"Vocoder": {
630+
"type": "effect",
631+
"category": "Misc",
632+
"files": {
633+
"Vocoder.jsfx": {
634+
"main": false
635+
},
636+
"themes/bitmap-simple/theme-dark-blue.png": {
637+
"main": false
638+
},
639+
"themes/backgrounds/umberto-jXd2FSvcRr8-unsplash.png": {
640+
"main": false
641+
}
642+
}
628643
}
629644
},
630645
"url": "https://geraintluff.github.io/jsfx",
204 KB
Loading

0 commit comments

Comments
 (0)