Plug'n'Script - Midi Delay

Official support for: bluecataudio.com
Post Reply New Topic
RELATED
PRODUCTS

Post

Hello,

Firstly, I'm really excited about this product! Great job, Blue Cat!

I've been using an old version of EnergyXT and a lot of MIDI VST2 plugins in order to create customized MIDI processing chains. Some of these are quite complex, so the thought of making them more simple and resource friendly by incorporating those into scripts is a very interesting alternative for me.

Having said that, I've built my first script, saving about 30 instances of other 'building-block' VST (e.g. pizmidi) and it works great.

Now coming to my question: the next thing I would need is the ability to push some midi events to the output, but with some delay: think about creating some sort of strumming effect.

Is there any way to create a Midi Delay in a script, where the input parameter is the delay time in ms?

A push method which would include some delay would be interesting (but maybe not efficient).
I've studied the way you make delays for audio, and it involves calculating the delay in number of samples, based on Sample Rate and the delay time. Then it involves a circular sample buffer which helps `looking into the past`. I'm thinking to use this approach to build my MIDI delay, but have not been very successful, because I want to deal with midi events instead of samples in the audio buffer.

Do you have any hints?
I think it would also be nice to add a Midi Delay script as an example tutorial along with your other example scripts!

Thanks in advance for your feedback!

Post

A MIDI delay is quite different from an audio delay, as MIDI events may arrive at random times. You need to keep a buffer of events and fire them later, when the delay has passed, and the appropriate offset should also be set.

I guess the easiest way to do it is to store events when they arrive, with a timestamp equal to delay+offset, then everytime a process() call happens, remove the samplesToProcess value to the timeStamp. When the timestamp reaches 0, it's time to send the event.

We'll try to add a sample in a future release.

Post

Greetings-
Not Plug 'n' Script, but JSFX: the "MIDI Delay" effect that comes with Reaper might be of some interest:

Code: Select all

desc:MIDI Delay

slider1:0<0,1000,1>Delay (ms)
slider2:0<0,16,0.25>Delay (QN)
slider3:0<0,10000,1>Delay (samples)
slider4:0<0,16,1>Channel (0=omni)
slider5:0<0,16,1>Bus (0=all buses)

in_pin:none
out_pin:none

@init
ext_midi_bus=ext_nodenorm=1;
buf_l=buf_r=0;
buf_hdr = 3; // position, length (incl header), bus
max_ram = __memtop();

@slider
chan = slider4-1;
bus = slider5-1;

@block
delay_samples = floor((slider1*0.001 + slider2*60.0/tempo)*srate + slider3 + 0.5);
delay_sc = (delay_samples + samplesblock);
delay_isc = 1.0 / delay_sc;

// process incoming events
while((l=midirecv_buf(offs,buf_r+buf_hdr,max_ram-buf_r-buf_hdr))>0)
(
  (bus<0 || midi_bus == bus) &&
  (chan<0 || (l <= 3 && (fb=buf_r[buf_hdr])<0xf0 && (fb&0xf) == chan)) ? (
    buf_r[0] = (delay_samples+offs) * delay_isc;
    buf_r[1] = buf_hdr + l;
    buf_r[2] = midi_bus;
    buf_r += buf_hdr + l;
  ) : (
    midisend_buf(offs,buf_r+buf_hdr,l);
  );
);

// process outgoing events
rd = buf_l;
while (rd<buf_r)
(
  rd==buf_l && (offs=floor(rd[0]*delay_sc+0.5)) < samplesblock ? (
    midi_bus=rd[2];
    l = rd[1];
    midisend_buf(max(offs,0),rd+buf_hdr,l-buf_hdr);
    buf_l = (rd += l);
  ) : (
    rd[0] -= samplesblock * delay_isc;
    rd += rd[1];
  );
);

// compact buf if needed
buf_l >= buf_r ? (
  buf_l=buf_r=0;
) : (
  buf_l > max(1024,buf_r*.5) ? (
    (buf_r-=buf_l) > 0 ? memcpy(0,buf_l,buf_r) : buf_r=0;
    buf_l=0; 
  );
);
HTH, Charles
Tranzistow Tutorials: http://vze26m98.net/tranzistow/
Xenakis in America: http://oneblockavenue.net

Post

I wrote this as one of my first experiments. It delays MIDI, although not in ms, but in number of callbacks:

Code: Select all

#include "library/Midi.hxx"

string name = "MIDI Delay";
string description = "Simple script to delay midi events";

// Input parameter section
// One input parameter: the delay time

array<string> inputParametersNames = {
    "Delay"
};
array<double> inputParametersMin = {
    0
};
array<double> inputParametersMax = {
    250
};
array<double> inputParametersDefault = {
    6
};
array<int> inputParametersSteps = {
    6
};
array<string> inputParametersFormats = {
    ".0"
};
array<double> inputParameters(inputParametersNames.length);

// Global variables

/// maximum delay + 1, unit is number of calls
const uint16 maxDelay = 251;

/// array of event lists; each slot is a time step corresponding to a call to
/// processBlock.
array< array<MidiEvent> > eventQueue(251);

/// current position in the array, walks over eventQueue in circular fashion
uint16 curPos = 0;

// global variables set by updateInputParameters()

uint16 delay = 0;

/** processBlock plays the queue of events arriving at its call after "delay"
    more calls. First it appends the incoming events to the delayed position,
    then plays the current position, which allows delay to be 0, as well as
    modification of delay during playing. The only thing it doesn't compensate
    for is balanced events, e.g. note on/off; when the delay is shortened
    between such events, the note off may occur before the note on, resulting
    in a stuck note.
 */
void processBlock(BlockData& data) {
    uint delayedPosition = (curPos + delay) % maxDelay;
    array<MidiEvent>@ delayQueue = @eventQueue[delayedPosition];

    for (uint i = 0; i < data.inputMidiEvents.length; i++) {
        delayQueue.insertLast(data.inputMidiEvents[i]);
    }
    array<MidiEvent>@ curQueue = @eventQueue[curPos];
    for (uint i = 0; i < curQueue.length; i++) {
        data.outputMidiEvents.push(curQueue[i]);
    }
    curQueue.resize(0);
    curPos = (curPos + 1) % maxDelay;
}

void updateInputParameters() {
    delay = uint16(inputParameters[0] + 0.1);
    print(delay + " " + inputParameters[0]);
}

Post

Thank you all for your valuable inputs! Greatly appreciated!

Post Reply

Return to “Blue Cat Audio”