-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbrumbear_LiveBeatRepeater.jsfx
299 lines (255 loc) · 11.9 KB
/
brumbear_LiveBeatRepeater.jsfx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
desc: LiveBeatRepeater
author: brumbear
version: 1.03
changelog:
Added Features
- Additional Loop Lengths ("Tripplets" and as short as 1/128)
- Logarithmic scaling of continuous loop length slider
link: Forum thread https://forum.cockos.com/showthread.php?t=211834
about:
# LiveBeatRepeater
JSFX for live performance stutter effects. Zero latency. Synchronized to project tempo and time signature. Works well with linear songs as well as loop based material. Mashes up nicely your rhythms on drum tracks/percussion. Great toy on voices.
What makes this fx different from the classical looper or "instant" samplers is the fact that it constantly samples the input and repeats what you just heard when you trigger the repeat mode. I.e. you do not have to trigger recording a loop blindly hoping it will be what you want, but you repeat what was just played.
Most fun if controlled via a MIDI/OSC hardware device (pad controller, touch screen device). E.g. Repeat On-Off to a pad, continuous Loop Length to a rotary encoder (relative mode) and stepped Loop Length to dedicated pads (one for each loop length).
Separate loop audio routing allows to feed the repeating loop into any custom fx chain. Resonant HP/LP filters with variable cut off frequency, ping pong delays, distortion etc work particularly well with the stutter and allow spontaneous live (or automated) build ups.
// This effect Copyright (C) 2018 and later Pacific Peaks Studio
// License: GPLv3 - http://www.gnu.org/licenses/gpl
// desc: LiveBeatRepeater (brumbear@pacific_peaks)
// author: brumbear @ pacific peaks
// 2020-06-09: Release V1.03
//------------------------------------------------------------------------------------------------
slider1:0<0,1,1{Off,On}>Repeat
slider2:5<0,13,1{2,1,1T(=2/3),1/2,1/2T(=1/3),1/4,1/4T(=1/6),1/8,1/8T(=1/12),1/16,1/16T(=1/24),1/32, 1/64, 1/128}>Loop Length [bars] (Stepped Mode)
// Continuous Loop Length will be reset to closest stepped Loop Length when Repeat is switched from ON to OFF unless Transition is set to "Immediately"
slider3:48<0,128,0.5>Loop Length (Continuous Mode)
slider4:1<0,3,1{Immediately,At Next Beat,At Next Bar}>Loop Start
slider5:2<0,2,1{Immediately,At Next Beat,At Loop End}>Length Transitions (Stepped Mode)
slider6:1<0,1,1{Immediately,Soft Fade Out}>Repeat Ending
// chose "Separate Loop Channels" to route loop via other (external) effect plugins
slider7:0<0,1,1{Combined Main Output,Loop on Separate Fx Output only}>Fx Channel Routing
in_pin:left input
in_pin:right input
out_pin:left main output
out_pin:right main output
out_pin:left loop fx output
out_pin:right loop fx output
//------------------------------------------------------------------------------------------------
@init
log2 = log(2);
function loop_length_slider2(n) // returns the inverse of the fractional loop length of a bar (e.g. 4 for 1/4bar), see values for slider2
(
n == 0 ? 0.5 // slider3 = 0
: n == 1 ? 1 // slider3 = 16
: n == 2 ? 1.5 // slider3 = 25.36
: n == 3 ? 2 // slider3 = 32
: n == 4 ? 3 // etc... calculation as per below function slider3_from_slider2
: n == 5 ? 4
: n == 6 ? 6
: n == 7 ? 8
: n == 8 ? 12
: n == 9 ? 16
: n == 10 ? 24
: n == 11 ? 32
: n == 12 ? 64
: n == 13 ? 128 // 128
: 0;
);
function slider3_from_slider2(x)
// calculate the exact value for continuous loop length with double float precision
// the exact value of slider 3 will later be used to calculated the loop size [samples]
(
(log(loop_length_slider2(x))/log2 + 1) * 16;
);
function Nearest_Loop_Length(i) // aka "slider2_from_slider3", returns slider2 index from slider3 value
(
i <= 8 ? 0 // midpoint between slider3 = 0 and slider3 = 16
: i <= 20.68 ? 1 // midpoint between slider3 = 16 and slider3 = 25.36
: i <= 28.68 ? 2 // etc...
: i <= 36.68 ? 3
: i <= 44.68 ? 4
: i <= 52.68 ? 5
: i <= 60.68 ? 6
: i <= 68.68 ? 7
: i <= 76.68 ? 8
: i <= 84.68 ? 9
: i <= 92.68 ? 10
: i <= 104 ? 11
: i <= 120 ? 12
: 13;
);
// need some defaults when sliders have not been touched yet, no playback or just input monitoring
default_samples_per_bar = 96000; // default values at 120bpm, 4/4 signature and 48kHz sample rate
samples_per_bar = default_samples_per_bar;
// create a buffer hosting 2 sections: track ring buffer and loop copy buffer
buf_size = 5 * samples_per_bar; //track ring buffer has to be > 2x max loop length sample
buf0 = 0; buf1 = buf0 + buf_size;
buf_pos_WRITE = 0; // position in track ring buffer that is being filled with current track samples
// buf0[loop_copy_offset + pos] and buf1[loop_copy_offset + pos] store a copy of the max length loop portion from track ring buffer.
loop_copy_offset = 2 * buf_size;
loop_pos_COPY = 0;
loop_copied = 0; // flag to indicate that the max length loop portion (= 2 bars) has been fully copied
// end of default values
loop_size = 0; // repeat loop length
loop_start = 0; // start position of loop within track ring buffer
loop_end = buf_size; // end position of loop within track ring buffer. Frozen when repeat mode is switched on.
loop_overflow = 0; // flag to indicate if loop stretches beyond buffer end, i.e. loop_end < loop_start
loop_pos_READ = 0; // position in buffer that will be played back when repeat is on
loop_trigger_beat_pos = 0;
play_loop = 0;
next_loop_start = 0;
next_loop_overflow = 0;
next_loop_trigger_beat_pos = 0;
last_slider1 = 0;
last_slider2 = 5;
last_slider3 = 48;
immediate_transition_override = 0; // flag to indicate if we are in Continuos Mode (NOT discrete loop length)
//------------------------------------------------------------------------------------------------
@slider
// adjust buffer size to follow tempo changes
(ts_num > 0) && (tempo > 0) ? (
samples_per_bar = floor(ts_num*(srate/tempo)*60 + 0.5); // ts_num is equal to beats per bar)
):(
samples_per_bar = default_samples_per_bar;
);
buf_size = 5*samples_per_bar;
buf0 = 0; buf1 = buf0 + buf_size;
loop_copy_offset = 2 * buf_size;
// synchronize loop length sliders
(last_slider3 != slider3) && (last_slider2 == slider2) ? (
slider2 = Nearest_Loop_Length(slider3);
last_slider2 = slider2;
last_slider3 = slider3;
immediate_transition_override = 1;
):(
last_slider2 != slider2 ? (
slider3 = slider3_from_slider2(slider2);
last_slider2 = slider2;
last_slider3 = slider3;
immediate_transition_override = 0;
);
);
// adjust loop start position when loop length changes
loop_size = floor(samples_per_bar /(2^(slider3/16-1)));
(slider5 == 1) && (immediate_transition_override == 0) ? (
// loop start point may only be updated at next beat
next_loop_trigger_beat_pos = floor(beat_position + 1);
next_loop_start = loop_end - loop_size;
next_loop_start <0 ? (next_loop_start += buf_size; next_loop_overflow = 1;) : (next_loop_overflow = 0);
):(
// update the loop start position
loop_start = loop_end - loop_size;
loop_start <0 ? (loop_start += buf_size; loop_overflow = 1;) : (loop_overflow = 0);
);
// check if repeat switched to ON
slider1 > last_slider1 ? (
slider4 == 0 ? loop_trigger_beat_pos = beat_position
: slider4 == 1 ? loop_trigger_beat_pos = floor(beat_position + 1)
: loop_trigger_beat_pos = (floor(beat_position/ts_num) + 1) * ts_num
);
// check if repeat switched to OFF
slider1 < last_slider1 ? (
play_loop = 0;
immediate_transition_override = 0;
slider5 != 0 ? (
// revert back to Stepped Mode and set slider 3 to closest stepped length unless transition was set to "Immediately"
slider3 = slider3_from_slider2(slider2);
last_slider2 = slider2;
last_slider3 = slider3;
);
slider6 ? ( // fade out requested?
loop_pos_READ <= loop_end ? (
remaining_loop_length = loop_end - loop_pos_READ;
):(
remaining_loop_length = buf_size - loop_pos_READ + loop_end;
);
remaining_loop_length == 0 ? ( // no need for fade out as we are exactly at the loop end
fade_vol = 0;
):(
fade_step = 1/remaining_loop_length;
fade_vol =1;
);
);
);
last_slider1 = slider1;
@sample
//------------------------------------------------------------------------------------------------
// Continously fill the track ring buffer with the audio input
buf0[buf_pos_WRITE] = spl0; buf1[buf_pos_WRITE] = spl1;
//------------------------------------------------------------------------------------------------
play_loop ? (
// Copy the max length loop portion (2 bars) of the track ring section to the loop_copy section of the buffer
loop_copied == 0 ? (
buf0[loop_copy_offset + loop_pos_COPY] = buf0[loop_pos_COPY];
buf1[loop_copy_offset + loop_pos_COPY] = buf1[loop_pos_COPY];
loop_pos_COPY += 1; loop_pos_COPY >= buf_size ? loop_pos_COPY = 0;
loop_pos_COPY == loop_end ? loop_copied = 1;
);
// check if there is the need to update the loop start position
(slider5 == 1) && (immediate_transition_override == 0) ? (
loop_start != next_loop_start ? (
beat_position >= next_loop_trigger_beat_pos ? (
loop_start = next_loop_start;
loop_overflow = next_loop_overflow;
loop_pos_READ = loop_start;
);
);
);
// play back the loop & mute what is coming from the input channels
spl2 = buf0[loop_copied*loop_copy_offset + loop_pos_READ]; spl3 = buf1[loop_copied*loop_copy_offset + loop_pos_READ];
Slider7 ? (
spl0 = 0; spl1 = 0;
):(
spl0 = spl2; spl1 = spl3;
);
// advancing the loop READ position depending on specific settings
(slider5 == 2) && (immediate_transition_override == 0) ? (
//loop must play till end before READ position may be set to current loop start position
loop_pos_READ == loop_end ? (
loop_pos_READ = loop_start;
):(
loop_pos_READ += 1; loop_pos_READ >= buf_size ? loop_pos_READ = 0;
);
):(
// loop READ position moves to (new) start position immediately if it has reached the loop end or if loop length has been shortened while playing
loop_pos_READ += 1; loop_pos_READ >= buf_size ? loop_pos_READ = 0;
loop_overflow == 0 ? (
(loop_pos_READ > loop_end)||(loop_pos_READ < loop_start) ? loop_pos_READ = loop_start;
):(
(loop_pos_READ > loop_end)&&(loop_pos_READ < loop_start) ? loop_pos_READ = loop_start;
);
);
//------------------------------------------------------------------------------------------------
):(
// No continuous loop playback from here
// check if loop playback requested and we are ready to go
slider1 ? (
beat_position >= loop_trigger_beat_pos ? (
// freeze the loop end point at the current position, set loop start point and flag to play the loop
loop_end = buf_pos_WRITE;
loop_start = loop_end - loop_size;
loop_start <0 ? (loop_start += buf_size; loop_overflow = 1;) : (loop_overflow = 0);
next_loop_start = loop_start; next_loop_overflow = loop_overflow;
loop_pos_READ = loop_start;
loop_pos_COPY = loop_end - 2*samples_per_bar; // start copy at max length that the looping section can have
loop_pos_COPY <0 ? loop_pos_COPY += buf_size;
loop_copied = 0;
play_loop = 1;
);
);
// is there a loop fading out?
fade_vol > 0 ? (
track_X_vol = sqrt(1-fade_vol); loop_X_vol = sqrt(fade_vol); // equal power cross fade
spl2 = buf0[loop_copied*loop_copy_offset+loop_pos_READ]*loop_X_vol; spl3 = buf1[loop_copied*loop_copy_offset+loop_pos_READ]*loop_X_vol;
slider7 ? (
spl0 = spl0*track_X_vol; spl1 = spl1*track_X_vol;
):(
spl0 = spl0*track_X_vol + spl2; spl1 = spl1*track_X_vol + spl3;
);
fade_vol -= fade_step;
loop_pos_READ += 1; loop_pos_READ >= buf_size ? loop_pos_READ = 0;
);
);
//------------------------------------------------------------------------------------------------
// Move buffer write position forward
buf_pos_WRITE += 1; buf_pos_WRITE >= buf_size ? buf_pos_WRITE = 0;
//------------------------------------------------------------------------------------------------