DangerShield drawbars

//
// Danger Shield MIDI drawbars for Yamaha PSR/Tyros
// Sketch source file: DrawbarsTest.ino
//

//
// Author:  P.J. Drongowski
// Date:    27 March 2014
// Version: 1.0
//
// Copyright (c) 2014 Paul J. Drongowski
// Permission to use, copy, modify and distribute as long as this
// attribution appears in the code.
//

//
// Shift register bit values to display a hexadecimal digit 0-F on
// the seven segment display.
// Bit order:  HGFEDCBA where H is the decimal point
//      --A--
//     |     |
//     F     B
//     |     |
//      --G--
//     |     |
//     E     C
//     |     |
//      --D--   H
//
const byte sevenSegmentDigits[16] = {

  B00111111, // 0
  B00000110, // 1
  B01011011, // 2
  B01001111, // 3
  B01100110, // 4
  B01101101, // 5
  B01111101, // 6
  B00000111, // 7
  B01111111, // 8
  B01101111, // 9
  B01110111, // A
  B01111100, // b
  B00111001, // C
  B01011110, // d
  B01111001, // E
  B01110001  // F
};

#define sevenSegment_A B01110111
#define sevenSegment_b B01111100
#define sevenSegment_c B01011000
#define sevenSegment_C B00111001
#define sevenSegment_d B01011110
#define sevenSegment_E B01111001
#define sevenSegment_F B01110001
#define sevenSegment_g B01101111
#define sevenSegment_h B01110100
#define sevenSegment_H B01110110
#define sevenSegment_i B00010000
#define sevenSegment_I B00110000
#define sevenSegment_J B00011110
#define sevenSegment_l B00011000
#define sevenSegment_L B00111000
#define sevenSegment_n B01010100
#define sevenSegment_o B01011100
#define sevenSegment_O B00111111
#define sevenSegment_P B01110011
#define sevenSegment_q B01100111
#define sevenSegment_r B01010000
#define sevenSegment_S B01101101
#define sevenSegment_t B01111000
#define sevenSegment_u B00011100
#define sevenSegment_y B01101110
#define sevenSegment_Z B01011011

#define sevenSegment_DP     B10000000
#define sevenSegment_BLANK  B00000000

//
// Pin definitions (See Sparkfun DangerShield documentation)
//
#define SLIDER0  0
#define SLIDER1  1
#define SLIDER2  2

#define KNOCK    5

#define BUTTON1  10
#define BUTTON2  11
#define BUTTON3  12

#define LED1  5
#define LED2  6

#define BUZZER   3

#define TEMP  4

#define LIGHT  3

#define LATCH 7
#define CLOCK 8
#define DATA 4

//
// The variable "mode" keeps track of the operating mode, i.e.,
// one of {drawbar, effect, preset} modes. Start in drawbar
// mode.
//
#define DRAWBAR_MODE    1
#define EFFECT_MODE     2
#define PRESET_MODE     3
int mode = DRAWBAR_MODE ;

//
// Current slider (drawbar) group for each mode. The slider
// group determines the slider's meaning (functionality) within
// the current operating mode. It's a way to control up to nine
// different drawbar settings (effects, presets) with just
// three physical sliders.
//
#define DRAWBAR_A  1
#define DRAWBAR_B  2
#define DRAWBAR_C  3
int drawbarGroup = DRAWBAR_A ;

#define EFFECT_A  1
#define EFFECT_B  2
#define EFFECT_C  3
int effectGroup = EFFECT_A ;

#define PRESET_A  1
#define PRESET_B  2
#define PRESET_C  3
int presetGroup = PRESET_A ;

//
// Slider values. These variables track the raw slider values.
//
int sliderLeft = 0 ;
int sliderMiddle = 0 ;
int sliderRight = 0 ;
int newValue = 0 ;

