Beginner's guide to MIDI coding in VM

ColinP
Posts: 1000
Joined: Mon Aug 03, 2020 7:46 pm

Beginner's guide to MIDI coding in VM

Post by ColinP »

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.

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
			}
      		}
   	}
   	
}
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!
User avatar
seal58
Posts: 377
Joined: Fri Jul 12, 2019 5:28 pm
Location: Rostock, Germany
Contact:

Re: Beginner's guide to MIDI coding in VM

Post by seal58 »

A while ago I started to use MIDI in VMD and got sticked. Your Beginner's guide helps me to reinforce my former try.
Thank you, Collin. :)

Roland
drohnee
Posts: 21
Joined: Mon Mar 07, 2022 8:26 pm

Re: Beginner's guide to MIDI coding in VM

Post by drohnee »

Thanks for the clear explanation, Colin. This is a helpful resource for those of us that are considering dipping a toe in the custom module waters. The guide to audio / MIDI programming in Java also looks quite useful. All of this combined with the freely-available code from utdgrant should be enough to get started.

One naive question here: is there a recommended practice for persisting data that can be accessed on subsequent calls of ProcessSample()? For example, if one wanted to create a queue of MIDI messages to dispatch at a future time based on incoming MIDI, is there a standard place to store such data so that later instances of ProcessSample() can check the contents of the queue and send the programmatically-generated MIDI messages if the time has arrived? Will need to learn a bit about how these things are best handled in Java...
ColinP
Posts: 1000
Joined: Mon Aug 03, 2020 7:46 pm

Re: Beginner's guide to MIDI coding in VM

Post by ColinP »

drohnee wrote: Sat Jun 17, 2023 6:29 pm is there a recommended practice for persisting data that can be accessed on subsequent calls of ProcessSample()? For example, if one wanted to create a queue of MIDI messages to dispatch at a future time based on incoming MIDI, is there a standard place to store such data so that later instances of ProcessSample() can check the contents of the queue and send the programmatically-generated MIDI messages if the time has arrived? Will need to learn a bit about how these things are best handled in Java...
If the data you wanted to store matched the settings of knobs or such like on the panel then this is easy as their state is stored and restored automatically. Otherwise you need to use the SetStateInformation() and GetStateInformation() mechanism, or perhaps you could store strings in XML files via methods such as SetManufacturerProperty() and GetManufacturerProperty(). Or you could write custom code to save your own files but I wouldn't recommend that unless there is no other option.

Get/SetStateInformation() is the way to go really but it's beyond the scope of this thread. I've touched on using strings to represent module state in other threads for instance the recent one about undo/redo. Get/SetStateInformation() pass byte arrays and you can translate these to ByteBuffers and other classes for binary applications or to strings but it's not something one can cover in a few paragraphs. CA recommend using java.util.Properties for persistence but I've not used it. I think it's a serializable hash table class but AFAIK it stores strings rather than objects, so you still need to handle conversions.

It's not tricky in simple applications but general persistence is a big subject and when you scratch the surface you begin to look at reflection and transaction management which are even deeper. I spent a couple of years doing research in these areas so I'm perhaps not the best person to ask about this for an introduction as I'll just go off at a tangent....
UrbanCyborg
Posts: 625
Joined: Mon Nov 15, 2021 9:23 pm

Re: Beginner's guide to MIDI coding in VM

Post by UrbanCyborg »

I believe he was asking about persisting MIDI data between calls to ProcessSample(), not between uses of the module. And the short answer to that is, using any data structure you want, so long as you don't make it an automatic variable local to the call.

Reid
Cyberwerks Heavy Industries -- viewforum.php?f=76
User avatar
Waverley Instruments
Posts: 147
Joined: Thu May 05, 2022 2:10 pm

Re: Beginner's guide to MIDI coding in VM

Post by Waverley Instruments »

Slightly more succinct approach to loop through the messages without querying the size and a for / next loop would be:

