arduino midi - · pdf filearduino midi pieter p, 08-03-2017 this is a guide that covers the...

29
Arduino MIDI Pieter P, 08-03-2017 This is a guide that covers the basics of the Musical Instrument Digital Interface (MIDI) protocol and its implementation on the Arduino platform. The format of the protocol is explained in the first chapter. Chapter two goes over the hardware. In chapter three, example code for sending MIDI is presented. Chapter four contains everything needed to build a working MIDI controller. MIDI input is covered in chapter five, and chapter six extends this by adding support for System Exclusive (SysEx) messages.

Upload: trandung

Post on 05-Mar-2018

253 views

Category:

Documents


2 download

TRANSCRIPT

ArduinoMIDIPieterP, 08-03-2017

ThisisaguidethatcoversthebasicsoftheMusicalInstrumentDigitalInterface(MIDI)protocolanditsimplementationontheArduinoplatform.Theformatoftheprotocolisexplainedinthefirstchapter.Chaptertwogoesoverthehardware.Inchapterthree,examplecodeforsendingMIDIispresented.ChapterfourcontainseverythingneededtobuildaworkingMIDIcontroller.MIDIinputiscoveredinchapterfive,andchaptersixextendsthisbyaddingsupportforSystemExclusive(SysEx)messages.

TheMIDIprotocolTheMIDIspecificationcanbefoundhere:https://www.midi.org/specifications/item/the-midi-1-0-specification

TheMIDIprotocoldescribesasetofMIDIevents.Forexample,anoteisplayed,oranoteisturnedoff,acontrollerismovedandsettoanewvalue,anewinstrumentisselected,etc.TheseeventscorrespondtoMIDImessagesthatcanbesentovertheMIDIhardwareconnection.

Therearetwomaintypesofmessages:channelmessagesandsystemmessages.Mostperformanceinformationwillbesentaschannelmessages,whilesystemmessagesareusedforthingslikeproprietaryhandshakes,manufacturer-specificsettings,sendinglongpacketsofdata,real-timemessagesforsynchronizationandtuning,andotherthingsthatarenotreallyofinteresttosomeonewhojustwantstomakeanArduinoMIDIinstrumentorcontroller.That'swhythisguidewillmainlyfocusonchannelmessages.

ChannelmessagesThereare16MIDIchannels.EachMIDIinstrumentcanplaynotesononeofthesechannels,andtheycanapplydifferentvoicesorpatchestodifferentchannels,aswellassettingsomecontrollerslikevolume,pan,balance,sustainpedal,pitchbend,etc.MIDImessagesthattargetaspecificchannelarecalledchannelmessages.

AMIDIchannelmessageconsistofaheaderbyte,referredtoasthestatusbyte,followedbyoneortwodatabytes:

Status ─ Data

Status ─ Data1─ Data2

Eachbyteconsistsof8binarydigits.Todistinguishbetweenstatusanddatabytes,andtopreventframingerrors,statusbyteshavethemostsignificantbit(msb)settoone(1),anddatabyteshavethemsbsettozero(0).

Statusbyte Databyte1 Databyte2(optional)Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0Value 1 x x x x x x x 0 x x x x x x x 0 x x x x x x x

Statusbytes

Thestatusbyteofchannelmessagesisdividedintotwo4-bitnibbles.Thehighnibble(bits4-7)specifiesthemessagetype,andthelownibble(bits0-3)specifiestheMIDIchannel.Becausethemostsignificantbithastobeone,thereare8differentmessagetypes(0b1000-0b1111or0x8-0xF),and16differentchannels(0x0-0xF).Messagetype0xFisusedforsystemmessages,soitwon'tbecoveredinthissectiononchannelmessages.

StatusbyteBit 7 6 5 4 3 2 1 0Value m m m m n n n nWheremmmmisthemessagetype(0x8-0xE)andnnnnisthechannelnibble.Notethatthechannelsstartfromnnnn=0forMIDIchannel1.(nnnn=channel-1)

Databytes

Eachdatabytecontainsa7-bitvalue,anumberbetween0and127(0b01111111or0x7F).Themeaningofthisvaluedependsonthemessagetype.Forexample,itcantellthereceiverwhatnoteisplayed,howhardthekeywasstruck,whatinstrumenttoselect,whatvalueacontrollerissetto,etc.

ChannelMessages:messagetypes

Thefollowingsectionwillgooverthedifferentchannelmessagesandtheirstatusanddatabytes.Keepinmindthatnnnn=channel-1.

NoteOff(0x8)

Anoteoffeventisusedtostopaplayingnote.Forexample,whenakeyisreleased.

Data1(0b0kkkkkkk): Notenumber(key).SeeMIDInotenames.Data2(0b0vvvvvvv): Velocity(howfastthekeyisreleased).

Avelocityof0isnotdefined,andsomesoftwareordevicesmaynotregisterthenoteoffeventifthevelocityiszero.Mostsoftwareordeviceswillignorethenoteoffvelocity.Insteadofanoteoffevent,anoteoneventwithavelocityofzeromaybeused.Thisisespeciallyusefulwhenusingarunningstatus.

Statusbyte Notenumber VelocityBit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0Value 1 0 0 0 n n n n 0 k k k k k k k 0 v v v v v v v

NoteOn(0x9)

Anoteoneventisusedtoplayanote.Forexample,whenakeyispressed.

Data1(0b0kkkkkkk): Notenumber(key).SeeMIDInotenames.Data2(0b0vvvvvvv): Velocity(howfast/hardthekeyispressed).

Ifthevelocityiszero,thenoteoneventisinterpretedasanoteoffevent.Thisisespeciallyusefulwhenusingarunningstatus.

Statusbyte Notenumber VelocityBit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0Value 1 0 0 1 n n n n 0 k k k k k k k 0 v v v v v v v

Polyphonickeypressure(0xA)

Apolyphonickeypressureeventisusedwhenthepressureonakeyorapressuresensitivepadchangesafterthenoteonevent.

Data1(0b0kkkkkkk): Notenumber(key).SeeMIDInotenames.Data2(0b0vvvvvvv): Pressureonthekey.

MostnormalMIDIkeyboardsdonotimplementthisevent.Keypressureissometimesreferredtoasafter-touchorafter-pressure.

Statusbyte Notenumber PressureBit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0Value 1 0 1 0 n n n n 0 k k k k k k k 0 v v v v v v v

Controlchange(0xB)

Acontrolchangeeventisusedwhenthevalueofacontrollerchanges.

Data1(0b0ccccccc): Controllernumber.SeeControllernumbers.Data2(0b0vvvvvvv): Thevalueofthecontroller.

Controllernumbers120-127arereservedas"ChannelModeMessages".

Statusbyte Controllernumber ValueBit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0Value 1 0 1 1 n n n n 0 c c c c c c c 0 v v v v v v v

Programchange(0xC)

Aprogramchangeeventisusedtochangetheprogram(i.e.sound,voice,tone,presetorpatch)ofagivenchannelischanged.

Data1(0b0ppppppp): Programnumber.SeeProgramnumbers.

Controllernumbers120-127arereservedas"ChannelModeMessages".

Statusbyte ProgramnumberBit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0Value 1 1 0 0 n n n n 0 c c c c c c c

Channelpressure(0xD)

Achannelpressureeventisusedwhenthepressureonakeyorapressuresensitivepadchangesafterthenoteonevent.Unlikepolyphonickeypressure,channelpressureaffectsallnotesplayingonthechannel.

Data1(0b0vvvvvvv): Pressurevalue.

MostnormalMIDIkeyboardsdonotimplementthisevent.Channelpressureissometimesreferredtoasafter-touchorafter-pressure.

Statusbyte PressureBit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0Value 1 1 0 1 n n n n 0 v v v v v v v

Pitchbendchange(0xE)

Apitchbendchangeeventisusedtoalterthepitchofthenotesplayedonagivenchannel.

Data1(0b0lllllll): Leastsignificantbyte(bits0-7)ofthepitchbendvalue.Data2(0b0mmmmmmm): Mostsignificantbyte(bits8-13)ofthepitchbendvalue.

Thecenterposition(nopitchchange)isrepresentedbyLSB=0x0,MSB=0x40

Statusbyte LSB MSBBit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0Value 1 1 1 0 n n n n 0 l l l l l l l 0 m m m m m m m

Runningstatus

Therearealotofcircumstanceswhereyouhavetosendmanymessagesofthesametype.Forexample,ifyouhaveadigitalkeyboard,prettymuchallmessageswillbenoteonandnoteoffevents,orwhenyouturnaknobonaMIDIcontroller,alotofcontrolchangemessageswillbesenttoupdatethecontrollervalue.Tosavebandwidthinthesekindsofsituations,youonlyhavetosendthestatusbyteonce,followedbyonlydatabytes.Thistechniqueiscalled"runningstatus".Becausenoteoneventsaremostlikelytobefollowedbynoteoffevents(ormorenoteonevents),theMIDIstandardallowsyoutouseanoteoneventwithavelocityofzeroinsteadofanoteoffevent.Thismeansthatyouonlyneedonestatusbyteforallnoteevents,drasticallyreducingthedatathroughput,thusminimizingthedelaybetweenevents.