//
// Current PSR/Tyros organ flute values. We need to maintain
// the entire state of the organ flute settings such that we
// can send out a complete SysEx message with all of the current
// setting values.
//
int drawbar_1 = 0 ;      // Drawbar group 3, right
int drawbar_113 = 0 ;    // Drawbar group 3, middle
int drawbar_135 = 0 ;    // Drawbar group 3, left
int drawbar_2 = 0 ;      // Drawbar group 2, right
int drawbar_223 = 0 ;    // Drawbar group 2, middle
int drawbar_4 = 0 ;      // Drawbar group 2, left
int drawbar_8 = 8 ;      // Drawbar group 1, right
int drawbar_513 = 8 ;    // Drawbar group 1, middle
int drawbar_16 = 8 ;     // Drawbar group 1, left
int attack_2 = 0 ;       // Perc group
int attack_223 = 0 ;     // No user control (always off)
int attack_4 = 0 ;       // Perc group
int attack_length = 0 ;  // Perc group
int response = 0 ;       // Perc group
int wave_variation = 1 ; // No user control (always vintage)
int volume = 8 ;         // Perc group

//
// Current effect values: Percussion (ON/OFF), rotary speaker
// speed (SLOW/FAST), Percussion volume (NORMAL/SOFT),
// percussion decay (SLOW/FAST), percussion harmonic (2nd/3rd),
// vibrato (ON/OFF), vibrato depth (1/2/3), organ volume.
//
#define PERC_OFF 0
#define PERC_ON  1
int percussionStatus = PERC_OFF ;

#define ROTARY_SLOW 0
#define ROTARY_FAST 1
int rotarySpeed = ROTARY_SLOW ;

#define PERC_NORMAL 0
#define PERC_SOFT   1
#define ATTACK_NORMAL 8
#define ATTACK_SOFT   4
#define ATTACK_OFF    0
int percussionVolume = PERC_NORMAL ;

#define PERC_SLOW 0
#define PERC_FAST 1
#define ATTACK_SLOW 8
#define ATTACK_FAST 1
int percussionDecay = PERC_SLOW ;

#define PERC_SECOND 0
#define PERC_THIRD  1
int percussionHarmonic = PERC_SECOND ;

#define VIBRATO_OFF 0
#define VIBRATO_ON  1
int vibratoStatus = VIBRATO_OFF ;

//
// MIDI CC values for vibrato depth. The selected depth (1/2/3)
// must be mapped to an appropriate MIDI CC value.
//
#define VIBRATO_DEPTH_1    0x45
#define VIBRATO_DEPTH_2    0x48
#define VIBRATO_DEPTH_3    0x4B
#define VIBRATO_DEPTH_OFF  0x40
int vibratoDepth = 0 ;

#define VIBRATO_OFFSET  20
#define VIBRATO_SCALE   10
int vibratoSpeed = VIBRATO_DEPTH_1 ;

int organVolume = 8 ;

//
// SysEx message buffers.
//
unsigned char OrganSysEx[34] ;  // Organ flutes message
unsigned char dspSysEx[8] ;     // Change DSP message
unsigned char midiShort[8] ;    // Any short MID message

