//
// 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
}
}
}