SystemMessages

SystemmessagesareMIDImessagesthatdonotcarrydataforaspecificMIDIchannel.Therearethreetypesofsystemmessages:

SystemCommonMessages

SystemCommonmessagesareintendedforallreceiversinthesystem.Thesemessagesarebeyondthescopeofthisguide.Ifyouwantmoreinformation,refertopage27oftheMIDI1.0DetailedSpecification4.2.

MIDITimeCodeQuarterFrame(0xF1)SongPositionPointer(0xF2)SongSelect(0xF3)TuneRequest(0xF6)EOX(EndofExclusive)(0xF7)

SystemRealTimeMessages

SystemRealTimemessagesareusedforsynchronizationbetweenclock-basedMIDIcomponents.Thesemessagesarebeyondthescopeofthisguide.Ifyouwantmoreinformation,refertopage30oftheMIDI1.0DetailedSpecification4.2.

TimingClock(0xF8)Start(0xFA)Continue(0xFB)Stop(0xFC)ActiveSensing(0xFE)SystemReset(0xFF)

SystemExclusiveMessages

SystemExclusive(SysEx)messagesareusedforthingslikesettingsynthesizerorpatchsettings,sendingsamplerdata,memorydumps,etc.MostSysExmessagesaremanufacturer-specific,soitisbesttoconsulttheMIDIimplementationinthemanual.Ifyouwantmoreinformationonthetopic,youcanfinditonpage34oftheMIDI1.0DetailedSpecification4.2.

Asystemexclusivemessagestartswithastatusbyte0xF0,followedbyanarbitrarynumberofdatabytes,andendswithanotherstatusbyte0xF7.

SysExstart Data … SysExendBit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 … 7 6 5 4 3 2 1 0Value 1 1 1 1 0 0 0 0 0 d d d d d d d … 1 1 1 1 0 1 1 1

AppendicesAllnumbersareinhexadecimalrepresentation,unlessotherwisespecified.

MIDInotenames

MiddleCorC4isdefinedasMIDInote0x3C.Thelowestnoteonastandard88-keypianoisA0(0x15)andthehighestnoteisC8(0x6C).

NoteOctave C C# D D# E F F# G G# A A# B

-1 00 01 02 03 04 05 06 07 08 09 0A 0B0 0C 0D 0E 0F 10 11 12 13 14 15 16 171 18 19 1A 1B 1C 1D 1E 1F 20 21 22 232 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F3 30 31 32 33 34 35 36 37 38 39 3A 3B4 3C 3D 3E 3F 40 41 42 43 44 45 46 475 48 49 4A 4B 4C 4D 4E 4F 50 51 52 536 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F7 60 61 62 63 64 65 66 67 68 69 6A 6B8 6C 6D 6E 6F 70 71 72 73 74 75 76 779 78 79 7A 7B 7C 7D 7E 7F

Controllernumbers

ThisisanoverviewoftheMIDIcontrollernumbersthatcanbeusedasthefirstdatabyteofacontrolchangeevent.Theseconddatabyteisthevalueforthecontroller.Thisvalueis7bitswide,sohasarangeof[0,127].Controllernumbers0x00-0x1Fcanbecombinedwithnumbers0x20-0x3Ffor14-bitresolution.Inthiscase,numbers0x00-0x1FsettheMSB,andnumbers0x20-0x3FtheLSB.Controllernumbers120-127arereservedforChannelModeMessages,whichratherthancontrollingsoundparameters,affectthechannel'soperatingmode.Controller Function Value Used

Dec Hex as0 00 BankSelect 00-7F MSB1 01 ModulationWheelorLever 00-7F MSB2 02 BreathController 00-7F MSB3 03 Undefined 00-7F MSB4 04 FootController 00-7F MSB5 05 PortamentoTime 00-7F MSB6 06 DataEntryMSB 00-7F MSB7 07 ChannelVolume(formerlyMainVolume) 00-7F MSB8 08 Balance 00-7F MSB9 09 Undefined 00-7F MSB10 0A Pan 00-7F MSB11 0B ExpressionController 00-7F MSB12 0C EffectControl1 00-7F MSB13 0D EffectControl2 00-7F MSB14 0E Undefined 00-7F MSB15 0F Undefined 00-7F MSB16 10 GeneralPurposeController1 00-7F MSB17 11 GeneralPurposeController2 00-7F MSB18 12 GeneralPurposeController3 00-7F MSB19 13 GeneralPurposeController4 00-7F MSB20 14 Undefined 00-7F MSB21 15 Undefined 00-7F MSB22 16 Undefined 00-7F MSB23 17 Undefined 00-7F MSB24 18 Undefined 00-7F MSB25 19 Undefined 00-7F MSB26 1A Undefined 00-7F MSB27 1B Undefined 00-7F MSB28 1C Undefined 00-7F MSB29 1D Undefined 00-7F MSB30 1E Undefined 00-7F MSB31 1F Undefined 00-7F MSB32 20 LSBforControl0(BankSelect) 00-7F LSB33 21 LSBforControl1(ModulationWheelorLever) 00-7F LSB34 22 LSBforControl2(BreathController) 00-7F LSB35 23 LSBforControl3(Undefined) 00-7F LSB36 24 LSBforControl4(FootController) 00-7F LSB37 25 LSBforControl5(PortamentoTime) 00-7F LSB38 26 LSBforControl6(DataEntry) 00-7F LSB39 27 LSBforControl7(ChannelVolume,formerlyMainVolume) 00-7F LSB40 28 LSBforControl8(Balance) 00-7F LSB41 29 LSBforControl9(Undefined) 00-7F LSB42 2A LSBforControl10(Pan) 00-7F LSB43 2B LSBforControl11(ExpressionController) 00-7F LSB44 2C LSBforControl12(Effectcontrol1) 00-7F LSB45 2D LSBforControl13(Effectcontrol2) 00-7F LSB46 2E LSBforControl14(Undefined) 00-7F LSB47 2F LSBforControl15(Undefined) 00-7F LSB48 30 LSBforControl16(GeneralPurposeController1) 00-7F LSB49 31 LSBforControl17(GeneralPurposeController2) 00-7F LSB50 32 LSBforControl18(GeneralPurposeController3) 00-7F LSB

51 33 LSBforControl19(GeneralPurposeController4) 00-7F LSB52 34 LSBforControl20(Undefined) 00-7F LSB53 35 LSBforControl21(Undefined) 00-7F LSB54 36 LSBforControl22(Undefined) 00-7F LSB55 37 LSBforControl23(Undefined) 00-7F LSB56 38 LSBforControl24(Undefined) 00-7F LSB57 39 LSBforControl25(Undefined) 00-7F LSB58 3A LSBforControl26(Undefined) 00-7F LSB59 3B LSBforControl27(Undefined) 00-7F LSB60 3C LSBforControl28(Undefined) 00-7F LSB61 3D LSBforControl29(Undefined) 00-7F LSB62 3E LSBforControl30(Undefined) 00-7F LSB63 3F LSBforControl31(Undefined) 00-7F LSB64 40 DamperPedalon/off(Sustain) ≤3Foff,≥40on ---65 41 PortamentoOn/Off ≤3Foff,≥40on ---66 42 SostenutoOn/Off ≤3Foff,≥40on ---

67 43 SoftPedalOn/Off ≤3Foff,≥40on ---

68 44 LegatoFootswitch ≤3FNormal,≥40Legato ---

69 45 Hold2 ≤3Foff,≥40on ---70 46 SoundController1(default:SoundVariation) 00-7F LSB71 47 SoundController2(default:Timbre/HarmonicIntens.) 00-7F LSB72 48 SoundController3(default:ReleaseTime) 00-7F LSB73 49 SoundController4(default:AttackTime) 00-7F LSB74 4A SoundController5(default:Brightness) 00-7F LSB75 4B SoundController6(default:DecayTime-seeMMARP-021) 00-7F LSB76 4C SoundController7(default:VibratoRate-seeMMARP-021) 00-7F LSB77 4D SoundController8(default:VibratoDepth-seeMMARP-021) 00-7F LSB78 4E SoundController9(default:VibratoDelay-seeMMARP-021) 00-7F LSB79 4F SoundController10(defaultundefined-seeMMARP-021) 00-7F LSB80 50 GeneralPurposeController5 00-7F LSB81 51 GeneralPurposeController6 00-7F LSB82 52 GeneralPurposeController7 00-7F LSB83 53 GeneralPurposeController8 00-7F LSB84 54 PortamentoControl 00-7F LSB85 55 Undefined --- ---86 56 Undefined --- ---87 57 Undefined --- ---88 58 HighResolutionVelocityPrefix 00-7F LSB89 59 Undefined --- ---90 5A Undefined --- ---

