Old 09-20-2016, 06:03 PM   #1
preferred.nomenclature
Human being with feelings
 
Join Date: Dec 2014
Posts: 371
Default 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!
preferred.nomenclature is offline   Reply With Quote
Old 09-22-2016, 09:12 AM   #2
geraintluff
Human being with feelings
 
geraintluff's Avatar
 
Join Date: Nov 2009
Location: mostly inside my own head
Posts: 346
Default

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.
geraintluff is offline   Reply With Quote
Old 09-22-2016, 09:16 AM   #3
preferred.nomenclature
Human being with feelings
 
Join Date: Dec 2014
Posts: 371
Default

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?
preferred.nomenclature is offline   Reply With Quote
Old 09-22-2016, 09:31 AM   #4
geraintluff
Human being with feelings
 
geraintluff's Avatar
 
Join Date: Nov 2009
Location: mostly inside my own head
Posts: 346
Default

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.
geraintluff is offline   Reply With Quote
Old 09-22-2016, 09:42 AM   #5
preferred.nomenclature
Human being with feelings
 
Join Date: Dec 2014
Posts: 371
Default

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!
preferred.nomenclature is offline   Reply With Quote
Old 09-22-2016, 09:52 AM   #6
preferred.nomenclature
Human being with feelings
 
Join Date: Dec 2014
Posts: 371
Default

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
preferred.nomenclature is offline   Reply With Quote
Old 09-22-2016, 01:00 PM   #7
geraintluff
Human being with feelings
 
geraintluff's Avatar
 
Join Date: Nov 2009
Location: mostly inside my own head
Posts: 346
Default 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.
geraintluff is offline   Reply With Quote
Old 09-22-2016, 01:13 PM   #8
geraintluff
Human being with feelings
 
geraintluff's Avatar
 
Join Date: Nov 2009
Location: mostly inside my own head
Posts: 346
Default

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.
geraintluff is offline   Reply With Quote
Old 09-22-2016, 01:47 PM   #9
preferred.nomenclature
Human being with feelings
 
Join Date: Dec 2014
Posts: 371
Default

Damn, this is going to be fun, thanks!! https://youtu.be/deglSA7qnzs
preferred.nomenclature is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 11:18 PM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.