Ebay Classic organs

Collapse

Announcement

Collapse
No announcement yet.

Teensy Encoder Project

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Teensy Encoder Project

    Here's a photo of the boards I will start installing in a Rodgers 32B console today. The encoder uses the Teensy 3.6 and I'm guessing the whole assembly didn't cost more than $250 at the most. I'll share the sketch when I'm satisfied with the completed install. The four input boards are for three manuals, pedals, and pistons. I may add another input board for the stop tabs which are input only as they are not SAMs.

    Click image for larger version

Name:	TeensyEncoder.jpg
Views:	1
Size:	151.1 KB
ID:	615504
    http://www.kinkennon.com

  • #2
    That's pretty cool. I'm excited to see how this project goes. :D

    It looks like this is a bussed console, and those interface boards you made are a pile of daisychained shift registers? Are you running 5v at the key switches, or 3.3v?
    1914 Estey Parlor Organ. 196x Allen T-12a "Special" (MIDI VPO project). Digital piano. Various guitars. Autoharp. Banjo. Bowed saw. Musical Cat.

    Comment


    • #3
      Yes, the input boards are totally conventional using the 74HC165N shift registers. That means that the Teensy could work with DINS and DOUTs from MIDIbox or a host of similar boards. At the moment I've got eight analog inputs though I seldom use more than four. There are 8 GPIO open collector outputs that can drive LEDs, 12v lamps, or power relays -- I use solid state relays to turn on audio power for example. Based on how I used the same Teensy board for a Moller Artiste capture system I know I can drive output boards for lighted stops or drive SAMs.

      The Teensy coding is SO easy. I'm working on a new PIC32MZ based encoder/decoder but the challenges are huge by comparison, especially doing surface mount components with a basic reflow oven and without the ability to do affordable boards due to the small quantities. The Teensy boards (all models) are superb and the Teensy 3.6 is blazingly fast -- overkill really.

      EDIT: The input board can be strapped for pull-up or pull-down resistors and has pads to install resistors to create voltage dividers for 12v keying. I will continue to use 3.3v until some keyboard starts acting up. The "key" is to start with clean contacts.
      http://www.kinkennon.com

      Comment


      • #4
        Cool. I've been a little worried about using 3.3v for my Allen project, but my contacts seem to be good and clean, so maybe everything will be ok!
        1914 Estey Parlor Organ. 196x Allen T-12a "Special" (MIDI VPO project). Digital piano. Various guitars. Autoharp. Banjo. Bowed saw. Musical Cat.

        Comment


        • #5
          Originally posted by Mr. Polecat View Post
          Cool. I've been a little worried about using 3.3v for my Allen project, but my contacts seem to be good and clean, so maybe everything will be ok!
          I've had excellent success with 3.3v keying on Allen consoles, but with the Rodgers using the tarnished phosphor-bronze bus wire and whiskers I am having difficulties. I did a thorough cleaning yesterday but it is likely we will convert to 12v keying unless the contacts improve with the cleaning and more use. Fortunately the input boards are designed to allow the insertion of resistor packs to handle the higher voltage.

          I've posted the project on the Hauptwerk forum but want to add it here as well as the Teensy Encoder is great for other virtual organ software as well. So here's the code:

          Code:
          // Name:       MIDI_Encoder.ino
          // Created:	   12/1/2018
          // Author:     John Kinkennon
          
          #include <arduino.h>
          #include <SPI.h>
          #include "User.h"
          #include "Midi.h"
          
          const int slaveOnePin = 10;   // chip select for SPI input
          const int slaveTwoPin = 9;    // chip select for SPI output
          const int analogPin1 = 14;    // potentiometer input pins
          const int analogPin2 = 15;
          const int analogPin3 = 16;
          const int analogPin4 = 17;
          const int analogPin5 = 18;
          const int analogPin6 = 19;
          const int analogPin7 = 20;
          const int analogPin8 = 21;
                                        // GPIO pins
          const int relay1 = 24;        // power relay (available)
          const int relay2 = 25;        // audio power relay
          const int powerLamp = 26;     // 12v power lamp (available)
          const int audioLamp = 27;     // audio power lamp
          const int cresc1 = 28;        // crescendo stage 1 lamp (for HW cresc)
          const int cresc2 = 29;        // crescendo stage 2
          const int cresc3 = 30;        // crescendo stage 3
          const int cresc4 = 35;        // crescendo stage 4
          
          // set up the speed, mode and endianness device
          SPISettings settingsIn(1000000, MSBFIRST, SPI_MODE0);
          
          unsigned char sysex[64];  // Buffer to assemble SysEx message
          int sx = 0;               // Index into sysex[64]
          keyTable8_t keyTable;
          keyTableB_t keyBit;		// true for keys which are depressed
          keyTableB_t keyOn;		// true for keys which are on after debounce
          spiTable_t spiInput;	// buffer variable to store read data
          spiTable_t spiOutput;	// buffer variable to store write data
          uint16_t newPotValue[NUM_POTS];
          uint16_t oldPotValue[NUM_POTS];
          int keyScanCount;     // cycles 0 through 7 to allow slowing some tasks
          
          // Sysex messages require HW output, see General Settings->General->Advanced settings
          void mySystemExclusiveChunk(const byte *data, uint16_t length, bool last) {
            int i;
            for (i = sx; i < length; i++) {
              sysex[sx++] = data[i];
            }
            if (sx == 6) {
              switch (sysex[3]) {
              case 33:            // IsSetterModeOn
                        // TODO: Turn on a yellow LED
                break;
              case 38:            // IsOrganReady
                        // Match LED states to relays
                if (sysex[4] == 1) {
                  digitalWrite(relay2, HIGH);
                  digitalWrite(audioLamp, HIGH);
                }
                else if (sysex[4] == 0) {
                  digitalWrite(relay2, LOW);
                  digitalWrite(audioLamp, LOW);
                }
                break;
              case 39:            // IsInErrorState
                        // TODO: Turn on a red LED
                break;
              case 47:      // Master Crescendo Pedal
                digitalWrite(cresc1, LOW);
                digitalWrite(cresc2, LOW);
                digitalWrite(cresc3, LOW);
                digitalWrite(cresc4, LOW);
                if (sysex[4] > 0) {
                  digitalWrite(cresc1, HIGH);
                }
                if (sysex[4] > 8) {
                  digitalWrite(cresc2, HIGH);
                }
                if (sysex[4] > 16) {
                  digitalWrite(cresc3, HIGH);
                }
                if (sysex[4] > 24) {
                  digitalWrite(cresc4, HIGH);
                }
                break;
              case 84:            // IsOrganLoaded
                        // Possible substitute for IsOrganReady
                break;
              case 85:            // IsOrganLoading
                        // TODO: Turn on a yellow LED
                break;
              }
            }
            if (sx == 39) {
              switch (sysex[3]) {
              case 0:
                        // TODO: Write to LCD #1
          //      for (i = 6; i < sx - 1; i++) {
          //        HWSERIAL1.write(sysex[i]);
          //      }
                break;
          
              case 1:
                        // TODO: Write to LCD #2
          //      for (i = 6; i < sx - 1; i++) {
          //        HWSERIAL2.write(sysex[i]);
          //      }
                break;
          
              default:
                break;
              }
            }
            if (last) {
              sx = 0;
              for (i = 0; i < 64; i++)
                sysex[i] = 0;
            }
          }
          
          // the setup function runs once when you press reset or power the board
          void setup() {
          	int i, j;
          	pinMode(slaveOnePin, OUTPUT);
          	pinMode(slaveTwoPin, OUTPUT);
          	pinMode(analogPin1, INPUT);
          	pinMode(analogPin2, INPUT);
          	pinMode(analogPin3, INPUT);
          	pinMode(analogPin4, INPUT);
          	pinMode(analogPin5, INPUT);
          	pinMode(analogPin6, INPUT);
          	pinMode(analogPin7, INPUT);
          	pinMode(analogPin8, INPUT);
            pinMode(relay1, OUTPUT);  // SSR_1, Switched Power
            pinMode(relay2, OUTPUT);  // SSR_2, Organ Ready Power
            pinMode(powerLamp, OUTPUT); // Ground to turn on power
            pinMode(audioLamp, OUTPUT); // Ground to turn on audio pwr
            pinMode(cresc1, OUTPUT);  // Ground to turn on cresc1 lamp
            pinMode(cresc2, OUTPUT);  // Ground to turn on cresc2 lamp
            pinMode(cresc3, OUTPUT);  // Ground to turn on cresc3 lamp
            pinMode(cresc4, OUTPUT);  // Ground to turn on cresc4 lamp
           
          	digitalWrite(slaveOnePin, HIGH); // active LOW, so this disables loading
          	digitalWrite(slaveTwoPin, HIGH);
          
          	for (i = 0; i < NUM_SWITCHES; i++) {
          		keyTable.O[i] = 0;
          		keyBit.O[i] = false;
          		keyOn.O[i] = false;
          	}
          	for (i = 0; i < NUM_CHANNELS; i++) {
          		for (j = 0; j < NUM_REGISTERS; j++) {
          			spiInput.C[i][j] = 0;
          			spiOutput.C[i][j] = 0;
          		}
          	}
           for (i = 0; i < NUM_POTS; i++) {
            newPotValue[i] = 0;
            oldPotValue[i] = 0;
           }
          
           keyScanCount = 0;
          
          	// initialize SPI:
          	SPI.begin();
          }
          
          // the loop function runs over and over again until power down or reset
          void loop() {
          	int i, j, k;
          
            keyScanCount++;
            if (keyScanCount > 7) keyScanCount = 0;
          
          	// read and write all SPI devices
          	SPI.beginTransaction(settingsIn);
          
          	// read and write 8 registers
          	digitalWrite(slaveOnePin, HIGH);	// Enable SPI port 1
          
          	for (i = 0; i < NUM_CHANNELS; i++) {
          		for (j = 0; j < NUM_REGISTERS; j++) {
          			spiInput.C[i][j] = SPI.transfer(spiOutput.C[i][j]);
          		}
          	}
          	digitalWrite(slaveOnePin, LOW);		// Disable port 1	
          	SPI.endTransaction();
          
          	for (i = 0; i < NUM_CHANNELS; i++) {
          		for (j = 0; j < NUM_REGISTERS; j++) {
          			uint8_t tempData = spiInput.C[i][j];
          #ifdef INVERT_SPI_DATA
          			tempData ^= 0xffffffff;
          #endif
          			int endIndex = (NUM_REGISTERS - j) * 8;
          			int startIndex = endIndex - 8;
          			for (k = startIndex; k < endIndex; k++) {
          				keyBit.C[i][k] = tempData & 0b00000001;
          				tempData >>= 1;
          			}
          		}
          
          		for (int key = 0; key < 64; key++) {
          			uint8_t prevData = keyTable.C[i][key];
          			bool newKey = keyBit.C[i][key];   // is keyDown now?
          			bool keyDown = (prevData & 0x80); // was keyDown true on previous scan?
          			prevData <<= 1;						        // shift the data left one bit
          			if (newKey) prevData++;		        // set newest bit
          			prevData &= 0x0f;					        // strip any high bits
          
          			if (!keyDown) { // if note is off in keyTable see if it should be on
          				if (prevData == 0b00000011) {	  // if 3 consecutive keyDown
          				//if (prevData > 0) {           // this is a fast noteOn for testing
          					keyDown = true;				        // turn key on in keyTable
          					usbMIDI.sendNoteOn(key + 0x24, M_VELOCITY_ON, i + 1, 0);
          					keyOn.C[i][key] = true;
          				}
          			}
          			else if (keyDown) {	// if note is on in keyTable see if it should be off
          				if (prevData == 0b00000000) {	  // if 3 consecutive keyUp
          					keyDown = false;			        // turn key off in keyTable
          					usbMIDI.sendNoteOff(key + 0x24, M_VELOCITY_OFF, i + 1, 0);
          					keyOn.C[i][key] = false;
          				}
          			}
          			if (keyDown) prevData |= 0x80;		// store the data
          			keyTable.C[i][key] = prevData;
          		}
          	}
          
            i = keyScanCount;
          	newPotValue[i] = analogRead(i + analogPin1);  // sequential pin #'s, nice
            newPotValue[i] >>= 3;
          	newPotValue[i] = (newPotValue[i] + oldPotValue[i]) >> 1; // average old and new
          	int diff = newPotValue[i] - oldPotValue[i];              // different value?
            if (diff != 0) {
          		usbMIDI.sendControlChange(M_CC_VOLUME, newPotValue[i], i + 1, 0);
          	}
          	oldPotValue[i] = newPotValue[i];
          	
          	usbMIDI.read();							// prime USB to receive any MIDI packet
          }
          This is the schematic:
          Click image for larger version