91 5B Effects1Depth(default:ReverbSendLevel-seeMMARP-023)(formerlyExternalEffectsDepth) 00-7F ---

92 5C Effects2Depth(formerlyTremoloDepth) 00-7F ---

93 5D Effects3Depth(default:ChorusSendLevel-seeMMARP-023)(formerlyChorusDepth) 00-7F ---

94 5E Effects4Depth(formerlyCeleste[Detune]Depth) 00-7F ---95 5F Effects5Depth(formerlyPhaserDepth) 00-7F ---96 60 DataIncrement(DataEntry+1)(seeMMARP-018) N/A ---97 61 DataDecrement(DataEntry-1)(seeMMARP-018) N/A ---98 62 Non-RegisteredParameterNumber(NRPN)-LSB 00-7F LSB99 63 Non-RegisteredParameterNumber(NRPN)-MSB 00-7F MSB100 64 RegisteredParameterNumber(RPN)-LSB* 00-7F LSB

101 65 RegisteredParameterNumber(RPN)-MSB* 00-7F MSB102 66 Undefined --- ---103 67 Undefined --- ---104 68 Undefined --- ---105 69 Undefined --- ---106 6A Undefined --- ---107 6B Undefined --- ---108 6C Undefined --- ---109 6D Undefined --- ---110 6E Undefined --- ---111 6F Undefined --- ---112 70 Undefined --- ---113 71 Undefined --- ---114 72 Undefined --- ---115 73 Undefined --- ---116 74 Undefined --- ---117 75 Undefined --- ---118 76 Undefined --- ---119 77 Undefined --- ---

Channelmode Function ValueDec Hex120 78 AllSoundOff 00121 79 ResetAllControllers 00122 7A LocalControlOn/Off 00off,7Fon123 7B AllNotesOff 00

124 7C OmniModeOff(+allnotesoff) 00

125 7D OmniModeOn(+allnotesoff) 00

126 7E MonoModeOn(+polyoff,+allnotesoff)

Note:Thisequalsthenumberofchannels,orzeroifthenumberofchannelsequalsthenumberofvoicesinthereceiver.

127 7F PolyModeOn(+monooff,+allnotesoff) 0

Source

Programnumbers

TheMIDIspecificationdoesn'tspecifyinstrumentsorvoicesforprogramnumbers.TheGeneralMIDI1soundsetdoesdefinealistofsoundsandfamiliesofsounds.Program FamilyName1-8 Piano9-16 ChromaticPercussion17-24 Organ25-32 Guitar33-40 Bass41-48 Strings49-56 Ensemble57-64 Brass65-72 Reed73-80 Pipe81-88 SynthLead89-96 SynthPad

97-104 SynthEffects

105-112 Ethnic113-120 Percussive121-128 SoundEffects

Program InstrumentName1 AcousticGrandPiano2 BrightAcousticPiano3 ElectricGrandPiano4 Honky-tonkPiano5 ElectricPiano16 ElectricPiano27 Harpsichord8 Clavi9 Celesta10 Glockenspiel11 MusicBox12 Vibraphone13 Marimba14 Xylophone15 TubularBells16 Dulcimer17 DrawbarOrgan18 PercussiveOrgan19 RockOrgan20 ChurchOrgan21 ReedOrgan22 Accordion23 Harmonica24 TangoAccordion25 AcousticGuitar(nylon)26 AcousticGuitar(steel)27 ElectricGuitar(jazz)28 ElectricGuitar(clean)29 ElectricGuitar(muted)30 OverdrivenGuitar31 DistortionGuitar32 Guitarharmonics33 AcousticBass34 ElectricBass(finger)35 ElectricBass(pick)36 FretlessBass37 SlapBass138 SlapBass239 SynthBass140 SynthBass241 Violin42 Viola43 Cello44 Contrabass45 TremoloStrings46 PizzicatoStrings47 OrchestralHarp48 Timpani

49 StringEnsemble150 StringEnsemble251 SynthStrings152 SynthStrings253 ChoirAahs54 VoiceOohs55 SynthVoice56 OrchestraHit57 Trumpet58 Trombone59 Tuba60 MutedTrumpet61 FrenchHorn62 BrassSection63 SynthBrass164 SynthBrass265 SopranoSax66 AltoSax67 TenorSax68 BaritoneSax69 Oboe70 EnglishHorn71 Bassoon72 Clarinet73 Piccolo74 Flute75 Recorder76 PanFlute77 BlownBottle78 Shakuhachi79 Whistle

80 Ocarina81 Lead1(square)82 Lead2(sawtooth)83 Lead3(calliope)84 Lead4(chiff)85 Lead5(charang)86 Lead6(voice)87 Lead7(fifths)88 Lead8(bass+lead)89 Pad1(newage)90 Pad2(warm)91 Pad3(polysynth)92 Pad4(choir)93 Pad5(bowed)94 Pad6(metallic)95 Pad7(halo)96 Pad8(sweep)97 FX1(rain)98 FX2(soundtrack)99 FX3(crystal)100 FX4(atmosphere)

101 FX5(brightness)102 FX6(goblins)103 FX7(echoes)104 FX8(sci-fi)105 Sitar106 Banjo107 Shamisen108 Koto109 Kalimba110 Bagpipe111 Fiddle112 Shanai113 TinkleBell114 Agogo115 SteelDrums116 Woodblock117 TaikoDrum118 MelodicTom119 SynthDrum120 ReverseCymbal

121 GuitarFretNoise122 BreathNoise123 Seashore124 BirdTweet125 TelephoneRing126 Helicopter127 Applause128 Gunshot

Source

MIDIhardwareTheMIDIhardwarelinkisjusta5mAcurrentloopthatasynchronouslysendsandreceives8-bitbytesatabaudrateof31250symbolspersecond.ThismeansthattheArduino'shardwareUARTcanbeusedfortransmittingandreceivingMIDI.DIN5pin(180degree)femalereceptaclesareusedforMIDIin,outandthroughconnectors.

Thisistheoriginalschematicthatcanbefoundinthe1996MIDI1.0DetailedSpecification4.2:

Thecurrentloopconsistsofaanopencollectoroutputonthetransmittingend(MIDIoutandMIDIthrough),andanopto-isolatoratthereceivingend(MIDIin).Whena'zero'issent,theopencollectoroutputsinkscurrent,turningontheLEDoftheopto-isolator.Thiswillinturnbringlowtheopencollectoroutputoftheopto-isolator,resultinginalowsignal.Thereasonforusingacurrentloopinsteadofavoltage,isthatthesenderandthereceivercan

beatdifferentpotentials,becauseeverythingisgalvanicallyisolated.Thisalsopreventsgroundloops,whichcanresultinnoise.Notethatthegroundandshielding(pin2onthe5-pinDINconnector)isconnectedtothegroundoftheMIDIoutandthroughcircuits,butnottothegroundofthereceiverintheMIDIincircuit.

Thestandardwasupdatedin2014toincludespecificationsfor3.3VMIDIdevices.(MIDI1.0ElectricalSpecificationUpdate(CA-033)(2014).MMATechnicalStandardsBoard/AMEIMIDICommittee.)

Pin2mustbetiedtogroundontheMIDItransmitteronly.

ThebufferbetweentheUARTtransmitterandRC isoptionalandsystem-dependent.

TheUARTisconfiguredwith8databits,noparity,and1stopbit,or8-N-1.

Theresistorvaluesdependonthetransmissionsignalingvoltage,VTX,asdetailedbelow.

Theoptionalferritebeadsare1k-ohmat100MHzsuchasMMZ1608Y102BTorsimilar.

VTX +5V±10% +3.3V±5%

RA 220Ω5%0.25W 33Ω5%0.5W

RC 220Ω5%0.25W 10Ω5%0.25W

31,250bits/secUARTReceiver

VTX

RE

RF

N/CN/C

THRU

RB220

RD

VRX OptionalMIDIThruCircuit

ChooseRE andRF basedonVTX inthesamewayasdescribedforMIDIOutRA

andRC

Opto-Isolatorsuchas

PC900Vor6N138

IN

1

2

34

5

1

2

34

5

DonotconnectanypinsoftheMIDIINjackdirectlytoground ValueofRD

dependsonopto-isolatorandVRX.

Recommendedvalueis280ΩforPC900V

withVRX=5V.

D11N914

FB31K@100MHz

FB41K@100MHz

N/CN/C

OptionalferritebeadstoimproveEMI/EMCperformance

FB51K@100MHz

