I gathered quite some knowledge about MIDI since I started developing modules for Rack a few years ago - and so I thought it is time for some generic MIDI processor within a module. As far as I know there is no similar module which can do this (except for VCV Host and using some script VST). The module is completely working though I haven’t pushed the code yet to my GitHub repository for my own reasons.
Since version 2.2.0 Rack has its own Loopback MIDI driver and with this driver MIDI-KIT can be used as an insert: incoming MIDI messages can be processed before reaching the actual MIDI module (like MIDI-CC or MIDI-CV or MIDI-MAP or one of my own modules like MIDI-CAT). Outgoing MIDI messages can be processed the same way.
The module has four CV input and four parameters. These can be used within the scripts for adding some dynamic facets if needed. So, CV-modulated MIDI message are doable - even “crazy things” like selecting the MIDI channel by CV
There is also a Trigger-input which typically will receive a clock signal for synchonizing (means, delaying) MIDI messages until an upcoming trigger.
The module is purely event-based: It is only active if a MIDI message arrives on the selected MIDI device. But the API provides ability for creating new MIDI messages, one arriving message can result in up to 16 MIDI messages.
The module uses internally a very basic implementation of a JavaScript engine (Elk) for interpreting your custom scripts. It is certainly not the fastest way for running JavaScript from C/C++ but MIDI messages are relatively rare events (in contrast to audio/dsp-processing) and I don’t think perfomence will be an issue. I suspect scripts will be quite simple most of the time.
This is a sample script I’m using for testing:
let processMidi = function(msg) {
if (midi.isCc(msg)) {
if (midi.getChannel(msg) === 1) {
midi.setChannel(msg, 2);
let msg2 = midi.create();
midi.setCc(msg2, 4, 11, midi.getValue(msg));
midi.send(msg2);
}
if (midi.getChannel(msg) === 4) {
let ch = number.ceil(param.getValue(1) * 16);
midi.setChannel(msg, ch);
log("ch " + number.toString(ch));
overlay(number.toString(midi.getValue(msg)));
}
midi.send(msg);
}
if (midi.isNoteOn(msg)) {
if (midi.getChannel(msg) === 5) {
midi.send(msg);
let msg2 = midi.create();
midi.setNoteOn(msg2, 6, midi.getNote(msg), midi.getValue(msg));
midi.sendAfterTrigger(msg2, 2);
}
}
};
As the underlaying script engine provides no standard libraries or built-in functions I needed to add everything on my own - which I consider a good thing for security and simplicity reasons. This is the list of API I implemented so far:
Global functions
-
log(string)
: Logs a text menssage on the display of the module. -
overlay(string)
: Displays a message in an overlay widget. -
processMidi(msg)
: Main entry point of the script. This function is called by the module on each incoming MIDI message.
input
-
input.getName(arg)
: This function is used by the module to display a tooltip text for the input. The default implementation can be replaced to display some additional information for the input. -
input.getVoltage(arg)
: Reads the current voltage on the input portarg
witharg
between 1 and 4.
param
-
param.getName(arg)
: This function is used by the module to display a tooltip text for the parameter. The default implementation can be replaced to display some additional information for the parameter. -
param.getValueFormat(arg)
: This function is used by the module to display a formated value on the tooltip for the parameter. The default implementation can be replaced. -
param.getValue(arg)
: Reads the value of the parameter with indexarg
witharg
between 1 and 4. The return value is in the range [0, 1].
number
-
number.ceil(arg)
: Computes the largest integer value not less than arg. -
number.floor(arg)
: Computes the largest integer value not greater than arg. -
number.max(arg1, arg2)
: Returns the greater of two arguments. -
number.min(arg1, arg2)
: Returns the smaller of two arguments. -
number.random()
: Returns a random number in the range [0, 1). -
number.toString(arg)
: Converts arg to a string representation.
midi
midi.create()
midi.getChannel(msg)
midi.getNote(msg)
midi.getPitchWheel(msg)
midi.getValue(msg)
midi.isCc(msg)
midi.isChanPressure(msg)
midi.isClock(msg)
midi.isContinue(msg)
midi.isNoteOff(msg)
midi.isNoteOn(msg)
midi.isPitchWheel(msg)
midi.isProgramChange(msg)
midi.isStart(msg)
midi.isStop(msg)
midi.send(msg)
midi.sendAfterMs(msg, arg)
midi.sendAfterTrigger(msg, arg)
midi.setCc(msg, channel, cc, value)
midi.setChannel(msg, channel)
midi.setChanPressure(msg, channel, value)
midi.setKeyPressure(msg, channel, note, value)
midi.setNote(msg, note)
midi.setPitchWheel(msg, channel, value)
midi.setProgramChange(msg, channel, prg)
midi.setValue(msg, value)
As mentioned the module is fully working but I’m still working on the internal structure for some possible expanders in the future.
I would appreciate any feedback and feature ideas are welcome as always.