This is a part of the Kinetiszer project.
Diff: midi.c
- Revision:
- 0:cb80470434eb
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/midi.c Tue Oct 28 12:19:42 2014 +0000 @@ -0,0 +1,573 @@ +/* +Copyright 2013 Paul Soulsby www.soulsbysynths.com + This file is part of Atmegatron. + + Atmegatron is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Atmegatron is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Atmegatron. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "atmegatron.h" + + +//frequency of all midi notes (rounded to nearest integer) +const uint16_t MIDI_freqs[128] = +{ + 8, 9, 9,10,10,11,12,12,13,14,15,15,16,17,18,19, + 21,22,23,24,26,28,29,31,33,35,37,39,41,44,46,49, + 52,55,58,62,65,69,73,78,82,87,92,98,104,110,117,123, + 131,139,147,156,165,175,185,196,208,220,233,247,262,277,294,311, + 330,349,370,392,415,440,466,494,523,554,587,622,659,698,740,784, + 831,880,932,988,1047,1109,1175,1245,1319,1397,1480,1568,1661,1760,1865,1976, + 2093,2217,2349,2489,2637,2794,2960,3136,3322,3520,3729,3951,4186,4435,4699,4978, + 5274,5588,5920,6272,6645,7040,7459,7902,8372,8870,9397,9956,10548,11175,11840,12544 +}; + + +//local vars + +boolean arp_triggernext=false; //trigger arpeggiator note on next midi clock +boolean gettingSYSEX=false; //in the middle of getting sysex message +boolean memDumping=false; //in the middle of dumping memory to librarian software +byte curMemDump=0; //current stage of memory dump +byte SYSEXbytenum=0; //byte number of sysex message +byte SYSEXmesstype=0; //type of sysex message + +//lets and gets +byte MIDI_curnote = 57; //current note pressed +byte MIDI_curbutnote = 60; //current note of test button +boolean MIDI_keydown[128]; //store which keys are down for arpeggiator +signed char MIDI_totnoteson =0; //total notes held down +char MIDI_lasttotnoteson = 0; //last total notes held down +boolean MIDI_clockpresent=false; //is midi clock present? +boolean MIDI_SYSEXread=false; //is sysex read enabled? +unsigned long MIDI_clocktick=0; +unsigned long MIDI_clockticksperarpstep=12; +unsigned long MIDI_clocknextarp=0; +char MIDI_pbend_level=0; +byte MIDI_channel=0; + +//Lets and Gets +byte MIDI_Get_curNote(void) +{ //get current midi note + return MIDI_curnote; +} + + +unsigned int MIDI_Get_Freq(byte notenum) +{ //get current midi frequency (potentially different to actual current frequency in pitch tab) + return MIDI_freqs[notenum]; +} + + +boolean MIDI_Get_KeyDown(byte notenum) +{ //find out if note is down + return MIDI_keydown[notenum]; +} + + +void MIDI_Let_ClockArpSpeed(unsigned int newspeed) +{ //set clock arp speed. not actual speed. see Arp_Let_Speed for how this works + MIDI_clockticksperarpstep = ((unsigned long)newspeed+1) * 24 >> 13; +} + + +void MIDI_Let_SYSEXRead(boolean newval) +{ //set sysex read mode + MIDI_SYSEXread = newval; +} + + +//boolean MIDI_Get_SYSEXRead(void) +//{ +// return MIDI_SYSEXread; +//} + + +boolean MIDI_Get_ClockPresent(void) +{ //return if midi clock is preset + return MIDI_clockpresent; +} + + +void MIDI_Set_Channel(byte newchannel) +{ + if(newchannel!=MIDI_channel){ + MIDI_channel = newchannel; + Memory_Channel_Write(newchannel); + } +} + +//MIDI meat +//initialise serial port +void MIDI_Init(void) +{ //enable midi. just uses standard arduino serial ports. + MIDI_channel = Memory_Channel_Read(); + Serial.begin(31250); +} + + +//********MIDI note stuff*********** +//press key +void MIDI_NoteOn(byte notenum) +{ + MIDI_curnote = notenum; //set current note = new note + MIDI_lasttotnoteson = MIDI_totnoteson; //set last total notes on + MIDI_totnoteson++; //increment total notes on + MIDI_keydown[notenum] = true; //set midi key down = true +#ifdef __HAS_ARPEGGIATOR__ + if (Arp_Get_Type()==0) +#endif // __HAS_ARPEGGIATOR__ + { //if arpeggiator off then + MIDI_TriggerNote(notenum); //trigger note + } +#ifdef __HAS_ARPEGGIATOR__ + else if (MIDI_lasttotnoteson==0 && MIDI_totnoteson==1) { //if arpeggiator on and this is the first note pressed down + Arp_Reset(); //reset arpeggiator and lfo, so they sync together + LFO_Reset(); + } +#endif // __HAS_ARPEGGIATOR__ +} + + +//release key +void MIDI_NoteOff(byte notenum) +{ + MIDI_keydown[notenum] = false; //set midi key down = false + MIDI_lasttotnoteson = MIDI_totnoteson; //set last total notes on + MIDI_totnoteson--; //decrement total notes on + if (MIDI_totnoteson<=0){ //if total notes on = 0 (ie all keys released) (use <= for safety) + AEnv_Release(); //release amplitude envelope + FEnv_Release(); //release filter envelope + if (MIDI_totnoteson<0){ //safety - if tot notes on < 0 + MIDI_totnoteson = 0; //reset to 0 (<0 not possible) + } + } +} + + +//*********TRIGGER A NOTE**************** (used by arpeggiator too) +void MIDI_TriggerNote(byte notenum) +{ + Pitch_Let_NextFreq(MIDI_freqs[notenum]); //set next frequency (pitch) + Pitch_ResetPorta(); //set portamento start tick + AEnv_Trigger(); //trigger amplitude envelope + FEnv_Trigger(); //trigger filter envelope +} + + +//reset all notes +void MIDI_Reset(void) +{ + uint8_t i; + for (i=0; i<=127; i++) + { + MIDI_NoteOff(i); + } +} + + +//press the test button (value knob) +void MIDI_TestButtonDown(void) +{ + Pitch_Let_NextFreq(MIDI_freqs[MIDI_curbutnote]); //set next frequency + Pitch_ResetPorta(); //set portamento start tick + AEnv_Trigger(); //trigger amplitude envelope + FEnv_Trigger(); //trigger filter envelope +} + + +//release the test button (value knob) +void MIDI_TestButtonUp(void) +{ + if (MIDI_totnoteson<=0){ //prevent stuck notes + AEnv_Release(); //release amplitude envelope + FEnv_Release(); //release filter envelope + } +} + + +//increment test button note (clockwise turn) +void MIDI_TestButtonInc(void) +{ + if (MIDI_curbutnote<126){ //if cur test button note < max midi note + MIDI_curbutnote++; //increment + Pitch_Let_NextFreq(MIDI_freqs[MIDI_curbutnote]); //set next frequency (pitch) + } +} + + +//decrement test button note (anti clockwise turn) +void MIDI_TestButtonDec(void) +{ + if (MIDI_curbutnote>1){ //if cur test button note > min midi note + MIDI_curbutnote--; //decrement + Pitch_Let_NextFreq(MIDI_freqs[MIDI_curbutnote]); //set next frequency (pitch) + } +} + + +//*******MIDI clock stuff******** +//MIDI clock start received +void MIDI_ClockStart(void) +{ + MIDI_clockpresent = true; //set clock present + MIDI_clocktick = 0; //reset clock tick counter + MIDI_clocknextarp = 0; //reset next arpeggiator note tick +#ifdef __HAS_ARPEGGIATOR__ + if (Arp_Get_Type()!=0) { //if arpeggiator on + Arp_Reset(); //reset it (so synced with clock) + } +#endif // __HAS_ARPEGGIATOR__ + LFO_Reset(); //reset lfo (so synced with clock) +} + + +//No implementation of clock continue at present. just does clock start +void MIDI_ClockContinue(void) +{ + MIDI_ClockStart(); +} + + +//MIDI clock stop received +void MIDI_ClockStop(void) +{ + MIDI_clockpresent = false; //set clock not present +} + + +//MIDI clock tick received +void MIDI_ClockTick(void) +{ + if (MIDI_clockpresent==true) { //check clock has acutally been started (not rogue data) +#ifdef __HAS_ARPEGGIATOR__ + if (MIDI_clocktick>=MIDI_clocknextarp && Arp_Get_Type()!=0){ //if midi clock counter > midi clock value for next arppeggiator trigger (and arpeggitor on) : + arp_triggernext = true; //trigger arpeggiator at end of current midi receive (bottom of MIDI_poll) + } +#endif // __HAS_ARPEGGIATOR__ + if (MIDI_clocktick%24 == 0){ //every 24 clock ticks (every beat): + Hardware_LED_StartFlash(4,1); //flash the midi led + } + MIDI_clocktick++; //increment midi clock counter + } + +} + + +//get midi clock counter +unsigned long MIDI_Get_ClockTick(void) +{ + return MIDI_clocktick; +} + + +//get pitch bend (only using msb of pitch bend data) +char MIDI_Get_PitchBend_Level(void) +{ + return MIDI_pbend_level; +} + + +//***********SYSEX stuff*********** +//byte of sysex data received +void MIDI_SYSEX_read(byte databyte) +{ + //int addr; + int samp; + + switch (SYSEXbytenum){ //SYSEXbytenum - counter used to log position in sysex message + case 1: //Manufactuer ID 125 (one day I'll get my own!) + if (databyte!=125){ + gettingSYSEX = false; + Hardware_LED_SetState(4,LOW); + } + break; + case 2: //Product ID (0 for Atmegatron) + if (databyte!=0){ + gettingSYSEX = false; + Hardware_LED_SetState(4,LOW); + } + break; + case 3: //Message type ID (0 = patch, 1 = waveform, 2 = mem dump) + SYSEXmesstype = databyte; + break; + default: + if (SYSEXmesstype==SYSEX_PATCH && MIDI_SYSEXread==true){ //********Patch******** + if (SYSEXbytenum>=4 && SYSEXbytenum<=19){ //set relevent value (decided by SYSEXbytenum (position in message)) + Hardware_Let_Value(SYSEXbytenum-4,databyte); + } + if (SYSEXbytenum>=20 && SYSEXbytenum<=25){ + Hardware_Let_Ctrl(0,SYSEXbytenum-20,databyte<<1); //values that are byte (0-255) are *2, because sysex max value is 127 + } + if (SYSEXbytenum>=26 && SYSEXbytenum<=29){ + Hardware_Let_Ctrl(1,SYSEXbytenum-24,databyte<<1); + } + if (SYSEXbytenum==30){ + Hard_Let_Shift(FUNC_WAVE,databyte); + } + if (SYSEXbytenum==31){ + Hard_Let_Shift(FUNC_FILT,databyte); + } + if (SYSEXbytenum==32){ + Hard_Let_Shift(FUNC_FENVA,databyte); + } + if (SYSEXbytenum==33){ + Hard_Let_Shift(FUNC_LFOTYPE,databyte); + } + if (SYSEXbytenum==34){ +#ifdef __HAS_ARPEGGIATOR__ + Hard_Let_Shift(FUNC_ARPTYPE,databyte); +#endif // __HAS_ARPEGGIATOR__ + } + if (SYSEXbytenum==35){ + Hard_Let_Shift(FUNC_PORTA,databyte); + } + if (SYSEXbytenum==36){ //this is the last byte + Hard_Let_Shift(FUNC_BITCRUSH, databyte); + Hardware_LED_StartFlash(0,5); //Flash function knob led. this means all bytes received (different to end of sysex message (F7)) + if(memDumping==true){ //****if this is part of a mem dump receive:**** + Serial.flush(); //flush serial buffer - need all the mem clear for next message + Memory_Save(curMemDump); //save current received patch to flash mem + curMemDump++; //increment mem dump counter (patch num to request) + Serial.write(SYSEXBEGIN); //0 begin sysex message + Serial.write(125); //1 manufacturer ID + Serial.write(0); //2 product ID + if (curMemDump<16){ //first 16 dumps are patches, if more patches to recieve: request next patch + Serial.write(SYSEX_CALLPATCH); //3 message type + } + else{ //or request first user wave + Serial.write(SYSEX_CALLWAVE); //3 message type + curMemDump = 0; //reset counter for user waves + } + Serial.write(curMemDump); //4 mem dump number (so librarian knows which patch/wave) + Serial.write(SYSEXEND); //end message + } + } + } + + if (SYSEXmesstype==SYSEX_WAVE && MIDI_SYSEXread==true){ //***********User Wave************** + if (SYSEXbytenum>=4 && SYSEXbytenum<=35){ //bytes of sample data + samp = (int)databyte; //convert to integer + samp *= 2; //*2 (librarian outputs (-63 - 63), we need (-127 - 128, actually use -126 - 126) + samp -= 126; //apply offset (byte transmitted as positive integers) + Wave_Let_UserWave(SYSEXbytenum-4,(signed char)samp); //convert to char and set current user wave + } + if (SYSEXbytenum==35){ //last byte of data + Hardware_LED_StartFlash(0,5); //Flash function knob led. this means all bytes received (different to end of sysex message (F7)) + if(memDumping==true){ //****if this is part of a mem dump receive:**** + Serial.flush(); //flush serial buffer - need all the mem clear for next message + Memory_UserWave_Write(curMemDump); //save current received user wave to flash mem + curMemDump++; //increment mem dump counter (user wave to request) + if (curMemDump<6){ //if more user waves still to receive: request next user wave + Serial.write(SYSEXBEGIN); //0 begin message + Serial.write(125); //1 manufacturer ID + Serial.write(0); //2 product ID + Serial.write(SYSEX_CALLWAVE); //3 message type + Serial.write(curMemDump); //4 mem dump number (so librarian knows which patch/wave) + Serial.write(SYSEXEND); //end message + } + else{ //finished final user wave + memDumping = false; //stop dumping + } + } + } + } + + if (SYSEXmesstype==SYSEX_MEM && MIDI_SYSEXread==true){ //*************Memory Dump************** + curMemDump = 0; //counter used to track which patch/wave is being dumped + memDumping = true; //memory dump started + Serial.write(SYSEXBEGIN); //request first patch from librarian + Serial.write(125); //1 manufacturer ID + Serial.write(0); //2 product ID + Serial.write(SYSEX_CALLPATCH); //3 message type + Serial.write(curMemDump); //4 mem dump number (patch 0) + Serial.write(SYSEXEND); //end message + } + } +} + + +//Write patch to librarian +void MIDI_SYSEX_write_patch(void) +{ + byte data; + byte i; + //boolean b; + + Serial.write(SYSEXBEGIN); + Serial.write(125); // 1 manufacturer ID + Serial.write(0); // 2 product ID + Serial.write(SYSEX_PATCH); // 3 message type + for (i=0;i<16;i++){ // 4-19 function vals + Serial.write(Hardware_Get_Value(i)); + } + for (i=0;i<6;i++){ // 20-25 ctrl vals bank=0 + data = Hardware_Get_Ctrl(0, i) >> 1; + Serial.write(data); + } + for (i=2;i<6;i++){ // 26-29 ctrl vals bank=1 + data = Hardware_Get_Ctrl(1, i) >> 1; + Serial.write(data); + } + Serial.write(Hard_Get_Shift(FUNC_WAVE)); //30-36 shift mode for functions + Serial.write(Hard_Get_Shift(FUNC_FILT)); + Serial.write(Hard_Get_Shift(FUNC_FENVA)); + Serial.write(Hard_Get_Shift(FUNC_LFOTYPE)); +#ifdef __HAS_ARPEGGIATOR__ + Serial.write(Hard_Get_Shift(FUNC_ARPTYPE)); +#else + Serial.write(0); +#endif // __HAS_ARPEGGIATOR__ + Serial.write(Hard_Get_Shift(FUNC_PORTA)); + Serial.write(Hard_Get_Shift(FUNC_BITCRUSH)); + Serial.write(SYSEXEND); //37 end message + +} + + +//**************MIDI POLL ********************* +void MIDI_Poll(void) +{ + byte incomingByte=0; + static byte notebyte=0; //these are all static because there's a small chance that the subroutine is exited before the 2 bytes of MIDI needed for note_on are received. + static byte velocitybyte=0; + static byte statusbuffer=0; + static boolean firstbyte; + if (Serial.available() > 0) { //if there is something in the serial input buffer + do { + incomingByte = Serial.read(); // read the incoming byte: + if (incomingByte>247) { // ****this is where MIDI clock stuff is done*** + switch (incomingByte){ + case 248: //MIDI clock tick + MIDI_ClockTick(); + break; + case 250: //MIDI clock start + MIDI_ClockStart(); + break; + case 251: //MIDI clock continue (not implemented) + MIDI_ClockContinue(); + break; + case 252: //MIDI clock stop + MIDI_ClockStop(); + break; + } + } + else if (incomingByte>239) { //*****this is where SYSEX stuff is done***** + statusbuffer = 0; + if (incomingByte==SYSEXBEGIN){ //start of sysex message + gettingSYSEX = true; + SYSEXbytenum = 0; //reset message byte counter + Hardware_LED_SetState(4,HIGH); //turn midi light on while receiving + } + if (incomingByte==SYSEXEND){ //end of sysex message + gettingSYSEX = false; + Hardware_LED_SetState(4,LOW); //turn midi light off (this doesn't guarantee that message was successfully read though! Func knob flashes for that) + } + } + else if (incomingByte>127) { //*****this is where all other message types are done ****** + statusbuffer = incomingByte; //status buffer stores what kind of buffer it is + firstbyte = true; //it must be the first byte of pair + notebyte = 0; //so reset the note byte + velocitybyte = 0; //and velocity byte, ready incase it's a note + gettingSYSEX = false; //safety - note a sysex message + } + else if (gettingSYSEX==true){ //****SYSEX data******* if getting sysex, it HAS to be sysex data if < 128 + SYSEXbytenum++; //inc message byte counter + MIDI_SYSEX_read(incomingByte); //process the incoming byte + } + else if (statusbuffer!=0) { //******all other message data******* + if (firstbyte == true) { //if it's first byte of pair + notebyte = incomingByte; //set note = incoming byte + firstbyte = false; //and set = false, so ready for second byte + } + else { // so must be second byte of pair then + velocitybyte = incomingByte; //set velocitybyte as incoming byte (even if not note data) + if (statusbuffer == (NOTE_ON | MIDI_channel) && velocitybyte != 0) { //is it a note on? + Hardware_LED_StartFlash(4,1); //flash midi led + MIDI_NoteOn(notebyte); //trigger note + } + else if (statusbuffer == (NOTE_OFF | MIDI_channel) || (statusbuffer == (NOTE_ON | MIDI_channel) && velocitybyte == 0)) { //is it a note off? (there's 2 ways to trigger note off: note_off or Note_on with velocity =0) + Hardware_LED_StartFlash(4,1); //flash midi led + MIDI_NoteOff(notebyte); //release note + } + else if (statusbuffer == (PITCH_WHEEL | MIDI_channel)){ //is it pitch wheel? + MIDI_pbend_level = velocitybyte - 64; //set pbend (may aswell do the offset here). only msb of pitch bend used + } + else if (statusbuffer == (CONTROLLER | MIDI_channel)){ //is it a controller message? I have attempted to use controller numbers appoximating the spec of official controller list. Used "MISC" controllers where necessary + switch (notebyte) { + case CC_PITCHLFO: //pitch lfo (mod wheel). This is just an override of the pitch lfo knob + Hardware_Let_Ctrl(1,CTRL_LFO,velocitybyte >> 1); //default uses >>1 for typical mod wheel vibrato. use << 1 for full range (default full range = 1 octave) + break; + case CC_PORTAMENTO: //portamento + Hardware_Let_Value(FUNC_PORTA,velocitybyte >> 3); //scale to values 0-15 + break; + case CC_FILTERENV: //filter envelope + Hardware_Let_Ctrl(0,CTRL_ENV,velocitybyte << 1); //scale to values 0-255 + break; + case CC_DISTORTION: //distortion + Hardware_Let_Ctrl(0,CTRL_FX,velocitybyte << 1); //scale to values 0-255 + break; + case CC_FILTCUTOFF: //filter cutoff frequency + Hardware_Let_Ctrl(0,CTRL_FILT,velocitybyte << 1); //scale to values 0-255 + break; + case CC_AMPENVR: //amplitude envelope release + Hardware_Let_Value(FUNC_AENVR,velocitybyte >> 3); //scale to values 0-15 + break; + case CC_AMPENVA: //amplitude envelope attack + Hardware_Let_Value(FUNC_AENVA,velocitybyte >> 3); //scale to values 0-15 + break; + case CC_FILTRES: //filter resonance + Hardware_Let_Ctrl(0,CTRL_Q,velocitybyte << 1); //scale to values 0-255 + break; + case CC_AMPENVD: //amplitude envelope decay + Hardware_Let_Value(FUNC_AENVD,velocitybyte >> 3); //scale to values 0-15 + break; + case CC_LFOCLOCKDIV: //lfo rate (speed). ******DUBSTEP PRODUCERS THIS IS CONTROLLER 79!!!******** + Hardware_Let_Value(FUNC_LFOSPEED,velocitybyte >> 3); //scale to values 0-15 + break; + case CC_PWM: //pulse width modulation + Hardware_Let_Ctrl(1,CTRL_AMP,velocitybyte << 1); //scale to values (0-255) + break; + case CC_AMPLFO: //amplitude lfo + Hardware_Let_Ctrl(0,CTRL_AMP,velocitybyte << 1); //scale to values (0-255) + break; + case CC_FILTLFO: //filter lfo + Hardware_Let_Ctrl(0,CTRL_LFO,velocitybyte << 1); + break; + case CC_PITCHENV: //pitch envelope + Hardware_Let_Ctrl(1,CTRL_ENV,velocitybyte << 1); + break; + case CC_FLANGE: //phaser + Hardware_Let_Ctrl(1,CTRL_FX,velocitybyte << 1); + break; + } + } + notebyte = 0; //now reset them ready for next note + velocitybyte = 0; + firstbyte = true; + } + } + } while (Serial.available() > 0); //loop until serial buffer is empty + +#ifdef __HAS_ARPEGGIATOR__ + if (arp_triggernext==true && MIDI_totnoteson>0){ //midi clock tick was received and it's time to fire an arpeggiator note! + do{ //nasty do loop. this is important, incase midi clock ticks are faster than Atmegatron polling loop and multiple notes need to be triggered now + Arp_TriggerStep(); //trigger arpeggiator + MIDI_clocknextarp += MIDI_clockticksperarpstep; //MIDI_clockticksperarpstep MUST never be 0 + } while (MIDI_clocknextarp<MIDI_clocktick); //loop until all notes triggered that need to be are + arp_triggernext = false; //triggered all notes, so set false + } +#endif // __HAS_ARPEGGIATOR__ + } +} +