FB61K@100MHz

OptionalferritebeadstoimproveEMI/EMCperformance

Reversevoltageprotectionforopto-isolator

Jackshield– N/CoroptionalgroundtoimproveEMI/EMCperformance

Jackshield– N/Coroptionalsmallcapacitor(0.1 µFtypical)toimprove

EMI/EMCperformance

Pin2– N/Coroptionalsmallcapacitor(0.1 µFtypical)toimprove RFgrounding

SendingMIDIoverSerialTheeasiestwaytosendoutMIDIpacketsistousetheSerial.write(uint8_tdata);function.Thisfunctionwritesoutone8-bitbyteovertheSerialconnection(eitherhardwareUART0orthevirtualCOMportoverUSB).TosendoutaMIDIpacket,wejusthavetowriteoutthethreebytesthatmakeupthepacket:firstthestatusbyte,thenthetwodatabytes.

voidsendMIDI(uint8_tstatusByte,uint8_tdataByte1,uint8_tdataByte2){Serial.write(statusByte);Serial.write(dataByte1);Serial.write(dataByte2);}

InordertosupportMIDIpacketswithonlyonedatabyteaswell,wecanjustoverloadthesendMIDIfunction.Thismeansthatwecreatetwofunctionswiththesamename,butwithdifferentparameters.

voidsendMIDI(uint8_tstatusByte,uint8_tdataByte){Serial.write(statusByte);Serial.write(dataByte);}

Initscurrentform,thesendMIDIfunctionisquitesilly.AlthoughitsendsoutMIDIpackets,itdoesn'tautomaticallycreatethesepacketsforus,westillhavetoputtogetherthestatusanddatabytesourselves,andwehavetomakesurethatitisavalidMIDIpacketbeforecallingsendMIDI.Let'screateamoreusefulfunctionthattakesamessagetype,channelnumberanddataasinputs,createsaMIDIpacket,andsendsitovertheSerialport.

