Search:
Submit Search


Teensy Encoder for Rodgers 32B Project

Building organ consoles for use with Hauptwerk, adding MIDI to existing consoles, obtaining parts, ...

Teensy Encoder for Rodgers 32B Project

Postby jkinkennon » Sun Nov 25, 2018 11:02 pm

I am working with Darin Molnar of Tortuga Early Instruments to convert a Rodgers 32B console for a VPO. We are using a Teensy 3.6 for the three manuals, pedals, pistons, and stops. The stops are not SAMs but the encoder could certainly handle that task as I've already used the same setup to add a capture system to a Moller Artiste.

The sketch for the Teensy will be published once we have finished in a week or so. The input boards shown are totally vanilla shift registers using the 74HC165s. That makes the Teensy setup compatible with many existing consoles including those modified with MIDIbox DINs and DOUTs.

As always the PCBs can be ordered from https://oshpark.com/profiles/jkinkennon.

http://www.kinkennon.com/images/TeensyEncoder.jpg
jkinkennon
Member
 
Posts: 1002
Joined: Thu May 07, 2009 10:43 am
Location: Vancouver, WA

Re: Teensy Encoder for Rodgers 32B Project

Postby jkinkennon » Sun Dec 02, 2018 2:00 pm

I mentioned in another post that I would be publishing the sketch for the Teensy Encoder.

Code: Select all
// 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
}


Here is the schematic:

Image

A quick look at the code will show plenty of opportunity to expand the project and add features. Hopefully this can serve as a starting point for those wanting to do their own encoder without too painful a learning curve. Questions welcome.

EDIT: For a higher resolution schematic go to http://www.kinkennon.com/images/Teensy% ... erORIG.png.
jkinkennon
Member
 
Posts: 1002
Joined: Thu May 07, 2009 10:43 am
Location: Vancouver, WA

Re: Teensy Encoder for Rodgers 32B Project

Postby jkinkennon » Sun Dec 02, 2018 2:20 pm

The project needs these files in addition to the main program listed previously.

Midi.h
Code: Select all
// 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: Select all
// 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: Select all
// 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
};
jkinkennon
Member
 
Posts: 1002
Joined: Thu May 07, 2009 10:43 am
Location: Vancouver, WA


Return to DIY organ consoles / MIDI

Who is online

Users browsing this forum: No registered users and 4 guests