Here's a version that uses linear interpolation (which is not great, and if you're using it for real projects, give me a shout and I'll do something better).
It's monophonic, using only the most recent note. If a new note is triggered before the release of the previous one has finished, that counts as a legato, and it will continue to use the buffer, sliding between notes.
Pitch is determined by MIDI note relative to the "reference note", which defaults to Middle C. If you have slowed down (so you have a buffer that's growing), using legato you can play back
faster than real-time using notes above Middle C, but when the buffer is empty it will snap back to realtime.
Pitch-bend is currently not supported, but could be if that's important to you. Polyphony would be a bit more fuss. If anyone else feels like adding better interpolation, go for it - even a 10-tap windowed sinc would be an improvement (probably fine to just ignore the latency, the attack cross-fade should make it OK).
Code:
desc:Live Downsampler (linear interpolation, monophonic)
slider1:reference_note=60<0,127,1>reference note
slider2:sampler_attack_ms=1<0.1,50,0.1>attack (ms)
slider3:sampler_release_ms=5<0.1,50,0.1>release (ms)
slider4:sampler_slide_ms=10<1,100,0.1>slide (ms)
@init
freemem = 0;
sample_buffer = freemem;
sample_write_index = 0;
sample_read_index = 0;
sampler_note = -1;
sampler_speed = 1;
sampler_target_speed = 1;
sampler_fade = 0;
@block
sampler_attack_samples = srate*0.001*sampler_attack_ms;
sampler_attack_step = 1/sampler_attack_samples;
sampler_release_samples = srate*0.001*sampler_release_ms;
sampler_release_step = 1/sampler_release_samples;
sampler_slide_samples = srate*0.001*sampler_slide_ms;
sampler_slide_factor = 1 - exp(-1/sampler_slide_samples);
while (midirecv(midi_offset, midi_msg1, midi_msg2, midi_msg3)) (
midi_type = midi_msg1>>4;
midi_channel = midi_msg1&0x0f;
(midi_type == 0x9 && midi_msg3 != 0) ? (
sampler_target_speed = pow(2, (midi_msg2 - reference_note)/12);
sampler_fade == 0 ? (
sampler_speed = sampler_target_speed;
sample_write_index = 0;
sample_read_index = 0;
);
sampler_note = midi_msg2;
) : (midi_type == $x8 || (midi_type == $x9 && midi_msg3 == 0)) ? (
sampler_note = -1;
);
);
@sample
sampler_speed += (sampler_target_speed - sampler_speed)*sampler_slide_factor;
// Fade in/out
sampler_note < 0 ? (
sampler_fade = max(0, sampler_fade - sampler_release_step);
) : (
sampler_fade = min(1, sampler_fade + sampler_attack_step);
);
sampler_fade > 0 ? (
sample_buffer[sample_write_index*2] = spl0;
sample_buffer[sample_write_index*2 + 1] = spl1;
// Linear sample interpolation
index0 = floor(sample_read_index);
ratio = sample_read_index - index0;
l0 = sample_buffer[index0*2];
l1 = sample_buffer[(index0 + 1)*2];
spl0 += (l0 + (l1 - l0)*ratio - spl0)*sampler_fade;
r0 = sample_buffer[index0*2 + 1];
r1 = sample_buffer[(index0 + 1)*2 + 1];
spl1 += (r0 + (r1 - r0)*ratio - spl1)*sampler_fade;
sample_write_index += 1;
sample_read_index = min(sample_write_index, sample_read_index + sampler_speed);
);
If you want it to always slide down from real-time (e.g. to make a tape-stop effect), change "sampler_speed = sampler_target_speed;" to "sampler_speed = 1;", and increase the "sampler_slide_ms" slider range up from 100.
Geraint