//
// Convert current drawbar and attack values to a valid
// Yamaha organ flutes SysEx message. This function maps the
// Arduino-side settings into SysEx values understood by the
// Yamaha keyboard.
//
void makeOrganSysEx()
{
  int i ;
  int sum ;
  int attack ;

  OrganSysEx[0] = 0xF0 ;
  OrganSysEx[1] = 0x43 ;   // Yamaha ID
  OrganSysEx[2] = 0x73 ;   // Clavinova ID
  OrganSysEx[3] = 0x01 ;   // Model ID
  OrganSysEx[4] = 0x06 ;   // Bulk ID
  OrganSysEx[5] = 0x0B ;   // Bulk number (organ flutes data)
  OrganSysEx[6] = 0x00 ;   // Data length (4 bytes)
  OrganSysEx[7] = 0x00 ;
  OrganSysEx[8] = 0x01 ;
  OrganSysEx[9] = 0x06 ;
  OrganSysEx[10] = 0x00 ;  // Channel number
  OrganSysEx[11] = drawbar_1 ;
  OrganSysEx[12] = drawbar_113 ;
  OrganSysEx[13] = drawbar_135 ;
  OrganSysEx[14] = drawbar_2 ;
  OrganSysEx[15] = drawbar_223 ;
  OrganSysEx[16] = drawbar_4 ;
  OrganSysEx[17] = drawbar_513 ;
  OrganSysEx[18] = drawbar_8 ;
  OrganSysEx[19] = drawbar_16 ;
  if (percussionStatus == PERC_OFF) {
    OrganSysEx[21] = 0x00 ;  // Attack 2 2/3'
    OrganSysEx[22] = 0x00 ;  // Attack 4'
    OrganSysEx[23] = 0x00 ;  // Attack length
  } else {
    if (percussionVolume == PERC_NORMAL) {
      attack = ATTACK_NORMAL ;
    } else {
      attack = ATTACK_SOFT ;
    }
    if (percussionHarmonic == PERC_SECOND) {
      OrganSysEx[22] = attack ;  // Attack 4'
    } else {
      OrganSysEx[21] = attack ;  // Attack 2 2/3'
    }
    if (percussionDecay == PERC_SLOW) {
      OrganSysEx[23] = ATTACK_SLOW ;  // Attack length
    } else {
      OrganSysEx[23] = ATTACK_FAST ;  // Attack length
    } 
  }
  OrganSysEx[20] = 0x00 ;  // Attack 2' (Not authentic B-3)
  OrganSysEx[24] = 0x00 ;  // Response
  OrganSysEx[25] = 0x01 ;  // Attack mode (always first)
  OrganSysEx[26] = 0x01 ;  // Wave (always vintage)
  OrganSysEx[27] = 0x08 ;  // Volume (always maximum)
  OrganSysEx[28] = 0x00 ;
  OrganSysEx[29] = 0x00 ;
  OrganSysEx[30] = 0x00 ;
  OrganSysEx[31] = 0x00 ;
  OrganSysEx[32] = 0x00 ;  // Checksum
  OrganSysEx[33] = 0xF7 ;
  
  // Compute checksum
  sum = 0 ;
  for (i = 10 ; i < 32 ; i++) sum = sum + OrganSysEx[i] ;
  OrganSysEx[32] = 128 - (sum & 0x7F) ;
}

//
// Send organ flutes SysEx message to PSR/Tyros keyboard
//
void sendOrganSysEx()
{
  int i ;

  makeOrganSysEx() ;

#ifdef TRACE
      Serial.println("Send drawbar SysEx") ;
#else
  for (i = 0 ; i < 34 ; i++) {
    // Send MIDI byte
    Serial.write(OrganSysEx[i]) ;
  }
#endif
}

//
// Send a short 3-byte MIDI message. This could be any MIDI
// channel message like note ON, note OFF, continuous
// controller (CC), etc.
//
void sendMidiShort(int status, int data1, int data2)
{
#ifdef TRACE
  Serial.println("Send MIDI") ;
#else
  midiShort[0] = status ;
  midiShort[1] = data1 ;
  midiShort[2] = data2 ;
  Serial.write(status) ;
  Serial.write(data1) ;
  Serial.write(data2) ;
#endif
}

//
// Send MIDI messages to change vibrato depth
//
void sendVibratoDepth(int depth)
{
  sendMidiShort(0xB0, 0x63, 0x01) ; // NRPN MSB
  sendMidiShort(0xB0, 0x62, 0x09) ; // NRPN LSB
  
   // Data Entry MSB
  switch (depth) {
    case 0: {
      // Same as vibrato OFF
      sendMidiShort(0xB0, 0x06, VIBRATO_DEPTH_OFF) ;
      break ;
    }
    case 1: {
      sendMidiShort(0xB0, 0x06, VIBRATO_DEPTH_1) ;
      break ;
    }
    case 2: {
      sendMidiShort(0xB0, 0x06, VIBRATO_DEPTH_2) ;
      break ;
    }
    case 3: {
      sendMidiShort(0xB0, 0x06, VIBRATO_DEPTH_3) ;
      break ;
    }
  }
  sendMidiShort(0xB0, 0x26, 0x00) ; // Data Entry LSB
}

