|
|
|
09-20-2016, 06:03 PM
|
#1
|
Human being with feelings
Join Date: Dec 2014
Posts: 371
|
Novel buffer effect idea
I have an idea for a relatively simple (I think?) but potentially useful effect and I wonder if anyone would like to take a crack at it. If it's not too crass to suggest, I would be willing do donate to see it made...
Parameters:
Buffer size (in beats?)
Buffer playback rate (say .125 to 1)
MIDI trigger note
So, incoming audio is fed into the buffer. When the midi trigger note is recieved, the buffer starts playing back from that instant at the set rate. So if for example the rate is set to .5, and I play a note on my bass and hit the trigger simultaneously, I'll hear the same note coming out at half speed/an octave down right? Maybe there could be a setting to determine what happens when the buffer runs out - loop/ping pong/stop. When I hit the trigger again, the buffer re-starts from that instant. Am I making sense?? Aside from resampling-based harmonising, this could be used to swing incoming audio by feeding it steady 8th notes with the buffer rate set to .75 ...these are the kinds of things I like to do with a sampler but this way they could be applied to any incoming audio in real time. Could be very cool and I don't think it exists!
|
|
|
09-22-2016, 09:12 AM
|
#2
|
Human being with feelings
Join Date: Nov 2009
Location: mostly inside my own head
Posts: 346
|
Could be interesting - I might have a go.
Just to clarify - the buffer would in fact be empty until a MIDI note triggers it? So when a note is triggered, it starts filling the buffer while simultaneously playing it back at a different speed. When the buffer is full it stops collecting, and (some time later) when the playback reaches the end something happens to loop/continue the sound.
|
|
|
09-22-2016, 09:16 AM
|
#3
|
Human being with feelings
Join Date: Dec 2014
Posts: 371
|
Yes! That makes more sense than what I wrote. I wonder if the buffer even needs a predetermined length in that case, although it could still be fun to have the option...
Edit: I guess it would, otherwise it would fill up forever?
|
|
|
09-22-2016, 09:31 AM
|
#4
|
Human being with feelings
Join Date: Nov 2009
Location: mostly inside my own head
Posts: 346
|
Yes, the buffer length needs to have some maximum length, determined up-front.
However, there's another setup which might work quite well: What if the recording keeps looping around the buffer until it's about to overtake the playback position?
To illustrate, let's say that our buffer is one second long. When a note is pressed, we start recording that buffer, and also start playing it back at 0.5x speed. After 1 second, the playback is 0.5s behind the note - so we can actually keep recording for another full second, and only need to loop after 2 seconds. This means that the "loop" portion would then be looping the second second of the input audio, not the first, which might give a better "sustain" sound.
|
|
|
09-22-2016, 09:42 AM
|
#5
|
Human being with feelings
Join Date: Dec 2014
Posts: 371
|
For how I work, I'm less concerned with sustaining sounds vs. keeping things rhythmically related, but if you're gonna take a crack at it I say do whatever makes sense to you, please!
|
|
|
09-22-2016, 09:52 AM
|
#6
|
Human being with feelings
Join Date: Dec 2014
Posts: 371
|
Oooh, it just occurred to me that if the rate could be adjusted in real time one could do something like this on the fly, pitching only down but still... https://youtu.be/EyYKs9RXMBI
|
|
|
09-22-2016, 01:00 PM
|
#7
|
Human being with feelings
Join Date: Nov 2009
Location: mostly inside my own head
Posts: 346
|
v1
Alright, here's something - the algorithm is:
Algorithm
There is a circular buffer. When a note is pressed down, the buffer starts being filled. The buffer also starts being played. When either the write-position is about to catch up with the read-position, or sufficient time has elapsed, writing stops, and the buffer then loops endlessly until note-off.
There is a 5ms fade-in/fade-out when playback starts/stops. None of the original signal is preserved - if you want both, use REAPER's dry/wet slider.
Controls
A full second of buffer is allocated, but only some of it is used, depending on the "loop length" slider. Shorter buffers might be useful if your input sound dies away, in which case a full second of looped audio will have noticeable volume variation.
When the writing stops, it doesn't stop immediately, it "fades away" so that the looped buffer smoothly transitions. How long this takes is what the "cross-fade" slider determines - so if it's at 30%, then this transition takes 30% of your loop.
The "maximum attack" slider determines the minimum amount of time before the buffer starts looping. Again, this is useful if your input sound dies away quickly, so that the loop captures the sound while it's still playing.
The detuning amount depends on the note - middle C is neutral, everything lower than that is proportional. If you press down one note before the previous one is finished, it changes the pitch ratio without resetting the buffer.
I should add a constant-ratio detuning slider as well, so you can do mathematical ratios, but I haven't yet.
Code:
desc:Live Resampler
slider1:300<10,1000,1>Loop length(ms)
slider2:30<1,100,1>Cross-fade (%)
slider3:1000<100,2000,1>Attack (ms)
slider3:500<100,2000,1>Maximum attack (ms)
@init
buffer_max = srate; // Max 1s loop
buffer_left = 0;
buffer_right = buffer_left + buffer_max;
buffer_end = buffer_right + buffer_max;
@slider
buffer_length = min(buffer_max, floor(slider1/1000*srate));
buffer_read = 0;
buffer_readpos = 0;
buffer_write = 0;
buffer_writepos = 0;
buffer_speed = 0.5;
buffer_amp = 0;
current_note = 0;
fadeout_samples = min(buffer_length*slider2/100, buffer_length/2);
fadein_samples = min(0.005*srate, buffer_length/2);
max_attack_samples = slider3/1000*srate + buffer_length - fadeout_samples;
@block
while (midirecv(offset, msg1, msg2, msg3)) (
msg1 == $x90 && msg3 != 0 ? (
!current_note || offset >= note_end_offset ? (
samples_from_start = 0;
buffer_writepos = buffer_readpos;
buffer_write = 1;
buffer_read = 1;
current_note = msg2;
buffer_speed = min(1, pow(2, (current_note - 60)/12));
) : (
current_note = msg2;
buffer_speed = min(1, pow(2, (current_note - 60)/12));
);
note_end_offset = srate*60*60*24;
): msg1 == $x80 || (msg1 == $x90 && msg3 == 0) ? (
current_note == msg2 ? (
note_end_offset = offset;
);
) : msg1 == $xB0 ? (
// MIDI controllers: note/sound off
msg2 == 123 || msg2 == 120 ? (
note_end_offset = offset;
);
);
);
@sample
note_end_offset <= -fadein_samples ? (
buffer_read = 0;
current_note = 0;
);
buffer_write ? (
samples_from_start += 1;
buffer_writepos += 1;
while (buffer_writepos >= buffer_length) (
buffer_writepos -= buffer_length;
);
diff = (buffer_writepos - buffer_readpos + buffer_length);
while (diff > buffer_length/2) (
diff -= buffer_length;
);
write_amp = 1;
diff > 0 && diff < 1 ? ( // Stop if we've just crossed the boundary
// Note: this check is performed when the write position has been incremented, but the read position has not
// So at the very start, diff will be == 1, so the exact comparison is important
buffer_write = 0;
write_amp = 0;
) : diff <= 0 ? ( // Fade out if we're getting close to the read position
diff > -fadeout_samples ? (
write_amp = -diff/fadeout_samples;
);
) : samples_from_start >= max_attack_samples ? ( // Stop writing if we reach our attack limit
write_amp = 1 - (samples_from_start - max_attack_samples)/fadeout_samples;
write_amp <= 0 ? (
buffer_write = 0;
write_amp = 0;
);
);
buffer_left[buffer_writepos] += (spl0 - buffer_left[buffer_writepos])*write_amp;
buffer_right[buffer_writepos] += (spl1 - buffer_right[buffer_writepos])*write_amp;
diff_seconds = diff/srate;
);
buffer_readpos += buffer_speed;
while (buffer_readpos >= buffer_length) (
buffer_readpos -= buffer_length;
);
lowindex = floor(buffer_readpos);
highindex = (lowindex + 1)%buffer_length;
ratio = buffer_readpos - lowindex;
maxindex = max(maxindex, lowindex);
buffer_left[lowindex] == 0 ? (
debug.zeroindex = lowindex;
);
envelope_ratio = min(1, samples_from_start/fadein_samples);
envelope_ratio *= min(1, note_end_offset/fadein_samples + 1);
value = buffer_left[lowindex];
value += (buffer_left[highindex] - value)*ratio;
spl0 = value*buffer_read*envelope_ratio;
value = buffer_right[lowindex];
value += (buffer_right[highindex] - value)*ratio;
spl1 = value*buffer_read*envelope_ratio;
note_end_offset -= 1;
If you want, I can stick a nice UI on it so you can see the buffer that's been captured, but it's OK for now.
|
|
|
09-22-2016, 01:13 PM
|
#8
|
Human being with feelings
Join Date: Nov 2009
Location: mostly inside my own head
Posts: 346
|
So, some example parameters:
If you're looking to slow down something rhythmic, and you're not interested in loop/sustain, then you'll want the longest loop length possible, and a fairly short cross-fade (because you hope to never *reach* the cross fade). "Maximum attack" should also be set as long as possible.
If you're looking to sustain something, you will want a longer cross-fade. If your instrument naturally dies away (like a guitar) then you might want a shorter loop and a short-ish "maximum attack", so that the loop from early on in the note.
If you're looking to sustain something that *doesn't* die away (such a voice or instruments like flute/cello/horn) then you can have a longer loop and longer maximum attack, but you probably still want a reasonable amount of cross-fade.
Again, this would be clearer to explain if I gave it a nice UI, but I'll maybe do that later.
|
|
|
09-22-2016, 01:47 PM
|
#9
|
Human being with feelings
Join Date: Dec 2014
Posts: 371
|
Damn, this is going to be fun, thanks!! https://youtu.be/deglSA7qnzs
|
|
|
Thread Tools |
|
Display Modes |
Linear Mode
|
Posting Rules
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
HTML code is Off
|
|
|
All times are GMT -7. The time now is 11:18 PM.
|