Commits

Gordon McGregor  committed f63734d

added SysEx processing, still need to implement after dispatch

  • Participants
  • Parent commits 25d192f

Comments (0)

Files changed (6)

File bank_config.pde

+
+
+/*
+ Bank structure will be stored in EEPROM memory and fetched on reset.
+ Individual switches can be reconfigured using SysEx messages.
+ The bank structure can be stored and retrived using SysEx messages.
+ 
+ SysEx Messages
+ 
+ 0xF0 0x78 0xCMD 0xBANK 0xSWITCH 0xTOGGLE 0xS00 0xS01 0xS02 0xS10 0xS11 0xS12 0xF7
+ 
+ 0xCMD 
+ 0 reconfigure 
+ 1 reset to default
+ 2 store config 0xCMD 0xCONFIG_BANK
+ 3 store config 0xCMD 0xCONFIG_BANK
+ 
+ Reconfigure switch command : 
+ Bank # [0 or 1]       1 byte
+ Switch # [0,1,2,3]    1 byte
+ Toggle state [0,1]    1 byte
+ 
+ state0_message        3 bytes
+ state1_message        3 bytes
+ 
+ 
+ Manage Configuration command:
+ Reset current onfiguration to zero
+ Store current configuration in slot [0, 1, 2, 3, 4]
+ Retrive configuration [0, 1, 2, 3, 4]
+ 
+ EEPROM is 512 bytes
+ Bank structure is 96 bytes, so there is room for 5 configurations to be pre-stored on the switch
+ 
+ Maybe make some sort of 'special switch combo' to select from previously stored banks ?
+ Toggle switch 4,4 times quickly (set up a time window - 2 seconds?) to put it into config select mode, then hit a switch for the bank you want 0-4
+ Strobe LEDs to indicate successful config.  Drive logic out 2,3,4,5,6 to indicate which is the active config.  Phase/ PWM to indicate change mode.
+ 
+ 
+ SysEx format from http://www.2writers.com/eddie/TutSysEx.htm
+ 
+ This discussion on SysEx is aimed at people using Roland equipment, but will put you in good stead to apply the knowledge to other makes of MIDI equipment.
+ The idea of SysEx is to change settings in a synth that cannot be accessed by any other means. Usually, anything that can be changed in a synth can be done
+ via SysEx, but because of its unwieldy structure and length, the more common parameters can be accessed via controllers (like volume et al) and special 
+ events (like patch change, pitch bend et al). SysEx is the only means of retrieving data from a synth.
+ The Roland SysEx message is made up of nine parts. All notation is in hex, but without the trailing "h" for simplicity's sake.
+ 
+ 
+ [1]  [2]  [3]  [4]  [5]  [6]      [7]  [8]  [9]
+ F0   41   10   42   12   40007F   00   41   F7
+ 
+ I will discuss each part briefly before going into detail on the usage of SysEx messages.
+ 
+ Parts [1], [2] and [9] are part of the MIDI specification and are required by all SysEx messages. What is in between is specific to the manufacturer, 
+ identified by part [2], which is 41h in Roland's case.
+ 
+ Part [3] is known as the Device ID. Most Roland MIDI devices use the default of 10h, but is provided for us to change as we see fit. The idea behind
+ the Device ID is that if you have more than one MIDI device of the same type in a daisy chain (connected to one another) you can change the Device ID
+ on each of them so that you can send SysEx messages that will be accepted by only one of them, not all.
+ 
+ Part [4] is the Model ID. GS synths will all respond to SysEx with a Model ID of 42h, however they generally have their own Model ID as well.
+ 
+ Part [5] specifies whether we are sending (12h) or requesting (11h) information. If a synth receives a SysEx message it recognizes, it will look this
+ part to determine whether it needs to change an internal setting or reply (with its own SysEx message) with the value of a setting.
+ 
+ Part [6] is the start address on which the SysEx intends to act. It is at this address that you may wish to put a value (or values) or retrieve the
+ current value(s). It always contains three bytes. Most synth manuals will provide you with a long "address map" table which explains what lives at
+ each address. Although daunting on a first perusal, once you understand its function it becomes a wonderful resource.
+ 
+ Part [7] has two functions. If part [5] is 12h (sending data) then part [7] contains the data we are sending and can be one byte or many bytes in
+ length. If it is 11h (requesting data) then it is the number of bytes we want the synth to send us. I will expand on this later with examples.
+ 
+ Part [8] is the infamous Roland checksum which gets a whole section to itself in this tutorial.
+ 
+ That, in a nutshell, is what a SysEx message is made up of. If you're still a little hazy then don't be concerned. By way of examples you will
+ see SysEx in action and will soon realize that there is no mystery to SysEx after all. 
+ */
+
+
+
+
+
+void setup_default_bank() {
+
+  for (int b = 0 ; b < MAX_BANKS; b++) {
+    for (int s = 0 ; s < MAX_SWITCHES; s++) {
+      debug("Setup b", b, DEBUG);
+      debug("Setup s", s, DEBUG);
+      banks.bank[b].switches[s].toggle_not_immediate = b;
+      banks.bank[b].switches[s].state = STATE0;
+      banks.bank[b].switches[s].messages[0][0] = 0xb1;
+      banks.bank[b].switches[s].messages[0][1] = 12 + s;
+      banks.bank[b].switches[s].messages[0][2] = 0;
+      banks.bank[b].switches[s].messages[1][0] = 0xb1;
+      banks.bank[b].switches[s].messages[1][1] = 12 + s;
+      banks.bank[b].switches[s].messages[1][2] = 127;      
+    }
+  }
+}
+
+void handle_system_reset_midi() {
+  setup_default_bank();
+}
+
+typedef enum {
+  CMD_RECONFIG, CMD_RESET, CMD_STORE, CMD_RETRIVE} 
+midi_sysex_cmds_e;
+
+
+void reconfig_command(byte * data, byte size) {
+  indicate_error();
+}
+
+void store_bank(byte * data, int size){
+  indicate_error();
+
+}
+void retrive_bank(byte * data, int size) {
+  indicate_error();
+}
+
+void handle_system_exclusive_midi(byte * data, byte size) {
+
+  if (data[1] != MIDI_DEVICE_ID) {
+    indicate_error();
+    return;
+  }
+  if (size < 4) {
+    indicate_error();
+    return;
+
+    switch (data[2]) {
+    case CMD_RECONFIG :  
+      reconfig_command(data, size);
+      break;
+    case CMD_RESET: 
+      setup_default_bank();
+      break;
+    case CMD_STORE: 
+      store_bank(data, size);
+      break;
+    case CMD_RETRIVE: 
+      retrive_bank(data, size);
+      break;
+    default: 
+      indicate_error();
+      break;
+    }
+  }
+}
+

