A few months ago I decided to start building an all analog, modular synthesizer, which is still in it’s build process. Some time after that, my school asked us to take on a project of our choice. I decided to create code for a MIDI to CV interface to complement the modular. This video explain it’s overall functions and modes.
Basically, this « pseudolibrary », aka a .h file which is just located in the sketch’s folder uses the MIDI.h library to read for incoming signals. When an incoming signal is received, it gets to a function which manages it. If there is no output which carries that that type of output, it’s ignored. If one of the outputs does carry this type of signal, the signal goes on to a function which determines to which output the signal has to be sent by looking up it’s type and parameters if it has any (for example, a CC’s id). When a match is found, the value gets sent to a function which maps the value so that the output voltage makes sense (for notes, it’s mapped in either Hz/V or V/oct). For notes and data that are linked to individual voices (gate, note, velocity and poly aftertouch), there are a few extra steps relative to polyphony gestion.
This is the circuit I’m currently using to connect the MIDI input to the Arduino. If you plan on building an interface, I’m strongly recommending to use a DAC with a minimal resolution 12 bit over 5 volts if you want to get decent accuracy. Take into account that, using volts/oct, the steps are 0.083v each, so just a small error can cause a lot of detuning.
I also recommend using a MCP4728, which is a 4 output 12 bit DAC you can get for 3$. Someone has also written a library for it, which you can get here. This library was written on an older version of the Arduino IDE, and some basic function syntax has changed since, which causes the library to fail to compile. Fixing it is quite easy and is described in this thread.
To make this work properly, we have to do a few things. First off, at the top of your file, you must include the
MIDI.h library and initialize it (get it here if you don’t already have it).
Before we include
MIDItoCV.h, we must define a few values it needs.
//#define HZV // uncomment this for hertz/volt mode
//#define VOCT // uncomment this for volt/octave mode
#define AMOUNTOUTPUTS 4 // this is the amount of outputs you have
#define CHANNEL 1 // this is the MIDI channel you want to use
#define POLY 4 // this is the maximum amount of polyphony/voices. more voices = less avaliable RAM
Ok! now we can include
MIDItoCV.h Here’s the download link for my code , released under Creative Commons liscence BY. Remember,
MIDItoCV.h is not a library! you must put it in your sketch’s folder!
In your setup, you must call
setupMIDItoCV(), a function that sets up variables and inner-workings of MIDItoCV:
Finally, you need to put
MIDI.read() in your loop so that we read incoming MIDI data.
Now, you may use a few functions in your setup to assign modes, parameters and ways to output data to each of your outputs. Note that you are not limited to using these functions in the setup.
Let’s start with assigning a mode. The function used for that is
setoutputaction(outputnumber, mode, parameter)
outputnumberis the number of the output, ranging from 1 to
AMOUNTOUTPUTSyou defined earlier.
modecan be one of the 8 modes;
parameteris additional parameter that is only used for polyphonic modes and
CC. For polyphonic modes, (
POLYAT), the parameter is the number of the voice. For
CC, it simply is the continuous controller id.
Next, let’s see how we can output data. The function we have to do that is a bit special. Here’s what it looks like:
outputnumberis the output’s number
outputfunctionis a function to which the output value is passed
If you were a bit confused about the last parameter, it’s essentially the name of a function which takes a value and outputs it where you want. Here’s an example.
int outputfunction1(int value) // this is the function that takes the received value and outputs it
analogWrite(6, value); // our output is pin 6 using analogWrite
setoutputfunction(1, outputfunction1); // we set the output to use our output function
Now, the program needs some info about the way you are outputting your data to correctly output volts per octave or hertz per volt. Here’s the function:
setoutputrange(outputnumber, minvalue, maxvalue, minvoltage, maxvoltage)
outputnumberis the output’s number
minvalueis what your output function expects as a minimum value
maxvalueis what your output function expects as a maximum value
minvoltageis the voltage your output function will output when at
minvalue, in millivolts
maxvoltageis the voltage your output function will output when at
maxvalue, in millivolts
Let’s have an example.
int outputfunction1(int value) // Cette fonction reçoit les données de l'output 1 et l'écrit par PWM sur la pin 6
analogWrite(6, value); // la valeur est écrite sur la pin 6
setoutputfunction(1, outputfunction1); // on définit la fonction de sortie
setoutputrange(1, 0, 255, 0, 5000); // analogWrite prend des valeurs de 0 à 255 produit des voltages de 0V à 5V
Since it’s quite likely that your outputs use the same way of outputting data (the same range of values over the same voltage), it would be annoying to have to define their output range/voltage parameters individually, which is why the function
setoutputrangeall(minvalue, maxvalue, minvoltage, maxvoltage) eexists. The parameters are the same as the last functions apart from the absence of the
outputnumber parameter, since you are selecting all outputs.
A in-depth guide covering advanced functions should be released soon.
Written by Simon Demeule, 2015