├── README.md ├── RibbonController_CV ├── ArduinoMicro_Ribbon_CV.fzz └── RibbonController_CV.ino ├── MidiSequencer ├── Button.h ├── MIDI_Codes.h ├── MIDI_Note_Buffer.h └── MidiSequencer.ino ├── MidiEcho └── MidiEcho.ino ├── BreathController_MIDI └── BreathController_MIDI.ino ├── MidiRibbonController └── MidiRibbonController.ino ├── MidiClockToCVTrigger └── MidiClockToCVTrigger.ino └── MidiRibbonController_Filtering └── MidiRibbonController_Filtering.ino /README.md: -------------------------------------------------------------------------------- 1 | ArduinoMidi 2 | =========== 3 | -------------------------------------------------------------------------------- /RibbonController_CV/ArduinoMicro_Ribbon_CV.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chipaudette/ArduinoMidi/HEAD/RibbonController_CV/ArduinoMicro_Ribbon_CV.fzz -------------------------------------------------------------------------------- /MidiSequencer/Button.h: -------------------------------------------------------------------------------- 1 | #ifndef Button_h 2 | #define Button_h 3 | 4 | class Button { 5 | private: 6 | int pin; 7 | 8 | public: 9 | int state; 10 | int has_state_changed; 11 | Button(int p) { 12 | pin = p; 13 | state = 0; 14 | has_state_changed=0; 15 | 16 | pinMode(pin, INPUT_PULLUP); //prepare the input 17 | } 18 | 19 | int update(void) { 20 | int raw_val = !digitalRead(pin); 21 | if (raw_val != state) { 22 | state = raw_val; 23 | has_state_changed=1; 24 | } else { 25 | has_state_changed = 0; 26 | } 27 | return state; 28 | } 29 | }; 30 | 31 | #endif Button_h 32 | -------------------------------------------------------------------------------- /MidiSequencer/MIDI_Codes.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef MIDI_Codes_h 3 | #define MIDI_Codes_h 4 | 5 | // MIDI Code information 6 | #define MIDI_PPQN (24) //MIDI clock "pulses per quarter note" 7 | #define MIDI_CHAN (0x00) //0x00 is omni 8 | #define MIDI_CLOCK (0xF8) //Code for MIDI Clock 9 | #define MIDI_START (0xFA) //start clock...is this also a clock code? 10 | #define NOTE_ON (0x90+MIDI_CHAN) //Code for MIDI Note On 11 | #define NOTE_OFF (0x80+MIDI_CHAN) //Code for MIDI Note Off 12 | #define MIDI_CC (0xB0+MIDI_CHAN) //Code for MIDI CC 13 | #define MIDI_AT (0b11010000+MIDI_CHAN) //Code for Per-Channel Aftertouch 14 | #define MIDI_PB (0xE0+MIDI_CHAN) //pitch bend 15 | #define MODWHEEL_CC (0x01) //CC code for the mod wheel (for my prophet 6) 16 | 17 | #endif MIDI_Codes_h 18 | -------------------------------------------------------------------------------- /MidiEcho/MidiEcho.ino: -------------------------------------------------------------------------------- 1 | // SparkFun MIDI Sheild and MIDI Breakout test code 2 | // Defines bare-bones routines for sending and receiving MIDI data 3 | // 4 | // Chip Audette. http://synthhacker.blogspot.com 5 | // 6 | // Updated June 2016 7 | 8 | 9 | #define ECHOMIDI (true) //echo MIDI messages? 10 | #define ECHOMIDI_CLOCK (false) //echo the MIDI clock messages? (hard to see anything else!) 11 | #define ECHO_AUX_AND_BUTTONS (true) //check the MIDI shield's buttons (and my AUX inputs)? 12 | 13 | // defines for MIDI Shield components (NOT needed if you just want to echo the MIDI codes) 14 | #define KNOB1 A0 //potentiometer on sparkfun MIDI shield 15 | #define KNOB2 A1 //potentiometer on sparkfun MIDI shield 16 | #define BUTTON1 2 //pushbutton on sparkfun MIDI shield 17 | #define BUTTON2 3 //pushbutton on sparkfun MIDI shield 18 | #define BUTTON3 4 //pushbutton on sparkfun MIDI shield 19 | #define STAT1 7 //status LED on sparkfun MIDI shield 20 | #define STAT2 6 //status LED on sparkfun MIDI shield 21 | int AUX_pins[4] = {A2, A3, A4, A5}; //check the analog inputs...I often have things attached here 22 | int BUT_pins[3] = {2, 3, 4}; //check the analog inputs...I often have things attached here 23 | int AUX_states[4]; //check the analog inputs...I often have things attached here 24 | int BUT_states[3]; //check the analog inputs...I often have things attached here 25 | 26 | //other variables 27 | byte byte1, byte2, byte3; //standard 3 byte MIDI transmission 28 | //int FS1,FS2; //foot swtiches connected to the analog inputs 29 | 30 | 31 | void setup() { 32 | //setup input pins for MIDI shield 33 | if (ECHO_AUX_AND_BUTTONS) { 34 | pinMode(KNOB1,INPUT);pinMode(KNOB2,INPUT); 35 | for (int i=0; i < 4; i++) { pinMode(AUX_pins[i],INPUT_PULLUP); AUX_states[i]=0; }; 36 | for (int i=0; i < 3; i++) { pinMode(BUT_pins[i],INPUT_PULLUP); BUT_states[i] = 0; }; 37 | } 38 | 39 | //setup LED pins 40 | pinMode(STAT1,OUTPUT); //prepare the LED outputs 41 | pinMode(STAT2,OUTPUT); //prepare the LED outputs 42 | turnOffStatLight(STAT1); //turn off the STAT1 light 43 | turnOffStatLight(STAT2); //turn off the STAT1 light 44 | 45 | 46 | //start serial with midi baudrate 31250 47 | Serial.begin(31250); 48 | } 49 | 50 | void loop () { 51 | static byte incomingByte; 52 | static int noteNum; 53 | static int velocity; 54 | //static int pot; 55 | //static int gate; 56 | 57 | turnOffStatLight(STAT1); //turn off the STAT1 light 58 | 59 | //Are there any MIDI messages 60 | if(Serial.available() > 0) 61 | { 62 | turnOnStatLight(STAT1); //turn on the STAT1 light indicating that it's received some Serial comms 63 | 64 | //read the first byte 65 | byte1 = Serial.read(); 66 | 67 | if (byte1 == 0xF8) { 68 | //timing signal, skip 69 | if (ECHOMIDI_CLOCK) Serial.write(byte1); 70 | } else { 71 | if (ECHOMIDI) Serial.write(byte1); 72 | 73 | //none of the rest is needed if you just want to echo the MIDI codes 74 | if ((byte1 == 0x90) | (byte1 == 0x80) | (byte1 == 0xB0)) { 75 | byte2 = 0xF8; 76 | while (byte2 == 0xF8) { while (Serial.available() == 0); byte2 = Serial.read(); }//wait and read 2nd byte in message 77 | noteNum = (int)byte2; 78 | if (ECHOMIDI) Serial.write(byte2); 79 | byte3 = 0xF8; 80 | while (byte3 == 0xF8) { while (Serial.available() == 0); byte3 = Serial.read(); }//wait and read 2nd byte in message 81 | velocity = (int)byte3; 82 | if (ECHOMIDI) Serial.write(byte3); 83 | } 84 | 85 | //check message type 86 | switch (byte1) { 87 | case 0xF8: 88 | //timing message. ignore 89 | break; 90 | case 0x90: 91 | //note on message 92 | turnOnStatLight(STAT2);//turn on STAT2 light indicating that a note is active 93 | //while (Serial.available() == 0); byte2 = Serial.read(); //wait and read 2nd byte in message 94 | 95 | break; 96 | case 0x80: 97 | //note off message 98 | turnOffStatLight(STAT2);//turn off light that had been indicating that a note was active 99 | //while (Serial.available() == 0); byte2 = Serial.read(); //wait and read 2nd byte in message 100 | 101 | break; 102 | case 0xB0: 103 | //CC changes (mod wheel, footswitch, etc) 104 | break; 105 | } 106 | } 107 | 108 | } else { //if not Serial.isAvailable() 109 | 110 | if (ECHO_AUX_AND_BUTTONS) { //look for changes in state of the buttons and AUX inputs 111 | //check MIDI shield functions (totally optional!) 112 | int raw_state; 113 | for (int i=0; i < 4; i++) { //loop over 4 auxiliary inputs 114 | raw_state = !digitalRead(AUX_pins[i]); 115 | if (AUX_states[i] != raw_state) { 116 | AUX_states[i] = raw_state; 117 | Serial.print("AUX"); Serial.print(i); Serial.print(": State = "); Serial.println(AUX_states[i]); 118 | } 119 | } 120 | for (int i=0; i < 3; i++) { //loop over 3 buttons 121 | raw_state = !digitalRead(BUT_pins[i]); 122 | if (BUT_states[i] != raw_state) { 123 | BUT_states[i] = raw_state; 124 | Serial.print("BUT"); Serial.print(i); Serial.print(": State = "); Serial.println(BUT_states[i]); 125 | } 126 | } 127 | } 128 | 129 | delay(1); //delay...just to keep the loop() from spinning to fast and blocking the incoming Serial comms 130 | } 131 | } 132 | 133 | 134 | void turnOnStatLight(int pin) { 135 | digitalWrite(pin,LOW); 136 | } 137 | void turnOffStatLight(int pin) { 138 | digitalWrite(pin,HIGH); 139 | } 140 | 141 | -------------------------------------------------------------------------------- /MidiSequencer/MIDI_Note_Buffer.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef MIDI_Note_Buffer_h 3 | #define MIDI_Note_Buffer_h 4 | 5 | #include 6 | #include "MIDI_Codes.h" 7 | 8 | typedef struct MIDI_Note_t { 9 | boolean isActive; 10 | int timeOn; 11 | int timeOff; 12 | byte noteNum; 13 | byte onVel; 14 | byte offVel; 15 | }; 16 | 17 | #define MAX_N_NOTES 120 18 | class MIDI_Note_Buffer { 19 | private: 20 | MIDI_Note_t note_buffer[MAX_N_NOTES]; 21 | const int max_n_notes = MAX_N_NOTES; 22 | int current_note_buffer_index=0; 23 | Stream *serial = &Serial; 24 | 25 | public: 26 | boolean is_recording = false; 27 | MIDI_Note_Buffer(HardwareSerial *s) { 28 | serial = s; 29 | clear(); 30 | } 31 | 32 | void clear(void) { 33 | stopPlayedNotes(); 34 | for (int i=0; i < max_n_notes; i++) { 35 | if (note_buffer[i].isActive) sendNoteOffMessage(i); 36 | note_buffer[i].timeOn = -1; 37 | note_buffer[i].timeOff = -1; 38 | }; 39 | current_note_buffer_index = 0; 40 | } 41 | 42 | void addNoteOffForOnNotes(int cur_time_ind) { 43 | //step through array and see if any notes are open; 44 | for (int i=0; i < max_n_notes; i++) { 45 | if ((note_buffer[i].timeOn > -1) & (note_buffer[i].timeOff < 0)) { 46 | byte new_message[] = {(byte)NOTE_OFF, (byte)note_buffer[i].noteNum, (byte)64}; 47 | saveThisNoteOffMessage(cur_time_ind,new_message[1],new_message[2]); 48 | serial->write(new_message,3); //and write the new message to turn off the note 49 | } 50 | } 51 | } 52 | 53 | 54 | int saveThisNoteOnMessage(const int &cur_time_ind, const byte ¬eNum, const byte &vel) { 55 | if (is_recording) { 56 | incrementNoteBufferIndex(); 57 | 58 | //save the note information 59 | note_buffer[current_note_buffer_index].timeOn = cur_time_ind; 60 | note_buffer[current_note_buffer_index].timeOff = -1; 61 | note_buffer[current_note_buffer_index].noteNum = noteNum; 62 | note_buffer[current_note_buffer_index].onVel = vel; 63 | return current_note_buffer_index; 64 | } 65 | return -1; 66 | } 67 | 68 | void incrementNoteBufferIndex(void) { 69 | current_note_buffer_index++; 70 | if (current_note_buffer_index >= max_n_notes) { 71 | current_note_buffer_index=0; //overwrite the beginning, if necessary 72 | if (note_buffer[current_note_buffer_index].isActive) { //before overwriting the note, should we turn it off first? 73 | if (note_buffer[current_note_buffer_index].timeOff > -1) { //it is a valid noteOff message 74 | //write the Note Off message 75 | sendNoteOffMessage(current_note_buffer_index); 76 | } 77 | } 78 | } 79 | } 80 | 81 | void sendNoteOffMessage(const int &ind) { 82 | serial->write(NOTE_OFF); 83 | serial->write(note_buffer[ind].noteNum); 84 | serial->write(note_buffer[ind].offVel); 85 | note_buffer[ind].isActive=0; 86 | } 87 | 88 | void sendNoteOnMessage(const int &ind) { 89 | serial->write(NOTE_ON); 90 | serial->write(note_buffer[ind].noteNum); 91 | serial->write(note_buffer[ind].onVel); 92 | note_buffer[ind].isActive=1; 93 | } 94 | 95 | int saveThisNoteOffMessage(const int &cur_time_ind, const byte ¬eNum, const byte &vel) { 96 | if (is_recording) { 97 | int ind = findNoteInBuffer(noteNum); 98 | if (ind < 0) return ind; 99 | 100 | //save the note information 101 | note_buffer[ind].timeOff = cur_time_ind; 102 | note_buffer[ind].offVel = vel; 103 | return ind; 104 | } 105 | return -1; 106 | } 107 | 108 | int findNoteInBuffer(const byte ¬eNum) { 109 | int ind = -1; 110 | for (int i=0; i= max_time_ind) prev_time_ind = -1; 123 | 124 | //look through the note buffer 125 | for (int i=0; i < max_n_notes; i++) { 126 | if (note_buffer[i].timeOn == cur_time_ind) sendNoteOnMessage(i); //turn on notes 127 | if ((note_buffer[i].timeOff > prev_time_ind) && (note_buffer[i].timeOff <= cur_time_ind)) sendNoteOffMessage(i); //turn off note 128 | } 129 | 130 | //now, capture cases where we're not playing the full buffer's worth of time (ie, max_time_ind < MAX_MAX_TIME_IND) 131 | //such that notes might get turned on during valid time, but the note_off messages are beyond the end of valid 132 | //time. If they're beyond the end, turn them off just before we wrap around in time. 133 | if (cur_time_ind == (max_time_ind-1)) { //is it the last time index? 134 | for (int i=0; i < max_n_notes; i++) { //loop over each note in the buffer 135 | if (note_buffer[i].timeOff > cur_time_ind) { //is the noteOff past the end of valid time 136 | if (note_buffer[i].isActive == 1) { //is the note currently active? 137 | sendNoteOffMessage(i); //turn off the note 138 | } 139 | } 140 | } 141 | } 142 | 143 | if (cur_time_ind < (max_time_ind-1)) { 144 | prev_time_ind = cur_time_ind; 145 | } else { 146 | prev_time_ind = -1; 147 | } 148 | } 149 | 150 | 151 | 152 | 153 | void stopPlayedNotes(void) { 154 | for (int i=0; i < max_n_notes; i++ ) { 155 | if (note_buffer[i].isActive) sendNoteOffMessage(i); 156 | } 157 | } 158 | 159 | 160 | }; 161 | 162 | #endif 163 | -------------------------------------------------------------------------------- /BreathController_MIDI/BreathController_MIDI.ino: -------------------------------------------------------------------------------- 1 | /* 2 | BreathController_MIDI 3 | 4 | Creates a MIDI breath controller by reads an analog pressure sensor and generating 5 | relevant MIDI messages. Assumes the use of a Sparkfun MIDI shield. 6 | 7 | Creare: Chip Audette, OpenAudio, Sept 2019 8 | 9 | MIT License, Use at your own risk 10 | */ 11 | 12 | #define DEBUG false //set to true to debug via Serial Monitor on PC, or to do calibration. Set to false for MIDI 13 | 14 | // define MIDI Code information 15 | // https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message 16 | #define MIDI_CHAN (0x04) //0x00 is "Channel 1", 0x01 is "Channel 2", etc. 17 | #define MIDI_AT (0b11010000+MIDI_CHAN) //MIDI Code for Aftertouch (Channel) 18 | #define MIDI_PB (0xE0+MIDI_CHAN) //MIDI Code for a pitch bend (Channel) 19 | #define MIDI_CC (0xB0+MIDI_CHAN) //MIDI Code for MIDI CC message 20 | 21 | // Specific CC codes for different functions 22 | // https://www.midi.org/specifications-old/item/table-3-control-change-messages-data-bytes-2 23 | #define CC_MODWHEEL (0x01) //CC code for the mod wheel 24 | #define CC_BREATH (0x02) 25 | #define CC_FOOT (0x04) 26 | #define CC_EXPRESSION (0x0B) 27 | #define CC_LOWPASS_CUTOFF (102) //for my prophet 6 28 | 29 | #define CC_TO_USE CC_MODWHEEL //change this to one of the entries above 30 | 31 | // the setup routine runs once when you press reset 32 | void setup() { 33 | // initialize serial communication depending upon debug vs actual MIDI 34 | #if (DEBUG == true) 35 | Serial.begin(115200); //debugging 36 | #else 37 | Serial.begin(31250); //MIDI standard speed 38 | #endif 39 | } 40 | 41 | int readPressureSensor(void) { 42 | int sensorValue = analogRead(A2) - 1023 / 2; 43 | int sensorValue2 = analogRead(A3) - 1023 / 2; 44 | return sensorValue - sensorValue2; 45 | } 46 | 47 | // the loop routine runs over and over again forever: 48 | float raw_value = 0.0, scaled_value = 0.0; 49 | int count = 0; 50 | int n_ave = 10; 51 | float calibration__value_at_zero = -10.4; //with no breath, what is the typical raw reading? 52 | float calibration__max_value = 55.0; //with max breath, what is the typical max raw reading? 53 | void loop() { 54 | 55 | //copy any incoming time-related MIDI message back to the output (so that this is MIDI thru) 56 | while (Serial.available()) { 57 | byte foo = Serial.read(); 58 | if (foo >= 0b11111000) Serial.write(foo);//echo the byte only if a MIDI timing-related message 59 | } 60 | 61 | //read sensor 62 | raw_value = raw_value + (float)readPressureSensor(); //should be 0 to 1023 63 | count++; 64 | 65 | //do we do anything with the value, yet? 66 | if (count >= n_ave) { 67 | 68 | //compute the average value, remove offset, and do initial scaling 69 | raw_value = raw_value / ((float)n_ave); 70 | scaled_value = raw_value - calibration__value_at_zero; //remove offset (must manually obtain this offset value! 71 | scaled_value = scaled_value / calibration__max_value; //should be approximately -1.0 to 1.0 72 | 73 | //scale and remove offset 74 | float pot_scale_fac = (1.0 - ((float)analogRead(A0)) / ((float)1023)) * 3.0; //should be zero to 2.0 75 | scaled_value = scaled_value * pot_scale_fac; 76 | scaled_value = max(min(scaled_value, 1.0), -1.0); //should now again be limited to -1.0 to 1.0 77 | 78 | //what kind of MIDI message to send? Chose based on second potentiometer 79 | float modePotValue = ((float)(1023 - analogRead(A1)))/1023.0; //why are the pots backwards? 80 | int transmit_threshold = 1; 81 | if (modePotValue < (0.33 * 0.75)) { 82 | sendAftertouchMessage(scaled_value,transmit_threshold); 83 | } else if (modePotValue < (0.33 * 1.25)) { 84 | sendAftertouchMessage(scaled_value,transmit_threshold); 85 | sendPitchBendMessage(scaled_value,transmit_threshold); 86 | } else if (modePotValue < (0.33 * 1.75)) { 87 | sendPitchBendMessage(scaled_value,transmit_threshold); 88 | } else if (modePotValue < (0.33 * 2.25)) { 89 | sendPitchBendMessage(scaled_value,transmit_threshold); 90 | sendCCMessage(scaled_value,transmit_threshold,CC_TO_USE); 91 | } else { 92 | sendCCMessage(scaled_value,transmit_threshold,CC_TO_USE); 93 | } 94 | 95 | //reset values for next iteration 96 | raw_value = 0.0f; count = 0; 97 | } 98 | delay(1); // delay in between reads for stability 99 | } 100 | 101 | // /////////////////////////// 102 | 103 | int calc_MIDI_value(float scaled_value, const int bipolar_mode) { 104 | int MIDI_value = 0; 105 | switch (bipolar_mode) { 106 | case 0: 107 | //send just positive values 108 | MIDI_value = max(min((int)(scaled_value * 127.0), 127), 0); 109 | break; 110 | case 1: 111 | //let's try negative values (vaccum) as having same effect as positive (blowing) 112 | MIDI_value = max(min((int)(abs(scaled_value) * 127.0), 127), 0); 113 | break; 114 | case 2: 115 | //send both positive and negative values 116 | MIDI_value = max(min((int)(scaled_value * 63.0 + 63.0), 127), 0); //should be 0 to 127 117 | break; 118 | } 119 | return MIDI_value; 120 | } 121 | 122 | void sendAftertouchMessage(float scaled_value, int transmit_threshold) { 123 | static int prev_MIDI_value = -100; //this is remembered from call-to-call. Init to large neg number. 124 | 125 | const int bipolar_mode = 1; // choose 0 for pos only, 1 for pos and neg becoming pos, and 2 for full bipolar 126 | int MIDI_value = calc_MIDI_value(scaled_value, bipolar_mode); 127 | 128 | //decide whether to transmit MIDI message 129 | if (abs(MIDI_value - prev_MIDI_value) > transmit_threshold) { 130 | #if (DEBUG == false) 131 | Serial.write(MIDI_AT); 132 | //Serial.write(MIDI_AT); 133 | Serial.write(MIDI_value); 134 | #else 135 | //debugging 136 | Serial.print(raw_value); 137 | Serial.print(", "); Serial.print(scaled_value); 138 | Serial.print(", "); Serial.print(MIDI_value); 139 | Serial.println(); 140 | #endif 141 | 142 | prev_MIDI_value = MIDI_value; 143 | } 144 | } 145 | 146 | void sendPitchBendMessage(float scaled_value, int transmit_threshold) { 147 | static int prev_MIDI_value = -100; //this is remembered from call-to-call. Init to large neg number. 148 | int MIDI_value; 149 | 150 | //pitch bend mode...14-bit number centered on 0x2000 (ie 8192) 151 | int MIDI_value_7bit = max(min((int)(scaled_value * 63.0 + 63.0), 127), 0); //should be 0 to 127 152 | uint16_t MIDI_value_14bit = max(min((int)(scaled_value * 8192.0 + 8192.0 + 0.5),2*8192-1),0); 153 | //decide whether to transmit MIDI message 154 | if (abs(MIDI_value_7bit - prev_MIDI_value) > transmit_threshold) { 155 | #if (DEBUG == false) 156 | Serial.write(MIDI_PB); 157 | Serial.write((byte) (MIDI_value_14bit & 0x7F)); //LSB 158 | Serial.write((byte) ((MIDI_value_14bit >> 7) & 0x7F)); //MSB 159 | #else 160 | //debugging 161 | Serial.print(raw_value); 162 | Serial.print(", "); Serial.print(scaled_value); 163 | Serial.print(", "); Serial.print(MIDI_value_7bit); 164 | Serial.print(", "); Serial.print(MIDI_value_14bit); 165 | Serial.print(", "); Serial.print(MIDI_value_14bit,BIN); 166 | Serial.print(", "); Serial.print((byte) (MIDI_value_14bit & 0x7F),BIN); 167 | Serial.print(", "); Serial.print((byte) ((MIDI_value_14bit >> 7) & 0x7F),BIN); 168 | 169 | Serial.println(); 170 | #endif 171 | 172 | prev_MIDI_value = MIDI_value_7bit; 173 | } 174 | } 175 | 176 | void sendCCMessage(float scaled_value, int transmit_threshold, const byte CC_ID) { 177 | static int prev_MIDI_value = -100; //this is remembered from call-to-call. Init to large neg number. 178 | 179 | const int bipolar_mode = 1; // choose 0 for pos only, 1 for pos and neg becoming pos, and 2 for full bipolar 180 | int MIDI_value = calc_MIDI_value(scaled_value, bipolar_mode); 181 | 182 | //decide whether to transmit MIDI message 183 | if (abs(MIDI_value - prev_MIDI_value) > transmit_threshold) { 184 | #if (DEBUG == false) 185 | Serial.write(MIDI_CC); 186 | Serial.write(CC_ID); 187 | Serial.write(MIDI_value); 188 | #else 189 | //debugging 190 | Serial.print(raw_value); 191 | Serial.print(", "); Serial.print(scaled_value); 192 | Serial.print(", "); Serial.print(MIDI_value); 193 | Serial.println(); 194 | #endif 195 | 196 | prev_MIDI_value = MIDI_value; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /MidiRibbonController/MidiRibbonController.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Created: Chip Audette, Feb/Mar 2016 3 | Purpose: Sense Reisitive Ribbon and Output MIDI notes 4 | 5 | Physical Setup: 6 | Using old Sparkfun MIDI shield 7 | Using Ribbon connected to A3 (wiper) and Gnd 8 | 9 | */ 10 | 11 | // initialize variables for analogIn 12 | const int ribbonVccPin = A2; //this is the "top" of the ribbon (the "bottom" is gnd) 13 | const int ribbonPin = A3; //this is the "wiper" of the ribbon 14 | const int ribbonGnd = A1; //this is the "bottom" of the ribbon 15 | 16 | //Define ribbon parameters 17 | #define RIBBON_SPAN (1023) // the Arduino AnalogRead() spans 0-1023 18 | const int ribbon_max_val = 339; //for my ribbon on my Arduino UNO 19 | const int ribbon_min_val = 13; //for my ribbon on my Arduino UNO 20 | float R_pullup_A3_kOhm = 0.0; //computed in setup() 21 | float R_ribbon_min_kOhm = 0.0; //computed in setup() 22 | const float R_ribbon_max_kOhm = 17.45; //for my ribbon, measured via multi-meter 23 | const float ribbon_span_half_steps_float = 36; //how many half-steps do I want my ribbon to represent 24 | const float ribbon_span_extra_half_steps_float = 0.5; //twiddle factor 25 | 26 | //what note do you want the bottom of the ribbon to represent? 27 | #define MIDI_note_bottom (43-7) //Note C1 28 | 29 | //what MIDI channel do you want to use 30 | #define MIDI_CHANNEL (0x01) //let's use channel 1. Channel 0 is universal. 31 | 32 | //define MIDI message numbers 33 | #define NOTE_ON (0x90+MIDI_CHANNEL) 34 | #define NOTE_OFF (0x80+MIDI_CHANNEL) 35 | #define PITCH_BEND (0xE0+MIDI_CHANNEL) 36 | #define NOTE_ON_VEL (127) //0x7F 37 | #define NOTE_OFF_VEL (0) //0x7F 38 | 39 | //Below, set the range of the synth's bend wheel. 40 | //Many synths default to +/- 2 half steps. You can 41 | //often change it to a bigger value. Bigger values 42 | //permit more smooth pitch changes 43 | #define synth_bend_half_steps (2) 44 | 45 | //define some global variables 46 | int prev_note_num = 0; 47 | boolean prev_note_on = false; 48 | int MIDI_note_num = 0, prev_MIDI_note_num = 0; 49 | unsigned int bend_int = 0, prev_bend_int = 0; 50 | byte pitch_bend_LSB, pitch_bend_MSB; 51 | boolean flag_pitch_is_bent = false; 52 | 53 | 54 | //setup routine 55 | void setup() { 56 | //setup the ribbon pins and the analogRead reference 57 | pinMode(ribbonVccPin, INPUT); //set to high impedance 58 | pinMode(ribbonPin, INPUT_PULLUP); //set as input, but with pullup 59 | pinMode(ribbonGnd, OUTPUT); digitalWrite(ribbonGnd,LOW); //use as ground for the ribbon 60 | 61 | //compute scaling parameters for the ribbon 62 | R_pullup_A3_kOhm = R_ribbon_max_kOhm * ( ((float)RIBBON_SPAN) / ((float)ribbon_max_val) - 1.0 ); 63 | R_ribbon_min_kOhm = ((float)ribbon_min_val) / ((float)ribbon_max_val) * R_ribbon_max_kOhm; 64 | 65 | //initialize the MIDI communications: 66 | //Serial.begin(115200); //for debugging only (not sending MIDI to synth!) 67 | Serial.begin(31250); // MIDI standard speed 68 | } 69 | 70 | void loop() { 71 | // read the ribbon and send the MIDI commands 72 | updateTheSystem(); 73 | 74 | // delay a bit so that we only get an update rate of 60-100Hz 75 | delay(10); 76 | } 77 | 78 | 79 | void updateTheSystem() { 80 | 81 | //read input 82 | int ribbon_value = analogRead(ribbonPin); 83 | 84 | //process the ribbon value 85 | float note_num_float = processRibbonValue(ribbon_value); //zero is bottom of ribbon, 1.0 is top of ribbon 86 | 87 | //figure out which note number the ribbon value corresponds to 88 | note_num_float = note_num_float - ribbon_span_extra_half_steps_float/2.0; 89 | int note_num = ((int)(note_num_float + 0.5)); //round 90 | 91 | //is the ribbon being pressed? Is a note on? 92 | boolean note_on = false; 93 | if (ribbon_value < (RIBBON_SPAN-10)) { 94 | if ((note_num >= 0) && (note_num <= ribbon_span_half_steps_float)) note_on = true; 95 | } else { 96 | //the ribbon value is weird. use the old note number because it's safer 97 | note_num = prev_note_num; //just in case there was a bad value 98 | } 99 | 100 | //calc MIDI note number 101 | note_num = constrain(note_num, 0, ribbon_span_half_steps_float); 102 | MIDI_note_num = note_num + MIDI_note_bottom; 103 | boolean note_change = (note_on != prev_note_on) | (note_num != prev_note_num); 104 | 105 | //compute the amount of bend to apply to the note 106 | computePitchBend(note_num_float,note_num); 107 | 108 | //write MIDI commands 109 | writeMIDI(note_change,note_on,prev_note_on,MIDI_note_num); 110 | 111 | //save state for next time through the loop 112 | prev_note_num = note_num; 113 | prev_MIDI_note_num = MIDI_note_num; 114 | prev_note_on = note_on; 115 | prev_bend_int = bend_int; 116 | } 117 | 118 | float processRibbonValue(int ribbon_value) { 119 | //apply the first scaling to do the primary linearization of the response. 120 | //This chunk of code assumes that the soft pot is a perfect potentiometer 121 | //and that the wiper is being pulled up via a pullup resistor (built into the 122 | //arduino). When the pullup resistor is a similar value as the ribbon (which it is) 123 | //the voltage at the wiper is not as linear as we'd like. The equations below 124 | //are simply solve the resistor divider to figure out where the user touched 125 | //the potentiometer. 126 | float ribbon_value_float = (float)ribbon_value; 127 | float foo_float = 1.0 / (((float)RIBBON_SPAN) / ribbon_value_float - 1.0); 128 | float ribbon_frac = (R_pullup_A3_kOhm / R_ribbon_max_kOhm) * foo_float; 129 | float ribbon_R_kOhm = R_pullup_A3_kOhm * foo_float; 130 | float ribbon_span_frac = (ribbon_R_kOhm - R_ribbon_min_kOhm) / (R_ribbon_max_kOhm - R_ribbon_min_kOhm); 131 | ribbon_span_frac = max(0.0, ribbon_span_frac); 132 | float note_num_float = ribbon_span_frac * (ribbon_span_half_steps_float + ribbon_span_extra_half_steps_float); 133 | note_num_float = note_num_float - ribbon_span_extra_half_steps_float/2.0; 134 | 135 | //could apply more code here to calibrate for additional weirdness in your 136 | //own ribbon 137 | // 138 | //more code 139 | //more code 140 | 141 | return note_num_float; 142 | } 143 | 144 | void computePitchBend(float note_num_float, int note_num) { 145 | float partial_note_num_float = note_num_float - ((float)note_num); 146 | float partial_bend_float = partial_note_num_float / ((float)synth_bend_half_steps); 147 | partial_bend_float = constrain(partial_bend_float, -1.0, 1.0); 148 | #define MIDI_MAX_BEND (8192) //can go up and down by this amount 149 | 150 | //these are the values that are output and used by the rest of the system 151 | bend_int = (unsigned int)(MIDI_MAX_BEND + (partial_bend_float * ((float)MIDI_MAX_BEND))); 152 | pitch_bend_LSB = ((byte)bend_int) & 0b01111111; //get lower 7 bits, ensure first bit to zero 153 | pitch_bend_MSB = ((byte)(bend_int >> 7)) & 0b01111111; //get upper 7 bits, set first bit to zero 154 | } 155 | 156 | void transmitPitchBend(void) { 157 | Serial.write((byte)PITCH_BEND); 158 | Serial.write((byte)pitch_bend_LSB); 159 | Serial.write((byte)pitch_bend_MSB); 160 | flag_pitch_is_bent = true; 161 | } 162 | 163 | void recenterMidiPitchBend(void) { 164 | Serial.write((byte)PITCH_BEND); 165 | Serial.write((byte)0x00); //center,LSB 166 | Serial.write((byte)64); //center,MSB} 167 | flag_pitch_is_bent = false; 168 | }; 169 | 170 | void writeMIDI(boolean note_change,boolean note_on,boolean prev_note_on,int MIDI_note_num) { 171 | //write MIDI messages 172 | if (note_change) { //has a note turned on/off or changed value? 173 | if (note_on) { 174 | //turn on new note 175 | Serial.write((byte)NOTE_ON); 176 | Serial.write((byte)MIDI_note_num); 177 | Serial.write((byte)NOTE_ON_VEL); //velocity 178 | 179 | //send pitch bend info 180 | transmitPitchBend(); 181 | 182 | //turn off the old note? 183 | if (prev_note_on == true) { 184 | //previous note was on...so assume legato...turn off old note now that new one is already started 185 | Serial.write((byte)NOTE_OFF); 186 | Serial.write((byte)prev_MIDI_note_num); 187 | Serial.write((byte)NOTE_OFF_VEL); 188 | } 189 | 190 | } else if ((note_on == false) && (prev_note_on == true)) { 191 | //no other notes are active. turn note off 192 | Serial.write((byte)NOTE_OFF); 193 | Serial.write((byte)prev_MIDI_note_num); 194 | Serial.write((byte)NOTE_OFF_VEL); 195 | 196 | //remove the pitch bend command 197 | recenterMidiPitchBend(); 198 | } 199 | } else { 200 | //we're not changing note number...but we should still update the pitch bend info 201 | if (note_on == true) { 202 | if (bend_int != prev_bend_int) { //but only if it has changed 203 | transmitPitchBend(); 204 | } 205 | } 206 | } 207 | } 208 | 209 | 210 | -------------------------------------------------------------------------------- /MidiClockToCVTrigger/MidiClockToCVTrigger.ino: -------------------------------------------------------------------------------- 1 | 2 | //Chip Audette 3 | //May 1, 2013 4 | //Assumes use of MIDI Shield from Sparkfun 5 | // -- Instead of pots, has dangling 1/8 jacks wired in. 6 | //Input is MIDI signal with the MIDI pulse 7 | //Output is HIGH/LOW signal on A0/A2 (pulse high) and A1/A3 (pulse low) 8 | //Output is 1/16th note on A0/A1, 1/8th note on A2/A3 9 | //Output is also an echo of all incoming MIDI traffice to the Serial Out (ie, MIDI out) 10 | 11 | typedef unsigned long micros_t; 12 | typedef unsigned long millis_t; 13 | 14 | #define ECHOMIDI (true) 15 | 16 | // defines for MIDI Shield components only 17 | //define KNOB1 0 18 | //define KNOB2 1 19 | 20 | //define BUTTON1 2 21 | //define BUTTON2 3 22 | //define BUTTON3 4 23 | 24 | #define STAT1 7 25 | #define STAT2 6 26 | 27 | #define OFF 1 28 | #define ON 2 29 | #define WAIT 3 30 | 31 | //define ISR_HIGHLOW_PIN 9 32 | 33 | byte byte1; 34 | byte byte2; 35 | byte byte3; 36 | //int noteTranspose = -27; 37 | //int curNote=0; 38 | //int newNote=0; 39 | //int FS1,FS2; 40 | 41 | //define analog input pins for reading the analog inputs 42 | #define N_INPUTS (2) 43 | int inputPin[] = {A0, A1}; 44 | #define N_PUSHBUTTONS (3) 45 | int pushbuttonPin[] = {2,3,4}; 46 | int prevPushbuttonState[N_PUSHBUTTONS]; 47 | boolean flag_middlePushbutton = false; 48 | 49 | //define the midi timing info 50 | #define MIDI_PPQN 24 //how many MIDI time pulses per quarter note...24 is standard 51 | micros_t lastMessageReceived_micros=0; 52 | micros_t pulseTimeout_micros = 2000000UL; 53 | 54 | //define the outputs 55 | #define OUTPIN_MODE OUTPUT //INPUT or OUTPUT? INPUT gives more protection 56 | #define N_OUTPUTS (2) //how many outputs are attached 57 | #define N_COUNTERS (N_OUTPUTS+1) //the extra counter is for the LED 58 | int pulseCountsPerOutput[N_COUNTERS]; //how many midi pulses before issuing a trigger command 59 | int pulse_counter[N_COUNTERS]; 60 | int outputPin_high[] = {A2, A4, STAT1}; 61 | int outputPin_low[] = {A3, A5, 8}; //the last one can be any pin at all that is not yet used 62 | 63 | 64 | #define ANALOG_UPDATE_MILLIS 200 //update analog inputs after this period of milliseconds 65 | 66 | void turnOnStatLight(int pin) { 67 | digitalWrite(pin,LOW); 68 | } 69 | void turnOffStatLight(int pin) { 70 | digitalWrite(pin,HIGH); 71 | } 72 | void activateTrigger(int pinToHigh, int pinToLow, int LEDpin) { 73 | digitalWrite(pinToHigh,HIGH); 74 | digitalWrite(pinToLow,LOW); 75 | if (LEDpin > 0) turnOnStatLight(LEDpin); 76 | } 77 | void deactivateTrigger(int pinToLow, int pinToHigh, int LEDpin) { 78 | digitalWrite(pinToHigh,HIGH); 79 | digitalWrite(pinToLow,LOW); 80 | if (LEDpin > 0) turnOffStatLight(LEDpin); 81 | } 82 | void setup() { 83 | 84 | //set pin modes for the lights 85 | pinMode(STAT1,OUTPUT); 86 | pinMode(STAT2,OUTPUT); 87 | 88 | //set pin modes for the analog inputs 89 | for (int I=0;I 25) { 134 | //Serial.println("loop count achieved"); 135 | loopCount=0; 136 | curTime_millis = millis(); 137 | 138 | //check to see if there's been any MIDI traffic...if not, shut off the LED 139 | if (curTime_millis < lastMIDImessage_millis) lastMIDImessage_millis = 0; //simplistic reset 140 | if ((curTime_millis - lastMIDImessage_millis) > 1000) turnOffStatLight(STAT2); //turn off after 1 second 141 | 142 | //see if it's time to check the user analog inputs 143 | if (curTime_millis < lastAnalogInputs_millis) { 144 | //Serial.println("reseting lastAnalogInputs_millis"); 145 | lastAnalogInputs_millis = 0; //simplistic reset 146 | } 147 | if ((curTime_millis - lastAnalogInputs_millis) > ANALOG_UPDATE_MILLIS) { 148 | lastAnalogInputs_millis = curTime_millis; 149 | 150 | //check the potentiometers 151 | for (int I=0;I 0) 183 | { 184 | //read the byte 185 | byte1 = Serial.read(); 186 | 187 | //echo the byte 188 | Serial.write(byte1); 189 | 190 | //act on the byte 191 | switch (byte1) { 192 | case 0xF8: //MIDI Clock Pulse 193 | 194 | //maybe a lot of time has passed because the cable was unplugged. 195 | //if so, ...check to see if enough time has passed to reset the counter 196 | currentTime_micros = micros(); 197 | if (currentTime_micros < lastMessageReceived_micros) lastMessageReceived_micros = 0; //it wrapped around, simplistic reset 198 | if ((currentTime_micros - lastMessageReceived_micros) > pulseTimeout_micros) resetCounters(); //reset the counters! 199 | lastMessageReceived_micros = currentTime_micros; 200 | 201 | //loop over each channel 202 | for (int Icounter=0;Icounter (pulseCountsPerOutput[Icounter]-1)) val -= pulseCountsPerOutput[Icounter]; //faster than mod operator 210 | while (val < 0) val += pulseCountsPerOutput[Icounter]; //faster than mod operator 211 | pulse_counter[Icounter] = val; 212 | 213 | //act upon the counter 214 | if (pulse_counter[Icounter] == 0) { 215 | activateTrigger(outputPin_high[Icounter],outputPin_low[Icounter],0); 216 | } else if (pulse_counter[Icounter] >= min(pulseCountsPerOutput[Icounter]/2,MIDI_PPQN/2)) { 217 | deactivateTrigger(outputPin_high[Icounter],outputPin_low[Icounter],0); 218 | } 219 | } 220 | break; 221 | case 0xFA: //MIDI Clock Start 222 | //restart the counter 223 | resetCounters(); 224 | break; 225 | } 226 | 227 | //toggle the LED indicating serial traffic 228 | MIDI_LED_state = !MIDI_LED_state; 229 | if (MIDI_LED_state) { 230 | turnOnStatLight(STAT2); //turn on the STAT light indicating that it's received some Serial comms 231 | } else { 232 | turnOffStatLight(STAT2); //turn on the STAT light indicating that it's received some Serial comms 233 | } 234 | lastMIDImessage_millis = millis(); 235 | 236 | } else { 237 | delay(1); 238 | } 239 | } 240 | 241 | void resetCounters(void) { 242 | for (int I=0;I 0) { 73 | 74 | //read the byte 75 | foo_byte = Serial.read(); 76 | 77 | //what kind of message is it? 78 | if (foo_byte == MIDI_CLOCK) { //MIDI CLOCK 79 | 80 | if (ECHOMIDI_CLOCK) Serial.println(foo_byte,HEX); 81 | incrementTheTimeAndAct(); 82 | 83 | } else if (foo_byte == MIDI_START) { 84 | 85 | if (ECHOMIDI) { Serial.print(foo_byte,HEX); Serial.print(" "); } 86 | if (ECHOMIDI_BIN) { Serial.write(foo_byte); } 87 | resetCurTimeIndex(); 88 | hasTimeCounterBeenExternallySet = true; 89 | 90 | } else { //not a MIDI Clock 91 | 92 | if (ECHOMIDI) { Serial.print(foo_byte,HEX); Serial.print(" "); } 93 | if (ECHOMIDI_BIN) { Serial.write(foo_byte); } 94 | receiveMIDIByteAndAct(foo_byte); 95 | 96 | } //close if MIDI Clock 97 | 98 | } else { //if Serial.isAvailable() 99 | 100 | //service the pushbuttons, knobs, and footswtiches 101 | serviceUserControls(); 102 | 103 | } //end if Serial.isAvailable() 104 | } //end loop() 105 | 106 | 107 | void incrementTheTimeAndAct(void) { 108 | raw_current_time_index++; 109 | if (raw_current_time_index >= MAX_MAX_TIME_INDEX) current_time_index = 0; 110 | current_time_index = raw_current_time_index % max_time_index; 111 | resetMessageCounters(); 112 | playCodesForThisTimeStep(current_time_index); 113 | } 114 | 115 | void receiveMIDIByteAndAct(const byte &foo_byte) { 116 | if (foo_byte & (0b10000000+MIDI_CHAN)) { //bitwise compare to detect start of MIDI_Message (for this channel) 117 | turnOnStatLight(STAT1); //turn on the STAT1 light indicating that it's received some Serial comms 118 | if (byte_counter > 0) saveMessage(current_time_index,rx_bytes); //save the previously started message 119 | clear_rx_bytes(); byte_counter = -1; //clear the temporary storage 120 | if ((foo_byte == NOTE_ON) || (foo_byte == NOTE_OFF) || (foo_byte == MIDI_CC) || (foo_byte == MIDI_PB)) { 121 | //if ((foo_byte == MIDI_CC) || (foo_byte == MIDI_PB)) { 122 | byte_counter++; 123 | //Serial.print("start, byte_count "); Serial.println(byte_counter); 124 | rx_bytes[byte_counter] = foo_byte; //only store these certain types of messages 125 | } else { 126 | //Serial.println("start, rejected"); 127 | } 128 | 129 | } else if (byte_counter >= 0) { //other bytes in the alrady started MIDI Message 130 | //Serial.print("mid, byte_count "); Serial.println(byte_counter); 131 | byte_counter++; rx_bytes[byte_counter] = foo_byte; 132 | } 133 | //if we've gotten all three bytes, save the message 134 | if (byte_counter == (3-1)) { saveMessage(current_time_index,rx_bytes); clear_rx_bytes(); byte_counter = -1; } 135 | } 136 | 137 | void serviceUserControls(void) { 138 | //check the pushbuttons and footswitches 139 | for (int i=0; i<3; i++) pushbutton[i].update(); 140 | for (int i=0; i<2; i++) footswitch[i].update(); 141 | 142 | //act on the footswitches 143 | if (footswitch[0].has_state_changed) setRecordingState(footswitch[0].state,current_time_index); 144 | 145 | //act on the pushbuttons (once released) 146 | if ((pushbutton[1].state == 0) && (pushbutton[1].has_state_changed)) togglePlaybackState(); 147 | if ((pushbutton[2].state == 0) && (pushbutton[2].has_state_changed)) stopAllNotes(); 148 | 149 | //act on combo-presses foe the push buttons...clear the MIDI recording? 150 | if ((pushbutton[0].state==1) & (pushbutton[2].state==1)) clearRecordedMIDI(); //combo to clear memory 151 | 152 | //check potentiometer to set max length of loop 153 | int knob_val = analogRead(KNOB1); 154 | updateMaxTimeIndex(analogRead(KNOB1)); 155 | } 156 | 157 | void resetMessageCounters(void) { 158 | CC_counter = 0; 159 | PB_counter = 0; 160 | } 161 | 162 | void updateMaxTimeIndex(const int &knob_val) { 163 | static const int n_table = 8; 164 | static int value_bounds[n_table] = {-1, 511-50, 767-40, 895-30, 959-20, 991-10, 1007, 5000}; //end with some very large value 165 | static int max_times[n_table] = {MAX_MAX_TIME_INDEX, 8*MIDI_PPQN, 4*MIDI_PPQN, 2*MIDI_PPQN, MIDI_PPQN, MIDI_PPQN/2, MIDI_PPQN/4, MIDI_PPQN/8}; //end with some value that doesn't matter 166 | int prev_max_time_index = max_time_index; 167 | 168 | //search the table of values 169 | int ind = 1; 170 | while ((knob_val > value_bounds[ind]) && (ind < n_table)) ind++; 171 | ind--; 172 | 173 | //set the new max time index 174 | max_time_index = max_times[ind]; 175 | 176 | //if it has changed, do something 177 | //if (max_time_index != prev_max_time_index) { 178 | //} 179 | } 180 | 181 | void setRecordingState(int state, int cur_time_ind) { 182 | if (is_recording && (state==0)) { 183 | //turning OFF recording... 184 | note_buffer.addNoteOffForOnNotes(cur_time_ind); //if needed, create NOTE_OFF for any open notes 185 | 186 | //recenter the pitch and mod wheels 187 | addPitchAndModCenterCodes(cur_time_ind); //if needed, create centering coes for pitch and mod wheels 188 | } else { 189 | //turning on recording... 190 | MODWHEEL_on = 0; 191 | PB_on = 0; 192 | } 193 | clear_rx_bytes(); 194 | is_recording = state; 195 | note_buffer.is_recording = state; 196 | } 197 | 198 | void togglePlaybackState(void) { 199 | note_buffer.stopPlayedNotes(); 200 | is_playing = !is_playing; 201 | } 202 | 203 | void stopAllNotes(void) { //http://www.music-software-development.com/midi-tutorial.html 204 | Serial.write(0b10110000+MIDI_CHAN); //CC 205 | Serial.write((byte)123); //all notes off 206 | Serial.write((byte)0); 207 | } 208 | 209 | void clearRecordedMIDI(void) { 210 | note_buffer.clear(); 211 | for (int i=0; i < MAX_N_CODES; i++) MIDI_time_buffer[i] = -1; 212 | resetMessageCounters(); 213 | hasTimeCounterBeenExternallySet = false; 214 | } 215 | 216 | void addPitchAndModCenterCodes(int cur_time_ind) { 217 | if (MODWHEEL_on) { 218 | byte new_message[] = {MIDI_CC, MODWHEEL_CC, (byte)0x00}; 219 | saveMessage(cur_time_ind, new_message); 220 | MODWHEEL_on = 0; //flag MODWHEEL as off 221 | } 222 | if (PB_on) { 223 | byte new_message[] = {MIDI_PB, 0x00, 0x40}; //this is PB centered on my Prophet 6 224 | saveMessage(cur_time_ind,new_message); 225 | PB_on = 0; //flag PitchBend as off 226 | } 227 | } 228 | 229 | void playCodesForThisTimeStep(int cur_time_ind) { 230 | //step through all the codes and play those at this timestep 231 | if (is_playing) { 232 | note_buffer.playCodesForThisTimeStep(cur_time_ind,max_time_index); 233 | 234 | //look through the other MIDI code buffer 235 | for (int i=0; i < MAX_N_CODES; i++) { //step through ALL time because they may be out of order (from layer-on-layer recording) 236 | if (MIDI_time_buffer[i] == cur_time_ind) Serial.write(MIDI_command_buffer[i],3); //send the three byte MIDI message 237 | } 238 | } 239 | } 240 | 241 | void clear_rx_bytes(void) { 242 | rx_bytes[0]=0; 243 | rx_bytes[1]=0; 244 | rx_bytes[2]=0; 245 | } 246 | 247 | void saveMessage(int cur_time_ind, byte given_bytes[]) { 248 | int ind; 249 | if (is_recording) { 250 | //Serial.print("saveMess "); Serial.print(rx_bytes[0],HEX); Serial.print(" "); Serial.println(cur_time_ind); 251 | 252 | if (hasTimeCounterBeenExternallySet==false) { 253 | cur_time_ind = resetCurTimeIndex(); 254 | hasTimeCounterBeenExternallySet = true; 255 | } 256 | 257 | switch (given_bytes[0]) { 258 | case (NOTE_ON): 259 | ind = note_buffer.saveThisNoteOnMessage(cur_time_ind, given_bytes[1],given_bytes[2]); 260 | if ((ind > -1) && (!ECHOMIDI_BIN)) note_buffer.sendNoteOnMessage(ind); //echo the note on message 261 | break; 262 | case (NOTE_OFF): 263 | ind = note_buffer.saveThisNoteOffMessage(cur_time_ind, given_bytes[1],given_bytes[2]); 264 | if ((ind > -1) && (!ECHOMIDI_BIN)) note_buffer.sendNoteOffMessage(ind); //echo the note off message 265 | break; 266 | case (MIDI_CC): 267 | if (CC_counter == 0) { //save only the first of this message type 268 | saveThisMessage(cur_time_ind, given_bytes); 269 | CC_counter++; 270 | if (given_bytes[1] == MODWHEEL_CC) { 271 | MODWHEEL_on = 1; 272 | if (given_bytes[2] == 0x00) MODWHEEL_on = 0; 273 | } 274 | } 275 | break; 276 | case (MIDI_PB): 277 | if (PB_counter == 0) { //save only the first of this message type 278 | saveThisMessage(cur_time_ind, given_bytes); 279 | PB_counter++; 280 | PB_on = 1; if ((given_bytes[1] == 0x00) && (given_bytes[2] == 0x40)) PB_on = 0; 281 | } 282 | break; 283 | } 284 | 285 | } 286 | } 287 | 288 | int saveThisMessage(const int &cur_time_ind, byte given_bytes[]) { 289 | //should only be used for pitch bend and CC messages 290 | if (is_recording) { 291 | if ((buffer_counter == 0) || 292 | !(((MIDI_time_buffer[buffer_counter]>>1) == (cur_time_ind>>1)) && //only allow a new code every other MIDI time code 293 | (MIDI_command_buffer[buffer_counter][0] == given_bytes[0]) && 294 | (MIDI_command_buffer[buffer_counter][1] == given_bytes[1]))) { 295 | 296 | buffer_counter++; 297 | if (buffer_counter >= MAX_N_CODES) buffer_counter=0; //overwrite the beginning, if necessary 298 | MIDI_time_buffer[buffer_counter] = cur_time_ind; 299 | MIDI_command_buffer[buffer_counter][0] = given_bytes[0]; 300 | MIDI_command_buffer[buffer_counter][1] = given_bytes[1]; 301 | MIDI_command_buffer[buffer_counter][2] = given_bytes[2]; 302 | return buffer_counter; 303 | } 304 | } 305 | return -1; 306 | } 307 | 308 | int resetCurTimeIndex(void) { 309 | current_time_index = 0; 310 | raw_current_time_index = 0; 311 | return current_time_index; 312 | } 313 | 314 | void turnOnStatLight(const int &pin) { 315 | digitalWrite(pin,LOW); 316 | } 317 | void turnOffStatLight(const int &pin) { 318 | digitalWrite(pin,HIGH); 319 | } 320 | 321 | -------------------------------------------------------------------------------- /MidiRibbonController_Filtering/MidiRibbonController_Filtering.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Created: Chip Audette, Feb/Mar 2016 3 | Purpose: Sense Reisitive Ribbon and Output MIDI notes 4 | 5 | This code is aimed toward the Sequential Prophet 6 synthesizer. 6 | Specifically, it automatically changes the synth's bend range to +/- 1 octave. 7 | 8 | Physical Setup: 9 | Using old Sparkfun MIDI shield 10 | Using Ribbon connected to A3 (wiper) and Gnd 11 | 12 | */ 13 | 14 | #include //http://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ 15 | 16 | 17 | //debugging info 18 | #define PRINT_TEXT (0) 19 | #define PRINT_BEND_TEXT (0) 20 | #define PRINT_CAL_TEXT (0) 21 | #define PRINT_RAW_RIBBON_VALUE (0) 22 | #define WRITE_MIDI (1) 23 | 24 | #define USE_ARDUINO 1 25 | //#define USE_TEENSY 1 26 | //#define Serial_t (Serial1) //for MIDI for Arduino Micro or for TEENSY 27 | #define Serial_t (Serial) //for MIDI for Arduino UNO 28 | 29 | 30 | // initialize variables for analogIn 31 | const int ribbonPin = A3; 32 | const int ribbonVccPin = A2; 33 | const int ribbonGnd = A1; 34 | //int drivePin = 4; //Digital pin #4 35 | //int drivePin_state = HIGH; 36 | const int pushButton1_pin = 2; // Push button, Digital Pin 37 | const int pushButton2_pin = 3; // Push button, Digital Pin 38 | const int pushButton3_pin = 3; // Push button, Digital Pin 39 | const int LED1_pin = 6; //on Sparkfun MIDI shield 40 | const int LED2_pin = 7; //on Sparkfun MIDI shield 41 | const int POT1_pin = A0; 42 | //const int comparePin = A4; 43 | //const int comparePinGnd = A5; 44 | 45 | //sampling 46 | volatile boolean flag_update_system = true; 47 | volatile boolean flag_cannot_keep_up = false; 48 | #define SAMPLE_RATE_HZ (600.0) 49 | 50 | //filtering 51 | #define DO_NOTCH_FILTERING (1) 52 | #define NOTCH_FREQ_HZ (60.0) 53 | #define NOTCH_Q (0.707) //higher is sharper. 0.707 is crtically damped (butterworth) 54 | #define FILTER_PEAK_GAIN_DB (0.0) //doesn't matter for Lowpass, highpass, notch, or bandpass 55 | Biquad notch_filter(bq_type_notch,NOTCH_FREQ_HZ / SAMPLE_RATE_HZ, NOTCH_Q, FILTER_PEAK_GAIN_DB); //one for each channel because the object maintains the filter states 56 | Biquad notch_filter2(bq_type_notch,(2.0*NOTCH_FREQ_HZ) / SAMPLE_RATE_HZ, NOTCH_Q, FILTER_PEAK_GAIN_DB); //one for each channel because the object maintains the filter states 57 | 58 | //decimation 59 | #define PROCESSING_DECIMATION (12) 60 | #define PROCESSING_SAMPLE_RATE_HZ (SAMPLE_RATE_HZ/PROCESSING_DECIMATION) 61 | #define LP_Q (0.707) //higher is sharper. 0.707 is crtically damped (butterworth) 62 | Biquad lp_filter(bq_type_lowpass,PROCESSING_SAMPLE_RATE_HZ/SAMPLE_RATE_HZ, LP_Q, FILTER_PEAK_GAIN_DB); //one for each channel because the object maintains the filter states 63 | volatile int decimation_counter = 0; 64 | 65 | //Define ribbon parameters 66 | #define RIBBON_BY_VCC 1 67 | #define RIBBON_BY_WIPER 2 68 | const int ribbon_mode = RIBBON_BY_WIPER; 69 | #define RIBBON_SPAN (1023) 70 | int ribbon_max_val = 339,ribbon_min_val = 13; //for my Arduino UNO 71 | //int ribbon_max_val = 520,ribbon_min_val = 22; //for my Arduino UNO, External 3.3V ref 72 | //int ribbon_max_val = 330,ribbon_min_val = 10; //for my Arduino Micro, 5V pullup 73 | //int ribbon_max_val = 548,ribbon_min_val = 0; //for my Arduino Micro, 15K external pullup 74 | //int ribbon_max_val = 344,ribbon_min_val = 0; //for my Teensy 75 | //int ribbon_max_val = 592,ribbon_min_val = 0; //for my Teensy with double pullups 76 | //int ribbon_max_val = 585,ribbon_min_val = 0; //for my Teensy 15k pullup 77 | //int ribbon_max_val = 545, ribbon_min_val = 0; //Teensy plus opamp w/ 33K pullup 78 | //int ribbon_max_val = 842, ribbon_min_val = 0; //Teensy plus opamp w/ 15K pullup 79 | float R_pullup_A3_kOhm = 0.0; 80 | float R_ribbon_min_kOhm = 0.0; 81 | const float R_ribbon_max_kOhm = 17.45; 82 | const float ribbon_span_half_steps_float = 36; 83 | const float ribbon_span_extra_half_steps_float = 0.5; 84 | const int max_allowed_jump_steps = 5; 85 | 86 | boolean flag_bend_range_is_for_ribbon = false; 87 | #define BEND_DEFAULT 0 88 | #define BEND_FOR_RIBBON 1 89 | 90 | boolean flag_pitch_is_bent = false; 91 | float time_for_recentering_sec = 2.5; 92 | long int time_for_recentering_counts = 0; 93 | long int counts_until_recentering = 0; 94 | 95 | #ifdef USE_TEENSY 96 | IntervalTimer sampleTimer; 97 | #endif 98 | 99 | #define MIDI_note_bottom (43-7) //Note C1 100 | 101 | #define MIDI_CHANNEL (0x01) 102 | #define NOTE_ON (0x90+MIDI_CHANNEL) 103 | #define NOTE_OFF (0x80+MIDI_CHANNEL) 104 | #define NOTE_ON_VEL (127) //0x7F 105 | #define NOTE_OFF_VEL (0) //0x7F 106 | #define PITCH_BEND (0xE0+MIDI_CHANNEL) 107 | #define MIDI_CC (0b10110000 + MIDI_CHANNEL) 108 | #define BRIGHTNESS_CC ((byte)74) 109 | 110 | #define synth_bend_half_steps (12) //what is the synth's pitch wheel set to? 111 | 112 | //interrupt service routine...format of function call changes depending upon platform 113 | #ifdef USE_TEENSY 114 | void serviceTimer(void) //Teensy convention 115 | #else 116 | ISR(TIMER1_COMPA_vect) //arduino convention 117 | #endif 118 | { 119 | if (flag_update_system) { 120 | flag_cannot_keep_up = true; 121 | } 122 | 123 | read_ribbon(); 124 | decimation_counter++; 125 | if (decimation_counter >= PROCESSING_DECIMATION) { 126 | decimation_counter = 0; 127 | flag_update_system = true; 128 | } 129 | } 130 | 131 | 132 | //setup routine 133 | void setup() { 134 | //setup the ribbon pins and the analogRead reference 135 | //analogReference(INTERNAL); 136 | //analogReference(EXTERNAL); 137 | pinMode(ribbonVccPin, INPUT); //set to high impedance 138 | pinMode(ribbonPin, INPUT_PULLUP); 139 | pinMode(ribbonGnd,OUTPUT); digitalWrite(ribbonGnd,LOW); //use as ground 140 | pinMode(POT1_pin,INPUT); 141 | 142 | //setup the LEDs 143 | pinMode(LED1_pin, OUTPUT); digitalWrite(LED1_pin, HIGH); //HIGH is off for he Sparkfun shield 144 | pinMode(LED2_pin, OUTPUT); digitalWrite(LED2_pin, HIGH); //HIGH is off for he Sparkfun shield 145 | 146 | // initialize the MIDI communications: 147 | Serial.begin(115200*2); //for Arduino Micro only 148 | if (WRITE_MIDI) Serial_t.begin(31250); 149 | 150 | //set scaling for ribbon 151 | R_pullup_A3_kOhm = R_ribbon_max_kOhm * ( ((float)RIBBON_SPAN) / ((float)ribbon_max_val) - 1.0 ); 152 | R_ribbon_min_kOhm = ((float)ribbon_min_val) / ((float)ribbon_max_val) * R_ribbon_max_kOhm; 153 | 154 | //setup the timer 155 | float dt_sec_sample = 1.0 / SAMPLE_RATE_HZ;//arduino can run up to 1200Hz as of Mar 5, 2016 156 | 157 | #ifdef USE_TEENSY 158 | int timer_loop_usec = (int)(dt_sec_sample*1000000.0); //microseconds 159 | sampleTimer.begin(serviceTimer, timer_loop_usec); 160 | #else 161 | int timer_clock_divider = 64; //see which divider I'm using in setupTimer() 162 | int timer_value = (int)( (16000000L) / ((long)timer_clock_divider) / ((long)SAMPLE_RATE_HZ) ); 163 | setupTimer(timer_value); 164 | #endif 165 | 166 | //decide how long to wait until pitch bend is recentered 167 | float dt_sec_processing = 1.0 / PROCESSING_SAMPLE_RATE_HZ; 168 | time_for_recentering_counts = (long int)(time_for_recentering_sec / dt_sec_processing + 0.5); //time steps to count off 169 | } 170 | 171 | 172 | 173 | void loop() { 174 | static int idle_counter = 0; 175 | 176 | if (flag_update_system) { 177 | flag_update_system = false; 178 | updateTheSystem(); 179 | 180 | //check to see if we should turn off the bend commands 181 | if (flag_pitch_is_bent) { 182 | counts_until_recentering--; //decrement counter 183 | if (counts_until_recentering <= 0) { 184 | #if WRITE_MIDI 185 | recenterMidiPitchBend(); //remove any bend commands still active 186 | setBendRange(BEND_DEFAULT); //turn the bend range back to the default 187 | transmitBrightness(0); 188 | #endif 189 | } 190 | } 191 | 192 | } else { 193 | idle_counter++; //kill time without being stuck in an empty loop 194 | } 195 | } 196 | 197 | 198 | //static int prev_FS2_val; 199 | int prev_but_val[] = {HIGH, HIGH, HIGH}; 200 | int raw_ribbon_value = 0; 201 | int ribbon_value = 0; 202 | float ribbon_value_float = 0.0; 203 | int note_num = 0; 204 | int closest_note_num = 0; 205 | float note_num_float = 0.0; 206 | 207 | int prev_note_num = 0; 208 | int prev_closest_note_num = 0; 209 | int prev_MIDI_note_num = 0; 210 | boolean prev_note_on = false; 211 | float prev_ribbon_val_float = 1023.0; 212 | unsigned int prev_bend_int = 0; 213 | byte prev_pitch_bend_MSB = 63; 214 | byte prev_pitch_bend_LSB = 63; 215 | 216 | float foo_float, ribbon_frac, ribbon_R_kOhm, ribbon_span_frac; 217 | float brightness_frac; 218 | 219 | boolean note_on; 220 | int MIDI_note_num; 221 | boolean note_change; 222 | float partial_note_num_float; 223 | float partial_bend_float ; 224 | #define MIDI_MAX_BEND (8192) //can go up and down by this amount 225 | unsigned int bend_int; 226 | byte pitch_bend_LSB, pitch_bend_MSB; 227 | void updateTheSystem() { 228 | 229 | //read input 230 | //read_ribbon(); //this is now done in the interrupt service routine! 231 | 232 | //process input 233 | process_ribbon(); 234 | 235 | //is the note on? 236 | note_on = false; 237 | if (ribbon_value < (RIBBON_SPAN-10)) { 238 | if ((note_num >= 0) && (note_num <= ribbon_span_half_steps_float)) note_on = true; 239 | if (abs(note_num - prev_note_num) > max(max_allowed_jump_steps,synth_bend_half_steps)) note_on = false; 240 | } else { 241 | note_num = prev_note_num; //just in case there was a bad value 242 | } 243 | 244 | //calc MIDI note number 245 | note_num = constrain(note_num, 0, ribbon_span_half_steps_float); 246 | int MIDI_note_num = note_num + MIDI_note_bottom; 247 | boolean note_change = (note_on != prev_note_on) | (note_num != prev_note_num); 248 | 249 | //compute the amount of bend 250 | partial_note_num_float = note_num_float - ((float)note_num); 251 | partial_bend_float = partial_note_num_float / ((float)synth_bend_half_steps); 252 | partial_bend_float = constrain(partial_bend_float, -1.0, 1.0); 253 | #define MIDI_MAX_BEND (8192) //can go up and down by this amount 254 | bend_int = (unsigned int)(MIDI_MAX_BEND + (partial_bend_float * ((float)MIDI_MAX_BEND))); 255 | pitch_bend_LSB = ((byte)bend_int) & 0b01111111; //get lower 7 bits, ensure first bit to zero 256 | pitch_bend_MSB = ((byte)(bend_int >> 7)) & 0b01111111; //get upper 7 bits, set first bit to zero 257 | 258 | //compute the brightness 259 | #define BIT_SHIFT (10-7) //analog read is 10 bits. MIDI message is 7 bis 260 | int pot_val = map(analogRead(POT1_pin),0,1023,127,0); //reverse direction and fit within 0-127 261 | float max_bright_val = (float)pot_val; 262 | byte brightness_val = (byte)(brightness_frac * max_bright_val + 0.5); //0-127. The 0.5 is so that it rounds 263 | //brightness_val = 127 - brightness_val; //reverse the direction because the potentiometer is backwards 264 | 265 | //light LED if note is on 266 | digitalWrite(LED1_pin, !note_on); //if note is on (ie HIGH) turn on the LED (LOW for the Sparkfun shield) 267 | 268 | //read putshbutton 269 | boolean but_val = (digitalRead(pushButton1_pin) == LOW); //pressed makes it LOW...so set the value as TRUE 270 | boolean but2_val = (digitalRead(pushButton2_pin) == LOW); //pressed makes it LOW...so set the value as TRUE 271 | //boolean but3_val = (digitalRead(pushButton3_pin) == LOW); //pressed makes it LOW...so set the value as TRUE 272 | 273 | //print messages 274 | if (note_change) { 275 | 276 | digitalWrite(LED2_pin, LOW); //LOW is "on" for the sparkfun MIDI shield 277 | if (PRINT_TEXT) { 278 | Serial.print("ribbon value = "); 279 | Serial.print(ribbon_value); 280 | Serial.print(", R = "); 281 | Serial.print(ribbon_R_kOhm); 282 | Serial.print(" kOhm, Frac MAX = "); 283 | Serial.print( ribbon_span_frac ); 284 | 285 | Serial.print(", Note On = "); 286 | Serial.print(note_on); 287 | Serial.print(", Note Float = "); 288 | Serial.print(note_num_float); 289 | Serial.print(", Note Num = "); 290 | Serial.print(note_num); 291 | Serial.print(", MIDI Note = "); 292 | Serial.println(MIDI_note_num); 293 | } //else { 294 | if (note_on) { 295 | #if WRITE_MIDI 296 | //ensure that the bend range has been set 297 | if (flag_bend_range_is_for_ribbon==false) setBendRange(BEND_FOR_RIBBON); 298 | 299 | //turn on new note 300 | Serial_t.write((byte)NOTE_ON); 301 | Serial_t.write((byte)MIDI_note_num); 302 | Serial_t.write((byte)NOTE_ON_VEL); //velocity 303 | 304 | //send pitch bend info 305 | transmitPitchBend(); 306 | 307 | //send the brightness info 308 | transmitBrightness(brightness_val); 309 | 310 | //turn off the old note? 311 | if (prev_note_on == true) { 312 | //previous note was on...so assume legato...turn off old note now that new one is already started 313 | Serial_t.write((byte)NOTE_OFF); 314 | Serial_t.write((byte)prev_MIDI_note_num); 315 | Serial_t.write((byte)NOTE_OFF_VEL); 316 | } 317 | #endif 318 | 319 | } else if ((note_on == false) && (prev_note_on == true)) { 320 | //no other notes are active. turn note off 321 | #if WRITE_MIDI 322 | Serial_t.write((byte)NOTE_OFF); 323 | Serial_t.write((byte)prev_MIDI_note_num); 324 | Serial_t.write((byte)NOTE_OFF_VEL); 325 | #endif 326 | } 327 | //} 328 | digitalWrite(LED2_pin, HIGH); //HIGH is "on" for the sparkfun MIDI shield 329 | } else if (1) { 330 | //not changing notes...but we should still update the pitch bend info 331 | if (note_on == true) { 332 | if (bend_int != prev_bend_int) { //but only if it has changed 333 | #if WRITE_MIDI 334 | transmitPitchBend(); 335 | transmitBrightness(brightness_val); 336 | #endif 337 | } 338 | } 339 | } 340 | 341 | //save state 342 | prev_note_num = note_num; 343 | prev_closest_note_num = closest_note_num; 344 | prev_MIDI_note_num = MIDI_note_num; 345 | prev_note_on = note_on; 346 | prev_but_val[0] = but_val; 347 | prev_but_val[1] = but2_val; 348 | prev_bend_int = bend_int; 349 | prev_pitch_bend_MSB = pitch_bend_MSB; 350 | prev_pitch_bend_LSB = pitch_bend_LSB; 351 | prev_ribbon_val_float = ribbon_value_float; 352 | } 353 | 354 | int toggle_counter = 0; 355 | const int filt_reset_thresh = ribbon_max_val + max(1,(RIBBON_SPAN - ribbon_max_val)/6); 356 | void read_ribbon(void) { 357 | static int prev_raw_ribbon_value = 0; 358 | 359 | //read the ribbon value 360 | raw_ribbon_value = analogRead(ribbonPin); 361 | ribbon_value = raw_ribbon_value; 362 | ribbon_value_float = (float)ribbon_value; 363 | //if (drivePin_state == LOW) ribbon_value_float = RIBBON_SPAN - ribbon_value_float; 364 | 365 | // //invert the drive pin 366 | // toggle_counter++; 367 | // if (toggle_counter >= 1) { 368 | // toggle_counter = 0; 369 | // drivePin_state = !drivePin_state; 370 | // digitalWrite(drivePin,drivePin_state); 371 | // } 372 | 373 | //if the ribbon value is above some limit (ie, not being touched), don't apply the filters. 374 | //This is to prevent the internal filter states getting spun up onto these non-real values. 375 | //Hopefully, this'll improve the pitch transitions on short, sharp, stacato notes. 376 | if (ribbon_value < filt_reset_thresh) { 377 | //apply notch filtering 378 | if (DO_NOTCH_FILTERING) { 379 | //apply filtering 380 | ribbon_value_float = notch_filter.process(ribbon_value_float); //60 Hz 381 | //ribbon_value_float = notch_filter2.process(ribbon_value_float); //120 Hz 382 | } 383 | 384 | //apply low-pass filtering (for decimation) 385 | if (PROCESSING_DECIMATION > 1) { 386 | ribbon_value_float = lp_filter.process(ribbon_value_float); 387 | } 388 | 389 | //if previously the note was off, force the system to update now for max responsiveness 390 | if (prev_raw_ribbon_value >= filt_reset_thresh) { 391 | decimation_counter = PROCESSING_DECIMATION; 392 | } 393 | } else { 394 | //if note was previously on, now it's off, so force the systme to update now for max responsiveness 395 | if (prev_raw_ribbon_value <= ribbon_max_val) { 396 | //this is the note turning off. Force the system to update now 397 | decimation_counter = PROCESSING_DECIMATION; 398 | } 399 | } 400 | prev_raw_ribbon_value = raw_ribbon_value; 401 | 402 | //print some debugging info 403 | if (PRINT_RAW_RIBBON_VALUE) { 404 | if (flag_cannot_keep_up) { 405 | Serial.print(-ribbon_value); //enable this to get value at top and bottom of ribbon to calibrate 406 | } else { 407 | Serial.print(ribbon_value); //enable this to get value at top and bottom of ribbon to calibrate 408 | } 409 | Serial.print(", "); //enable this to get value at top and bottom of ribbon to calibrate 410 | Serial.print((int)ribbon_value_float); //enable this to get value at top and bottom of ribbon to calibrate 411 | //Serial.print(compare_value); //enable this to get value at top and bottom of ribbon to calibrate 412 | Serial.println(); 413 | flag_cannot_keep_up = false; 414 | } 415 | 416 | //save for next time 417 | prev_raw_ribbon_value = raw_ribbon_value; 418 | } 419 | void process_ribbon(void) { 420 | 421 | //process ribbon value 422 | foo_float = 1.0 / (((float)RIBBON_SPAN) / ribbon_value_float - 1.0); 423 | ribbon_frac = (R_pullup_A3_kOhm / R_ribbon_max_kOhm) * foo_float; 424 | ribbon_R_kOhm = R_pullup_A3_kOhm * foo_float; 425 | ribbon_span_frac = (ribbon_R_kOhm - R_ribbon_min_kOhm) / (R_ribbon_max_kOhm - R_ribbon_min_kOhm); 426 | ribbon_span_frac = max(0.0, ribbon_span_frac); 427 | note_num_float = ribbon_span_frac * (ribbon_span_half_steps_float + ribbon_span_extra_half_steps_float); 428 | note_num_float = note_num_float - ribbon_span_extra_half_steps_float/2.0; 429 | 430 | 431 | 432 | //apply a calibration (of sorts) to better linearize the response 433 | if (1) { 434 | float bottom_transition = 1.5; 435 | float first_transition = 8.0; 436 | float second_transition = -2.0; //-10 437 | float third_transition = 1.0; //-5 438 | 439 | if (PRINT_CAL_TEXT) { 440 | if ((note_num_float > -10.0) & (note_num_float < 38.0)) { 441 | Serial.print("Applying Cal: raw_ribbon_val = "); 442 | Serial.print(raw_ribbon_value); 443 | Serial.print(", note_num_float = "); 444 | Serial.print(note_num_float); 445 | } 446 | } 447 | 448 | //float cal_amount = 1.0; // Uno 449 | float cal_amount = 1.0; // micro 450 | if (note_num_float > bottom_transition) { 451 | if (note_num_float < first_transition) { 452 | note_num_float += (cal_amount*((note_num_float-bottom_transition)/(first_transition-bottom_transition))); 453 | } else if (note_num_float < (ribbon_span_half_steps_float+second_transition)) { 454 | note_num_float += cal_amount; 455 | } else if (note_num_float < (ribbon_span_half_steps_float+third_transition)) { 456 | float foo = note_num_float - (ribbon_span_half_steps_float+second_transition); //how many above the transition 457 | float full_segment = -second_transition + third_transition; //positive number 458 | note_num_float += cal_amount*(1.0 - foo/full_segment); 459 | } 460 | } 461 | 462 | if (PRINT_CAL_TEXT) { 463 | if ((note_num_float > -10.0) & (note_num_float < ribbon_span_half_steps_float+2.0)) { 464 | Serial.print(", final note_num_float = "); 465 | Serial.println(note_num_float); 466 | } 467 | } 468 | } 469 | note_num = ((int)(note_num_float + 0.5)); //round 470 | 471 | //look for special case...decide to change note or just bend a lot 472 | closest_note_num = note_num; 473 | if (0) { 474 | //add some hysteresis to prevent fluttering between two notes 475 | //if you keep you finger on the line between them 476 | if (abs(note_num - prev_note_num) == 1) { //is it a neighboring note? 477 | if (note_num > prev_note_num) { 478 | note_num = ((int)(note_num_float + 0.35)); //need extra to get up to next note 479 | } else if (note_num < prev_note_num) { 480 | note_num = ((int)(note_num_float + 0.65)); //next extra to get down to next note 481 | } 482 | } 483 | } else { 484 | //if it can bend the note, just let it bend the note 485 | if ( ((prev_note_on==true) && (abs(note_num_float - ((float)prev_note_num)) < (synth_bend_half_steps-0.5))) || //bend if we can 486 | ((prev_note_on==false) && (abs(note_num - prev_note_num) < min(2,synth_bend_half_steps))) ) { //if new note, only bend if we're off by one note 487 | //don't change note, step back to original note...the rest of the code will just bend more 488 | note_num = prev_note_num; 489 | } 490 | } 491 | 492 | //calc the desired brightness CC value (as a value of 0.0 to 1.0) 493 | brightness_frac = min(1.0,max(0.0,note_num_float / ribbon_span_half_steps_float)); 494 | } 495 | 496 | void setBendRange(int type) { 497 | //for Prophet 6. From user manual Page 71 "Received NRPN Messages" 498 | //Pitch Bend Range is parameter 31. Values are 0-24. 499 | byte CC_byte = 0b10110000+MIDI_CHANNEL; //Control change 500 | 501 | Serial_t.write(CC_byte); //control change 502 | Serial_t.write(0b01100011); //NRPN parameter number MSB CC 503 | Serial_t.write((byte)0); //Parameter number MSB 504 | 505 | Serial_t.write(CC_byte); //control change 506 | Serial_t.write(0b01100010); //NRPN parameter number LSB CC 507 | Serial_t.write((byte)31); //Parameter number LSB 508 | 509 | Serial_t.write(CC_byte); //control change 510 | Serial_t.write(0b00000110); //NRPN parameter value MSB CC 511 | Serial_t.write((byte)0); //Parameter value MSB 512 | 513 | Serial_t.write(CC_byte); //control change 514 | Serial_t.write(0b00100110); //NRPN parameter value LSB CC 515 | if (type == BEND_FOR_RIBBON) { 516 | Serial_t.write((byte)synth_bend_half_steps); //12 semitones 517 | flag_bend_range_is_for_ribbon = true; 518 | } else { 519 | Serial_t.write((byte)2); //assume default is 2 semitones 520 | flag_bend_range_is_for_ribbon = false; 521 | } 522 | } 523 | 524 | void transmitBrightness(byte value) { 525 | Serial_t.write((byte)MIDI_CC); 526 | Serial_t.write((byte)BRIGHTNESS_CC); 527 | Serial_t.write(0b01111111 & value); //be sure that first bit is zero 528 | } 529 | 530 | void transmitPitchBend(void) { 531 | //send pitch bend 532 | if (PRINT_BEND_TEXT) { 533 | //human readable 534 | Serial.print("Pitch Bend: "); 535 | Serial.print(", Note Num: "); 536 | Serial.print(note_num) ; 537 | Serial.print(", Frac Bent = "); 538 | Serial.print(partial_note_num_float); 539 | Serial.print(", Bend Int = "); 540 | Serial.print(bend_int); 541 | Serial.print(", Bend MIDI M/S = "); 542 | Serial.print(pitch_bend_MSB); 543 | Serial.print(" "); 544 | Serial.println(pitch_bend_LSB); 545 | } 546 | //else { 547 | //write MIDI 548 | Serial_t.write((byte)PITCH_BEND); 549 | Serial_t.write((byte)pitch_bend_LSB); 550 | Serial_t.write((byte)pitch_bend_MSB); 551 | //} 552 | flag_pitch_is_bent = true; 553 | counts_until_recentering = time_for_recentering_counts; 554 | } 555 | 556 | void recenterMidiPitchBend(void) { 557 | Serial_t.write((byte)PITCH_BEND); 558 | Serial_t.write((byte)0x00); //center,LSB 559 | Serial_t.write((byte)64); //center,MSB} 560 | flag_pitch_is_bent = false; 561 | }; 562 | 563 | #ifdef USE_ARDUINO 564 | ////Code from: http://letsmakerobots.com/node/28278 565 | void setupTimer(const int &match_value_counts) { 566 | noInterrupts(); // disable all interrupts 567 | //we're going to use timer1 568 | TCCR1A = 0; //enable normal operation (mode 0) 569 | TCCR1B = 0; 570 | TCNT1 = 0; //reset the counter 571 | 572 | // compare match register 573 | // OCR1A = 0x7a12; // compare match register...16MHz/prescaler/sample_rate_hz 574 | OCR1A = match_value_counts; 575 | TCCR1B |= (1 << WGM12); // CTC mode 576 | //TCCR1B |= (1 << CS10); // prescaler (CS10=1) 577 | //TCCR1B |= (1 << CS11); // prescaler (CS11=8) 578 | TCCR1B |= ((1 << CS11) | (1 << CS10)); //CS11+CS10 = 64 579 | //TCCR1B |= ((1 << CS12) | (1 << CS10)); //CS12+CS10 = 1024 580 | TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt 581 | interrupts(); // enable all interrupts 582 | } 583 | #endif 584 | 585 | -------------------------------------------------------------------------------- /RibbonController_CV/RibbonController_CV.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Created: Chip Audette, Apr 2016 3 | Purpose: Sense Reisitive Ribbon and Output MIDI/CV notes 4 | 5 | This code is aimed toward the Sequential Prophet 6 synthesizer. 6 | Specifically, it automatically changes the synth's bend range to +/- 1 octave. 7 | 8 | Physical Setup, Ribbon: 9 | Top of Soft Pot: (No Connection) 10 | Wiper of Soft Pot: A5 11 | Bottom of Soft Pot: Gnd 12 | 13 | Physical Setup, if using MIDI: 14 | Using old Sparkfun MIDI shield 15 | Using Ribbon connected to A3 (wiper) and Gnd 16 | 17 | Physical Setup, if using DAC for CV: DAC = MCP4922 12-bit 2-channel DAC 18 | MCP4922 Pin3 = (NOT)CS = Arduino Micro Pin 10 (SS) 19 | MCP4922 Pin4 = SCK = Arduino Micro Pin SCK (SCK) 20 | MCP4922 Pin5 = SDI = Arduino Micro Pin MO (MOSI) 21 | MCP4922 Pin8 = (NOT)LDAC = LOW 22 | MCP4922 Pin9 = (NOT)SHDN = HIGH 23 | MCP4922 Pin11 = Pin12 = Vref = HIGH 24 | 25 | MCP4922 Pin14 = Vout A...Pitch CV, Volt/Oct 26 | MCP4922 Pin10 = Vout B...Trigger, LOW means Note On 27 | 28 | */ 29 | 30 | #include 31 | #include //http://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ 32 | 33 | 34 | //debugging info 35 | #define PRINT_TEXT (0) 36 | #define PRINT_BEND_TEXT (0) 37 | #define PRINT_CAL_TEXT (0) 38 | #define PRINT_RAW_RIBBON_VALUE (0) 39 | #define PRINT_CV_TEXT (0) 40 | 41 | //choose output 42 | #define WRITE_MIDI (0) 43 | #define WRITE_DAC (1) 44 | 45 | #define USE_ARDUINO 1 46 | //#define USE_TEENSY 1 47 | #define Serial_t (Serial1) //for MIDI for Arduino Micro or for TEENSY 48 | //#define Serial_t (Serial) //for MIDI for Arduino UNO 49 | 50 | 51 | // define the various pins used on the Arduino/Teensy hardware 52 | const int ribbonPin = A5; 53 | const int ribbonVccPin = A4; 54 | const int ribbonGnd = A1; 55 | //int drivePin = 4; //Digital pin #4 56 | //int drivePin_state = HIGH; 57 | const int pushButton1_pin = 2; // Push button, Digital Pin 58 | const int pushButton2_pin = 3; // Push button, Digital Pin 59 | const int pushButton3_pin = 4; // Push button, Digital Pin 60 | const int LED0_pin = 13; //on Arduino Board 61 | const int LED1_pin = 6; //on Sparkfun MIDI shield 62 | const int LED2_pin = 7; //on Sparkfun MIDI shield 63 | const int POT1_pin = A0; 64 | //const int comparePin = A4; 65 | //const int comparePinGnd = A5; 66 | const int slaveSelectPin = 10; //for commanding the DAC 67 | const int trig_pin = 5; //for Trigger High/Low for CV 68 | 69 | //sampling 70 | volatile boolean flag_update_system = true; 71 | volatile boolean flag_cannot_keep_up = false; 72 | #define SAMPLE_RATE_HZ (600.0) 73 | 74 | //filtering 75 | #define DO_NOTCH_FILTERING (1) 76 | #define NOTCH_FREQ_HZ (60.0) 77 | #define NOTCH_Q (0.707) //higher is sharper. 0.707 is crtically damped (butterworth) 78 | #define FILTER_PEAK_GAIN_DB (0.0) //doesn't matter for Lowpass, highpass, notch, or bandpass 79 | Biquad notch_filter(bq_type_notch,NOTCH_FREQ_HZ / SAMPLE_RATE_HZ, NOTCH_Q, FILTER_PEAK_GAIN_DB); //one for each channel because the object maintains the filter states 80 | Biquad notch_filter2(bq_type_notch,(2.0*NOTCH_FREQ_HZ) / SAMPLE_RATE_HZ, NOTCH_Q, FILTER_PEAK_GAIN_DB); //one for each channel because the object maintains the filter states 81 | 82 | //decimation 83 | #define PROCESSING_DECIMATION (12) 84 | #define PROCESSING_SAMPLE_RATE_HZ (SAMPLE_RATE_HZ/PROCESSING_DECIMATION) 85 | #define LP_Q (0.707) //higher is sharper. 0.707 is crtically damped (butterworth) 86 | Biquad lp_filter(bq_type_lowpass,PROCESSING_SAMPLE_RATE_HZ/SAMPLE_RATE_HZ, LP_Q, FILTER_PEAK_GAIN_DB); //one for each channel because the object maintains the filter states 87 | volatile int decimation_counter = 0; 88 | 89 | //Define ribbon parameters 90 | #define RIBBON_BY_VCC 1 91 | #define RIBBON_BY_WIPER 2 92 | const int ribbon_mode = RIBBON_BY_WIPER; 93 | #define RIBBON_SPAN (1023) 94 | //int ribbon_max_val = 339,ribbon_min_val = 13; //for my Arduino UNO 95 | //int ribbon_max_val = 520,ribbon_min_val = 22; //for my Arduino UNO, External 3.3V ref 96 | int ribbon_max_val = 335,ribbon_min_val = 15; //for my Arduino Micro 97 | //int ribbon_max_val = 330,ribbon_min_val = 10; //for my Arduino Micro, 5V pullup 98 | //int ribbon_max_val = 548,ribbon_min_val = 0; //for my Arduino Micro, 15K external pullup 99 | //int ribbon_max_val = 344,ribbon_min_val = 0; //for my Teensy 100 | //int ribbon_max_val = 592,ribbon_min_val = 0; //for my Teensy with double pullups 101 | //int ribbon_max_val = 585,ribbon_min_val = 0; //for my Teensy 15k pullup 102 | //int ribbon_max_val = 545, ribbon_min_val = 0; //Teensy plus opamp w/ 33K pullup 103 | //int ribbon_max_val = 842, ribbon_min_val = 0; //Teensy plus opamp w/ 15K pullup 104 | float R_pullup_A3_kOhm = 0.0; 105 | float R_ribbon_min_kOhm = 0.0; 106 | const float R_ribbon_max_kOhm = 17.45; 107 | const float ribbon_span_half_steps_float = 36; 108 | const float ribbon_span_extra_half_steps_float = 0.5; 109 | const int max_allowed_jump_steps = 5; 110 | 111 | boolean flag_bend_range_is_for_ribbon = false; 112 | #define BEND_DEFAULT 0 113 | #define BEND_FOR_RIBBON 1 114 | 115 | boolean flag_pitch_is_bent = false; 116 | float time_for_recentering_sec = 2.5; 117 | long int time_for_recentering_counts = 0; 118 | long int counts_until_recentering = 0; 119 | 120 | #ifdef USE_TEENSY 121 | IntervalTimer sampleTimer; 122 | #endif 123 | 124 | //Define some MIDI parameters 125 | #define MIDI_note_bottom (43-7) //Note C1 126 | #define MIDI_CHANNEL (0x01) 127 | #define MIDI_NOTE_ON (0x90+MIDI_CHANNEL) 128 | #define MIDI_NOTE_OFF (0x80+MIDI_CHANNEL) 129 | #define MIDI_NOTE_ON_VEL (127) //0x7F 130 | #define MIDI_NOTE_OFF_VEL (0) //0x7F 131 | #define MIDI_PITCH_BEND (0xE0+MIDI_CHANNEL) 132 | #define MIDI_CC (0b10110000 + MIDI_CHANNEL) 133 | #define MIDI_BRIGHTNESS_CC ((byte)74) 134 | #define synth_bend_half_steps (12) //what is the synth's pitch wheel set to? 135 | 136 | //Define some CV parameters 137 | #define CV_note_bottom (12) //Note C1 instead of C0 138 | #define MAX_DAC_COUNTS (4095) 139 | #define VOLT_PER_OCTAVE (1.0) 140 | #define MAX_DAC_VOLT (5.180) //laptop is 5.04V, USB wall-wart is 5.182V 141 | const float DAC_counts_per_half_step = (((float)MAX_DAC_COUNTS/MAX_DAC_VOLT)*VOLT_PER_OCTAVE / 12.0); 142 | #define TRIG_NOTE_ON (0) // LOW 143 | #define TRIG_NOTE_OFF (MAX_DAC_COUNTS) //HIGH 144 | 145 | //interrupt service routine...format of function call changes depending upon platform 146 | #ifdef USE_TEENSY 147 | void serviceTimer(void) //Teensy convention 148 | #else 149 | ISR(TIMER1_COMPA_vect) //arduino convention 150 | #endif 151 | { 152 | if (flag_update_system) { 153 | flag_cannot_keep_up = true; 154 | } 155 | 156 | read_ribbon(); 157 | decimation_counter++; 158 | if (decimation_counter >= PROCESSING_DECIMATION) { 159 | decimation_counter = 0; 160 | flag_update_system = true; 161 | } 162 | } 163 | 164 | 165 | //setup routine 166 | void setup() { 167 | //setup the ribbon pins and the analogRead reference 168 | //analogReference(INTERNAL); 169 | //analogReference(EXTERNAL); 170 | pinMode(ribbonVccPin, INPUT); //set to high impedance 171 | pinMode(ribbonPin, INPUT_PULLUP); 172 | pinMode(ribbonGnd,OUTPUT); digitalWrite(ribbonGnd,LOW); //use as ground 173 | pinMode(POT1_pin,INPUT); 174 | 175 | //initialize the DAC 176 | pinMode(trig_pin,OUTPUT); digitalWrite(trig_pin,TRIG_NOTE_OFF); 177 | pinMode(slaveSelectPin, OUTPUT); 178 | digitalWrite(slaveSelectPin,HIGH); //set DAC ready to accept commands 179 | SPI.begin(); 180 | SPI.setDataMode(SPI_MODE0); //MCP4922 can be either Mode 0 or Mode 3 (supposedly) 181 | SPI.setBitOrder(MSBFIRST); 182 | 183 | //setup the LEDs 184 | pinMode(LED0_pin, OUTPUT); digitalWrite(LED0_pin, LOW); //LOW is off for he Arduino LED 185 | pinMode(LED1_pin, OUTPUT); digitalWrite(LED1_pin, HIGH); //HIGH is off for he Sparkfun shield 186 | pinMode(LED2_pin, OUTPUT); digitalWrite(LED2_pin, HIGH); //HIGH is off for he Sparkfun shield 187 | 188 | // initialize the MIDI communications: 189 | Serial.begin(115200*2); //for Arduino Micro only 190 | if (WRITE_MIDI) Serial_t.begin(31250); 191 | delay(500); 192 | 193 | //set scaling for ribbon 194 | R_pullup_A3_kOhm = R_ribbon_max_kOhm * ( ((float)RIBBON_SPAN) / ((float)ribbon_max_val) - 1.0 ); 195 | R_ribbon_min_kOhm = ((float)ribbon_min_val) / ((float)ribbon_max_val) * R_ribbon_max_kOhm; 196 | 197 | //setup the timer 198 | float dt_sec_sample = 1.0 / SAMPLE_RATE_HZ;//arduino can run up to 1200Hz as of Mar 5, 2016 199 | 200 | #ifdef USE_TEENSY 201 | int timer_loop_usec = (int)(dt_sec_sample*1000000.0); //microseconds 202 | sampleTimer.begin(serviceTimer, timer_loop_usec); 203 | #else 204 | int timer_clock_divider = 64; //see which divider I'm using in setupTimer() 205 | int timer_value = (int)( (16000000L) / ((long)timer_clock_divider) / ((long)SAMPLE_RATE_HZ) ); 206 | setupTimer(timer_value); 207 | #endif 208 | 209 | //decide how long to wait until pitch bend is recentered 210 | float dt_sec_processing = 1.0 / PROCESSING_SAMPLE_RATE_HZ; 211 | time_for_recentering_counts = (long int)(time_for_recentering_sec / dt_sec_processing + 0.5); //time steps to count off 212 | } 213 | 214 | 215 | 216 | void loop() { 217 | static int idle_counter = 0; 218 | 219 | if (flag_update_system) { 220 | flag_update_system = false; 221 | updateTheSystem(); 222 | 223 | //check to see if we should turn off the bend commands 224 | if (flag_pitch_is_bent) { 225 | counts_until_recentering--; //decrement counter 226 | if (counts_until_recentering <= 0) { 227 | #if WRITE_MIDI 228 | recenterMidiPitchBend(); //remove any bend commands still active 229 | setBendRange(BEND_DEFAULT); //turn the bend range back to the default 230 | transmitBrightness(0); 231 | #endif 232 | } 233 | } 234 | 235 | } else { 236 | idle_counter++; //kill time without being stuck in an empty loop 237 | } 238 | } 239 | 240 | //static int prev_FS2_val; 241 | int prev_but_val[] = {HIGH, HIGH, HIGH}; 242 | int raw_ribbon_value = 0, ribbon_value = 0; 243 | float ribbon_value_float = 1023.0, prev_ribbon_val_float = 1023.0; 244 | int closest_note_num = 0, prev_closest_note_num = 0; 245 | float note_num_float = 0.0, prev_note_num_float = 0.0; 246 | int note_num = 0, prev_note_num = 0; 247 | int MIDI_note_num = 0, prev_MIDI_note_num = 0; 248 | boolean is_note_on = false, prev_is_note_on = false; 249 | boolean note_change; 250 | 251 | float foo_float, ribbon_frac, ribbon_R_kOhm, ribbon_span_frac; 252 | float brightness_frac; 253 | 254 | float partial_note_num_float; 255 | float partial_bend_float ; 256 | #define MIDI_MAX_BEND (8192) //can go up and down by this amount 257 | unsigned int bend_int = 0, prev_bend_int = 0; 258 | byte pitch_bend_LSB, prev_pitch_bend_MSB = 63; 259 | byte pitch_bend_MSB, prev_pitch_bend_LSB = 63; 260 | 261 | void updateTheSystem() { 262 | 263 | //read input 264 | //read_ribbon(); //this is now done in the interrupt service routine! 265 | 266 | //process input 267 | process_ribbon(); 268 | 269 | //is the note on? 270 | is_note_on = false; 271 | if (ribbon_value < (RIBBON_SPAN-10)) { 272 | if ((note_num >= 0) && (note_num <= ribbon_span_half_steps_float)) is_note_on = true; 273 | if (abs(note_num - prev_note_num) > max(max_allowed_jump_steps,synth_bend_half_steps)) is_note_on = false; 274 | } else { 275 | note_num = prev_note_num; //just in case there was a bad value 276 | } 277 | 278 | //calc MIDI note number 279 | note_num = constrain(note_num, 0, ribbon_span_half_steps_float); 280 | int MIDI_note_num = note_num + MIDI_note_bottom; 281 | boolean note_change = (is_note_on != prev_is_note_on) | (note_num != prev_note_num); 282 | 283 | //compute the amount of bend 284 | partial_note_num_float = note_num_float - ((float)note_num); 285 | partial_bend_float = partial_note_num_float / ((float)synth_bend_half_steps); 286 | partial_bend_float = constrain(partial_bend_float, -1.0, 1.0); 287 | #define MIDI_MAX_BEND (8192) //can go up and down by this amount 288 | bend_int = (unsigned int)(MIDI_MAX_BEND + (partial_bend_float * ((float)MIDI_MAX_BEND))); 289 | pitch_bend_LSB = ((byte)bend_int) & 0b01111111; //get lower 7 bits, ensure first bit to zero 290 | pitch_bend_MSB = ((byte)(bend_int >> 7)) & 0b01111111; //get upper 7 bits, set first bit to zero 291 | 292 | //compute the brightness 293 | #define BIT_SHIFT (10-7) //analog read is 10 bits. MIDI message is 7 bis 294 | int pot_val = map(analogRead(POT1_pin),0,1023,127,0); //reverse direction and fit within 0-127 295 | float max_bright_val = (float)pot_val; 296 | byte brightness_val = (byte)(brightness_frac * max_bright_val + 0.5); //0-127. The 0.5 is so that it rounds 297 | //brightness_val = 127 - brightness_val; //reverse the direction because the potentiometer is backwards 298 | 299 | //light LEDs if note is on 300 | digitalWrite(LED0_pin, is_note_on); 301 | digitalWrite(LED1_pin, !is_note_on); //if note is on (ie HIGH) turn on the LED (LOW for the Sparkfun shield) 302 | 303 | 304 | //read putshbutton 305 | boolean but_val = (digitalRead(pushButton1_pin) == LOW); //pressed makes it LOW...so set the value as TRUE 306 | boolean but2_val = (digitalRead(pushButton2_pin) == LOW); //pressed makes it LOW...so set the value as TRUE 307 | //boolean but3_val = (digitalRead(pushButton3_pin) == LOW); //pressed makes it LOW...so set the value as TRUE 308 | 309 | //write commands 310 | if (note_change) { 311 | 312 | digitalWrite(LED2_pin, LOW); //LOW is "on" for the sparkfun MIDI shield 313 | if (PRINT_TEXT) { 314 | Serial.print("ribbon value = "); 315 | Serial.print(ribbon_value); 316 | Serial.print(", R = "); 317 | Serial.print(ribbon_R_kOhm); 318 | Serial.print(" kOhm, Frac MAX = "); 319 | Serial.print( ribbon_span_frac ); 320 | 321 | Serial.print(", Note On = "); 322 | Serial.print(is_note_on); 323 | Serial.print(", Note Float = "); 324 | Serial.print(note_num_float); 325 | Serial.print(", Note Num = "); 326 | Serial.print(note_num); 327 | Serial.print(", MIDI Note = "); 328 | Serial.println(MIDI_note_num); 329 | } //else { 330 | if (is_note_on) { 331 | #if WRITE_MIDI 332 | //ensure that the bend range has been set 333 | if (flag_bend_range_is_for_ribbon==false) setBendRange(BEND_FOR_RIBBON); 334 | 335 | //turn on new note 336 | Serial_t.write((byte)MIDI_NOTE_ON); 337 | Serial_t.write((byte)MIDI_note_num); 338 | Serial_t.write((byte)MIDI_NOTE_ON_VEL); //velocity 339 | 340 | //send pitch bend info 341 | transmitPitchBend(); 342 | 343 | //send the brightness info 344 | transmitBrightness(brightness_val); 345 | 346 | //turn off the old note? 347 | if (prev_is_note_on == true) { 348 | //previous note was on...so assume legato...turn off old note now that new one is already started 349 | Serial_t.write((byte)MIDI_NOTE_OFF); 350 | Serial_t.write((byte)prev_MIDI_note_num); 351 | Serial_t.write((byte)MIDI_NOTE_OFF_VEL); 352 | } 353 | #endif 354 | 355 | #if WRITE_DAC 356 | updateCV(note_num_float,is_note_on); 357 | #endif 358 | 359 | } else if ((is_note_on == false) && (prev_is_note_on == true)) { 360 | //no other notes are active. turn note off 361 | #if WRITE_MIDI 362 | Serial_t.write((byte)MIDI_NOTE_OFF); 363 | Serial_t.write((byte)prev_MIDI_note_num); 364 | Serial_t.write((byte)MIDI_NOTE_OFF_VEL); 365 | #endif 366 | 367 | #if WRITE_DAC 368 | note_num_float = prev_note_num_float; 369 | updateCV(note_num_float,is_note_on); 370 | #endif 371 | } 372 | //} 373 | digitalWrite(LED2_pin, HIGH); //HIGH is "on" for the sparkfun MIDI shield 374 | } else if (1) { 375 | //not changing notes...but we should still update the pitch bend info 376 | if (is_note_on == true) { 377 | if (bend_int != prev_bend_int) { //but only if it has changed 378 | #if WRITE_MIDI 379 | transmitPitchBend(); 380 | transmitBrightness(brightness_val); 381 | #endif 382 | } 383 | #if WRITE_DAC 384 | updateCV(note_num_float,is_note_on); 385 | #endif 386 | } 387 | } 388 | 389 | //save state 390 | prev_note_num = note_num; 391 | prev_note_num_float = note_num_float; 392 | prev_closest_note_num = closest_note_num; 393 | prev_MIDI_note_num = MIDI_note_num; 394 | prev_is_note_on = is_note_on; 395 | prev_but_val[0] = but_val; 396 | prev_but_val[1] = but2_val; 397 | prev_bend_int = bend_int; 398 | prev_pitch_bend_MSB = pitch_bend_MSB; 399 | prev_pitch_bend_LSB = pitch_bend_LSB; 400 | prev_ribbon_val_float = ribbon_value_float; 401 | } 402 | 403 | int toggle_counter = 0; 404 | const int filt_reset_thresh = ribbon_max_val + max(1,(RIBBON_SPAN - ribbon_max_val)/6); 405 | void read_ribbon(void) { 406 | static int prev_raw_ribbon_value = 0; 407 | 408 | //read the ribbon value 409 | raw_ribbon_value = analogRead(ribbonPin); 410 | ribbon_value = raw_ribbon_value; 411 | ribbon_value_float = (float)ribbon_value; 412 | //if (drivePin_state == LOW) ribbon_value_float = RIBBON_SPAN - ribbon_value_float; 413 | 414 | // //invert the drive pin 415 | // toggle_counter++; 416 | // if (toggle_counter >= 1) { 417 | // toggle_counter = 0; 418 | // drivePin_state = !drivePin_state; 419 | // digitalWrite(drivePin,drivePin_state); 420 | // } 421 | 422 | //if the ribbon value is above some limit (ie, not being touched), don't apply the filters. 423 | //This is to prevent the internal filter states getting spun up onto these non-real values. 424 | //Hopefully, this'll improve the pitch transitions on short, sharp, stacato notes. 425 | if (ribbon_value < filt_reset_thresh) { 426 | //apply notch filtering 427 | if (DO_NOTCH_FILTERING) { 428 | //apply filtering 429 | ribbon_value_float = notch_filter.process(ribbon_value_float); //60 Hz 430 | //ribbon_value_float = notch_filter2.process(ribbon_value_float); //120 Hz 431 | } 432 | 433 | //apply low-pass filtering (for decimation) 434 | if (PROCESSING_DECIMATION > 1) { 435 | ribbon_value_float = lp_filter.process(ribbon_value_float); 436 | } 437 | 438 | //if previously the note was off, force the system to update now for max responsiveness 439 | if (prev_raw_ribbon_value >= filt_reset_thresh) { 440 | decimation_counter = PROCESSING_DECIMATION; 441 | } 442 | } else { 443 | //if note was previously on, now it's off, so force the systme to update now for max responsiveness 444 | if (prev_raw_ribbon_value <= ribbon_max_val) { 445 | //this is the note turning off. Force the system to update now 446 | decimation_counter = PROCESSING_DECIMATION; 447 | } 448 | } 449 | prev_raw_ribbon_value = raw_ribbon_value; 450 | 451 | //print some debugging info 452 | if (PRINT_RAW_RIBBON_VALUE) { 453 | if (flag_cannot_keep_up) { 454 | Serial.print(-ribbon_value); //enable this to get value at top and bottom of ribbon to calibrate 455 | } else { 456 | Serial.print(ribbon_value); //enable this to get value at top and bottom of ribbon to calibrate 457 | } 458 | Serial.print(", "); //enable this to get value at top and bottom of ribbon to calibrate 459 | Serial.print((int)ribbon_value_float); //enable this to get value at top and bottom of ribbon to calibrate 460 | //Serial.print(compare_value); //enable this to get value at top and bottom of ribbon to calibrate 461 | Serial.println(); 462 | flag_cannot_keep_up = false; 463 | } 464 | 465 | //save for next time 466 | prev_raw_ribbon_value = raw_ribbon_value; 467 | } 468 | void process_ribbon(void) { 469 | 470 | //process ribbon value 471 | foo_float = 1.0 / (((float)RIBBON_SPAN) / ribbon_value_float - 1.0); 472 | ribbon_frac = (R_pullup_A3_kOhm / R_ribbon_max_kOhm) * foo_float; 473 | ribbon_R_kOhm = R_pullup_A3_kOhm * foo_float; 474 | ribbon_span_frac = (ribbon_R_kOhm - R_ribbon_min_kOhm) / (R_ribbon_max_kOhm - R_ribbon_min_kOhm); 475 | ribbon_span_frac = max(0.0, ribbon_span_frac); 476 | note_num_float = ribbon_span_frac * (ribbon_span_half_steps_float + ribbon_span_extra_half_steps_float); 477 | note_num_float = note_num_float - ribbon_span_extra_half_steps_float/2.0; 478 | 479 | 480 | 481 | //apply a calibration (of sorts) to better linearize the response 482 | if (1) { 483 | float bottom_transition = 1.5; 484 | float first_transition = 8.0; 485 | float second_transition = -2.0; //-10 486 | float third_transition = 1.0; //-5 487 | 488 | if (PRINT_CAL_TEXT) { 489 | if ((note_num_float > -10.0) & (note_num_float < 38.0)) { 490 | Serial.print("Applying Cal: raw_ribbon_val = "); 491 | Serial.print(raw_ribbon_value); 492 | Serial.print(", note_num_float = "); 493 | Serial.print(note_num_float); 494 | } 495 | } 496 | 497 | //float cal_amount = 1.0; // Uno 498 | float cal_amount = 1.0; // micro 499 | if (note_num_float > bottom_transition) { 500 | if (note_num_float < first_transition) { 501 | note_num_float += (cal_amount*((note_num_float-bottom_transition)/(first_transition-bottom_transition))); 502 | } else if (note_num_float < (ribbon_span_half_steps_float+second_transition)) { 503 | note_num_float += cal_amount; 504 | } else if (note_num_float < (ribbon_span_half_steps_float+third_transition)) { 505 | float foo = note_num_float - (ribbon_span_half_steps_float+second_transition); //how many above the transition 506 | float full_segment = -second_transition + third_transition; //positive number 507 | note_num_float += cal_amount*(1.0 - foo/full_segment); 508 | } 509 | } 510 | 511 | if (PRINT_CAL_TEXT) { 512 | if ((note_num_float > -10.0) & (note_num_float < ribbon_span_half_steps_float+2.0)) { 513 | Serial.print(", final note_num_float = "); 514 | Serial.println(note_num_float); 515 | } 516 | } 517 | } 518 | note_num = ((int)(note_num_float + 0.5)); //round 519 | 520 | //look for special case...decide to change note or just bend a lot 521 | closest_note_num = note_num; 522 | if (0) { 523 | //add some hysteresis to prevent fluttering between two notes 524 | //if you keep you finger on the line between them 525 | if (abs(note_num - prev_note_num) == 1) { //is it a neighboring note? 526 | if (note_num > prev_note_num) { 527 | note_num = ((int)(note_num_float + 0.35)); //need extra to get up to next note 528 | } else if (note_num < prev_note_num) { 529 | note_num = ((int)(note_num_float + 0.65)); //next extra to get down to next note 530 | } 531 | } 532 | } else { 533 | //if it can bend the note, just let it bend the note 534 | if ( ((prev_is_note_on==true) && (abs(note_num_float - ((float)prev_note_num)) < (synth_bend_half_steps-0.5))) || //bend if we can 535 | ((prev_is_note_on==false) && (abs(note_num - prev_note_num) < min(2,synth_bend_half_steps))) ) { //if new note, only bend if we're off by one note 536 | //don't change note, step back to original note...the rest of the code will just bend more 537 | note_num = prev_note_num; 538 | } 539 | } 540 | 541 | //calc the desired brightness CC value (as a value of 0.0 to 1.0) 542 | brightness_frac = min(1.0,max(0.0,note_num_float / ribbon_span_half_steps_float)); 543 | } 544 | 545 | void setBendRange(int type) { 546 | //for Prophet 6. From user manual Page 71 "Received NRPN Messages" 547 | //Pitch Bend Range is parameter 31. Values are 0-24. 548 | byte CC_byte = 0b10110000+MIDI_CHANNEL; //Control change 549 | 550 | Serial_t.write(CC_byte); //control change 551 | Serial_t.write(0b01100011); //NRPN parameter number MSB CC 552 | Serial_t.write((byte)0); //Parameter number MSB 553 | 554 | Serial_t.write(CC_byte); //control change 555 | Serial_t.write(0b01100010); //NRPN parameter number LSB CC 556 | Serial_t.write((byte)31); //Parameter number LSB 557 | 558 | Serial_t.write(CC_byte); //control change 559 | Serial_t.write(0b00000110); //NRPN parameter value MSB CC 560 | Serial_t.write((byte)0); //Parameter value MSB 561 | 562 | Serial_t.write(CC_byte); //control change 563 | Serial_t.write(0b00100110); //NRPN parameter value LSB CC 564 | if (type == BEND_FOR_RIBBON) { 565 | Serial_t.write((byte)synth_bend_half_steps); //12 semitones 566 | flag_bend_range_is_for_ribbon = true; 567 | } else { 568 | Serial_t.write((byte)2); //assume default is 2 semitones 569 | flag_bend_range_is_for_ribbon = false; 570 | } 571 | } 572 | 573 | void transmitBrightness(byte value) { 574 | Serial_t.write((byte)MIDI_CC); 575 | Serial_t.write((byte)MIDI_BRIGHTNESS_CC); 576 | Serial_t.write(0b01111111 & value); //be sure that first bit is zero 577 | } 578 | 579 | void transmitPitchBend(void) { 580 | //send pitch bend 581 | if (PRINT_BEND_TEXT) { 582 | //human readable 583 | Serial.print("Pitch Bend: "); 584 | Serial.print(", Note Num: "); 585 | Serial.print(note_num) ; 586 | Serial.print(", Frac Bent = "); 587 | Serial.print(partial_note_num_float); 588 | Serial.print(", Bend Int = "); 589 | Serial.print(bend_int); 590 | Serial.print(", Bend MIDI M/S = "); 591 | Serial.print(pitch_bend_MSB); 592 | Serial.print(" "); 593 | Serial.println(pitch_bend_LSB); 594 | } 595 | //else { 596 | //write MIDI 597 | Serial_t.write((byte)MIDI_PITCH_BEND); 598 | Serial_t.write((byte)pitch_bend_LSB); 599 | Serial_t.write((byte)pitch_bend_MSB); 600 | //} 601 | flag_pitch_is_bent = true; 602 | counts_until_recentering = time_for_recentering_counts; 603 | } 604 | 605 | void recenterMidiPitchBend(void) { 606 | Serial_t.write((byte)MIDI_PITCH_BEND); 607 | Serial_t.write((byte)0x00); //center,LSB 608 | Serial_t.write((byte)64); //center,MSB} 609 | flag_pitch_is_bent = false; 610 | }; 611 | 612 | #ifdef USE_ARDUINO 613 | ////Code from: http://letsmakerobots.com/node/28278 614 | void setupTimer(const int &match_value_counts) { 615 | noInterrupts(); // disable all interrupts 616 | //we're going to use timer1 617 | TCCR1A = 0; //enable normal operation (mode 0) 618 | TCCR1B = 0; 619 | TCNT1 = 0; //reset the counter 620 | 621 | // compare match register 622 | // OCR1A = 0x7a12; // compare match register...16MHz/prescaler/sample_rate_hz 623 | OCR1A = match_value_counts; 624 | TCCR1B |= (1 << WGM12); // CTC mode 625 | //TCCR1B |= (1 << CS10); // prescaler (CS10=1) 626 | //TCCR1B |= (1 << CS11); // prescaler (CS11=8) 627 | TCCR1B |= ((1 << CS11) | (1 << CS10)); //CS11+CS10 = 64 628 | //TCCR1B |= ((1 << CS12) | (1 << CS10)); //CS12+CS10 = 1024 629 | TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt 630 | interrupts(); // enable all interrupts 631 | } 632 | #endif 633 | 634 | int updateCV(const float ¬e_num_float,const boolean &is_note_on){ 635 | int trig_val_counts = TRIG_NOTE_ON; 636 | if (!is_note_on) trig_val_counts = TRIG_NOTE_OFF; 637 | 638 | int pitch_val_counts = (int)((note_num_float+CV_note_bottom) * DAC_counts_per_half_step); 639 | pitch_val_counts = constrain(pitch_val_counts,0,MAX_DAC_COUNTS); 640 | 641 | const float KBD_track = 0.6; 642 | int filter_val_counts = (int)(note_num_float * DAC_counts_per_half_step * KBD_track); 643 | filter_val_counts = constrain(filter_val_counts,0,MAX_DAC_COUNTS); 644 | 645 | if (PRINT_CV_TEXT) { 646 | Serial.print("DAC: is_note_on = "); Serial.print(is_note_on); 647 | Serial.print(", pitch = "); Serial.print(pitch_val_counts); 648 | Serial.print(", trig = "); Serial.print(trig_val_counts); 649 | Serial.println(); 650 | } 651 | if (false) { 652 | //write trigger on/off from the DAC chan #2 653 | MCP4922_write(slaveSelectPin,pitch_val_counts,trig_val_counts); 654 | } else { 655 | //write trigger from digital pin and write filter command from the DAC chan #2 656 | MCP4922_write(slaveSelectPin,pitch_val_counts,filter_val_counts); 657 | digitalWrite(trig_pin,constrain(trig_val_counts,LOW,HIGH)); 658 | } 659 | 660 | 661 | return pitch_val_counts; 662 | } 663 | 664 | void MCP4922_write(const int &slavePin,const int &value1,const int &value2) { 665 | int value = 0; 666 | byte configByte = 0; 667 | byte data=0; 668 | int channel=0; 669 | 670 | for (channel = 0; channel < 2; channel++) { 671 | digitalWrite(slavePin,LOW); //set DAC ready to accept commands 672 | if (channel == 0) { 673 | configByte = B01110000; //channel 0, Vref buffered, Gain of 1x, Active Mode 674 | value = value1; 675 | } else { 676 | configByte = B11110000; //channel 1, Vref buffered, Gain of 1x, Active Mode 677 | value = value2; 678 | } 679 | 680 | //write first byte 681 | data = highByte(value); 682 | data = B00001111 & data; //clear out the 4 command bits 683 | data = configByte | data; //set the first four command bits 684 | SPI.transfer(data); 685 | 686 | //write second byte 687 | data = lowByte(value); 688 | SPI.transfer(data); 689 | 690 | //close the transfer 691 | digitalWrite(slavePin,HIGH); //set DAC ready to accept commands 692 | } 693 | } 694 | --------------------------------------------------------------------------------