Code: Select all

for (ShortMessage message : inputMessages) {
	// ...
}
-Rob @ WI
User avatar
Waverley Instruments
Posts: 147
Joined: Thu May 05, 2022 2:10 pm

Re: Beginner's guide to MIDI coding in VM

Post by Waverley Instruments »

drohnee wrote: Sat Jun 17, 2023 6:29 pmOne naive question here: is there a recommended practice for persisting data that can be accessed on subsequent calls of ProcessSample()? For example, if one wanted to create a queue of MIDI messages to dispatch at a future time based on incoming MIDI
Hi. This sounds like we're talking about scheduled MIDI events where you can timestamp a MIDI message and Java has a MidiEvent class for this:

https://docs.oracle.com/javase/8/docs/a ... Event.html

Typically you'd add MIDI events to a sequence and use a sequencer play back these events.

The sequencer class might be overkill for what you want to do, but it's potentially a nice way of managing multiple tracks, especially if you need more than 16 tracks.

Personally, I've never needed to use scheduled MIDI events in any of our VM MIDI modules (yet...) but this is pretty much how scheduled events / MIDI sequencers work on other platforms, Core MIDI for example on Apple OSs.

Is there a standard way of doing this? Well... using a sequencer is probably a standard way of doing it, but you might get away with just creating MIDI events, putting them in a queue, then checking their timestamp and dispatching the message yourself when their time comes.

So yeah, kinda depends what you're wanting to do. If it was a MIDI note delay effect type thing, then a queue with timestamps should be fine. If it's a multi-track piano roll type thing, then you might want to look at the sequencer class.

Disclaimer, I've no idea if you could get something like this working in VM:

https://docs.oracle.com/javase/tutorial ... thods.html

One final thought is that whatever it is, I'd be inclined to decouple the thing from the MIDI I/O. Just my €0.02.

Cheers and good luck with your MIDI adventures on VM! -Rob @ WI
ColinP
Posts: 1000
Joined: Mon Aug 03, 2020 7:46 pm

Re: Beginner's guide to MIDI coding in VM

Post by ColinP »

UrbanCyborg wrote: Sun Jun 18, 2023 5:29 am I believe he was asking about persisting MIDI data between calls to ProcessSample(), not between uses of the module. And the short answer to that is, using any data structure you want, so long as you don't make it an automatic variable local to the call.

Reid
You could well be right Reid. I took "persisting data", "standard place to store such data" and "future instances of ProcessSample()" to imply persistence beyond process lifetime and ran with that, but I may have got the wrong end of the stick.

It could be about recording MIDI events then replaying them in the fashion of a sequencer as Rob suggests, or perhaps assigning a short string of MIDI messages to be triggered by something like a note on event - like when I press C2 on channel 2 send a specific bank select and program change for patch selection on channel 3 or any number of things.

Hopefully drohnee can come back with more info about the application they have in mind.
ColinP
Posts: 1000
Joined: Mon Aug 03, 2020 7:46 pm

Re: Beginner's guide to MIDI coding in VM

Post by ColinP »

Waverley Instruments wrote: Sun Jun 18, 2023 8:23 am Slightly more succinct approach to loop through the messages without querying the size and a for / next loop would be:

Code: Select all

for (ShortMessage message : inputMessages) {
	// ...
}
-Rob @ WI
I agree Rob. I even pondered writing it that way but opted for the more old-fashioned C style idiom as people new to Java might already be familiar with that.
ColinP
Posts: 1000
Joined: Mon Aug 03, 2020 7:46 pm

Re: Beginner's guide to MIDI coding in VM

Post by ColinP »

On the Java sequencing stuff linked to by Rob. I've not used it for actual sequencing in VM but have used the Sequence, Track, MidiEvent and MetaMessage classes for reading and writing MIDI files. It works and is easier than DIY.
Post Reply

Return to “Module Designer”