Tutorial 25. MIDI Output
Sequencing a melody on an external MIDI synth |
In addition to a MIDI input, MEAP also has a MIDI output! This has a plethora of uses, but in this tutorial, we'll be using to to sequence a melody on an external sound module using MEAP.
Documentation of how to send different MIDI message types using the arduino MIDI library.
For simplicity's sake we'll be using the same melody we used in our internal MEAP sequencing tutorial.
The slight complexity that will be added here is that in our previous tutorial, we just used note ons, and counted on our envelopes to end the notes for us. In MIDI, it is important to always send a note off after a note on otherwise you may end up with stuck notes that sustain forever. We'll take a manual and not particularly robust approach using a single EventDelay which will sustain all of our notes for the same amount of time, and turn them off before the next note is started. A polyphonic approach would require greater complexity.
We create a second EventDelay called note_off_timer
and a variable called note_off_number
. When a note
is triggered, we store the note's MIDI note number and start the timer for 100ms. After 100ms has passed,
we send a note off message to that note.
Note that there is no audio generated in updateAudio(). This MEAP program generates no sound on its own and must be connected to an external MIDI sound module. Connect a cable from MEAP's MIDI output jack (the bottom of the two pink jacks) to the MIDI input jack of your sound module. Connect your sound module up to some audio monitoring system and you should hear a melody!
FULL CODE BELOW
/*
Sends MIDI note on and note off messages to play a melody on MIDI channel 1
*/
#define CONTROL_RATE 128 // Hz, powers of 2 are most reliable
#include <Meap.h> // MEAP library, includes all dependent libraries, including all Mozzi modules
Meap meap; // creates MEAP object to handle inputs and other MEAP library functions
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); // defines MIDI in/out ports
enum ClockModes {
kINTERNAL,
kEXTERNAL
} clock_mode;
// MIDI clock timer
uint32_t clock_timer = 0;
uint32_t clock_period_micros = 10000; // dummy value, gets overwritten in setup
int clock_pulse_num = 0;
float clock_bpm = 120; // BPM when in internal clock mode
// ---------- YOUR GLOBAL VARIABLES BELOW ----------
EventDelay metro1;
int metro1_period; // period will be set by sequence below
int sequence_length = 20;
int sequence_pitches[20] = { mCs3, mFs3, mCs3, mA2, mCs3, mE3, mD3, mCs3, mB2, mA2, mCs3, mE3, mCs3, mA2, mCs3, mE3, mD3, mCs3, mB2, mA2 };
int sequence_rhythms[20] = { 2, 2, 2, 2, 2, 1, 1, 2, 1, 1, 2, 2, 2, 2, 2, 1, 1, 2, 1, 1 };
int sequence_index = 0;
int sixteenth_length = 125;
int note_length = 100;
EventDelay note_off_timer;
int note_off_number;
bool note_active = false;
void setup() {
Serial.begin(115200); // begins Serial communication with computer
Serial1.begin(31250, SERIAL_8N1, 43, 44); // sets up MIDI: baud rate, serial mode, rx pin, tx pin
startMozzi(CONTROL_RATE); // starts Mozzi engine with control rate defined above
meap.begin(); // sets up MEAP object
clock_mode = kINTERNAL; // set the midi clock mode to internal, ignores incoming clock messages
clock_period_micros = meap.midiPulseMicros(clock_bpm); // converts BPM into number of microseconds per 24 PPQ MIDI clock pulse
// ---------- YOUR SETUP CODE BELOW ----------
}
void loop() {
audioHook(); // handles Mozzi audio generation behind the scenes
if (MIDI.read()) // Is there a MIDI message incoming ?
{
midiEventHandler(); // function that parses midi messages, be careful about doing too much processing
// in here because it could disrupt audio generation
}
// handle generating midi clock if internal clock mode is selected
if (clock_mode == kINTERNAL) {
uint32_t t = micros();
if (t > clock_timer) {
clock_timer = t + clock_period_micros;
MIDI.sendRealTime(midi::Clock); // sends clock message to MIDI output port
clockStep();
}
}
}
/** Called automatically at rate specified by CONTROL_RATE macro, most of your mode should live in here
*/
void updateControl() {
meap.readInputs(); // reads DIP switches, potentiometers and touch inputs
// ---------- YOUR updateControl CODE BELOW ----------
if (metro1.ready()) {
metro1.start(sequence_rhythms[sequence_index] * sixteenth_length);
MIDI.sendNoteOn(sequence_pitches[sequence_index], 127, 1); // note, velocity, channel
note_off_number = sequence_pitches[sequence_index]; // MIDI number of note we need to turn off
note_off_timer.start(note_length); // set timer to turn note off in 100ms
note_active = true; // tells us that a note is on/needs too be turned off
sequence_index = (sequence_index + 1) % sequence_length;
}
if (note_active && note_off_timer.ready()) {
MIDI.sendNoteOn(note_off_number, 0, 1); // note, velocity, channel
note_active = false;
}
}
/** Called automatically at rate specified by AUDIO_RATE macro, for calculating samples sent to DAC, too much code in here can disrupt your output
*/
AudioOutput_t updateAudio() {
int64_t out_sample = 0;
return StereoOutput::fromNBit(8, (out_sample * meap.volume_val) >> 12, (out_sample * meap.volume_val) >> 12);
}
/**
* Runs whenever a touch pad is pressed or released
*
* int number: the number (0-7) of the pad that was pressed
* bool pressed: true indicates pad was pressed, false indicates it was released
*/
void updateTouch(int number, bool pressed) {
if (pressed) { // Any pad pressed
} else { // Any pad released
}
switch (number) {
case 0:
if (pressed) { // Pad 0 pressed
Serial.println("t0 pressed ");
} else { // Pad 0 released
Serial.println("t0 released");
}
break;
case 1:
if (pressed) { // Pad 1 pressed
Serial.println("t1 pressed");
} else { // Pad 1 released
Serial.println("t1 released");
}
break;
case 2:
if (pressed) { // Pad 2 pressed
Serial.println("t2 pressed");
} else { // Pad 2 released
Serial.println("t2 released");
}
break;
case 3:
if (pressed) { // Pad 3 pressed
Serial.println("t3 pressed");
} else { // Pad 3 released
Serial.println("t3 released");
}
break;
case 4:
if (pressed) { // Pad 4 pressed
Serial.println("t4 pressed");
} else { // Pad 4 released
Serial.println("t4 released");
}
break;
case 5:
if (pressed) { // Pad 5 pressed
Serial.println("t5 pressed");
} else { // Pad 5 released
Serial.println("t5 released");
}
break;
case 6:
if (pressed) { // Pad 6 pressed
Serial.println("t6 pressed");
} else { // Pad 6 released
Serial.println("t6 released");
}
break;
case 7:
if (pressed) { // Pad 7 pressed
Serial.println("t7 pressed");
} else { // Pad 7 released
Serial.println("t7 released");
}
break;
}
}
/**
* Runs whenever a DIP switch is toggled
*
* int number: the number (0-7) of the switch that was toggled
* bool up: true indicated switch was toggled up, false indicates switch was toggled
*/
void updateDip(int number, bool up) {
if (up) { // Any DIP toggled up
} else { //Any DIP toggled down
}
switch (number) {
case 0:
if (up) { // DIP 0 up
Serial.println("d0 up");
} else { // DIP 0 down
Serial.println("d0 down");
}
break;
case 1:
if (up) { // DIP 1 up
Serial.println("d1 up");
} else { // DIP 1 down
Serial.println("d1 down");
}
break;
case 2:
if (up) { // DIP 2 up
Serial.println("d2 up");
} else { // DIP 2 down
Serial.println("d2 down");
}
break;
case 3:
if (up) { // DIP 3 up
Serial.println("d3 up");
} else { // DIP 3 down
Serial.println("d3 down");
}
break;
case 4:
if (up) { // DIP 4 up
Serial.println("d4 up");
} else { // DIP 4 down
Serial.println("d4 down");
}
break;
case 5:
if (up) { // DIP 5 up
Serial.println("d5 up");
} else { // DIP 5 down
Serial.println("d5 down");
}
break;
case 6:
if (up) { // DIP 6 up
Serial.println("d6 up");
} else { // DIP 6 down
Serial.println("d6 down");
}
break;
case 7:
if (up) { // DIP 7 up
Serial.println("d7 up");
} else { // DIP 7 down
Serial.println("d7 down");
}
break;
}
}
/**
* Called whenever a MIDI message is recieved.
*/
void midiEventHandler() {
int channel = MIDI.getChannel();
int data1 = MIDI.getData1();
int data2 = MIDI.getData2();
switch (MIDI.getType()) // Get the type of the message we received
{
case midi::NoteOn: // ---------- MIDI NOTE ON RECEIVED ----------
break;
case midi::NoteOff: // ---------- MIDI NOTE OFF RECEIVED ----------
break;
case midi::ProgramChange: // ---------- MIDI PROGRAM CHANGE RECEIVED ----------
break;
case midi::ControlChange: // ---------- MIDI CONTROL CHANGE RECEIVED ----------
break;
case midi::PitchBend: // ---------- MIDI PITCH BEND RECEIVED ----------
break;
case midi::Clock: // ---------- MIDI CLOCK PULSE RECEIVED ----------
if (clock_mode == kEXTERNAL) {
clockStep();
}
break;
case midi::Start: // ---------- MIDI START MESSAGE RECEIVED ----------
break;
case midi::Stop: // ---------- MIDI STOP MESSAGE RECEIVED ----------
break;
case midi::Continue: // ---------- MIDI CONTINUE MESSAGE RECEIVED ----------
break;
}
}
// Executes when a clock step is received. Each "if" statement represents a musical division of a quarter note.
// For example, if you want an event to occur every eigth note, place the code for this event within the
// second if statement. If you want events to happen at different subdivisions of a quarter note add more if
// statements checking the value of clock_pulse_num.
void clockStep() {
if (clock_pulse_num % 24 == 0) { // quarter note
}
if (clock_pulse_num % 12 == 0) { // eighth note
}
if (clock_pulse_num % 6 == 0) { // sixteenth note
}
if (clock_pulse_num % 3 == 0) { // thirtysecond notex
}
clock_pulse_num = (clock_pulse_num + 1) % 24;
}