430 likes | 441 Views
Learn about MIDI manipulation techniques like double and triple tracking, chord generation, arpeggiator, delays, and more. Explore MIDI setups, Arduino integration, and analog tracking methods.
E N D
MIDI Manipulation Dr. Veton Kepuska
MIDI Manipulation • MIDI Manipulation • Double and Triple Tracking • Automatic Generation of triad Chords • A MIDI arpegiator • Delays and Echos • A MIDI Lopper Pedal
The Midi Setup • The projects discussed in this chapter related to intercepting MIDI data from a keyboard or other MIDI instruments and manipulating those intercepted data to achieve desired effect. • Two Basic Setups are displayed in the next figure.
MIDI Setup #1 Keyboard MIDI OUT MIDI IN Arduino & Shield MIDI OUT MIDI IN Audio Amplifier Sound Module Audio Speaker
MIDI Setup #2 Keyboard MIDI OUT MIDI IN Arduino & Shield MIDI OUT Laptop MIDI IN USB/MIDI Lead
The Midi Setup • Arduino can be used to act as a USB HID MIDI device. • USB Interfaces can be of 2 kids: • Client • Server • While the Client USB interface is simple, Server is significantly more complicated.
Double Tracking • Basic Double Tracking • Multiple Tracking
Basic Double Tracking • Double Tracking occurs anytime when an input ‘note on’ or ‘note off’ message is repeated on another channel.
/* MIDI Double Track * Listen for MIDI note on/off * data, and play it back on the * next track */ booleannoteDown = LOW; byte channel = 0; // MIDI Channel responds to this // change the channel number // MIDI channel = the value in ‘channel’ +1 void setup() { // start serial with MIDI bound rate Serial.begin(31250); } void loop () { chekIn(); } void checkIn() { #A static byte note = 60; static byte state = 0; // command waiting = 0 // note waiting = 1 // velocity waiting = 2 if (Serial.available() > 0) { byte incomingByte = Serial.read(); Serial.write(incomingByte); switch (state) { case 0: #B if (incomingByte == (0x90 | channel) { noteDown = HIGH; state = 1; } if (incomingByte == (0x80 | channel) { noteDown = LOW; state = 1; } if ((incomingByte & 0xE0) == 0xC0) { state = 4; } if ((incomingByte & 0xE0) == 0xA0) { state = 3; } break; case 1: #C if (incomingByte < 128) { note = incomingByte; state 2; } else { state = 0; } break; case 2: #D if (incomingByte < 128) { doNote(note, incomingByte, noteDown); } state = 0; break;
case 3: #B state = 4 break; case 4: #E state = 4 break; } } } void doNote(byte note, byte velocity, int down) { if ((down == HIGH) && (velocity == 0)) { down = LOW; } if (down == LOW) { noteSend(0x80, note, velocity); } else { noteSend(0x90, note, velocity); } } void noteSend(byte cmd, byte data1, byte data2) { cmd = cmd | byte((channel+1) & 0xf); Serial.write(cmd); Serial.write(data1); Serial.write(data2); } #A The state machine for reading in a MIDI message over several bytes #B This is the first byte so look and see what sort of message it might be #C The second byte in a note message will be the note number #D The third byte is the velocity – note message is complete #E A multi byte message that we are not interested in so we just let the data pass
MIDI Manipulation • checkIn() is the key function in the previous slide. • It reads each byte in turn, and latches onto the “note On” and “note Off” messages for our channel.
Analogue Double Tracking (ADT) • It is know as a ADT • Sound is generated normally at or close to the center of the stereo stage. • Then the sound is doubled by sending one note to the left and the other to the right side.
void setup() { Serial.begin(31250); //start serial with MIDI bound rate //set pan for the two channels controlSend(10, 0, channel); // MSB #A controlSend(42, 0, channel); // LSB #A controlSend(10, 127, (channel+1) & 0xf); // MSB#A controlSend(42, 127, (channel+1) & 0xf); // LSB #A }
void controlSend( byte CCnumber, byte CCdata, byte CCchannel) { #B CCchannel |= 0xB0; //convert to Control message Serial.write(CCchannel); Serial.write(CCnumber); Serial.write(CCdata); } #A Set up the stereo position of each channel #B Function to send out a controller change message to the specific channel
Variation of the double tracking by adding one more note: • Left, • Right, • Center.
void setup() { Serial.begin(31250); //start serial with MIDI bound rate //set pan for the two channels controlSend(10, 0, channel); // MSB #A controlSend(42, 0, channel); // LSB #A controlSend(10, 127, (channel+1) & 0xf); // MSB#A controlSend(42, 127, (channel+1) & 0xf); // LSB #A controlSend(10, 127, (channel+2) & 0xf); // MSB #A controlSend(42, 127, (channel+2) & 0xf); // LSB #A }
void doNote(byte note, byte velocity, int down) { // ifvelcotiy = 0 on a ‘Note ON’ command, treat it as a note off if ((down == HIGH) && (velocity == 0)) { down = LOW; } // send out this note mesage if (down == LOW) { // note off noteSend(0x80, note, velocity, 0x1); noteSend(0x80, note-12, velocity, 0x2); #B } else { // note on noteSend(0x90, note, velocity, 0x1); float v = (float) velocity * 0.75; #C noteSend(0x90, note-12, (byte)v, 0x2); // send third note } }
void controlSend( byte CCnumber, byte CCdata, byte CCchannel) { CCchannel |= 0xB0; //convert to Control message Serial.write(CCchannel); Serial.write(CCnumber); Serial.write(CCdata); } #A Set up the stereo position of each channel #B Note an octave or 12 semitones lower #C Reduce the volume by %75
Analog triple Tracking • As can be seen from the code of controlSend function is similar to the previous (double tracking) example and the setup function has additional channel in it. • The main change is in replacement of the doNote function with the one listed and the noteSend function so that it accepts a channel offset parameter. float v = (float) velocity * 0.75; noteSend(0x90, note – 12, (byte) v, 0x2); // send third note
Analog triple Tracking • Modifies the note by moving it down an octave; this is accomplished by subtracting 12 from the note number. • Reduction of the velocity and hence the value of it is being multiplied by 0.75 (quarter off its value). Because 0.75 is a fraction it needs to be computed using floating point multiplication instead of int.
Problem: How to modify the program so that a note is repeated or doubled on channel 9?
Doubling a note with Triple Tracking // variables setup constint led = 13; long time; booleannoteDonw = LOW; // MIDI channel to respond to (in this case channel 1) const byte ourChannel = 1; // setup: declaring inputs and outputs void setup() { pinMode(led, LOW); time = millis() + 2000; // setup call back channels #A usbMIDI.setHandleNoteOff(doNoteOff); usbMIDI.setHandleNoteOn(doNoteOn); usbMIDI.setHandleVelocityChange(doVelocityChange); usbMIDI.setHandleControlChange(doControlChange); usbMIDI.setHandleProgramChange(doProgramChange); usbMIDI.setHandleAfterTouch(doAfterTouch); usbMIDI.setHandlePitchChange(doPitchChange);
Doubling a note with Triple Tracking // setpan fro the three channels #B usbMIDI.sendControlChange(10, 0, outChannel); // MSB usbMIDI.sendControlChange(42, 0, outChannel); // LSB usbMIDI.sendControlChange(10, 127, (outChannel+1) & 0xf); // MSB usbMIDI.sendControlChange(42, 127, (outChannel+1) & 0xf); // LSB usbMIDI.sendControlChange(10, 64, (outChannel+2) & 0xf); // MSB usbMIDI.sendControlChange(42, 64, (outChannel+2) & 0xf); // LSB } // loop: wait for serial data, and interpret the message void loop () { usbMIDI.read(); #C } // call back functions basically echo most staff void doVecolcityChange(byte channel, byte note, byte velocity) { #D usbMIDI.sendPolyPressure(note, velocity, channel); } Void doControlChange(byte channel, byte control, byte value) { #D usbMIDI.sendControlChange(control, value, channel); }
Doubling a note with Triple Tracking void doProgramChange(byte channel, byte program) { #D usbMIDI.sendProgramChange(program, channel); } void doAfterTouchChange(byte channel, byte pressure) { #D usbMIDI.sendAfterTouchPressure(preassure, channel); } void doPitchChange(byte channel, int pitch) { #D usbMIDI.sendPitcChange(pitch, channel); } void doNoteOn(byte channel, byte note, byte velocity) { digitalWrite(led, HIGH); usbMIDI.sendNoteOn(note, velocity, channel); if (channel == ourChannel) { // pick out the note we are looking for doNote(note, velocity, false); } }
Doubling a note with Triple Tracking void doNote(byte note, byte velocity, int down) { // if velocity = 0 on a ‘Note ON’ command, treat it as a note off if ((down == HIGH) && (velocity == 0)) { down = LOW; } // send out this note message if (down == LOW) { // note off usbMIDI.sendNoteOff(note, velocity, (ourChannel + 1) & 0xf); usbMIDI.sendNoteOff(note, velocity, (ourChannel+ 2) & 0xf); } else { usbMIDI.sendNoteOn(note, velocity, (ourChannel+ 1) & 0xf); float v = (float)velocity * 0.75; usbMIDI.sendNoteOff(note - 12, (byte) v, (ourChannel+ 2) & 0xf); } }
Doubling a note with Triple Tracking #A Tell the built in MIDI library what function to call when each MIDI event happens #B Send stereo position information to your sound module #C Checks for any MIDI message and calls the previously declared function if one has arrived #D We don’t want to do anything about these messages just pass them through #E This is a message we want to do something with
Triad Chord Basics • Imagine that you had a limited keyboard skills. • If you are able to pick one note and as a result a hole cord was played! • That is what we have described earlier – generation of a triad of sounds. • Triad is set of three notes – a root node is played as additional tow other notes spaced 1/3 and 1/5 from the root note.
Triad Chord Basics • The four types of a Triad Chord
Creating a Triad Chord with Arduino Bass Triad on/off Triad on/off Chord Type Select
The “One Finger Wonder” // variables set up booleannoteDown = LOW; byte channel = 0; // MIDI channel to respnd to // MIDI channel = the value in ‘channel’ +1 boolean bass = true, enableTriad = true; constintbassPin = 12, triadPin = 10; constint tiradPin1 = 8, triadPin2 = 6; // Major, minor, diminished, augmented byte thirds [] = {4, 3, 3, 4}; byte fifths [] = {7, 7, 6, 8}; // setup: declaring input and outputs and begin serial void setup() { pinMode(bassPin, INPUT_PULLUP); #A pinMode(triadPin, INPUT_PULLUP); #A pinMode((triadPin1, INPUT_PULLUP); #A pinMode((triadPin2, INPUT_PULLUP); #A Serial.begin(31250); // start serial with MIDI baud rate }
The “One Finger Wonder” // loop: wait for serial data, and interpret the message void loop() { chekIn(); // see if anything has arrived at the input getControls(); // get switch values } Void getControls() { #B bass = digitalRead(bassPin); triad = (digitalRead(triadPin1) & 0x1) | ((digitalRead(triadPin2) & 0x1) << 1) enableTriad = digitalRead(traidPin); } void doNote(byte note, byte velocity, int down) { // if velocity = 0 ona ‘Note ON’ command, treat it as a note OFF if ((down == HIGH) && (velocity == 1)) { down = LOW; }
The “One Finger Wonder” // send the other notes of the triad if (down == LOW) { // note off if enableTriad) { noteSend(0x80, note+thirds[triad], velocity); noteSend(0x80, note+fifths[triad], velocity); if (bass) noteSend(0x80, note-12, velocity); } } else { // note on if (enableTriad) { noteSend(0x90, note+thirds[triad], velocity); noteSend(0x90, note+fifths[triad], velocity); if (bass) noteSend(0x90, note-12, velocity); } } }
The “One Finger Wonder” void noteSend(char cmd, char data1, cahr data2) { cmd = cmd | char(channel); // next channel number up Serial.write(cmd); Serial.write(data1); Serial.write(data2); } void checkIn() { static byte note = 60; static byte state = 0; // state machine variable if (Serial.available() > 0) { // read incoming byte byte incomingByte = Serial.read(); Serial.write(incomingByte); }
The “One Finger Wonder” switch (state) { case 0; // look for as status-byte, our channel, note on if (incomingByte == (0x90 | channel)) { noteDown = HIGH; state = 1; } //look for as status-byte, our channel , note off if (incomingByte == (0x80 | channel)) { noteDown = LOW; state = 1; } break;
The “One Finger Wonder” case 1; // get the note to play or stop if (incomingByte < 128) { note = inomingByte; state = 2; } else { state = 0; // reset state machine as this // be a note number } break; case 2; // Get the velocity if (incomingByte < 128) { // do something with the note on message doNote(note, incommingByte, noteDown); } state = 0; // reset state machine to start } } } #A Tell the Arduino to enable the internal pull up resistors so the inputs don’t float #B Loot at the state of the switches incase they have changed #C Send three notes all relative to the original note #D We have seen this before in the other listing – tease the MIDI message from the input stream