// // CAmiDion-3 ver.20121018 // #define CAMIDION_2 // Old CAmiDion matrix connection //#define USE_LCD //#define USE_74164_FOR_LCD #define USE_MIDI #define PWMDAC_OUTPUT_PIN 3 #include // 74HC165 PISO / 74HC595 SIPO #define SHIFTIN_DATA_PIN 4 // 74HC165 QH (pin#9) #define SHIFT_CLOCK_PIN 5 // 74HC165 CLK (pin#2) / 74HC595 SCK (pin#11) #define SHIFT_LATCH_PIN 6 // 74HC165 SH/~LD (pin#1) / 74HC595 RCK (pin#12) #define SHIFTOUT_DATA_PIN 7 // 74HC595 SER (pin#14) #define OCTAVE_ANALOG_PIN 0 class Co5Note { public: char co5_value; // Circle of Fifths value char note_value; // Chromatic note value Co5Note() { note_value = co5_value = 0; }; Co5Note(char co5) { co5_value = co5; updateNote(); } void updateNote() { if( (note_value = co5_value) & 1 ) note_value += 6; if( (note_value %= 12) < 0 ) note_value += 12; } }; typedef struct _MusicalChord { char root_co5; // root note in "hour" on Circle-of-fifths clock char offset3; // offset of major 3rd char offset5; // offset of perfect 5th char offset7; // 0:none -1:M7 -2:7th -3:6th/dim7th boolean has9; // add9 extended } MusicalChord; ////////// MIDI I/O //////////// #ifdef USE_MIDI #include #define MIDI_ENABLE_PIN 2 #endif // USE_MIDI ////////// LCD ///////////// #ifdef USE_LCD #ifdef USE_74164_FOR_LCD #include #include #define LCD_PARENT_CLASS Lcd74HC164 // data,clock,enable #define LCD_CONSTRUCTOR_ARGS 9,10,11 #else // USE_74164_FOR_LCD #include #define LCD_PARENT_CLASS LiquidCrystal // rs,enable,d4,d5,d6,d7 #define LCD_CONSTRUCTOR_ARGS 8,9,10,11,12,13 #endif // USE_74164_FOR_LCD #define CHARCODE_NATURAL 1 #define CHARCODE_SAWTOOTH_LEFT 2 #define CHARCODE_SAWTOOTH_RIGHT 3 #define CHARCODE_SQUARE_UP 4 #define CHARCODE_SQUARE_DOWN 5 #define CHARCODE_SINE_UP 6 #define CHARCODE_SINE_DOWN 7 #define CHARCODE_RANDOM 8 class CAmiDionLCD : public LCD_PARENT_CLASS { protected: byte columns; char line_buf[32]; char *bufp; MusicalChord current_chord; public: CAmiDionLCD() : LCD_PARENT_CLASS( LCD_CONSTRUCTOR_ARGS ) { // Nothing to do } void begin(byte cols, byte rows) { LCD_PARENT_CLASS::begin(cols,rows); byte sawtooth_left[8] = { B00000, B00000, B00000, B00011, B01100, B10000, B00000, }; byte sawtooth_right[8] = { B00011, B01101, B10001, B00001, B00001, B00001, B00001, }; byte square_up[8] = { B00111, B00100, B00100, B00100, B00100, B00100, B11100, }; byte square_down[8] = { B11100, B00100, B00100, B00100, B00100, B00100, B00111, }; byte sine_up[8] = { B00110, B01001, B10000, B10000, B00000, B00000, B00000, }; byte sine_down[8] = { B00000, B00000, B00000, B10000, B10000, B01001, B00110, }; byte random_pattern[8] = { B10101, B01010, B10101, B01010, B10101, B01010, B10101, }; byte natural[8] = { B01000, B01000, B01110, B01010, B01110, B00010, B00010, }; createChar(CHARCODE_SAWTOOTH_LEFT, sawtooth_left); createChar(CHARCODE_SAWTOOTH_RIGHT, sawtooth_right); createChar(CHARCODE_SQUARE_UP, square_up); createChar(CHARCODE_SQUARE_DOWN, square_down); createChar(CHARCODE_SINE_UP, sine_up); createChar(CHARCODE_SINE_DOWN, sine_down); createChar(CHARCODE_RANDOM, random_pattern); createChar(CHARCODE_NATURAL, natural); columns = min(sizeof(line_buf)-1,cols); setCursor(0,0); print("*** CAmiDion ***"); clearLineBuffer(); clearChord(); } void clearChord() { current_chord.root_co5 = 0x7F; } void clearLineBuffer() { memset( line_buf, ' ', columns ); bufp = line_buf; } byte printLineBuffer() { line_buf[columns+1] = '\0'; print(line_buf); clearLineBuffer(); } void setChar(char c) { *bufp++ = c; } void setString(char *str, byte len) { memcpy( bufp, str, len ); bufp += len; } void setString(char *str) { setString(str,strlen(str)); } void setNote(char co5) { *bufp++ = "FCGDAEB"[ (co5 += 15) % 7 ]; switch(co5 / 7) { case 2: break; case 3: *bufp++ = '#'; break; // sharp case 0: *bufp++ = 'b'; /* FALLTHROUGH */ // double flat case 1: *bufp++ = 'b'; break; // flat case 4: *bufp++ = 'x'; break; // double sharp } } void printKeySignature(char key_signature_value, boolean is_changing ) { setString("Key",3); *bufp++ = (is_changing ? '>' : ':'); char n = abs(key_signature_value); char b = key_signature_value < 0 ? 'b':'#'; if( n == 0 ) *bufp++ = CHARCODE_NATURAL; else if( n < 5 ) do { *bufp++ = b; } while(--n); else { *bufp++ = '0'+n; *bufp++ = b; } *bufp++ = '('; setNote(key_signature_value); *bufp++ = '/'; setNote(key_signature_value + 3); *bufp++ = 'm'; *bufp++ = ')'; setCursor(0,1); printLineBuffer(); } void printChord(struct _MusicalChord chord) { if( ! memcmp( ¤t_chord, &chord, sizeof(chord) ) ) return; current_chord = chord; setString("Chord:",6); setNote(chord.root_co5); if( chord.offset3 < 0 && chord.offset5 < 0 && chord.offset7 == -3 ) { setString("dim",3); *bufp++ = (chord.has9 ? '9':'7'); } else { if( chord.offset3 < 0 ) *bufp++ = 'm'; if( chord.offset5 > 0 ) setString("aug",3); switch( chord.offset7 ) { case 0: if(chord.has9) setString("add9",4); break; case -1: *bufp++ = 'M'; /* FALLTHROUGH */ case -2: *bufp++ = (chord.has9 ? '9':'7'); break; case -3: *bufp++ = '6'; if(chord.has9) *bufp++ = '9'; break; } if( chord.offset3 > 0 ) setString("sus4",4); if( chord.offset5 < 0 ) setString("(-5)",4); } setCursor(0,0); printLineBuffer(); } void printWaveform( char midi_channel, PROGMEM const char *pmstr, boolean channel_is_changing ) { *bufp++ = 'C'; *bufp++ = 'h'; if( midi_channel >= 10 ) { *bufp++ = '1'; midi_channel -= 10; } *bufp++ = midi_channel + '0'; *bufp++ = ( channel_is_changing ? '<' : ':' ); size_t len = strlen_P(pmstr); memcpy_P( bufp, pmstr, len ); bufp += len; setCursor(0,0); printLineBuffer(); clearChord(); } }; CAmiDionLCD lcd = CAmiDionLCD(); #endif // USE_LCD ////////////////////////////// // LED buffer (in HEX) // // CAmiDion-3: // 3 5 7 A C E // 0 1 2 4 6 8 9 B D F // // CAmiDion-2: // C 1 3 6 8 A // D E F 0 2 4 5 7 9 B ////////////////////////////// typedef union _LEDs { unsigned int value16; byte values8[2]; } LEDs; #ifdef CAMIDION_2 LEDs led_main = {0x8000}; LEDs led_key = {0x0001}; #define LED_NOTE_ON(led,pitch) ((led).value16 |= (0x0001 << (pitch))) #define LED_NOTE_OFF(led,pitch) ((led).value16 &= ~(0x0001 << (pitch))) #define LED_ALL_NOTES_OFF(led) ((led).value16 &= 0xF000) #define LED_LEFT_ON(led) sbi((led).values8[1],5) #define LED_LEFT_OFF(led) cbi((led).values8[1],5) #define LED_CENTER_ON(led) sbi((led).values8[1],6) #define LED_CENTER_OFF(led) cbi((led).values8[1],6) #define LED_RIGHT_ON(led) sbi((led).values8[1],7) #define LED_RIGHT_OFF(led) cbi((led).values8[1],7) #define LED_UPPER_ON(led) sbi((led).values8[1],4) #define LED_UPPER_OFF(led) cbi((led).values8[1],4) #else LEDs led_main = {0x0004}; LEDs led_key = {0x0010}; #define LED_NOTE_ON(led,pitch) ((led).value16 |= (0x0010 << (pitch))) #define LED_NOTE_OFF(led,pitch) ((led).value16 &= ~(0x0010 << (pitch))) #define LED_ALL_NOTES_OFF(led) ((led).value16 &= 0x000F) #define LED_LEFT_ON(led) sbi((led).values8[0],0) #define LED_LEFT_OFF(led) cbi((led).values8[0],0) #define LED_CENTER_ON(led) sbi((led).values8[0],1) #define LED_CENTER_OFF(led) cbi((led).values8[0],1) #define LED_RIGHT_ON(led) sbi((led).values8[0],2) #define LED_RIGHT_OFF(led) cbi((led).values8[0],2) #define LED_UPPER_ON(led) sbi((led).values8[0],3) #define LED_UPPER_OFF(led) cbi((led).values8[0],3) #endif LEDs led_ctrl = {0x0001}; // 0..15 = MIDI Channel 1..16 #define LED_SHOW_MIDI_CHANNEL(ch0) (led_ctrl.value16 = (1 << (ch0))); LEDs *led = &led_main; // LED buffer to display ///////////////////// // Waveform manager ///////////////////// PROGMEM const byte guitarWavetable[] = { 23, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 28, 29, 30, 32, 33, 34, 35, 36, 36, 36, 36, 36, 37, 37, 37, 38, 39, 39, 40, 40, 40, 40, 40, 39, 39, 40, 40, 40, 41, 41, 41, 41, 40, 41, 41, 41, 41, 42, 42, 42, 42, 41, 41, 41, 41, 42, 41, 41, 40, 40, 40, 39, 39, 38, 37, 37, 37, 37, 36, 36, 36, 35, 34, 33, 32, 31, 30, 30, 30, 30, 30, 30, 30, 30, 29, 28, 27, 27, 27, 26, 26, 26, 26, 25, 24, 24, 23, 23, 23, 23, 22, 22, 22, 22, 21, 21, 21, 21, 21, 21, 21, 20, 21, 20, 20, 19, 19, 18, 18, 18, 18, 18, 18, 18, 18, 17, 16, 16, 15, 14, 14, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 17, 17, 17, 17, 17, 17, 18, 18, 17, 17, 16, 15, 15, 14, 14, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 15, 15, 15, 15, 15, 14, 14, 13, 12, 11, 10, 9, 9, 9, 9, 8, 8, 8, 7, 6, 6, 5, 4, 3, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 3, 3, 2, 2, 2, 2, 2, 1, 2, 2, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7, 9, 10, 11, 11, 12, 12, 13, 13, 14, 14, 14, 15, 15, 16, 17, 17, 18, 19, 20, 21, 21, 22, }; PROGMEM const byte randomWavetable[] = { 39, 22, 21, 9, 23, 13, 28, 31, 15, 30, 40, 8, 29, 26, 27, 8, 34, 30, 4, 22, 39, 25, 35, 33, 38, 17, 7, 38, 18, 24, 12, 9, 8, 36, 27, 17, 33, 0, 13, 35, 20, 15, 34, 0, 34, 6, 29, 38, 30, 20, 16, 39, 26, 18, 28, 28, 24, 38, 27, 31, 25, 14, 9, 25, 31, 13, 3, 17, 29, 23, 18, 6, 12, 20, 30, 27, 1, 40, 9, 19, 26, 21, 4, 25, 1, 16, 11, 18, 15, 23, 30, 7, 37, 23, 11, 19, 30, 36, 7, 9, 17, 27, 8, 41, 4, 9, 26, 0, 24, 18, 6, 15, 30, 23, 7, 9, 9, 41, 1, 0, 33, 9, 34, 18, 19, 22, 25, 16, 22, 31, 41, 21, 27, 35, 38, 32, 17, 16, 10, 39, 36, 9, 37, 13, 16, 12, 10, 14, 21, 12, 19, 12, 11, 19, 0, 32, 13, 27, 32, 10, 18, 5, 22, 9, 6, 33, 29, 2, 4, 2, 17, 0, 38, 14, 18, 16, 25, 10, 8, 3, 3, 39, 3, 5, 7, 22, 6, 11, 16, 23, 23, 5, 3, 23, 22, 25, 17, 7, 19, 29, 41, 0, 10, 15, 14, 41, 6, 40, 8, 0, 19, 16, 30, 18, 2, 24, 17, 37, 11, 6, 1, 33, 10, 31, 33, 19, 20, 23, 24, 41, 4, 40, 29, 12, 4, 11, 38, 17, 41, 8, 15, 37, 19, 34, 36, 8, 19, 10, 32, 14, 19, 1, 1, 35, 35, 3, }; PROGMEM const byte *wavetables[] = { PWM_SYNTH.sineWavetable, PWM_SYNTH.triangleWavetable, guitarWavetable, PWM_SYNTH.sawtoothWavetable, PWM_SYNTH.squareWavetable, randomWavetable, }; #ifdef USE_LCD PROGMEM const char str_sine_wave[] = { CHARCODE_SINE_UP, CHARCODE_SINE_DOWN, CHARCODE_SINE_UP, CHARCODE_SINE_DOWN, CHARCODE_SINE_UP, CHARCODE_SINE_DOWN, CHARCODE_SINE_UP, CHARCODE_SINE_DOWN, CHARCODE_SINE_UP, CHARCODE_SINE_DOWN, '\0' }; PROGMEM const char str_triangle_wave[] = { '/',0xCD, '/',0xCD, '/',0xCD, '/',0xCD, '/',0xCD, '\0' }; PROGMEM const char str_guitar[] = "Guitar"; PROGMEM const char str_sawtooth_wave[] = { CHARCODE_SAWTOOTH_LEFT, CHARCODE_SAWTOOTH_RIGHT, CHARCODE_SAWTOOTH_LEFT, CHARCODE_SAWTOOTH_RIGHT, CHARCODE_SAWTOOTH_LEFT, CHARCODE_SAWTOOTH_RIGHT, CHARCODE_SAWTOOTH_LEFT, CHARCODE_SAWTOOTH_RIGHT, CHARCODE_SAWTOOTH_LEFT, CHARCODE_SAWTOOTH_RIGHT, '\0' }; PROGMEM const char str_square_wave[] = { CHARCODE_SQUARE_UP, CHARCODE_SQUARE_DOWN, CHARCODE_SQUARE_UP, CHARCODE_SQUARE_DOWN, CHARCODE_SQUARE_UP, CHARCODE_SQUARE_DOWN, CHARCODE_SQUARE_UP, CHARCODE_SQUARE_DOWN, CHARCODE_SQUARE_UP, CHARCODE_SQUARE_DOWN, '\0' }; PROGMEM const char str_ramdom_wave[] = { CHARCODE_RANDOM, CHARCODE_RANDOM, CHARCODE_RANDOM, CHARCODE_RANDOM, CHARCODE_RANDOM, CHARCODE_RANDOM, CHARCODE_RANDOM, CHARCODE_RANDOM, CHARCODE_RANDOM, CHARCODE_RANDOM, '\0' }; PROGMEM const char *wavetable_names[] = { str_sine_wave, str_triangle_wave, str_guitar, str_sawtooth_wave, str_square_wave, str_ramdom_wave, }; #endif byte current_midi_channel = 1; // 1==CH1, ... class WaveformController { protected: byte selected_waveforms[16]; byte *selected_waveform; public: WaveformController() { memset( selected_waveforms, 0, sizeof(selected_waveforms) ); selected_waveform = selected_waveforms + current_midi_channel - 1; } void show(boolean channel_is_changing) { if( *selected_waveform ) LED_LEFT_ON(led_main); else LED_LEFT_OFF(led_main); #ifdef USE_LCD lcd.printWaveform( current_midi_channel, (PROGMEM const char *)pgm_read_word( wavetable_names + *selected_waveform ), channel_is_changing ); #endif } void midiChannelChanged() { selected_waveform = selected_waveforms + current_midi_channel - 1; show(true); } void next() { selected_waveform = selected_waveforms + current_midi_channel - 1; if( ++*selected_waveform >= NumberOf(wavetables) ) *selected_waveform = 0; PWM_SYNTH.setWave( current_midi_channel, (byte *)pgm_read_word(wavetables + *selected_waveform) ); show(false); } }; WaveformController waveform = WaveformController(); PROGMEM EnvelopeParam ADSR_patterns[] = { {0x1800, 7, 0xFFFF, 3}, {0x0800, 7, 0x8000, 7}, {0x1800, 7, 0x3000, 7}, {0x2000, 6, 0x3000, 6}, {0x4000, 4, 0x0000, 4}, {0x8000, 3, 0x0000, 3}, }; class ADSRStatusManager { // ADSR envelope pattern manager protected: byte ADSR_pattern_by_channel[16]; byte *selected_adsr; void showInternal() { if( *selected_adsr ) LED_CENTER_ON(led_main); else LED_CENTER_OFF(led_main); } public: ADSRStatusManager() { memset( ADSR_pattern_by_channel, 0, sizeof(ADSR_pattern_by_channel) ); selected_adsr = ADSR_pattern_by_channel + current_midi_channel - 1; } void clear() { EnvelopeParam ep; memcpy_P( &ep, ADSR_patterns, sizeof(EnvelopeParam) ); PWM_SYNTH.setEnvelope(ep); // All MIDI channels } void midiChannelChanged() { selected_adsr = ADSR_pattern_by_channel + current_midi_channel - 1; showInternal(); } void next() { selected_adsr = ADSR_pattern_by_channel + current_midi_channel - 1; if( ++*selected_adsr >= NumberOf(ADSR_patterns) ) *selected_adsr = 0; EnvelopeParam ep; memcpy_P( &ep, ADSR_patterns + *selected_adsr, sizeof(EnvelopeParam) ); PWM_SYNTH.setEnvelope( current_midi_channel, ep ); showInternal(); } }; ADSRStatusManager adsr = ADSRStatusManager(); void changeMidiChannel(char offset) { char ch0 = current_midi_channel + offset - 1; current_midi_channel = (ch0 &= 0xF) + 1; LED_SHOW_MIDI_CHANNEL(ch0); waveform.midiChannelChanged(); adsr.midiChannelChanged(); } // Number of activated notes char note_on_counts[] = {0,0, 0,0, 0, 0,0, 0,0, 0,0, 0}; // // MIDI IN receive callbacks to control PWM sound void HandleNoteOff(byte channel, byte pitch, byte velocity) { PWM_SYNTH.noteOff(channel,pitch,velocity); char *cp = note_on_counts + (pitch %= 12); if( *cp > 0 ) --*cp; if( *cp <= 0 ) LED_NOTE_OFF( led_main, pitch ); } void HandleNoteOn(byte channel, byte pitch, byte velocity) { if( velocity == 0 ) { HandleNoteOff(channel,pitch,velocity); return; } PWM_SYNTH.noteOn(channel,pitch,velocity); char *cp = note_on_counts + (pitch %= 12); if( *cp < 0 ) *cp = 0; if( ++*cp > 0 ) LED_NOTE_ON( led_main, pitch ); } #ifdef USE_MIDI void setupMIDI() { MIDI.begin(MIDI_CHANNEL_OMNI); // receives all MIDI channels MIDI.turnThruOff(); // Disable MIDI IN -> MIDI OUT mirroring MIDI.setHandleNoteOff(HandleNoteOff); MIDI.setHandleNoteOn(HandleNoteOn); MIDI.setHandlePitchBend(PWM_SYNTH.pitchBend); MIDI.setHandleControlChange(PWM_SYNTH.controlChange); pinMode(MIDI_ENABLE_PIN,OUTPUT); digitalWrite(MIDI_ENABLE_PIN,HIGH); // enable MIDI port } #endif //////////////////////////// // Circle-of-5ths control // //////////////////////////// class Co5Key : public Co5Note { public: void shift(char offset, char v_min = -7, char v_max = 7) { co5_value += offset; if ( co5_value > v_max ) co5_value -= 12; else if( co5_value < v_min ) co5_value += 12; updateNote(); } void updateNote(boolean change = true) { Co5Note::updateNote(); LED_ALL_NOTES_OFF( led_key ); LED_NOTE_ON( led_key, note_value ); #ifdef USE_LCD lcd.printKeySignature(co5_value,change); #endif } }; Co5Key key_signature = Co5Key(); //////////////////// // Octave control // //////////////////// class NoteRange { protected: int min_note; int max_note; public: NoteRange(int chromatic_offset, int analog_value) { int fullrange_min_note = 0; int fullrange_max_note = 127 - 12; if( chromatic_offset ) { fullrange_min_note += chromatic_offset; fullrange_max_note += chromatic_offset; } min_note = map( analog_value, 0, 1023, fullrange_min_note, fullrange_max_note ); if( min_note > 127 - 12 || min_note < 0 ) { min_note -= chromatic_offset; } max_note = min_note + 11; } int shiftOctave(int note) { if( max_note < note ) { int octaves = (note - max_note) / 12; octaves++; note -= 12 * octaves; } else if( min_note > note ) { int octaves = (min_note - note - 1) / 12; octaves++; note += 12 * octaves; } return note; } }; ///////////////////////////// // Button control ///////////////////////////// //////////////////////////////////////////////////////////////////// // Button matrix anode6-cathode8 // // CAmiDion2: // 4-5 | 5-5 5-6 5-7 | // 3-5 4-6 | 2-0 2-1 2-2 2-3 2-4 2-5 2-6 2-7 | 5-0 5-1 5-2 5-3 5-4 // 3-6 4-7 | 1-0 1-1 1-2 1-3 1-4 1-5 1-6 1-7 | 4-0 4-1 4-2 4-3 4-4 // 3-7 | 0-0 0-1 0-2 0-3 0-4 0-5 0-6 0-7 | 3-0 3-1 3-2 3-3 3-4 // // CAmiDion3 (recommended): // 1-0 | 2-0 2-1 2-2 // 0-0 1-1 2-3 2-4 2-5 2-6 2-7 | 5-0 5-1 5-2 5-3 5-4 5-5 5-6 5-7 // 0-1 1-2 1-3 1-4 1-5 1-6 1-7 | 4-0 4-1 4-2 4-3 4-4 4-5 4-6 4-7 // 0-2 0-3 0-4 0-5 0-6 0-7 | 3-0 3-1 3-2 3-3 3-4 3-5 3-6 3-7 // //////////////////////////////////////////////////////////////////// typedef struct _Button { byte cathode8; byte anode6; } Button; class ButtonNote { protected: void clear() { midi_channel = button.cathode8 = button.anode6 = 0xFF; n_notes = 0; if( notes == NULL ) return; free(notes); notes = NULL; } public: Button button; byte n_notes; char *notes; byte midi_channel; ButtonNote() { notes = NULL; clear(); } char *reserve(size_t sz, Button b, byte midi_channel) { notes = (char *)malloc( sz * sizeof(char) ); if( notes == NULL ) return NULL; n_notes = sz; button = b; this->midi_channel = midi_channel; return notes; } void noteOn() { char n = n_notes; char *notep = notes; for( ; n>0; n--, notep++ ) { HandleNoteOn(midi_channel, *notep, 100); #ifdef USE_MIDI MIDI.sendNoteOn(*notep, 100, midi_channel); #endif } } void noteOff() { char n = n_notes; char *notep = notes; for( ; n>0; n--, notep++ ) { HandleNoteOff(midi_channel, *notep, 100); #ifdef USE_MIDI MIDI.sendNoteOff(*notep, 100, midi_channel); #endif } clear(); } }; class ButtonNotes { protected: ButtonNote button_notes[8]; public: ButtonNotes() { ButtonNote *bnp = button_notes; char i=NumberOf(button_notes); for( ; i>0; i--, bnp++ ) *bnp = ButtonNote(); } ButtonNote *search(struct _Button *button_p) { ButtonNote *bnp = button_notes; char i=NumberOf(button_notes); for( ; i>0; i--, bnp++ ) if( bnp->button.cathode8 == button_p->cathode8 && bnp->button.anode6 == button_p->anode6 ) return bnp; return NULL; } ButtonNote *search() { ButtonNote *bnp = button_notes; char i=NumberOf(button_notes); for( ; i>0; i--, bnp++ ) if( bnp->midi_channel == 0xFF ) return bnp; return NULL; } void turnOff(struct _Button *button_p) { ButtonNote *bnp = search(button_p); if( bnp != NULL ) bnp->noteOff(); } }; ButtonNotes button_notes = ButtonNotes(); #ifdef CAMIDION_2 #define ANODE_OFFSET 3 #define CATHODE_OFFSET 5 #else #define ANODE_OFFSET 0 #define CATHODE_OFFSET 0 #endif class ShiftButtonStatus { public: char offset5; // -1:dim(-5) 0:P5 +1:aug(+5) char offset7; // 0:octave -1:M7 -2:7th -3:6th(dim7) boolean has9; // add9 boolean ctrl; boolean key; boolean held; Button held_button; boolean is_onepush_chord_mode; ShiftButtonStatus() { offset5 = offset7 = 0; has9 = ctrl = key = held = false; held_button.cathode8 = held_button.anode6 = 0xFF; is_onepush_chord_mode = true; } void toggleHoldingStatus() { held = !held; if( held ) { LED_UPPER_ON(led_key); LED_UPPER_ON(led_main); } else { button_notes.turnOff(&held_button); held_button.cathode8 = held_button.anode6 = 0xFF; LED_UPPER_OFF(led_key); LED_UPPER_OFF(led_main); } } void buttonChanged(struct _Button *button_p) { button_notes.turnOff(&held_button); held_button = *button_p; } void buttonPressed(struct _Button *button_p) { switch(button_p->anode6) { case ANODE_OFFSET: switch(button_p->cathode8) { case CATHODE_OFFSET: ctrl = true; led = &led_ctrl; changeMidiChannel(0); #ifdef USE_LCD waveform.show(true); #endif break; case CATHODE_OFFSET + 1: has9 = true; break; case CATHODE_OFFSET + 2: offset5 = -1; break; } break; case ANODE_OFFSET + 1: switch(button_p->cathode8) { case CATHODE_OFFSET: key = true; led = &led_key; #ifdef USE_LCD lcd.printKeySignature( key_signature.co5_value, true ); #endif break; default: offset7 -= button_p->cathode8 - CATHODE_OFFSET; break; } break; case ANODE_OFFSET + 2: switch(button_p->cathode8) { case CATHODE_OFFSET: waveform.next(); break; case CATHODE_OFFSET + 1: if(key) toggleHoldingStatus(); else adsr.next(); break; case CATHODE_OFFSET + 2: is_onepush_chord_mode = !is_onepush_chord_mode; #ifdef USE_LCD lcd.setCursor(0,0); #endif if( is_onepush_chord_mode ) { LED_RIGHT_ON(led_main); #ifdef USE_LCD lcd.setString("Chord:",6); lcd.printLineBuffer(); #endif } else { LED_RIGHT_OFF(led_main); #ifdef USE_LCD waveform.show(false); #endif } break; } break; } } void buttonReleased(struct _Button *button_p) { switch(button_p->anode6) { case ANODE_OFFSET: switch(button_p->cathode8) { case CATHODE_OFFSET: ctrl = false; led = &led_main; #ifdef USE_LCD waveform.show(false); #endif break; case CATHODE_OFFSET + 1: has9 = false; break; case CATHODE_OFFSET + 2: offset5 = 0; break; } break; case ANODE_OFFSET + 1: switch(button_p->cathode8) { case CATHODE_OFFSET: key = false; led = &led_main; #ifdef USE_LCD lcd.printKeySignature( key_signature.co5_value, false ); #endif break; default: offset7 += button_p->cathode8 - CATHODE_OFFSET; break; } break; } } }; ShiftButtonStatus shift_button_status = ShiftButtonStatus(); void ButtonPressed(struct _Button *button_p) { // convert cathode8(0..7) x sw_anode6(0..5) // to x_pos(-6..+6) x y_pos(-1,0,+1); char x_pos, y_pos; #ifdef CAMIDION_2 if( button_p->anode6 < 3 ) { x_pos = button_p->cathode8 - 6; y_pos = button_p->anode6 - 1; } else if( button_p->cathode8 < 5 ) { x_pos = button_p->cathode8 + 2; y_pos = button_p->anode6 - 4; } #else if( button_p->anode6 >= 3 ) { x_pos = button_p->cathode8 - 1; y_pos = button_p->anode6 - 4; } else if( button_p->cathode8 >= 3 ) { x_pos = button_p->cathode8 - 9; y_pos = button_p->anode6 - 1; } #endif else { shift_button_status.buttonPressed(button_p); return; } if( shift_button_status.ctrl ) { if( y_pos ) changeMidiChannel(y_pos); return; } if( shift_button_status.key ) { if( x_pos ) key_signature.shift(x_pos); else if( y_pos ) key_signature.shift(y_pos * 7, -5, 6); return; } if( shift_button_status.held ) { shift_button_status.buttonChanged(button_p); } ButtonNote *bnp = button_notes.search(); if( bnp == NULL ) return; // Button note table full - reject it MusicalChord chord; chord.root_co5 = key_signature.co5_value + x_pos; if( (chord.offset3 = y_pos) < 0 ) chord.root_co5 += 3; Co5Note root_co5 = Co5Note(chord.root_co5); int analog_value = analogRead(OCTAVE_ANALOG_PIN); if( ! shift_button_status.is_onepush_chord_mode ) { // Single note if( bnp->reserve( 1, *button_p, current_midi_channel ) == NULL ) return; NoteRange range = NoteRange( (y_pos==1?12:0), analog_value ); *(bnp->notes) = range.shiftOctave(root_co5.note_value); } else { // Multiple notes (chord) if( bnp->reserve( 6, *button_p, current_midi_channel ) == NULL ) return; chord.offset5 = shift_button_status.offset5; if( chord.offset5 == -1 && chord.offset3 == 1 ) { // sus4 -5 chord.offset3 = 0; // Cancel sus4 chord.offset5 = 1; // Set +5 (aug) } chord.offset7 = shift_button_status.offset7; chord.has9 = shift_button_status.has9; NoteRange range = NoteRange(0,analog_value); NoteRange high_range = NoteRange(12,analog_value); char *notep = bnp->notes; *notep = high_range.shiftOctave( root_co5.note_value ); *++notep = high_range.shiftOctave( root_co5.note_value + 4 + chord.offset3 ); *++notep = high_range.shiftOctave( root_co5.note_value + 7 + chord.offset5 ); if( chord.offset7 ) *++notep = high_range.shiftOctave( root_co5.note_value + 12 + chord.offset7 ); else *++notep = range.shiftOctave( root_co5.note_value + 7 + chord.offset5 ); if( chord.has9 ) *++notep = high_range.shiftOctave( root_co5.note_value + 2 ); else *++notep = range.shiftOctave( root_co5.note_value + 4 + chord.offset3 ); *++notep = range.shiftOctave( root_co5.note_value ); } bnp->noteOn(); #ifdef USE_LCD if( shift_button_status.is_onepush_chord_mode ) lcd.printChord(chord); #endif } void setup() { #ifdef USE_LCD lcd.begin(16,2); #endif PWM_SYNTH.setup(); adsr.clear(); pinMode(SHIFTOUT_DATA_PIN,OUTPUT); pinMode(SHIFT_CLOCK_PIN,OUTPUT); pinMode(SHIFT_LATCH_PIN,OUTPUT); pinMode(SHIFTIN_DATA_PIN,INPUT); #ifdef USE_MIDI setupMIDI(); #endif #ifdef USE_LCD key_signature.updateNote(false); #endif } void loop() { static byte current_anode6_bits[]= { 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, }; Button button; byte cathode8_mask = 1; for( button.cathode8 = 0; button.cathode8 < 8; button.cathode8++ ) { // // Send [000LLCCC] data to 74HC595 shift register // CCC = code to 74HC138 3-to-8 decoder connected to LED/switch cathode // LL = bit to LED anode // |+-- LED 0..7 (left center right up C C# D Eb) // +--- LED 8..15 (E F F# G Ab A Bb B) // if( led->values8[0] & cathode8_mask ) button.cathode8 |= 0b00001000; if( led->values8[1] & cathode8_mask ) button.cathode8 |= 0b00010000; digitalWrite( SHIFT_LATCH_PIN, LOW ); // RCLK OFF shiftOut( SHIFTOUT_DATA_PIN, SHIFT_CLOCK_PIN, MSBFIRST, button.cathode8 ); digitalWrite( SHIFT_LATCH_PIN, HIGH ); // RCLK OFF -> ON button.cathode8 &= 0b00000111; // // Read button status (anode-side 6-bit) from 74HC165 shift register digitalWrite( SHIFT_LATCH_PIN, LOW ); // load button status to 74HC165 digitalWrite( SHIFT_LATCH_PIN, HIGH ); // switch to SHIFT mode button.anode6 = 7; byte anode6_bits = 0; while(true) { anode6_bits |= digitalRead(SHIFTIN_DATA_PIN); // 1:ButtonOFF 0:ButtonON if( button.anode6 == 0 ) break; digitalWrite( SHIFT_CLOCK_PIN, HIGH ); // rise to shift anode6_bits <<= 1; button.anode6--; digitalWrite( SHIFT_CLOCK_PIN, LOW ); } byte anode6_change = anode6_bits ^ current_anode6_bits[button.cathode8]; if( anode6_change ) { current_anode6_bits[button.cathode8] = anode6_bits; byte anode6_mask = 1; for( button.anode6 = 0; button.anode6 < 8; button.anode6++ ) { if( anode6_change & anode6_mask ) { if( anode6_bits & anode6_mask ) { #ifdef CAMIDION_2 if( button.cathode8 < 5 || button.anode6 < 3 ) #else if( button.cathode8 >= 3 || button.anode6 >= 3 ) #endif { if( ! shift_button_status.held ) button_notes.turnOff(&button); } else shift_button_status.buttonReleased(&button); } else ButtonPressed(&button); } anode6_mask <<= 1; } } PWM_SYNTH.updateEnvelopeStatus(); cathode8_mask <<= 1; } #ifdef USE_MIDI MIDI.read(); #endif }