//
// Send MIDI messages to change vibrato speed
//
// The argument speed has already been converted to the
// PSR/Tyros data range 0:127.
//
void sendVibratoSpeed(int speed)
{
  sendMidiShort(0xB0, 0x63, 0x01) ; // NRPN MSB
  sendMidiShort(0xB0, 0x62, 0x08) ; // NRPN LSB 
  sendMidiShort(0xB0, 0x06, (speed&0x7F)) ; // Data Entry MSB
  sendMidiShort(0xB0, 0x26, 0x00) ; // Data Entry LSB
}


//
// Send DSP variation ON/OFF SysEx message
//
// Currently assumes that the organ flutes voice is MIDI
// channel 0 (also known as "Part 1").
//
void sendDSPVarControl(int status)
{
  dspSysEx[0] = 0xF0 ;
  dspSysEx[1] = 0x43 ;
  dspSysEx[2] = 0x10 ;
  dspSysEx[3] = 0x4C ;
  dspSysEx[4] = 0x03 ;
  dspSysEx[5] = 0x01 ;
  dspSysEx[6] = 0x25 ;
  if (status) {
    dspSysEx[7] = 0x01 ;
  } else {
    dspSysEx[7] = 0x00 ;
  }
  dspSysEx[8] = 0xF7 ;
}


//
// Display a character in the 7 segment display
//
void displayChar(int segments, int dp)
{
  if (dp) segments = (segments | sevenSegment_DP) ;
  
  digitalWrite(LATCH, LOW) ;
  shiftOut(DATA, CLOCK, MSBFIRST, ~segments) ;
  digitalWrite(LATCH, HIGH) ;
}

//
// Display a (hexa)decimal digit in the 7 segment display
//
void displayDigit(int value, int dp)
{
  int i = (value & 0x000F) ;
  int segments = sevenSegmentDigits[i] ;
  
  if (dp) segments = (segments | sevenSegment_DP) ;
  
  digitalWrite(LATCH, LOW) ;
  shiftOut(DATA, CLOCK, MSBFIRST, ~segments) ;
  digitalWrite(LATCH, HIGH) ;
}

//
// Turn on the yellow LEDs according to group
//    0: All off
//    1: Top LED (LED1) on
//    2: Bottom LED (LED2) on
//    3: Top and bottom LEDs on
//
void displayGroup(int group)
{
  switch(group & 0x3) {
    case 0: {
      digitalWrite(LED1, LOW) ;
      digitalWrite(LED2, LOW) ;
      break ;
    }
    case 1: {
      digitalWrite(LED1, HIGH) ;
      digitalWrite(LED2, LOW) ;
      break ;
    }
    case 2: {
      digitalWrite(LED1, LOW) ;
      digitalWrite(LED2, HIGH) ;
      break ;
    }
    case 3: {
      digitalWrite(LED1, HIGH) ;
      digitalWrite(LED2, HIGH) ;
      break ;
    }
  }
}

void setup()
{
  int drawbarSet = 0 ;
  
  // TRACE mode writes messages back to the PC host and is used
  // for debugging _only_. If TRACE is not defined, then the
  // program writes binary MIDI messages to the serial port.
  // Don't cross the streams!
#ifdef TRACE
  Serial.begin(9600) ;
#else
  Serial.begin(31250) ;
#endif

  // Standard DangerShield pin initialization
  pinMode(BUTTON1, INPUT) ;
  pinMode(BUTTON2, INPUT) ;
  pinMode(BUTTON3, INPUT) ;
  
  digitalWrite(BUTTON1, HIGH) ;
  digitalWrite(BUTTON2, HIGH) ;
  digitalWrite(BUTTON3, HIGH) ;
  
  pinMode(BUZZER, OUTPUT) ;
  pinMode(LED1, OUTPUT) ;
  pinMode(LED2, OUTPUT) ;
  digitalWrite(LED1, HIGH) ;
  digitalWrite(LED2, LOW) ;
  pinMode(LATCH, OUTPUT) ;
  pinMode(CLOCK, OUTPUT) ;
  pinMode(DATA, OUTPUT) ;

  // Read and map slider values to the range [0:8]
  sliderLeft = (analogRead(SLIDER2) / 114) ;
  sliderMiddle = (analogRead(SLIDER1) / 114) ;
  sliderRight = (analogRead(SLIDER0) / 114) ;

#ifdef TRACE
  Serial.println("Drawbars test") ;
#endif

  // Send initial organ set-up to the PSR/Tyros
  sendOrganSysEx() ;

  displayChar(sevenSegment_P, 0) ; delay(300) ;
  displayChar(sevenSegment_J, 0) ; delay(300) ;
  displayChar(sevenSegment_d, 0) ; delay(30) ;
}