File bank_types.h

+/* Programmable switches
+ 
+ Bank0/Bank1 selects changes between two switch banks for the other 4 switches
+ 
+ So really 8 programmable switches on the board
+ Each switch has two possible behaviours
+ 
+ Momentary operation (provide a code for when pressed and when released)
+ Toggle operation (provide a code for each time pressed/ released)
+ 
+ Codes can be any valid MIDI message, except SysEx messages
+ 
+ Messages are either 1, 2 or 3 bytes long
+ 
+ First byte starts with a 1 in the MSB, data bytes have 0 in the MSB
+ After first byte, use a 1 in the MSB as a 'stop' bit for the word.
+ If the first byte starts with a 0 in the MSB, the switch function is disabled.
+ 
+ All messages are stored in a 3 byte array, with FF's used as terminators for 'missing' bytes.
+ 
+ Switch structure {
+ int toggle_not_immediate; // 1= toggle 0=immediate
+ byte [3] state0_message = { 00, 0xFF, 0xFF };  // 0 in first byte MSB is disabled
+ byte [3] state1_message = { 00, 0xFF, 0xFF };  // 1 in second or third byte MSB indicates byte is not sent (for shorter messages)
+ };
+ 
+ In toggle mode: After a switch is pressed, send the next state message. Nothing is sent on release.
+ In immediate mode: After a switch is pressed, send the state1 message. Send the state0 message on release.
+ 
+ 
+ Switches structure {
+ switch structure * [4] switch; 
+ }
+ 
+ Bank structure {
+ switches * [2] bank;
+ }
+ */
+
 // Types etc need to be in a seperate header file so they can be used in other functions because of the way the IDE generates function prototypes and orders the derived code.
 // http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1212169107
 // 'You need to put type definition inside a separate header file, as the function prototypes that are generated by the IDE are inserted above the typedef

