Converting an old organ to MIDI
You might remember that some years ago, I found a discarded organ and brought it home. Well, it sat around for a year, then I converted it to MIDI, then I moved across the country and only brought the keyboards, and finally I re-built it as the proper single-row unit featured in the above video.
The keys are laid out as a generic matrix-style keyboard, with a column for every note in an octave (12 in total), and a row for each octave bank (up to ten, although only 8 are in use). Using a Barebones Arduino clone and a couple of I/O expander chips (PCF854AN) for extra digital pins, I was able to convert the old keyboard into a MIDI device. This version works fine, however the circuit is a bit extravagant- If I ever re-build it, I will use a 3-to-8 line decoder for the select lines, which should free up enough digital pins on the Arduino to be able to get rid of the I/O expander chips. Really, though, I would prefer to convert it into a velocity sensitive keyboard, so any further effort will be in that direction. Rough schematic and source code after the break.
// Sketch to convert an older electric organ (Eminent S20) into a MIDI instrument. // By Matt Mets, completed in 2009 // // This code is released into the public domain. Attribution is appreciated. // // MIDI source code from the ITP website: // http://itp.nyu.edu/physcomp/Labs/MIDIOutput #include <Wire.h> #define LEDpin 13 // Just the standard output LED #define volumePedal 1 // The analog input for the volume control char velocity; // Use the foot pedal to determine this // Define the MIDI channels that each instrument transmits on #define keyboardChannel 0 // Number of key banks to scan. There are eight 'normal' key banks, and 2 special ones. #define numKeyBanks 8 #define numKeysPerBank 12 // Lookup table, used to determine the select line to turn on to read the keys in that key bank. static char keyBankSelectLines[] = {2,3,4,5,6,7,8,11}; // Storage for the previous value of each bank of keys. There are 10 access lines, with a maximum of 12 inputs per line. // The 12 bits of data on each line are split into low and high banks. unsigned char noteStatesL[numKeyBanks]; unsigned char noteStatesH[numKeyBanks]; // Storage for the instrument data associated with each key. Must be filled in at startup. unsigned char noteChannels[numKeyBanks][numKeysPerBank]; unsigned char noteValues[numKeyBanks][numKeysPerBank]; // Initialize IO ports void setup() { // Setup the I2C bus (analog pins 4&5) in master mode. This bus is used to talk to the IO expander chips. Wire.begin(); // Set the serial port to the correct baud rate for MIDI Serial.begin(31250); // Set all initial note bank vales to 0 for (unsigned int i = 0; i < numKeyBanks; i++) { noteStatesL[i] = 0; noteStatesH[i] = 0; } pinMode(LEDpin, OUTPUT); // Status LED // Set all of the bank select lines to outputs for (unsigned char bankNo = 0; bankNo < numKeyBanks; bankNo++) { pinMode(keyBankSelectLines[bankNo], OUTPUT); } // Set up the instrument data for the keys // first off, clear everything. for (unsigned char bank = 0; bank < numKeyBanks; bank ++) { for (unsigned char key = 0; key < numKeysPerBank; key++) { noteChannels[bank][key] = 0; noteValues[bank][key] = 0; } } // The bottom half of the keyboard comprised of banks 4-6, starting at F unsigned char noteValue = 24; // Octave 2, C note for (unsigned char bank = 4; bank < 7; bank ++) { for (unsigned char key = 0; key < 12; key++) { noteChannels[bank][key] = keyboardChannel; noteValues[bank][key] = noteValue++; } } // The top keyboard row is comprised of banks 0-3, starting at F for (unsigned char bank = 0; bank < 4; bank ++) { for (unsigned char key = 0; key < 12; key++) { noteChannels[bank][key] = keyboardChannel; noteValues[bank][key] = noteValue++; } } // And one extra one on bank 9 note 1 noteChannels[7][1] = keyboardChannel; noteValues[7][1] = noteValue++; } // Send a MIDI command. // cmd is the command and channel (0x90 for channel 1 note on) // data1 is the note // data2 is the velocity void midiCommand(char cmd, char data1, char data2) { Serial.print(cmd, BYTE); Serial.print(data1, BYTE); Serial.print(data2, BYTE); } // Map an integer into a charater given a set range unsigned char mapInteger(int input, int min, int max) { return (((input - min)*255)/(max-min)); } // Main loop unsigned char newNoteStateL; unsigned char newNoteStateH; void loop() { // Read the velocity in // velocity = mapInteger(analogRead(volumePedal),400,900); velocity = 90; for (char bankNo = 0; bankNo < numKeyBanks; bankNo++) { digitalWrite(keyBankSelectLines[bankNo], HIGH); // Select the key bank Wire.requestFrom(0x38, 1); // Read the low data while (Wire.available()) { newNoteStateL = Wire.receive(); } Wire.requestFrom(0x39, 1); // Read the high data while (Wire.available()) { newNoteStateH = Wire.receive(); } digitalWrite(keyBankSelectLines[bankNo], LOW); // Deselect the key bank if (newNoteStateL != noteStatesL[bankNo]) { // Check if any notes in this bank are different char stateDelta = newNoteStateL ^ noteStatesL[bankNo]; for (unsigned char i = 0; i < 8; i++) { if ((stateDelta >> i) & 1) { if ((newNoteStateL >> i) & 1) { // Send a MIDI on message midiCommand(0x90 + noteChannels[bankNo][i], noteValues[bankNo][i], velocity); } else { // Send a MIDI off message midiCommand(0x80 + noteChannels[bankNo][i], noteValues[bankNo][i], velocity); } } } } if (newNoteStateH != noteStatesH[bankNo]) { // Check if any notes in this bank are different digitalWrite(LEDpin, HIGH); char stateDelta = newNoteStateH ^ noteStatesH[bankNo]; for (unsigned char i = 0; i < 8; i++) { if ((stateDelta >> i) & 1) { if ((newNoteStateH >> i) & 1) { // Send a MIDI on message midiCommand(0x90 + noteChannels[bankNo][i+8], noteValues[bankNo][i+8], velocity); } else { // Send a MIDI off message midiCommand(0x80 + noteChannels[bankNo][i+8], noteValues[bankNo][i+8], velocity); } } } } noteStatesL[bankNo] = newNoteStateL; noteStatesH[bankNo] = newNoteStateH; } }