voidsendMIDI(uint8_tmessageType,uint8_tchannel,uint8_tdata1,uint8_tdata2){channel--;//Decrementthechannel,becauseMIDIchannel1correspondstobinarychannel0uint8_tstatusByte=messageType|channel;//CombinethemessageType(highnibble)//withthechannel(lownibble)//Boththemessagetypeandthechannelshouldbe4bitswideSerial.write(statusByte);Serial.write(data1);Serial.write(data2);}

WenowhaveaworkingfunctionthatsendsMIDIpackets,andtakesasomewhatsensibleinput,notjustthebytesofthepacket.Butthere'sstillnoguaranteethatitisavalidMIDImessage.Rememberthatthestatusbyteshouldhaveamostsignificantbitequalto1,andthedatabytesamostsignificantbitequalto0.We'llusesomebitwisemathtomakesurethatthisisalwaysthecase,nomatterwhatdatatheuserenters.

voidsendMIDI(uint8_tmessageType,uint8_tchannel,uint8_tdata1,uint8_tdata2){channel--;//Decrementthechannel,becauseMIDIchannel1//correspondstobinarychannel0uint8_tstatusByte=messageType|channel;//CombinethemessageType(highnibble)//withthechannel(lownibble)//Boththemessagetypeandthechannel//shouldbe4bitswidestatusByte|=0b10000000;//Setthemostsignificantbitofthestatusbytedata1&=0b01111111;//Clearthemostsignificantbitofthedatabytesdata2&=0b01111111;Serial.write(statusByte);//SendoverSerialSerial.write(data1);Serial.write(data2);}

voidsendMIDI(uint8_tmessageType,uint8_tchannel,uint8_tdata){channel--;//Decrementthechannel,becauseMIDIchannel1//correspondstobinarychannel0uint8_tstatusByte=messageType|channel;//CombinethemessageType(highnibble)//withthechannel(lownibble)//Boththemessagetypeandthechannel//shouldbe4bitswide

statusByte|=0b10000000;//Setthemostsignificantbitofthestatusbytedata&=0b01111111;//ClearthemostsignificantbitofthedatabyteSerial.write(statusByte);//SendoverSerialSerial.write(data);}

Beforesendingthepacket,wesetthemostsignificantbitofthestatusbytebyperformingabitwiseORoperation:

0bxsssssss0b10000000-----------|0b1sssssss

Where0bsssssssisthestatus,andxiseither1or0.Asyoucansee,nomatterthevalueofx,theresultwillalwaysbe0b1sssssss.WealsoclearthemostsignificantbitsofthedatabytesbyperformingabitwiseANDoperation:

0bxddddddd0b01111111-----------&0b0ddddddd

Where0bdddddddisthedata,andxiseither1or0.Nomatterwhatthevalueofxis,theresultwillalwaysbe0b0ddddddd

Youcouldgoevenfurtherbymakingsurethatthemessagetypeandthechanneldon'tinterferewitheachother.However,thatmightbeoverlydefensive.

voidsendMIDI(uint8_tmessageType,uint8_tchannel,uint8_tdata1,uint8_tdata2){channel--;//Decrementthechannel,becauseMIDIchannel1//correspondstobinarychannel0messageType&=0b11110000;//Makesurethatonlythehighnibble//ofthemessagetypeissetchannel&=0b00001111;//Makesurethatonlythelownibble//ofthechannelissetuint8_tstatusByte=messageType|channel;//CombinethemessageType(highnibble)//withthechannel(lownibble)//Boththemessagetypeandthechannel//shouldbe4bitswidestatusByte|=0b10000000;//Setthemostsignificantbitofthestatusbytedata1&=0b01111111;//Clearthemostsignificantbitofthedatabytesdata2&=0b01111111;Serial.write(statusByte);//SendoverSerialSerial.write(data1);Serial.write(data2);}

ImprovingreadabilityTosendaControlChange(0xB0)messageonchannel3forcontroller80withavalueof64,youwouldcallsendMIDI(0xB0,3,80,64);Tomakeitalittlemoreobviouswhat'sgoingon,wecoulddeclaresomeconstantsforthedifferentmessagetypes:

constuint8_tNOTE_OFF=0x80;constuint8_tNOTE_ON=0x90;constuint8_tKEY_PRESSURE=0xA0;constuint8_tCC=0xB0;constuint8_tPROGRAM_CHANGE=0xC0;constuint8_tCHANNEL_PRESSURE=0xD0;constuint8_tPITCH_BEND=0xE0;

YoucannowusesendMIDI(CC,3,80,64);whichwillmakethecodemucheasiertoread.Whenwritingcode,it'salwaysagoodideatokeepso-calledmagicnumberstoaminimum.Theseareseeminglyarbitrarynumericliteralsinyourcodethatdon'thaveaclearmeaning.Forexample,thiscodesnippetplaysachromaticglissando(allkeys,oneaftertheother)onanhonky-tonkpiano:

sendMIDI(0xC0,1,4);for(uint8_ti=21;i<=108;i++){sendMIDI(0x90,1,i,64);delay(100);

sendMIDI(0x80,1,i,64);}

Tosomeonewhohasneverseenthecode,orsomeonewhodoesn'tknowallMIDImessagetypecodesbyheart,it'snotclearwhatallthesenumbersmean.Amuchbettersketchwouldbe:

constuint8_thonkyTonkPiano=4;//GMdefinestheHonky-tonkPianoasinstrument#4

constuint8_tnote_A1=21;//lowestnoteonan88-keypianoconstuint8_tnote_C9=108;//highestnoteonan88-keypiano

uint8_tchannel=1;//MIDIchannel1uint8_tvelocity=64;//64=mezzoforte

sendMIDI(PROGRAM_CHANGE,channel,honkyTonkPiano);for(uint8_tnote=note_A1;note<=note_C9;note++){//chromaticglissandooverall88pianokeyssendMIDI(NOTE_ON,channel,note,velocity);delay(100);sendMIDI(NOTE_OFF,channel,note,velocity);}

Thissnippetdoesexactlythesamethingasthepreviousexample,butit'smucheasiertoreadandunderstand.

Usingstructs

AnotherapproachwouldbetocomposetheMIDImessageinabuffer,andthenjustwriteoutthatbuffer.WecandefineastructwiththedifferentfieldsofaMIDIevent.Takealookatthisstruct:

typedefstructMIDI_message_3B{unsignedintchannel:4;//secondnibble:MIDIchannel(0-15)unsignedintstatus:3;//firstnibble:statusmessageunsignedint_msb0:1;//mostsignificantbitofstatusbyte:shouldbe1accordingtoMIDIspecificationunsignedintdata1:7;//secondbyte:firstvalueunsignedint_msb1:1;//mostsignificantbitoffirstdatabyte:shouldbe0accordingtoMIDIspecificationunsignedintdata2:7;//thirdbyte:secondvalueunsignedint_msb2:1;//mostsignificantbitofseconddatabyte:shouldbe0accordingtoMIDIspecificationMIDI_message_3B():_msb0(1),_msb1(0),_msb2(0){}//setthecorrectmsb'sforMIDI};

Youmighthavenoticedthatthebitfieldsareinthewrongorder:forexample,thenormalorderofthestatusbytewouldbe1.mmm.ccccwithmmmthemessagetypeandccccthechannel.However,theorderinourstructiscccc.mmm.1.Tounderstandwhat'sgoingon,youhavetoknowthatArduinosareLittleEndian.Thismeansthatthefirstbitfieldtakesuptheleastsignificantbitsineachbyte.Inotherwords,thebitfieldswithineachbyteareinreversedorder,comparedtotheconventionalBigEndiannotation(thatisusedintheMIDIspecification).

Youcannowfillupallfieldsofthestruct,tocreateavalidMIDIpacket.Youdon'thavetoworryaboutthemostsignificantbitsofeachbyte,bitmaskingisdoneautomatically,becauseofthebitfields.Thesebitsaresettothecorrectvaluewhenamessageiscreated,intheinitializerlistoftheconstructor.Theonlythingyouneedtokeepinmindisthatthechannelsarezero-based.Alsonotethatthemessagetypesarenolonger0x80,0x90etc.,but0x8,0x9...

constuint8_tNOTE_ON=0x9;MIDI_message_3Bmsg;//Createavariablecalled'msg'ofthe'MIDI_message_3B'typewejustdefinedmsg.status=NOTE_ON;msg.channel=channel-1;//MIDIchannelsstartfrom0,sosubtract1msg.data1=note_A1;msg.data2=velocity;

Finally,youcanjustwriteoutthemessageovertheSerialport.We'llcreateanotheroverloadofthesendMIDIfunction:

voidsendMIDI(MIDI_message_3Bmsg){Serial.write((uint8_t*)&msg,3);}

We'reusingthewrite(uint8_t*buffer,size_tnumberOfBytes)function.Thefirstargumentisapointertoabuffer(orarray)ofdatabytestowriteout.Thepointerpointstothefirstelementofthisarray.Thesecondargumentisthenumberofbytestosend,startingfromthatfirstelement.There'soneminorproblem:msgisnotanarray,it'sanobjectoftypeMIDI_message_3B.Thewritefunctionexpectsapointertoanarrayofbytes(uint8_t).Togetaroundthis,wecanjusttaketheaddressofmsg,usingtheaddress-ofoperator(&)andcastittoapointertoanarrayofuint8_t'susing(uint8_t*).WeneedtowriteouttheentireMIDIpacket,whichis3byteslong,sothesecondargumentisjust3.Tousethefunction,justuse:

sendMIDI(msg);

Infact,wecoulddoevenbetter.NoweverytimethesendMIDIfunctioniscalled,themsgobjectiscopied.Thistakestimeandmemory.Topreventitfrombeingcopied,wecanpassonlyareferencetomsgtothefunction.Here'swhatthatlookslike:

voidsendMIDI(MIDI_message_3B&msg){Serial.write((uint8_t*)&msg,3);}

sendMIDI(msg);

Youcandothesamethingfortwo-byteMIDIpackets:

typedefstructMIDI_message_2B{unsignedintchannel:4;//secondnibble:MIDIchannel(0-15)unsignedintstatus:3;//firstnibble:messagetypeunsignedint_msb0:1;//mostsignificantbitofstatusbyte:shouldbe1accordingtoMIDIspecificationunsignedintdata:7;//secondbyte:firstvalueunsignedint_msb1:1;//mostsignificantbitoffirstdatabyte:shouldbe0accordingtoMIDIspecificationMIDI_message_2B():_msb0(1),_msb1(0){}//setthecorrectmsb'sforMIDI};

voidsendMIDI(MIDI_message_2B&msg){Serial.write((uint8_t*)&msg,2);}

Runningstatus

Asdiscussedinchapter1,youcanuserunningstatusestosavebandwidth.Theimplementationisrelativelyeasy:rememberthelaststatusbyte(header)thatwassent,andthencompareeveryfollowingstatusbytetothisheader.Ifit'sthesamestatus,sendthedatabytesonly,otherwise,sendthenewstatusbyte,andrememberthisheader.Torememberthepreviousheader,astaticvariableisused.Staticvariablesarenotdestroyedwhentheygooutofscope,sothevalueisretainedthenexttimethesendMIDIHeaderfunctionisexecuted.

voidsendMIDIHeader(uint8_theader){staticuint8_trunningHeader=0;if(header!=runningHeader){//IfthenewheaderisdifferentfromthepreviousSerial.write(header);//SendthestatusbyteoverSerialrunningHeader=header;//Rememberthenewheader}}

voidsendMIDI(uint8_tmessageType,uint8_tchannel,uint8_tdata1,uint8_tdata2){channel--;//Decrementthechannel,becauseMIDIchannel1//correspondstobinarychannel0uint8_tstatusByte=messageType|channel;//CombinethemessageType(highnibble)//withthechannel(lownibble)

//Boththemessagetypeandthechannel//shouldbe4bitswidestatusByte|=0b10000000;//Setthemostsignificantbitofthestatusbytedata1&=0b01111111;//Clearthemostsignificantbitofthedatabytesdata2&=0b01111111;

sendMIDIHeader(statusByte);//SendtheheaderoverSerial,usingrunningstatusSerial.write(data1);//SendthedatabytesoverSerialSerial.write(data2);}

voidsendMIDI(uint8_tmessageType,uint8_tchannel,uint8_tdata){channel--;//Decrementthechannel,becauseMIDIchannel1//correspondstobinarychannel0uint8_tstatusByte=messageType|channel;//CombinethemessageType(highnibble)//withthechannel(lownibble)//Boththemessagetypeandthechannel//shouldbe4bitswidestatusByte|=0b10000000;//Setthemostsignificantbitofthestatusbytedata&=0b01111111;//Clearthemostsignificantbitofthedatabyte

sendMIDIHeader(statusByte);//SendtheheaderoverSerial,usingrunningstatusSerial.write(data);//SendthedatabyteoverSerial}

Goingevenfurther,wecanreplacenoteoffeventsbynoteoneventswithavelocityofzero:

voidsendMIDI(uint8_tmessageType,uint8_tchannel,uint8_tdata1,uint8_tdata2){if(messageType==NOTE_OFF){//ReplacenoteoffmessagesmessageType=NOTE_ON;//withanoteonmessagedata2=0;//withavelocityofzero.}channel--;//Decrementthechannel,becauseMIDIchannel1//correspondstobinarychannel0uint8_tstatusByte=messageType|channel;//CombinethemessageType(highnibble)//withthechannel(lownibble)//Boththemessagetypeandthechannel//shouldbe4bitswidestatusByte|=0b10000000;//Setthemostsignificantbitofthestatusbytedata1&=0b01111111;//Clearthemostsignificantbitofthedatabytesdata2&=0b01111111;

sendMIDIHeader(statusByte);//SendtheheaderoverSerial,usingrunningstatusSerial.write(data1);//SendthedatabytesoverSerialSerial.write(data2);}

Toensurethatthereceiverwillknowwhattodowiththedata,evenifitmissedthefirstheaderbyte,itisagoodideatosendaheaderbyteregularly.Thiscanbedonebyrememberingthetimethelastheaderwassent:

constunsignedlongheaderResendTime=1000;//sendanewheadereverysecond

voidsendMIDIHeader(uint8_theader){staticunsignedlonglastHeaderTime=millis();staticuint8_trunningHeader=0;if(header!=runningHeader//Ifthenewheaderisdifferentfromtheprevious||(millis()-lastHeaderTime)>headerResendTime){//Orifthelastheaderwassentmorethan1sagoSerial.write(header);//SendthestatusbyteoverSerialrunningHeader=header;//RememberthenewheaderlastHeaderTime=millis();}}

Finishedcode

Youcannowputthesefunctionsinaseparatefile,sothatyoucanuseitinallofyoursketches.SaveitassendMIDI.h,you'llneeditinthefollowingchapters.

#ifndefsendMIDI_h_#definesendMIDI_h_

constuint8_tNOTE_OFF=0x80;constuint8_tNOTE_ON=0x90;constuint8_tKEY_PRESSURE=0xA0;constuint8_tCC=0xB0;constuint8_tPROGRAM_CHANGE=0xC0;constuint8_tCHANNEL_PRESSURE=0xD0;constuint8_tPITCH_BEND=0xE0;

constunsignedlongheaderResendTime=1000;//sendanewheadereverysecond

voidsendMIDIHeader(uint8_theader){staticunsignedlonglastHeaderTime=millis();staticuint8_trunningHeader=0;

if(header!=runningHeader//Ifthenewheaderisdifferentfromtheprevious||(millis()-lastHeaderTime)>headerResendTime){//Orifthelastheaderwassentmorethan1sagoSerial.write(header);//SendthestatusbyteoverSerialrunningHeader=header;//RememberthenewheaderlastHeaderTime=millis();}}

voidsendMIDI(uint8_tmessageType,uint8_tchannel,uint8_tdata1,uint8_tdata2){if(messageType==NOTE_OFF){//ReplacenoteoffmessagesmessageType=NOTE_ON;//withanoteonmessagedata2=0;//withavelocityofzero.}channel--;//Decrementthechannel,becauseMIDIchannel1//correspondstobinarychannel0uint8_tstatusByte=messageType|channel;//CombinethemessageType(highnibble)//withthechannel(lownibble)//Boththemessagetypeandthechannel//shouldbe4bitswidestatusByte|=0b10000000;//Setthemostsignificantbitofthestatusbytedata1&=0b01111111;//Clearthemostsignificantbitofthedatabytesdata2&=0b01111111;

sendMIDIHeader(statusByte);//SendtheheaderoverSerial,usingrunningstatusSerial.write(data1);//SendthedatabytesoverSerialSerial.write(data2);}voidsendMIDI(uint8_tmessageType,uint8_tchannel,uint8_tdata){channel--;//Decrementthechannel,becauseMIDIchannel1//correspondstobinarychannel0uint8_tstatusByte=messageType|channel;//CombinethemessageType(highnibble)//withthechannel(lownibble)//Boththemessagetypeandthechannel//shouldbe4bitswidestatusByte|=0b10000000;//Setthemostsignificantbitofthestatusbytedata&=0b01111111;//Clearthemostsignificantbitofthedatabyte

sendMIDIHeader(statusByte);//SendtheheaderoverSerial,usingrunningstatusSerial.write(data);//SendthedatabyteoverSerial}

#endif

MIDIControllersTheMIDIprotocolisoftenusedforMIDIcontrollers,deviceswithphysicalknobsandbuttonstocontrolsettingsinaDigitalAudioWorkstation(DAW),ortoenternotesinaudioormusicnotationsoftware.Thisisoftenmuchfasterandmoreintuitivethanusingthemouseandkeyboardforeverything.MIDIcontrollersarealsousedduringliveperformances,tocontroleffectmodules,samplers,synthesizers,DJsoftware,etc.ThischapterwillcoverhowtowritethecodeforaworkingMIDIcontrollerusingArduino.

ButtonsForsendingthestateofabutton,noteeventsareused.Whenthebuttonispressed,anoteoneventissent,whenit'sreleased,anoteoffeventissent.

Hardware

ConnectingabuttontotheArduinoisprettystraightforward:Connectoneleadofthebuttontoadigitalinputpin,andconnecttheotherleadtoground.

+5V

S1

Internalpull-upresistorR1

Arduinodigitalinput

Theinternalpull-upresistor*oftheinputpinwillbeused,soifthebuttonisreleased(ifitdoesn'tconduct),theinputwillbe"pulledup"to5V,anditwillreadadigital1.Whenthebuttonispressed,itconnectstheinputpindirectlytoground,soitwillreadadigital0.

(*)Themicrocontrollerhasbuilt-inpull-upresistors,tomakeworkingwithbuttonsandopen-collectoroutputsawholeloteasier.Thisresistorcanbeenabledinsoftware,usingpinMode(pushButtonPin,INPUT_PULLUP).Thismeansthatyoudon'thavetoaddaresistorexternally.

Software

TheMIDIcontrolleronlyhastosendeventswhenthestateofthebuttonchanges.Todothis,theinputwillconstantlybepolledintheloop,andthenthepreviousstateiskeptinastaticvariable.Whenthenewinputstatedoesnotequalthepreviousstate,thestateofthebuttonhaschanged,andaMIDIeventwillbesent.Ifthenewstateislow,thebuttonhasbeenpressedandanoteoneventissent.Ifit'shigh,ithasbeenreleased,andanoteoffeventissent.

constuint8_tpushButtonPin=2;

constuint8_tchannel=1;//MIDIchannel1constuint8_tnote=0x3C;//MiddleC(C4)constuint8_tvelocity=0x7F;//Maximumvelocity

voidsetup(){pinMode(pushButtonPin,INPUT_PULLUP);//Enabletheinternalpull-upresistorSerial.begin(31250);}

voidloop(){staticboolpreviousState=HIGH;//Declareastaticvariabletosavethepreviousstate

//andinitializeittoHIGH(notpressed).boolcurrentState=digitalRead(pushButtonPin);//Readthecurrentstateoftheinputpinif(currentState!=previousState){//Ifthecurrentstateisdifferentfromthepreviousstateif(currentState==LOW){//IfthebuttonispressedsendMIDI(NOTE_ON,channel,note,velocity);//Sendanoteonevent}else{//IfthebuttonisreleasedsendMIDI(NOTE_OFF,channel,note,velocity);//Sendanoteoffevent}previousState=currentState;//Rememberthecurrentstateofthebutton}}

Keepinmindthatthedeclarationandinitializationofastaticlocalvariablehappenonlyonce,thevalueisretainedthenexttimethefunctionisexecuted.

Inprinciple,thisapproachshouldwork,however,inpractice,therewillbecontactbounce.Whenyoupressorreleaseabutton,itactuallychangesstatemanytimesreallyquickly,beforesettlingtothecorrectstate.Thisiscalledbounce,andcanbearealproblemifyouwanttoreliablydetectbuttonpresses.Byincludingatimerinthecode,youcanmakesurethatthebuttonisstableforatleastacoupleoftensofmillisecondsbeforeregisteringthestatechange.Here'swhatthatlookslike:

constunsignedlongdebounceTime=25;//Ignoreallstatechangesthathappen25milliseconds//afterthebuttonispressedorreleased.voidloop(){staticboolpreviousState=HIGH;//Declareastaticvariabletosavethepreviousstateoftheinput//andinitializeittoHIGH(notpressed).staticboolbuttonState=HIGH;//Declareastaticvariabletosavethestateofthebutton//andinitializeittoHIGH(notpressed).staticunsignedlongpreviousBounceTime=0;//Declareastaticvariabletosavethetimethebuttonlast//changedstate(bounced).boolcurrentState=digitalRead(pushButtonPin);//Readthecurrentstateoftheinputpinif(currentState!=buttonState){//Ifthecurrentstateisdifferentfromthebuttonstateif(millis()-previousBounceTime>debounceTime){//Iftheinputhasbeenstableforatleast25msbuttonState=currentState;//Rememberthestatethatthe(debounced)buttonisinif(buttonState==LOW){//IfthebuttonispressedsendMIDI(NOTE_ON,channel,note,velocity);//Sendanoteonevent}else{//IfthebuttonisreleasedsendMIDI(NOTE_OFF,channel,note,velocity);//Sendanoteoffevent}}}if(currentState!=previousState){//Ifthestateoftheinputchanged(ifthebuttonbounces)previousBounceTime=millis();//RememberthecurrenttimepreviousState=currentState;//Rememberthecurrentstateoftheinput}}

buttonStatekeepsthestateoftheideal,debouncedbutton,whilepreviousStatekeepsthepreviousstateoftheactualinput.

PotentiometersandfadersMIDIcontrollersoftenfeaturepotentiometersandfadersforcontinuouscontrollerslikevolume,pan,modulation,etc.

Hardware

Thevariableresistors(potentiometersorfaders)arejustusedinavoltagedividerconfiguration,withthetwoouterpinsconnectedtogroundand5V,andthecenterpinconnectedtoananaloginputpinontheArduino.Keepinmindthatyouneedapotentiometerwithalineartaper(nota

logarithmicoraudiotaper).

R1

+5V

Arduinoanaloginput

ControlChange

Formostcontinuouscontrollers,controlchangeeventsareused.Mostsoftwareonlysupports7-bitcontrollers.Thisallowsforatotalof1920controllers(120oneachofthe16MIDIchannels).

Acontinuouscontrollercanbeimplementedasfollows:sampletheanaloginputintheloop,convertfromthe10-bitanalogvaluetoa7-bitControlChangevalue,ifit'sadifferentvaluethanlasttime,sendacontrolchangemessagewiththenewvalue.

constuint8_tanalogPin=A0;

constuint8_tchannel=1;//MIDIchannel1constuint8_tcontroller=0x10;//GeneralPurposeController1

voidsetup(){Serial.begin(31250);}

voidloop(){staticuint8_tpreviousValue=0b10000000;//DeclareastaticvariabletosavethepreviousCCvalue//andinitializeitto0b10000000(themostsignificantbitisset,//soitisdifferentfromanypossible7-bitCCvalue).

uint16_tanalogValue=analogRead(analogPin);//Readthevalueoftheanaloginputuint8_tCC_value=analogValue>>3;//Convertfroma10-bitnumbertoa7-bitnumberbyshifting//it3bitstotheright.if(CC_value!=previousValue){//IfthecurrentvalueisdifferentfromthepreviousvaluesendMIDI(CC,channel,controller,CC_value);//SendthenewvalueoverMIDIpreviousValue=CC_value;//Rememberthenewvalue}}

Theproblemisthattherecanbequitealotofnoiseontheanaloginputs.Soifthevaluefluctuatesalot,itwillconstantlysendnewCCmessages,eveniftheknobisnotbeingtouched.Topreventthis,arunningaveragefiltercanbeusedontheinput.

constuint8_taverageLength=8;//Averagetheanaloginputover8samples(maximum=2^16/2^10=2^6=64)

voidloop(){staticuint8_tpreviousValue=0b10000000;//DeclareastaticvariabletosavethepreviousCCvalue//andinitializeitto0b10000000(themostsignificantbitisset,//soitisdifferentfromanypossible7-bitCCvalue).

uint16_tanalogValue=analogRead(analogPin);//ReadthevalueoftheanaloginputanalogValue=runningAverage(analogValue);//Averagethevalueuint8_tCC_value=analogValue>>3;//Convertfroma10-bitnumbertoa7-bitnumberbyshifting//it3bitstotheright.if(CC_value!=previousValue){//IfthecurrentvalueisdifferentfromthepreviousvaluesendMIDI(CC,channel,controller,CC_value);//SendthenewvalueoverMIDIpreviousValue=CC_value;//Rememberthenewvalue}}

uint16_trunningAverage(uint16_tvalue){//https://playground.arduino.cc/Main/RunningAverage

staticuint16_tpreviousValues[averageLength];staticuint8_tindex=0;staticuint16_tsum=0;staticuint8_tfilled=0;

sum-=previousValues[index];previousValues[index]=value;sum+=value;index++;index=index%averageLength;if(filled<averageLength)filled++;returnsum/filled;}

PitchBend

Ifahigherresolutionisrequired,forexampleforvolumefaders,pitchbendeventsareused.Thismeansthattheyhavea14-bitaccuracy,however,mostdevicesonlyusethe10mostsignificantbits.Therecanbeonlyonepitchbendcontrolleroneachofthe16MIDIchannels.

Thecodeisprettysimilartothepreviousexample.Justshiftthevalue4bitstotheleftinsteadof3bitstotheright,andsendapitchbendmessageinsteadofacontrolchangemessage.Alsonotethatsomeofthevariablesarenowoflargerdatatypes,toaccommodatethe14-bitpitchbendvalues.Tosendthe14-bitpitchbendvalue,ithastobesplitupintotwo7-bitdatabytes.Thiscanbeacchievedbyshiftingit7bitstotheright,togetthe7mostsignificantbits.ThesendMIDIfunctiontakescareofthebitmaskingofthe7leastsignificantbits.

constuint8_tanalogPin=A0;

constuint8_tchannel=1;//MIDIchannel1

voidsetup(){Serial.begin(31250);}

constuint8_taverageLength=16;//Averagetheanaloginputover16samples(maximum=2^16/2^10=2^6=64)

voidloop(){staticuint16_tpreviousValue=0x8000;//Declareastaticvariabletosavethepreviousvalue//andinitializeitto0x8000(themostsignificantbitisset,//soitisdifferentfromanypossible14-bitpitchbendvalue).

uint16_tanalogValue=analogRead(analogPin);//ReadthevalueoftheanaloginputanalogValue=runningAverage(analogValue);//Averagethevalueuint16_tvalue=analogValue<<4;//Convertfroma10-bitnumbertoa14-bitnumberbyshifting//it4bitstotheleft(adds4paddingzerostotheright).if(value!=previousValue){//IfthecurrentvalueisdifferentfromthepreviousvaluesendMIDI(PITCH_BEND,channel,value,value>>7);//SendthenewvalueoverMIDI(splitupintotwo7-bitbytes)previousValue=value;//Rememberthenewvalue}}

uint16_trunningAverage(uint16_tvalue){//https://playground.arduino.cc/Main/RunningAveragestaticuint16_tpreviousValues[averageLength];staticuint8_tindex=0;staticuint16_tsum=0;staticuint8_tfilled=0;

sum-=previousValues[index];previousValues[index]=value;sum+=value;index++;index=index%averageLength;if(filled<averageLength)filled++;returnsum/filled;}

Rotaryencoders

Thedisadvantageofpotentiometersisthatthecomputercan'tchangetheirposition.Forexample,ifyouhaveapotentiometermappedtoapluginparameter,andyouselectadifferentplugin,thepotentiometerdoesn'tautomaticallymovetothepositionofthenewplugginparameter'svalue.Evenworse,ifyouaccidentallytouchthepotentiometer,itwilloverwritetheparameterwiththepositionofpotentiometer,regardlessofthevalueithadbefore.

Onesolutionistouserotaryencoders.Thisisarelativeorincrementaltypeofrotaryknob,whichmeansthatitdoesn'thaveanabsoluteposition,itonlysendsincrementalpositionchangeswhenmoved.Whentheencoderisturnedtwotickstotheright,itsendsavalueof+2,whenit'sturned5tickstotheleft,itsendsavalueof-5.

Hardware

Connectthecommonpinoftherotaryencodertoground,andconnecttheAandBpinstodigitalinputpins(preferablyinterruptcapablepins)oftheArduino.Asahardwaredebouncingmeasure,youcouldaddanRClow-passfilter.

Software

Theeasiestwaytoreadarotaryencoderistousealibrary.Thisensurescompatibilityonprettymuchallboards,andmanyoftheselibrariesaremuchmoreefficientthanwritingtheISRcodeyourself.MypersonalfavoriteisthePJRCEncoderlibrary.

#include<Encoder.h>//IncludethePJRCEncoderlibrary

constuint8_tchannel=1;//MIDIchannel1constuint8_tcontroller=0x10;//GeneralPurposeController1

Encoderencoder(2,3);//Arotaryencoderconnectedtopins2and3

voidsetup(){Serial.begin(31250);}

voidloop(){staticint32_tpreviousPosition=0;//Astaticvariableforsavingthepreviousencoderpositionint32_tposition=encoder.read();//Readthecurrentencoderpositionint32_tdifference=position-previousPosition;//Calculatetherelativemovementif(difference!=0){//IftheencoderwasmovedsendMIDI(CC,channel,controller,difference);//SendtherelativepositionchangeoverMIDIpreviousPosition=position;//Rememberthecurrentpositionasthepreviousposition}}

Mostrotaryencoderssend4pulsesforeveryphysical'tick'(indent).Itmakessensetodividethenumberofpulsesby4beforesendingitoverMIDI.Keepinmindthatisafloordivision,sowecan'tjustreplacepreviousPositionwithposition,becausewe'dlosepulses.Forexample,ifthecurrentpositionis6,andthepreviouspositionis0,differencewillbe6pulses.6/4=1completetick.Thenthepreviouspositionwillbesetto6.However,only1tick,i.e.4pulses,hasbeensent,and6%4=2pulseshavejustbeenlost.Thesolutionisverysimple:

voidloop(){staticint32_tpreviousPosition=0;//Astaticvariableforsavingthepreviousencoderpositionint32_tposition=encoder.read();//Readthecurrentencoderpositionint32_tdifference=position-previousPosition;//Calculatetherelativemovementdifference/=4;//Onetickforevery4pulsesif(difference!=0){//IftheencoderwasmovedsendMIDI(CC,channel,controller,difference);//SendtherelativepositionchangeoverMIDIpreviousPosition+=difference*4;//AddthepulsessentoverMIDItothepreviousposition}}

Therearethreewaystoencodenegativepositionchangesintoa7-bitMIDIdatabyte:

1. Two'scomplement

2. Signedmagnitude3. Offsetbinary

OntheArduino,allsignednumbersarerepresentedastwo'scomplement.Sosendingatwo'scomplementnumberoverMIDIisassimpleasjustsending(the7leastsignificantbitsof)thesignedvariable.Insignedmagnituderepresentation,bit6isusedasasignbit(0=positive,1=negative),andthe6leastsignificantbitsareusedtostoretheabsolutevalueofthesignednumber.Whenusingbinaryoffsetrepresentation,64isaddedtothesignednumbertomakeeverythingpositive.

Someprogramsdon'tsupportrelativechangesofmorethan15inoneMIDImessage,soweconstrainthedifferenceto15.

Thissketchallowsyoutochoosewhatrepresentationtouse,toguaranteecompatibilitywithmostsoftware,andalsolimitstherelativepositionchangeperMIDImessageto15.

#include<Encoder.h>//IncludethePJRCEncoderlibrary

enumrelativeCCmode{TWOS_COMPLEMENT,BINARY_OFFSET,SIGN_MAGNITUDE};

constuint8_tchannel=1;//MIDIchannel1constuint8_tcontroller=0x10;//GeneralPurposeController1

constEncoderencoder(2,3);//Arotaryencoderconnectedtopins2and3

constrelativeCCmodenegativeRepresentation=SIGN_MAGNITUDE;//Selectthewaynegativenumbersarerepresented

voidsetup(){Serial.begin(31250);}

voidloop(){staticint32_tpreviousPosition=0;//Astaticvariableforsavingthepreviousencoderpositionint32_tposition=encoder.read();//Readthecurrentencoderpositionint32_tdifference=position-previousPosition;//Calculatetherelativemovementdifference/=4;//Onetickforevery4pulsesdifference=constrain(difference,-15,15);//Makesurethatonly15ticksaresentatonceif(difference!=0){//Iftheencoderwasmoveduint8_tCC_value=mapRelativeCC(difference);//ChangetherepresentationofnegativenumberssendMIDI(CC,channel,controller,CC_value);//SendtherelativepositionchangeoverMIDIpreviousPosition+=difference*4;//AddthepulsessentoverMIDItothepreviousposition}}

uint8_ttwosComplementTo7bitSignedMagnitude(int8_tvalue){//Convertan8-bittwo'scomplementintegerto7-bitsign-magnitudeformatuint8_tmask=value>>7;uint8_tabs=(value+mask)^mask;uint8_tsign=mask&0b01000000;return(abs&0b00111111)|sign;}

uint8_tmapRelativeCC(int8_tvalue){//Convertan8-bittwo'scomplementintegertoa7-bitvaluetosendoverMIDIswitch(negativeRepresentation){caseTWOS_COMPLEMENT:returnvalue;//RememberthatthesendMIDIfunctiondoesthebitmasking,soyoudon'thavetoworryaboutbit7being1.caseBINARY_OFFSET:returnvalue+64;caseSIGN_MAGNITUDE:returntwosComplementTo7bitSignedMagnitude(value);}}

Object-OrientedapproachTheexamplesaboveonlyworkforasinglebutton,potentiometerorencoder.Justcopyingand

pastingthecodeforeachnewcomponentwouldleadtomanyrepetitionsandverymessycode.That'swhyit'sagoodideatoimplementthecodeindifferentclasses:aclassforbuttons,anotherclassforpotentiometers,etc.YoucanthenjustinstantiatemanyobjectsoftheseclassesforthemanybuttonsandknobsonyourMIDIcontroller.

IwroteanArduinoMIDIcontrollerlibrarythatmakesthisreallyeasy.Forexample,thisisallthecodeyouneedforaMIDIcontrollerwith4potentiometers,4buttonsand2rotaryencoders:

#include<MIDI_Controller.h>//Includethelibrary

/*Createfournewinstancesoftheclass'Analog'onpinsA0,A1,A2andA3,withcontrollernumber0x07(channelvolume),onMIDIchannels1through4.*/Analogpotentiometers[]={{A0,0x7,1},{A1,0x7,2},{A2,0x7,3},{A3,0x7,4},};/*Createfournewinstancesoftheclass'Digital'onpins4,5,6and7,withnotenumbers0x10through0x13(mute),onMIDIchannel1.*/Digitalbuttons[]={{4,0x10,1},{5,0x11,1},{6,0x12,1},{7,0x13,1},};/*Createtwonewinstancesoftheclass'RotaryEncoder'called'encoders',onpins0&1,and2&3,controllernumbers0x2Fand0x30,onMIDIchannel1,atnormalspeed,usingnormalencoders(4pulsesperclick/step),usingtwo'scomplementsignrepresentation.*/RotaryEncoderencoders[]={{0,1,0x2F,1,1,NORMAL_ENCODER,TWOS_COMPLEMENT},{2,3,0x30,1,1,NORMAL_ENCODER,TWOS_COMPLEMENT}};

voidsetup(){}

voidloop(){//RefreshallinputsMIDI_Controller.refresh();}

Asyoucansee,there'sonlythedefinitionsofallcontrols,thenanemptysetup,andfinallyjustaloopthatrefreshesallcontrolsindefinitely.TheMIDIControllerlibraryhandleseverythingdiscussedabove,andevenmore!Itallowsyoutoarrangecontrolsintodifferentbanks,switchbetweenbanks,choosebetweenmanydifferentMIDIinterfaces(USB,Serial,SoftwareSerial),hassupportformultiplexers,buttonmatrices,etc.

Youcandownloadthelibraryhere.

MIDIInputReadingMIDIcanbedoneusingtheArduino'sUART.TheMIDIspecificationproposesanalgorithmforreceivingMIDImessages:

?

?

StoreinRunningStatus

Buffer

ClearThirdByteFlag

?

StoreitinFIFO

IncrementPointer+1

(donotincrementpointerhere)

?

?

?

?

IgnoreDataByte

ClearThirdByteFlag

StoreThirdByteintoFIFO

IncrementPointer+3

?

ClearRunningStatusBuffer

?

?SetThirdByteFlag

StoreStatusintoFIFO

StoreDataByteintoFIFO

(donotincrementpointerhere)IgnoreStatus

IncrementPointer+2

StoreDataByteintoFIFO

StoreStatusintoFIFO

ReadSerialInputBit7=0Bit7=1

ThirdByteFlag=1Yes IsitaReal-TimeMessage?

No

No

Yes

IsthisaTuneRequest?

=F6H

Flag=0RunningStatusBuffer=0

BufferGreaterthan0 Less

thanC0H

LessthanE0H

BufferLessthanF0H

BufferGreaterthanF0H

Buffer=F2H

Buffer=F3HorF1H

Buffer>=F0H

ClearRunningStatusBuffer

ClearRunningStatusBuffer

Inthischapter,wewon'tbeconcernedwithSystemorReal-Timemessages.Theimplementationofthealgorithmaboveisprettystraightforward.Wewon'tuseaFIFO,buthandlethemessagesimmediately.

voidsetup(){Serial.begin(31250);}

voidhandleMIDI(uint8_tstatusByte,uint8_tdata1,uint8_tdata2=0){;}

voidloop(){staticuint8_trunningStatus=0;staticuint8_tdata1=0;staticboolthirdByte=false;

if(Serial.available()){uint8_tnewByte=Serial.read();if(newByte&0b10000000){//HeaderbytereceivedrunningStatus=newByte;thirdByte=false;}else{if(thirdByte){//Seconddatabytereceiveduint8_tdata2=newByte;handleMIDI(runningStatus,data1,data2);thirdByte=false;return;}else{//Firstdatabytereceivedif(!runningStatus)//nostatusbytereturn;//invaliddatabyteif(runningStatus<0xC0){//FirstdatabyteofNoteOff/On,KeyPressureorControlChangedata1=newByte;thirdByte=true;return;}if(runningStatus<0xE0){//FirstdatabyteofProgramChangeorChannelPressuredata1=newByte;handleMIDI(runningStatus,data1);return;}if(runningStatus<0xF0){//FirstdatabyteofPitchBenddata1=newByte;thirdByte=true;return;}else{;//Systemmessage(notimplemented)}}}}}

Thereareafewoptimizationswecando.Wecanjustcheckiftherunningstatusbytecontainsamessagetypeforatwo-orthree-bytemessage,insteadofthecomparisonswehaverightnow.Apartfromthat,wedon'treallyneedanextravariableforthethirdbyteflag,wecanjustusebit7ofthedata1variable.

constuint8_tNOTE_OFF=0x80;constuint8_tNOTE_ON=0x90;constuint8_tKEY_PRESSURE=0xA0;constuint8_tCC=0xB0;constuint8_tPROGRAM_CHANGE=0xC0;constuint8_tCHANNEL_PRESSURE=0xD0;constuint8_tPITCH_BEND=0xE0;

voidloop(){staticuint8_trunningStatus=0;staticuint8_tdata1=0b10000000;

if(Serial.available()){uint8_tnewByte=Serial.read();if(newByte&0b10000000){//StatusbytereceivedrunningStatus=newByte;data1=0b10000000;}else{if(data1!=0b10000000){//SeconddatabytereceivedhandleMIDI(runningStatus,data1,newByte);data1=0b10000000;return;}else{//Firstdatabytereceivedif(!runningStatus)//nostatusbytereturn;//invaliddatabyteif(runningStatus==PROGRAM_CHANGE||runningStatus==CHANNEL_PRESSURE){//FirstdatabyteofProgramChangeorChannelPressurehandleMIDI(runningStatus,newByte);return;}elseif(runningStatus<0xF0){//FirstdatabyteofNoteOff/On,Key

Pressure,ControlChangeorPitchBenddata1=newByte;return;}else{;//Systemmessage(notimplemented)}}}}}