File default_bank_config.pde

-

File faux_vox.pde

 #include "EEPROMAnything.h"
 #include "bank_types.h"
 
-enum {OFF, INFO, DEBUG, TRACE} debug_levels;
+enum {OFF=0, INFO, DEBUG, TRACE} debug_levels;
 
-const int DEBUG_LEVEL = INFO;
+const int MIDI_DEVICE_ID = 0x78;
+const int MIDI_SYSEX_LENGTH = 5;
+
+const int DEBUG_LEVEL = OFF;
+
+const int ERROR_LED = 6;
 
 int previous_index, current_bank;
 
 banks_t banks;
 
 void setup() {
-  if (DEBUG_LEVEL) {
+  if (DEBUG_LEVEL != OFF) {
     Serial.begin(115200);
   } else {
     Serial.begin(31250); 
+    MIDI.begin(MIDI_CHANNEL_OMNI);
+    MIDI.setHandleSystemExclusive(handle_system_exclusive_midi);
+    MIDI.setHandleSystemReset(handle_system_reset_midi);
   }
+  
+  
   current_bank = 0;
   previous_index = 0;
   memset(&banks, 0, sizeof(banks_t));
     }
   
   previous_index = index; // update the index variables at the end, so we can know which was changed through the processing stages
-  display_banks_status();
+
   } // end previos != index loop
   
+  MIDI.read();
+  display_banks_status();
   delay(100);// cheap debounce - probably needs to change
 }
 
 }
 
 void send_byte(byte b) {
-  if (!DEBUG_LEVEL) {
+  if (DEBUG_LEVEL == OFF) {
     Serial.write(b);
   } 
-  else {
-    Serial.println(b);
-  }
 }
 
-void setup_default_bank() {
-
-  for (int b = 0 ; b < MAX_BANKS; b++) {
-    for (int s = 0 ; s < MAX_SWITCHES; s++) {
-      debug("Setup b", b, DEBUG);
-      debug("Setup s", s, DEBUG);
-      banks.bank[b].switches[s].toggle_not_immediate = b;
-      banks.bank[b].switches[s].messages[0][0] = 0xb1;
-      banks.bank[b].switches[s].messages[0][1] = 12 + s;
-      banks.bank[b].switches[s].messages[0][2] = 0;
-      banks.bank[b].switches[s].messages[1][0] = 0xb1;
-      banks.bank[b].switches[s].messages[1][1] = 12 + s;
-      banks.bank[b].switches[s].messages[1][2] = 127;      
-    }
-  }
-}
 
 
 void debug(char*message, int val, int level) {
   flash_led(pin, 50);
 }
 
+void indicate_error() {
+ for (int i = 0; i<4; i++) {
+  fast_flash(ERROR_LED);
+ } 
+}
 
 void display_banks_status() {
 
   digitalWrite(LED_OFFSET, (current_bank == 0)?LOW: HIGH);
-  debug("LED bank", current_bank, INFO);
+  debug("LED bank", current_bank, TRACE);
 
   for (int s = 0 ; s < MAX_SWITCHES; s++) {
     if(banks.bank[current_bank].switches[s].state == STATE0) {
       digitalWrite(s + LED_OFFSET+1, LOW);
-      debug("LED LOW", s+LED_OFFSET+1, INFO);
+      debug("LED LOW", s+LED_OFFSET+1, TRACE);
     }
     else {
       digitalWrite(s + LED_OFFSET + 1, HIGH);
-      debug("LED HIGH", s+LED_OFFSET+1, INFO);
+      debug("LED HIGH", s+LED_OFFSET+1, TRACE);
     }      
   }
 }

File midi_tester.py

   # drop to console for test with python -i 
   a = Arduino('/dev/tty.usbmodem24371', 31250)
 
-  while (1):
-    command = a.read(3)
-    print hex(ord(command[0])), hex(ord(command[1])), hex(ord(command[2]))
+#  while (1):
+#    command = a.read(3)
+#    print hex(ord(command[0])), hex(ord(command[1])), hex(ord(command[2]))