Name:	Teensy Encoder.png
Views:	1
Size:	86.4 KB
ID:	606731

          A higher res schematic is available at http://www.kinkennon.com/images/Teen...ncoderORIG.png
          http://www.kinkennon.com

          Comment


          • #6
            A second post to show additional required files...

            Midi.h
            Code:
            // Kinkennon Services
            #pragma once
            /** MIDI Default Values ********************************************/
            #define M_VELOCITY_ON       0x7f    // default where no touch sensitivity
            #define M_VELOCITY_OFF      0x00    // default for note off msg
            #define M_KBD_FIRST_KEY     0x24    // first key on a 61 key kybd
            
            /** MIDI Channel Voice Messages ************************************/
            #define M_NOTE_OFF    0b10000000  // 0x8n - where n is the channel
            #define M_NOTE_ON     0b10010000  // 0x9n -	(MIDI ch 1 is n=0)
            #define M_AFTERTOUCH	0b10100000  // 0xAn - polyphonic key pressure
            #define M_CTRL_CHANGE	0b10110000  // 0xBn - control change
            #define M_PROG_CHANGE	0b11000000  // 0xCn - program change
            #define M_CH_PRESSURE	0b11010000  // 0xDn - also called aftertouch
            #define M_PITCH_WHEEL	0b11100000  // 0xEn - pitch wheel change
            
            /** MIDI Channel Mode Messages - only for CC's with channel > 119 **/
            #define M_ALL_SOUND_OFF 0b01111000  // 0x78 - all sound off
            #define M_RESET_ALL_C	  0b01111001  // 0x79 - reset all controllers
            #define M_LOCAL_CONTROL 0b01111010  // 0x7A - local control (0=OFF, 127=ON)
            #define M_ALL_NOTES_OFF 0b01111011  // 0x7B - all notes off
            #define M_OMNI_OFF      0b01111100  // 0x7C - omni mode off
            #define M_OMNI_ON       0b01111101  // 0x7D - omin mode on
            #define M_MONO_ON       0b01111110  // 0x7E - mono mode on
            #define M_POLY_ON       0b01111111  // 0x7F - poly mode on
            
            /** MIDI System Common Messages ************************************/
            #define M_SYSTEM_EX     0b11110000  // 0xF0 - system exclusive
            #define M_SYSEX_ID      0b01111101  // 0x7D - test or development id
            #define M_TIME_CODE_QF	0b11110001  // 0xF1 - time code quarter frame
            #define M_SONG_POS_PTR	0b11110010  // 0xF2 - song position pointer
            #define M_SONG_SELECT	  0b11110011  // 0xF3 - song select
            #define M_TUNE_REQUEST	0b11110110  // 0xF6 - tune request (tune osc's)
            #define M_END_EXCLUSIVE 0b11110111  // 0xF7 - end of exclusive (see F0)
            
            /** MIDI System Real-Time Messages *********************************/
            #define M_TIMING_CLOCK	0b11111000  // 0xF8 - timing clock
            #define M_START         0b11111010  // 0xFA - start
            #define M_CONTINUE      0b11111011  // 0xFB - continue
            #define M_STOP          0b11111100  // 0xFC - stop
            #define M_ACTIVE_SENSE	0b11111110  // 0xFE - active sensing
            #define M_RESET         0b11111111  // 0xFF - reset
            
            /** MIDI Channel Control Messages **********************************/
            #define M_CC_BANK_SEL	  0b00000000  // 0x00 - bank select
            #define M_CC_MOD_WHEEL	0b00000001  // 0x01 - modulation wheel
            #define M_CC_BREATH_CTL	0b00000010  // 0x02 - breath controller
            #define M_CC_FOOT_CTL	  0b00000100  // 0x04 - foot controller
            #define M_CC_PORTAMENTO	0b00000101  // 0x05 - portamento
            #define M_CC_VOLUME     0b00000111  // 0x07 - ch volume
            #define M_CC_BALANCE	  0b00001000  // 0x08 - balance
            #define M_CC_PAN        0b00001010  // 0x0A - pan
            #define M_CC_EXPRESSION	0b00001011  // 0x0B - expression
            User.h
            Code:
            // Name:       User.h for MIDI_Encoder.ino
            // Created:    12/1/2018
            // Author:     John Kinkennon
            
            #pragma once
            
            #define NUM_CHANNELS    6       // max number of input boards to read 
            #define NUM_KEYS			  64
            #define NUM_SWITCHES		(NUM_CHANNELS * NUM_KEYS)
            #define INVERT_SPI_DATA
            #define NUM_REGISTERS		8				// number of 8-bit SPI chips
            #define NUM_REGISTERS_TOTAL	40
            #define NUM_POTS			  8
            
            // typedefs that allow accessing tables by channel or by offset
            
            typedef union {
            	uint8_t C[NUM_CHANNELS][NUM_KEYS];		// channel access
            	uint8_t O[NUM_SWITCHES];              // offset access
            } keyTable8_t;
            
            typedef union {
            	bool C[NUM_CHANNELS][NUM_KEYS];			  // channel access
            	bool O[NUM_SWITCHES];                 // offset access
            } keyTableB_t;
            
            typedef union {
            	uint8_t C[NUM_CHANNELS][NUM_REGISTERS];	// channel access
            	uint8_t O[NUM_REGISTERS_TOTAL];			  // offset access
            } spiTable_t;
            name.c
            Code:
            // To give your project a unique name, this code must be
            // placed into a .c file (its own tab).  It can not be in
            // a .cpp file or your main sketch (the .ino file).
            
            #include "usb_names.h"
            
            // Edit these lines to create your own name.  The length must
            // match the number of characters in your custom name.
            
            #define MIDI_NAME   {'M','I','D','I',' ','B','u','s',' ','E','n','c','o','d','e','r'}
            #define MIDI_NAME_LEN  16
            
            // Do not change this part.  This exact format is required by USB.
            
            struct usb_string_descriptor_struct usb_string_product_name = {
                    2 + MIDI_NAME_LEN * 2,
                    3,
                    MIDI_NAME
            };
            Just put these files in the same folder as the main sketch.
            http://www.kinkennon.com

            Comment


            • #7
              Cool. I'll be referencing this when I start my project. ;P

              Thanks!!!
              1914 Estey Parlor Organ. 196x Allen T-12a "Special" (MIDI VPO project). Digital piano. Various guitars. Autoharp. Banjo. Bowed saw. Musical Cat.

              Comment


              • #8
                We did end up needing 12v keying with the nasty Rodgers phosphor-bronze contacts. That cleaned things up just fine. We ended up with 12v on the bus through 27k series resistors to 10k pull-down resistors for a nearly perfect input voltage to the 3.3v circuits. That's with a regulated 12v supply by the way. The typical 13v or more on an unregulated supply would have needed a resistance closer to 33k, or 30k if that's available in the 16-pin resistor networks.

                We added LED lighting to the Rodgers console as well so buying the 12v power supply served a couple of purposes.
                http://www.kinkennon.com

                Comment

                Working...
                X