Beginner's guide to MIDI coding in VM
Posted: Sat Jun 17, 2023 5:17 pm
This is a short beginner's guide to handling MIDI in Voltage Modular. It doesn't look at file operations, just real-time processing where MIDI messages are received and transmitted via sockets on a module's front panel.
The following assumes you've created a new project in VMD and used the Controls Pane and Design Pane to add a MIDI Input Jack and a MIDI Output Jack to your panel and used the Properties Pane to give them Variable Names of midiIn and midiOut. This automatically generates code in InitializeControls() that creates two VoltageMIDIJack objects when your module initializes.
In ProcessSample() you can read MIDI messages from midiIn with midiIn.GetMessages() and write MIDI messages to midiOut with midiOut.AddMessage( message). At one level it's as simple as that but let's look at the detail.
The MIDI messages that VM deals with are instances of the standard Java class ShortMessage. It's not exactly an obvious name for MIDI messages but at least Java looks after names using packages so there aren't going to be any conflicts.
To use ShortMessage you need to identify the Java package where it's defined so add...
import javax.sound.midi.ShortMessage;
to the [User Imports] area at the top of your source code where it says // Add your own imports here.
While you are there add the following line too...
import java.util.ArrayList;
This is needed because calls to GetMessages() return an ArrayList of ShortMessage objects.
Documentation for the ShortMessage class is here...
https://docs.oracle.com/javase/8/docs/a ... ssage.html
If you want to find more general information about audio programming in Java check this out...
https://docs.oracle.com/javase/8/docs/t ... tents.html
Let's jump straight in and code a ProcessSample() method that reads MIDI input from your midiIn socket, teases apart the components of the message, reassembles them into a new message and sends the result to your midiOut socket.
This does nothing useful as the output is exactly the same as the input but it demonstrates the core functionality of any kind of MIDI processing module.
This code executes for every single sample but MIDI events are comparatively rare so the vast majority of the time midiIn.GetMessages() doesn't have any messages to report so it returns a null reference. Therefore we test for this and bypass the rest of the code if there aren't any messages to handle. So when your module is just waiting for a MIDI message to arrive it's using up almost no CPU time.
Then we will typically get just one MIDI message so the ArrayList will have just one message in it. However it's possible that lots of MIDI messages will be buffered and arrive in a burst so the code has to be able to deal with this. That's why there's a for loop that repeats for however many messages are in the ArrayList.
ShortMessage message = inputMessages.get( i ); assigns a reference to the ith message in the ArrayList to the variable message then the next four lines of code pick apart this message extracting the MIDI command, MIDI channel and two data values.
Common examples of command values are ShortMessage.NOTE_ON, ShortMessage.NOTE_OFF and ShortMessage.CONTROL_CHANGE.
The data values depend on what the command is. If the command is NOTE_ON for instance then data1 is the note number and data2 is the velocity.
The next few lines of code construct a new message using the same components as just extracted from the input message and add that message to a list of messages that the midiOut socket should send.
We don't have any control over the timing of these events as everything is scheduled behind the scenes, but generally this works out just fine as the processing happens so quickly there aren't any isssues.
The try-ctach statement is required because the ShortMessage constructor will throw an InvalidMidiDataException if any of its parameters are invalid. In this particular case we just ignore any exceptions. This is normally very bad practice but in this particular situation, apart from for debugging purposes, there is nothing useful that we could do should a problem arise. In general VM logs exceptions but overwise attempts to continue operating. This is routine in realtime sound programming as the last thing you want to happen in a live performance is for some cryptic error message to pop up and your kit go silent.
The above is obviously not an exhaustive discussion of MIDI processing in VM but hopefully it provides you with enough pointers to get your own code up and running. Happy coding!
The following assumes you've created a new project in VMD and used the Controls Pane and Design Pane to add a MIDI Input Jack and a MIDI Output Jack to your panel and used the Properties Pane to give them Variable Names of midiIn and midiOut. This automatically generates code in InitializeControls() that creates two VoltageMIDIJack objects when your module initializes.
In ProcessSample() you can read MIDI messages from midiIn with midiIn.GetMessages() and write MIDI messages to midiOut with midiOut.AddMessage( message). At one level it's as simple as that but let's look at the detail.
The MIDI messages that VM deals with are instances of the standard Java class ShortMessage. It's not exactly an obvious name for MIDI messages but at least Java looks after names using packages so there aren't going to be any conflicts.
To use ShortMessage you need to identify the Java package where it's defined so add...
import javax.sound.midi.ShortMessage;
to the [User Imports] area at the top of your source code where it says // Add your own imports here.
While you are there add the following line too...
import java.util.ArrayList;
This is needed because calls to GetMessages() return an ArrayList of ShortMessage objects.
Documentation for the ShortMessage class is here...
https://docs.oracle.com/javase/8/docs/a ... ssage.html
If you want to find more general information about audio programming in Java check this out...
https://docs.oracle.com/javase/8/docs/t ... tents.html
Let's jump straight in and code a ProcessSample() method that reads MIDI input from your midiIn socket, teases apart the components of the message, reassembles them into a new message and sends the result to your midiOut socket.
This does nothing useful as the output is exactly the same as the input but it demonstrates the core functionality of any kind of MIDI processing module.
Code: Select all
public void ProcessSample()
{
// add your own code here
ArrayList<ShortMessage> inputMessages = midiIn.GetMessages();
if( inputMessages != null )
{
// process input messages one by one...
int numInputMessages = inputMessages.size();
for( int i = 0; i < numInputMessages; i++ )
{
ShortMessage message = inputMessages.get( i );
// extract components...
int command = message.getCommand();
int channel = message.getChannel();
int data1 = message.getData1();
int data2 = message.getData2();
// here is where you could add your own processing
// construct new message and send it...
try
{
ShortMessage outputMessage = new ShortMessage( command, channel, data1, data2 );
midiOut.AddMessage( outputMessage );
}
catch( Exception e )
{
// ignore
}
}
}
}
Then we will typically get just one MIDI message so the ArrayList will have just one message in it. However it's possible that lots of MIDI messages will be buffered and arrive in a burst so the code has to be able to deal with this. That's why there's a for loop that repeats for however many messages are in the ArrayList.
ShortMessage message = inputMessages.get( i ); assigns a reference to the ith message in the ArrayList to the variable message then the next four lines of code pick apart this message extracting the MIDI command, MIDI channel and two data values.
Common examples of command values are ShortMessage.NOTE_ON, ShortMessage.NOTE_OFF and ShortMessage.CONTROL_CHANGE.
The data values depend on what the command is. If the command is NOTE_ON for instance then data1 is the note number and data2 is the velocity.
The next few lines of code construct a new message using the same components as just extracted from the input message and add that message to a list of messages that the midiOut socket should send.
We don't have any control over the timing of these events as everything is scheduled behind the scenes, but generally this works out just fine as the processing happens so quickly there aren't any isssues.
The try-ctach statement is required because the ShortMessage constructor will throw an InvalidMidiDataException if any of its parameters are invalid. In this particular case we just ignore any exceptions. This is normally very bad practice but in this particular situation, apart from for debugging purposes, there is nothing useful that we could do should a problem arise. In general VM logs exceptions but overwise attempts to continue operating. This is routine in realtime sound programming as the last thing you want to happen in a live performance is for some cryptic error message to pop up and your kit go silent.
The above is obviously not an exhaustive discussion of MIDI processing in VM but hopefully it provides you with enough pointers to get your own code up and running. Happy coding!