It is currently Sat May 04, 2024 3:04 am


Lights on Crescendo

Hauptwerk software technical support only. Please make sure you have read the manual, tutorials and FAQ pages before requesting support.
  • Author
  • Message
Offline

waltk

Member

  • Posts: 15
  • Joined: Fri Apr 25, 2014 4:44 pm

Lights on Crescendo

PostTue Jul 23, 2019 11:38 pm

Environment: Mac Mini i5 16 Gb memory, Mojave 10.14.5. Hauptwerk 4.2.1, Mt. Carmel Skinner, Menesterol, and St Anne. Rodgers 330 converted to MIDI controller with DTS

As part of what was installed on the original Rodgers, I've got three lights which used to turn on with the crescendo (mp, mf, and ff. I would like to light these from the crescendo pedal (which is now a potentiometer generating MIDI, rather than a multicontact switch). Normally, crescendos don't cause stop movement on the console, so calling each one a stop probably won't work. Could someone point me to a solution (I can, of course, use a 16x2 char MIDI display to show "Crescendo: 20%") but idiot lights seem to be more convenient.

Thanks!
Walter Knowles
Everett, WA
Offline

jkinkennon

Member

  • Posts: 1208
  • Joined: Thu May 07, 2009 9:43 am
  • Location: Vancouver, WA

Re: Lights on Crescendo

PostWed Jul 24, 2019 8:51 am

I drive crescendo lamps from a Teensy 3.6 with an added ULN2803 for open collector outputs. Decode the HW crescendo status giving a number from 0 to 63 (or is it 1 to 64?) and then decide which lamps to turn on based on that number. Here's some code:

Code: Select all
// Name:       MIDI_Encoder.ino
// Created:      1/8/2019
// Author:     John Kinkennon

#include <arduino.h>
#include <SPI.h>
#include "User.h"
#include "Midi.h"

const int channel = 0;        // 0 to 15 for MIDI channel 1 to 16
const int slaveInPin = 10;    // chip select for SPI input
const int slaveOutPin = 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
keyTableB_t stopOn;       // true for stops that are on
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
elapsedMillis sincePending;
elapsedMillis sincePulse;

// 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:
      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(slaveInPin, OUTPUT);
   pinMode(slaveOutPin, 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
  HWSERIAL1.begin(9600);      // default, reliable baud rate
  HWSERIAL2.begin(9600);
  delay(500);                 // wait for LCDs to boot
  HWSERIAL1.write(254);
  HWSERIAL1.write(01);        // Clear displays
  HWSERIAL2.write(254);
  HWSERIAL2.write(01);
 
   digitalWrite(slaveInPin, HIGH);  // HIGH to shift, LOW to load
   digitalWrite(slaveOutPin, HIGH);  // HIGH edge to move data to outputs

   for (i = 0; i < NUM_SWITCHES; i++) {
      keyTable.O[i] = 0;
      keyBit.O[i] = false;
      keyOn.O[i] = false;
    stopOn.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();

  usbMIDI.setHandleNoteOff(myNoteOff);
  usbMIDI.setHandleNoteOn(myNoteOn);
  usbMIDI.setHandleSystemExclusive(mySystemExclusiveChunk);
  // similar callbacks available for other MIDI message types
}

// the loop function runs over and over again until power down or reset
void loop() {
   int i, j, k, x;

  keyScanCount++;
  if (keyScanCount > 7) keyScanCount = 0;

   // read and write all SPI devices
   SPI.beginTransaction(settingsIn);

   // read and write 8 registers
   digitalWrite(slaveInPin, HIGH);     // enable shifting
  digitalWrite(slaveOutPin, LOW);     // no effect

   for (i = 0; i < NUM_CHANNELS; i++) {
    k = (NUM_CHANNELS - 1) - i;
      for (j = 0; j < NUM_REGISTERS; j++) {
      if (j < 4) x = 3 - j;
      else x = 11 - j;
         spiInput.C[i][j] = SPI.transfer(spiOutput.C[k][x]);
      }
   }
   digitalWrite(slaveInPin, LOW);        // disable shifting
  digitalWrite(slaveOutPin, HIGH);    // high edge latches data
 
   SPI.endTransaction();

   for (i = 0; i < NUM_CHANNELS; i++) {
      for (j = 0; j < NUM_REGISTERS; j++) {
         uint8_t tempData = spiInput.C[i][j];
//      if (i == 4)     // pedal is MIDI channel 5, software ch 4
//        tempData ^= 0xff;
#ifdef INVERT_SPI_DATA
         tempData ^= 0xff;
#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
}

void myNoteOff(byte ch, byte note, byte velocity) {
  if ((ch == (channel + 1)) && (note < 100)) {
    int stop = note - 0x24;
    setSpiOutput(false, stop);
  }
}

void myNoteOn(byte ch, byte note, byte velocity) {
  if ((ch == (channel + 1)) && (note < 100)) {
    int stop = note - 0x24;
    setSpiOutput(true, stop);
  }
}

/* see Capture_System code for pulsing SAMs */
void setSpiOutput(bool turnOn, int stop) {
  stopOn.C[channel][stop] = turnOn;     // it turnOn true then turn on
//  stop +=32;                            // FIX for a single 32-pin output board
  int spiRegister = stop / 8;
  int spiBit = stop % 8;
  if (turnOn) {
    spiOutput.O[spiRegister] |= (1 << spiBit);  // set bit for this note (stop)
  }
  else {
    spiOutput.O[spiRegister] &= ~(1 << spiBit);  // clear bit for this note (stop)
  }
}


Adapt as needed or send me a PM or preferably an email to john@kinkennon.com. The outputs, relay1 through cresc4 are all run through the ULN2803 IC. I can also provide a couple of extra files that would be needed, the MIDI definitions in Midi.h and User.h, if the file is to be used as is for a full-blown encoder.
Offline

waltk

Member

  • Posts: 15
  • Joined: Fri Apr 25, 2014 4:44 pm

Re: Lights on Crescendo

PostWed Jul 24, 2019 3:01 pm

Thanks. Why am I getting the feeling that it's time to dust off my long-unused coding skills?

Walt
Offline

jkinkennon

Member

  • Posts: 1208
  • Joined: Thu May 07, 2009 9:43 am
  • Location: Vancouver, WA

Re: Lights on Crescendo

PostThu Jul 25, 2019 10:35 am

The beauty of the Teensy 3.6 is that it is both blazingly fast and the hard stuff already exists in the Arduino libraries and the Teensyduino extensions. It's become my platform of choice except for the largest consoles. Some of my setup() is actually just restating Teensy default but I do it for clarity and to document how I'm using some pins. The elapsedMillis() is one of a set of beautiful timing features for things like pulsing power to stops without interrupting scanning.

Return to Technical support

Who is online

Users browsing this forum: No registered users and 7 guests