void loop()
{
  if(!(digitalRead(BUTTON1))) // Select drawbar group
  {
    delay(200) ; // Debounce
    if (drawbarGroup >= DRAWBAR_C) drawbarGroup = DRAWBAR_A ;
    else drawbarGroup++ ;
    displayGroup(drawbarGroup) ;
    displayChar(sevenSegment_d, 0) ;
    mode = DRAWBAR_MODE ;
  }

  if(!(digitalRead(BUTTON2))) // Select effectGroup
  {
    delay(200) ; // Debounce
    if (effectGroup >= EFFECT_C) effectGroup = EFFECT_A ;
    else effectGroup++ ;
    displayGroup(effectGroup) ;
    displayChar(sevenSegment_E, 1) ;
    mode = EFFECT_MODE ;
  }
  
  if(!(digitalRead(BUTTON3))) // Select preset group
  {
    delay(200) ; // Debounce
    if (presetGroup >= PRESET_A) presetGroup = PRESET_A ;
    else presetGroup++ ;
    displayGroup(presetGroup) ;
    displayChar(sevenSegment_P, 0) ;
    mode = PRESET_MODE ;
  }

  //////////////////////////////////
  // Drawbar mode
  //////////////////////////////////
  if (mode == DRAWBAR_MODE) {
    newValue = (analogRead(SLIDER2) / 114) ;
    if (newValue != sliderLeft) {
      sliderLeft = newValue ;
      displayDigit(sliderLeft, 0) ;
#ifdef TRACE
      Serial.println("Drawbar left") ;
#endif
      switch (drawbarGroup) {
        case DRAWBAR_A: {
          drawbar_16 = sliderLeft ;
          break ;
        }
        case DRAWBAR_B: {
          drawbar_4 = sliderLeft ;
          break ;
        }
        case DRAWBAR_C: {
          drawbar_135 = sliderLeft ;
          break ;
        }
      }
    sendOrganSysEx() ;
    }
  
    newValue = (analogRead(SLIDER1) / 114) ;
    if (newValue != sliderMiddle) {
      sliderMiddle = newValue ;
      displayDigit(sliderMiddle, 0) ;
#ifdef TRACE
      Serial.println("Drawbar middle") ;
#endif
      switch (drawbarGroup) {
        case DRAWBAR_A: {
          drawbar_513 = sliderMiddle ;
          break ;
        }
        case DRAWBAR_B: {
          drawbar_223 = sliderMiddle ;
          break ;
        }
        case DRAWBAR_C: {
          drawbar_113 = sliderMiddle ;
          break ;
        }
      }
    sendOrganSysEx() ;
    }
  
    newValue = (analogRead(SLIDER0) / 114) ;
    if (newValue != sliderRight) {
      sliderRight = newValue ;
      displayDigit(sliderRight, 0) ;
#ifdef TRACE
      Serial.println("Drawbar right") ;
#endif
      switch (drawbarGroup) {
        case DRAWBAR_A: {
          drawbar_8 = sliderRight ;
          break ;
        }
        case DRAWBAR_B: {
          drawbar_2 = sliderRight ;
          break ;
        }
        case DRAWBAR_C: {
          drawbar_1 = sliderRight ;
          break ;
        }
      }
    sendOrganSysEx() ;
    }
  }

  //////////////////////////////////
  // Effect mode
  //////////////////////////////////
  else if (mode == EFFECT_MODE) {
    newValue = (analogRead(SLIDER2) / 114) ;
    if (newValue != sliderLeft) {
      sliderLeft = newValue ;
      displayDigit(sliderLeft, 1) ;
#ifdef TRACE
      Serial.println("Effect left") ;
#endif
      switch (effectGroup) {
        case EFFECT_A: {
          if (sliderLeft > 4) {
            rotarySpeed = ROTARY_SLOW ;
          } else {
            rotarySpeed = ROTARY_FAST ;
          }
          sendDSPVarControl(rotarySpeed) ;
          break ;
        }
        case EFFECT_B: {
          if (sliderLeft > 4) {
            percussionVolume = PERC_NORMAL ; 
          } else {
            percussionVolume = PERC_SOFT ;
          }
          sendOrganSysEx() ;
          break ;
        }
        case EFFECT_C: {
          // Organ volume (STUB: subject to change)
          organVolume = sliderLeft ;
          break ;
        }
      }
    }
  
    newValue = (analogRead(SLIDER1) / 114) ;
    if (newValue != sliderMiddle) {
      sliderMiddle = newValue ;
      displayDigit(sliderMiddle, 1) ;
#ifdef TRACE
      Serial.println("Effect middle") ;
#endif
      switch (effectGroup) {
        case EFFECT_A: {
          if (sliderMiddle > 4) {
            percussionStatus = PERC_OFF ; 
          } else {
            percussionStatus = PERC_ON ;
          }
          sendOrganSysEx() ;
          break ;
        }
        case EFFECT_B: {
          if (sliderMiddle > 4) {
            percussionDecay = PERC_SLOW ; 
          } else {
            percussionDecay = PERC_FAST ;
          }
          sendOrganSysEx() ;
          break ;
        }
        case EFFECT_C: {
          if (sliderMiddle < 3) vibratoDepth = 1 ;
          else if (sliderMiddle > 6) vibratoDepth = 3;
          else vibratoDepth = 2;
          if (vibratoStatus == VIBRATO_OFF) {
            sendVibratoDepth(VIBRATO_DEPTH_OFF) ;
          } else {
            sendVibratoDepth(vibratoDepth) ;
          }
          break ;
        }
      }
    }
  
    newValue = (analogRead(SLIDER0) / 114) ;
    if (newValue != sliderRight) {
      sliderRight = newValue ;
      displayDigit(sliderRight, 1) ;
#ifdef TRACE
      Serial.println("Effect right") ;
#endif
      switch (effectGroup) {
        case EFFECT_A: {
          if (sliderRight > 4) {
            vibratoStatus = VIBRATO_OFF ; 
            sendVibratoDepth(VIBRATO_DEPTH_OFF) ;
          } else {
            vibratoStatus = VIBRATO_ON ;
            sendVibratoDepth(vibratoDepth) ;
          }
          break ;
        }
        case EFFECT_B: {
          if (sliderRight > 4) {
            percussionHarmonic = PERC_THIRD ; 
          } else {
            percussionHarmonic = PERC_SECOND ;
          }
          sendOrganSysEx() ;
          break ;
        }
        case EFFECT_C: {
          vibratoSpeed = sliderRight ;
          sendVibratoSpeed(
            (vibratoSpeed*VIBRATO_SCALE)+VIBRATO_OFFSET) ;
          break ;
        }
      }
    }
  }

  //////////////////////////////////
  // Preset mode
  //////////////////////////////////
  else if (mode == PRESET_MODE) {
    // Allow nine presets
    newValue = (analogRead(SLIDER2) / 114) ;
    if (newValue != sliderLeft) {
      sliderLeft = newValue ;
      displayDigit(sliderLeft, 0) ;
#ifdef TRACE
      Serial.println("Preset left") ;
#endif
    }
  }
}