// // CAmiDion-2/3/4 ver.20130113 // #define CAMIDION_VERSION 2 // Function enable/disable #define USE_LED #define USE_MIDI_IN #define USE_MIDI_OUT #if CAMIDION_VERSION != 4 #define USE_LCD //#define USE_74164_FOR_LCD #define USE_OCTAVE_SHIFT #endif #if CAMIDION_VERSION == 2 #define USE_I2C_8x2_LCD // SB0802G Strawberry Linux I2C 8x2 LCD #endif // // MIDI #if defined(USE_MIDI_IN) || defined(USE_MIDI_OUT) #define USE_MIDI #include #define MIDI_ENABLE_PIN 2 #endif // // PWM DAC Synthesizer lib #define PWMDAC_OUTPUT_PIN 3 #include "PWMDAC_Synth.h" #if CAMIDION_VERSION == 3 // Matrix keyboard / LED // 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) #endif #ifdef USE_OCTAVE_SHIFT // Analog IN for octave shift #define OCTAVE_ANALOG_PIN 0 #endif /************************************************* * Musical classes */ class MusicalNote { protected: char co5_value; // Circle of Fifths value char note_value; // Chromatic note value public: MusicalNote() { note_value = co5_value = 0; } MusicalNote(char co5) { setCo5(co5); } void setCo5(char co5) { co5_value = co5; note_value = PWMDACSynth::musicalMod12( co5 + (co5 & 1) * 6 ); } char getCo5() { return co5_value; } char getNote() { return note_value; } }; class KeySignature : public MusicalNote { public: void shift(char offset, char v_min = -7, char v_max = 7) { offset += co5_value; if ( offset > v_max ) offset -= 12; else if( offset < v_min ) offset += 12; MusicalNote::setCo5(offset); } }; class MusicalChord : public MusicalNote { public: 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() : MusicalNote() { offset3 = offset5 = offset7 = 0; has9 = false; } MusicalChord(KeySignature key_sig, char offset, char offset3) { offset += key_sig.getCo5(); if( (this->offset3 = offset3) < 0 ) offset += 3; MusicalNote::setCo5(offset); offset5 = offset7 = 0; has9 = false; } char get3rdNote() { return note_value + 4 + offset3; } char get5thNote() { return note_value + 7 + offset5; } char get7thNote() { return note_value + 12 + offset7; } }; ////////// LCD ///////////// #if defined(USE_74164_FOR_LCD) || defined(USE_I2C_8x2_LCD) #define USE_LCD #endif #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 #elif defined(USE_I2C_8x2_LCD) #include #include #define LCD_PARENT_CLASS I2CLiquidCrystal // contrast(0-63),5V/3.3V #define LCD_CONSTRUCTOR_ARGS 63,true #else #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 2 #define CHARCODE_SQUARE_DOWN 3 #define CHARCODE_SINE_UP 2 #define CHARCODE_SINE_DOWN 3 #define CHARCODE_RANDOM 2 #define CHARCODE_BACKSLASH 2 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[] = { '/',CHARCODE_BACKSLASH, '/',CHARCODE_BACKSLASH, '/',CHARCODE_BACKSLASH, '/',CHARCODE_BACKSLASH, '/',CHARCODE_BACKSLASH, '\0' }; PROGMEM const char str_shepard_tone[] = "Shepard Tone"; PROGMEM const char str_guitar[] = "Guitar"; PROGMEM const char str_sawtooth_shapard_tone[] = { CHARCODE_SAWTOOTH_LEFT, CHARCODE_SAWTOOTH_RIGHT, 'S','h','e','p','a','r','d', '\0' }; 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[] = { #ifdef USE_OCTAVE_SHIFT str_sawtooth_wave, str_square_wave, str_guitar, str_sine_wave, str_triangle_wave, #endif str_shepard_tone, str_sawtooth_shapard_tone, str_ramdom_wave, }; class CAmiDionLCD : public LCD_PARENT_CLASS { protected: byte columns; char line_buf[32]; char *bufp; MusicalChord current_chord; void clearChord() { current_chord.setCo5(0x7F); } public: CAmiDionLCD() : LCD_PARENT_CLASS( LCD_CONSTRUCTOR_ARGS ) { current_chord = MusicalChord(); clearChord(); } void begin(byte cols, byte rows) { LCD_PARENT_CLASS::begin(cols,rows); byte natural[8] = { B01000, B01000, B01110, B01010, B01110, B00010, B00010, }; createChar(CHARCODE_NATURAL, natural); columns = min(sizeof(line_buf)-1,cols); setCursor(0,0); #ifdef USE_I2C_8x2_LCD print("CAmiDion"); #else print("*** CAmiDion ***"); #endif clearLineBuffer(); } 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"[PWMDACSynth::musicalMod7(++co5)]; if( co5 < 0 ) *bufp++ = 'b'; // flat or double flat if( co5 >= 14 ) *bufp++ = 'x'; // double sharp else if( co5 >= 7 ) *bufp++ = '#'; // sharp if( co5 < -7 ) *bufp++ = 'b'; // double flat } void printKeySignature(KeySignature keysig, boolean is_changing ) { setString("Key",3); *bufp++ = (is_changing ? '>' : ':'); char n = abs(keysig.getCo5()); char b = keysig.getCo5() < 0 ? 'b':'#'; if( n == 0 ) *bufp++ = CHARCODE_NATURAL; else if( n < 5 ) do { *bufp++ = b; } while(--n); else { *bufp++ = '0'+n; *bufp++ = b; } #ifndef USE_I2C_8x2_LCD *bufp++ = '('; setNote(keysig.getCo5()); *bufp++ = '/'; setNote(keysig.getCo5() + 3); *bufp++ = 'm'; *bufp++ = ')'; #endif setCursor(0,1); printLineBuffer(); } void printChord(MusicalChord chord) { if( ! memcmp( ¤t_chord, &chord, sizeof(chord) ) ) return; current_chord = chord; #ifndef USE_I2C_8x2_LCD setString("Chord:",6); #endif setNote(chord.getCo5()); 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 printEnvelope(EnvelopeParam *ep) { byte value; *bufp++ = 'a'; value = 0xF - PWM_SYNTH.log2(ep->attack_speed); *bufp++ = value + (value < 10 ?'0':'A'-10); *bufp++ = 'd'; *bufp++ = ep->decay_time + (ep->decay_time < 10 ?'0':'A'-10); *bufp++ = 's'; value = ep->sustain_level >> 12; *bufp++ = value + (value < 10 ?'0':'A'-10); *bufp++ = 'r'; *bufp++ = ep->release_time + (ep->release_time < 10 ?'0':'A'-10); setCursor(0,1); printLineBuffer(); } void printWaveform( char midi_channel, PROGMEM const char *pmstr, boolean channel_is_changing ) { 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 backslash_pattern[8] = { B00000, B10000, B01000, B00100, B00010, B00001, B00000, }; *bufp++ = 'C'; *bufp++ = 'h'; if( midi_channel >= 10 ) { *bufp++ = '1'; midi_channel -= 10; } *bufp++ = midi_channel + '0'; *bufp++ = ( channel_is_changing ? '>' : ':' ); if( pmstr == str_sine_wave ) { createChar(CHARCODE_SINE_UP, sine_up); createChar(CHARCODE_SINE_DOWN, sine_down); } else if( pmstr == str_triangle_wave ) { createChar(CHARCODE_BACKSLASH, backslash_pattern); } else if( pmstr == str_sawtooth_wave || pmstr == str_sawtooth_shapard_tone) { createChar(CHARCODE_SAWTOOTH_LEFT, sawtooth_left); createChar(CHARCODE_SAWTOOTH_RIGHT, sawtooth_right); } else if( pmstr == str_square_wave ) { createChar(CHARCODE_SQUARE_UP, square_up); createChar(CHARCODE_SQUARE_DOWN, square_down); } else if( pmstr == str_ramdom_wave ) { createChar(CHARCODE_RANDOM, random_pattern); } 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) // // 3 5 7 A C E // 0 1 2 4 6 8 9 B D F ////////////////////////////// #ifdef USE_LED typedef union _LEDs { unsigned int value16; byte values8[2]; } LEDs; #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) #define LED_ALL_NOTES_OFF(led) ((led).value16 &= 0x000F) #define LED_NOTE_ON(led,pitch) ((led).value16 |= (0x0010 << (pitch))) #define LED_NOTE_OFF(led,pitch) ((led).value16 &= ~(0x0010 << (pitch))) #define LED_SHOW_NOTE(led,pitch) (LED_ALL_NOTES_OFF(led),LED_NOTE_ON(led,pitch)) #define LED_SHOW_KEYSIG(led,keysig) LED_SHOW_NOTE(led,keysig.getNote()) #define LED_SHOW_MIDI_CHANNEL(ch0) (led_ctrl.value16 = (1 << (ch0))); LEDs led_main = {0x0004}; // One-push chord mode LEDs led_key = {0x0010}; // C major LEDs led_ctrl = {0x0001}; // Ch.1 (0..15 = MIDI Channel 1..16) LEDs *led = &led_main; // LED buffer to display #endif ///////////////////// // Waveform manager ///////////////////// PROGMEM const byte shepardToneSawtoothWavetable[] = { 0, 6, 6, 12, 6, 12, 12, 18, 5, 11, 11, 18, 11, 17, 17, 24, 5, 11, 11, 17, 11, 17, 17, 23, 11, 17, 17, 23, 17, 23, 23, 29, 5, 11, 11, 17, 11, 17, 17, 23, 11, 17, 17, 23, 17, 23, 23, 29, 11, 17, 17, 23, 17, 23, 23, 29, 16, 23, 23, 29, 22, 29, 29, 35, 4, 10, 10, 16, 10, 16, 16, 22, 10, 16, 16, 22, 16, 22, 22, 28, 10, 16, 16, 22, 16, 22, 22, 28, 16, 22, 22, 28, 22, 28, 28, 34, 9, 15, 15, 22, 15, 21, 21, 28, 15, 21, 21, 27, 21, 27, 27, 33, 15, 21, 21, 27, 21, 27, 27, 33, 21, 27, 27, 33, 27, 33, 33, 39, 3, 9, 9, 15, 9, 15, 15, 21, 8, 15, 15, 21, 14, 21, 21, 27, 8, 14, 14, 20, 14, 20, 20, 26, 14, 20, 20, 26, 20, 26, 26, 32, 8, 14, 14, 20, 14, 20, 20, 26, 14, 20, 20, 26, 20, 26, 26, 32, 14, 20, 20, 26, 20, 26, 26, 32, 20, 26, 26, 32, 26, 32, 32, 38, 7, 13, 13, 19, 13, 19, 19, 25, 13, 19, 19, 25, 19, 25, 25, 31, 13, 19, 19, 25, 19, 25, 25, 31, 19, 25, 25, 31, 25, 31, 31, 37, 12, 19, 19, 25, 18, 25, 25, 31, 18, 24, 24, 30, 24, 30, 30, 36, 18, 24, 24, 30, 24, 30, 30, 36, 24, 30, 30, 36, 30, 36, 36, 42, }; 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[] = { #ifdef USE_OCTAVE_SHIFT PWM_SYNTH.sawtoothWavetable, PWM_SYNTH.squareWavetable, guitarWavetable, PWM_SYNTH.sineWavetable, PWM_SYNTH.triangleWavetable, #endif PWM_SYNTH.shepardToneSineWavetable, shepardToneSawtoothWavetable, randomWavetable, }; byte current_midi_channel = 1; // 1==CH1, ... class WaveformController { protected: char selected_waveforms[16]; char *selected_waveform; public: WaveformController() { memset( selected_waveforms, 0, sizeof(selected_waveforms) ); selected_waveform = selected_waveforms + current_midi_channel - 1; } void clear() { PWM_SYNTH.setWave( (byte *)pgm_read_word(wavetables) ); } void show(boolean channel_is_changing) { #ifdef USE_LED if( *selected_waveform ) LED_UPPER_ON(led_main); else LED_UPPER_OFF(led_main); #endif #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 change(char offset) { selected_waveform = selected_waveforms + current_midi_channel - 1; *selected_waveform += offset; if( *selected_waveform < 0 ) *selected_waveform += NumberOf(wavetables); else if( *selected_waveform >= NumberOf(wavetables) ) *selected_waveform -= NumberOf(wavetables); PWM_SYNTH.getChannel(current_midi_channel)->wavetable = (byte *)pgm_read_word(wavetables + *selected_waveform); show(false); } }; WaveformController waveform = WaveformController(); void changeMidiChannel(char offset) { char ch0 = current_midi_channel + offset - 1; current_midi_channel = (ch0 &= 0xF) + 1; #ifdef USE_LED LED_SHOW_MIDI_CHANNEL(ch0); #endif waveform.midiChannelChanged(); #ifdef USE_LCD EnvelopeParam *ep = &(PWM_SYNTH.getChannel(current_midi_channel)->env_param); lcd.printEnvelope(ep); #endif } KeySignature key_signature = KeySignature(); // Number of activated notes #ifdef USE_LED char note_on_counts[] = {0,0, 0,0, 0, 0,0, 0,0, 0,0, 0}; #endif // // MIDI IN receive callbacks to control PWM sound void HandleNoteOff(byte channel, byte pitch, byte velocity) { PWM_SYNTH.noteOff(channel,pitch,velocity); #ifdef USE_LED pitch = PWMDACSynth::musicalMod12(pitch); char *cp = note_on_counts + pitch; if( *cp > 0 ) --*cp; if( *cp <= 0 ) LED_NOTE_OFF( led_main, pitch ); #endif } void HandleNoteOn(byte channel, byte pitch, byte velocity) { if( velocity == 0 ) { HandleNoteOff(channel,pitch,velocity); return; } PWM_SYNTH.noteOn(channel,pitch,velocity); #ifdef USE_LED pitch = PWMDACSynth::musicalMod12(pitch); char *cp = note_on_counts + pitch; if( *cp < 0 ) *cp = 0; if( ++*cp > 0 ) LED_NOTE_ON( led_main, pitch ); #endif } #ifdef USE_OCTAVE_SHIFT //////////////////// // 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; } }; #endif ///////////////////////////// // Button control ///////////////////////////// //////////////////////////////////////////////////////////////////// // Button matrix anode6(+1)-cathode8 // // 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 // //////////////////////////////////////////////////////////////////// class Button { public: char cathode8, anode6; }; class ButtonNote { public: Button button; byte n_notes; char *notes; byte midi_channel; ButtonNote() { button = Button(); n_notes = 0; notes = NULL; } 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_OUT MIDI.sendNoteOn(*notep, 100, midi_channel); #endif } } void noteOff() { char *notep = notes; for( ; n_notes>0; n_notes--, notep++ ) { HandleNoteOff(midi_channel, *notep, 100); #ifdef USE_MIDI_OUT MIDI.sendNoteOff(*notep, 100, midi_channel); #endif } free(notes); notes = NULL; } }; 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 *searchVacant() { ButtonNote *bnp = button_notes; char i=NumberOf(button_notes); for( ; i>0; i--, bnp++ ) if( bnp->n_notes == 0 ) return bnp; return NULL; // busy } void noteOff(Button *button_p) { ButtonNote *bnp = button_notes; char i=NumberOf(button_notes); for( ; i>0; i--, bnp++ ) if( bnp->n_notes && bnp->button.cathode8 == button_p->cathode8 && bnp->button.anode6 == button_p->anode6 ) { bnp->noteOff(); return; } } }; ButtonNotes button_notes = ButtonNotes(); class HoldableButton : public Button { public: boolean held; HoldableButton() : Button() { held = false; } void toggle() { held = !held; if( held ) { #ifdef USE_LED LED_LEFT_ON(led_key); LED_LEFT_ON(led_main); #endif } else { button_notes.noteOff(this); #ifdef USE_LED LED_LEFT_OFF(led_key); LED_LEFT_OFF(led_main); #endif } } void change(Button *button_p) { if( ! held ) return; button_notes.noteOff(this); cathode8 = button_p->cathode8; anode6 = button_p->anode6; } }; class OnepushChordStatus { public: char n_notes; OnepushChordStatus() { n_notes = 6; } void toggle() { n_notes ^= 7; #ifdef USE_LCD lcd.setCursor(0,0); #endif if( n_notes > 1 ) { #ifdef USE_LED LED_RIGHT_ON(led_main); #endif #ifdef USE_LCD lcd.setString("Chord:",6); lcd.printLineBuffer(); #endif } else { #ifdef USE_LED LED_RIGHT_OFF(led_main); #endif #ifdef USE_LCD waveform.show(false); #endif } } }; class ButtonStatus { public: byte current_anode6_bits[8]; 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; Button current_button; OnepushChordStatus onepush_chord_status; HoldableButton holdable_button; ButtonStatus() { offset5 = offset7 = 0; has9 = ctrl = key = false; memset( current_anode6_bits, 0xFF, sizeof(current_anode6_bits) ); current_button = Button(); holdable_button = HoldableButton(); onepush_chord_status = OnepushChordStatus(); } void pressed() { char x_pos, y_pos; if( current_button.anode6 >= 2 ) { x_pos = current_button.cathode8 - 1; y_pos = current_button.anode6 - 3; } else if( current_button.cathode8 >= 3 ) { x_pos = current_button.cathode8 - 9; y_pos = current_button.anode6; } else { switch(current_button.cathode8) { case 0: switch(current_button.anode6) { case -1: ctrl = true; #ifdef USE_LED led = &led_ctrl; #endif #ifdef USE_LCD changeMidiChannel(0); waveform.show(true); lcd.printEnvelope( &(PWM_SYNTH.getChannel(current_midi_channel)->env_param) ); #endif break; case 0: key = true; #ifdef USE_LED led = &led_key; LED_SHOW_KEYSIG( led_key, key_signature ); #endif #ifdef USE_LCD lcd.printKeySignature( key_signature, true ); #endif break; case 1: if(key) holdable_button.toggle(); break; } break; case 1: switch(current_button.anode6) { case -1: has9 = true; break; case 0: offset7 -= 1; break; case 1: break; } break; case 2: switch(current_button.anode6) { case -1: offset5 = -1; break; case 0: offset7 -= 2; break; case 1: onepush_chord_status.toggle(); break; } break; } return; } if( ctrl ) { if( y_pos == 0 ) { if( abs(x_pos) <= 1 ) changeMidiChannel(x_pos); } else switch(x_pos) { case 0: waveform.change(y_pos); break; case 1: { EnvelopeParam *ep = &(PWM_SYNTH.getChannel(current_midi_channel)->env_param); unsigned int *asp = &(ep->attack_speed); if( y_pos > 0 ) { if( *asp <= 0x0001 ) *asp = 0x8000; else *asp >>= 1; } else { if( *asp >= 0x8000 ) *asp = 0x0001; else *asp <<= 1; } #ifdef USE_LCD lcd.printEnvelope(ep); #endif } break; case 2: { EnvelopeParam *ep = &(PWM_SYNTH.getChannel(current_midi_channel)->env_param); byte *dtp = &(ep->decay_time); if( y_pos > 0 ) { if( *dtp >= 15 ) *dtp == 0; else (*dtp)++; } else { if( *dtp <= 0 ) *dtp = 15; else (*dtp)--; } #ifdef USE_LCD lcd.printEnvelope(ep); #endif } break; case 3: { EnvelopeParam *ep = &(PWM_SYNTH.getChannel(current_midi_channel)->env_param); unsigned int *slp = &(ep->sustain_level); if( y_pos > 0 ) *slp +=0x1000; else *slp -=0x1000; #ifdef USE_LCD lcd.printEnvelope(ep); #endif } break; case 4: { EnvelopeParam *ep = &(PWM_SYNTH.getChannel(current_midi_channel)->env_param); byte *rtp = &(ep->release_time); if( y_pos > 0 ) { if( *rtp >= 15 ) *rtp = 0; else (*rtp)++; } else { if( *rtp <= 0 ) *rtp = 15; else (*rtp)--; } #ifdef USE_LCD lcd.printEnvelope(ep); #endif } break; } return; } if( key ) { if(x_pos) key_signature.shift(x_pos); else key_signature.shift( 7 * y_pos, -5, 6 ); #ifdef USE_LED LED_SHOW_KEYSIG( led_key, key_signature ); #endif #ifdef USE_LCD lcd.printKeySignature( key_signature, true ); #endif return; } holdable_button.change(¤t_button); ButtonNote *bnp = button_notes.searchVacant(); if( bnp == NULL ) return; char *notep = bnp->reserve( onepush_chord_status.n_notes, current_button, current_midi_channel ); if( notep == NULL ) return; #ifdef USE_OCTAVE_SHIFT int analog_value = analogRead(OCTAVE_ANALOG_PIN); #endif MusicalChord chord = MusicalChord( key_signature, x_pos, y_pos ); if( onepush_chord_status.n_notes == 1 ) { #ifdef USE_OCTAVE_SHIFT NoteRange range = NoteRange( (y_pos==1 ? 12 : 0), analog_value ); *notep = range.shiftOctave( chord.getNote() ); #else *notep = chord.getNote() + 36; #endif bnp->noteOn(); return; } chord.offset5 = offset5; if( chord.offset5 == -1 && chord.offset3 == 1 ) { // sus4 -5 chord.offset3 = 0; // Cancel sus4 chord.offset5 = 1; // Set +5 (aug) } chord.offset7 = offset7; #ifdef USE_OCTAVE_SHIFT chord.has9 = has9; NoteRange range = NoteRange( 0, analog_value ); NoteRange high_range = NoteRange( 12, analog_value ); *notep = high_range.shiftOctave( chord.getNote() ); *++notep = high_range.shiftOctave( chord.get3rdNote() ); *++notep = high_range.shiftOctave( chord.get5thNote() ); if( chord.offset7 ) *++notep = high_range.shiftOctave( chord.get7thNote() ); else *++notep = range.shiftOctave( chord.get5thNote() ); if( chord.has9 ) *++notep = high_range.shiftOctave( chord.getNote() + 2 ); else *++notep = range.shiftOctave( chord.get3rdNote() ); *++notep = range.shiftOctave( chord.getNote() ); #else *notep = chord.getNote() + 24; *++notep = chord.get3rdNote() + 24; *++notep = chord.get5thNote() + 24; *++notep = chord.get7thNote() + 24; *++notep = (has9 ? 2 + chord.getNote() : chord.get3rdNote()) + 36; *++notep = chord.get5thNote() + 36; #endif bnp->noteOn(); #ifdef USE_LCD lcd.printChord(chord); #endif } void released() { if( current_button.cathode8 >= 3 || current_button.anode6 >= 2 ) { if( holdable_button.held ) return; button_notes.noteOff(¤t_button); return; } switch(current_button.cathode8) { case 0: switch(current_button.anode6) { case -1: ctrl = false; #ifdef USE_LED led = &led_main; #endif #ifdef USE_LCD waveform.show(false); lcd.printKeySignature( key_signature, false ); #endif break; case 0: key = false; #ifdef USE_LED led = &led_main; #endif #ifdef USE_LCD lcd.printKeySignature( key_signature, false ); #endif break; } break; case 1: switch(current_button.anode6) { case -1: has9 = false; break; case 0: offset7 += 1; break; } break; case 2: switch(current_button.anode6) { case -1: offset5 = 0; break; case 0: offset7 += 2; break; } break; } } }; ButtonStatus button_status = ButtonStatus(); void setup() { #ifdef USE_LCD #ifdef USE_I2C_8x2_LCD lcd.begin(8,2); #else lcd.begin(16,2); #endif #endif PWM_SYNTH.setup(); waveform.clear(); #if CAMIDION_VERSION == 2 DDRB &= 0b11000000; // port 8..13 for INPUT (13's default was OUTPUT !?) PORTB |= 0b00111111; // analog HIGH -> INPUT_PULLUP DDRD |= 0b01110000; // port 4..6 for OUTPUT #elif CAMIDION_VERSION == 3 pinMode(SHIFTOUT_DATA_PIN,OUTPUT); pinMode(SHIFT_CLOCK_PIN,OUTPUT); pinMode(SHIFT_LATCH_PIN,OUTPUT); pinMode(SHIFTIN_DATA_PIN,INPUT); #elif CAMIDION_VERSION == 4 PORTC |= 0b00111111; // analog HIGH -> INPUT_PULLUP DDRB |= 0b00111111; // port 8..13 for OUTPUT PORTB |= 0b00111111; // and HIGH DDRD |= 0b11000000; // port 6,7 for OUTPUT PORTD |= 0b11000000; // and HIGH #endif // CAMIDION_VERSION #ifdef USE_MIDI MIDI.begin(MIDI_CHANNEL_OMNI); // receives all MIDI channels MIDI.turnThruOff(); // Disable MIDI IN -> MIDI OUT mirroring #ifdef USE_MIDI_IN MIDI.setHandleNoteOff(HandleNoteOff); MIDI.setHandleNoteOn(HandleNoteOn); MIDI.setHandlePitchBend(PWM_SYNTH.pitchBend); MIDI.setHandleControlChange(PWM_SYNTH.controlChange); #endif pinMode(MIDI_ENABLE_PIN,OUTPUT); digitalWrite(MIDI_ENABLE_PIN,HIGH); // enable MIDI port #endif #ifdef USE_LCD lcd.printKeySignature( key_signature, false ); #endif } void loop() { byte *current_anode6_bits_p = button_status.current_anode6_bits; byte mask, cathode8_mask, anode6_bits, anode6_change; button_status.current_button.cathode8 = 0; for( cathode8_mask = 1; cathode8_mask; cathode8_mask <<= 1 ) { #if CAMIDION_VERSION == 3 // // 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) // byte cathode8_bits = button_status.current_button.cathode8; #ifdef USE_LED if( led->values8[0] & cathode8_mask ) cathode8_bits |= 0b01000; if( led->values8[1] & cathode8_mask ) cathode8_bits |= 0b10000; #endif // USE_LED digitalWrite( SHIFT_LATCH_PIN, LOW ); // RCLK OFF for( mask=0b10000000; mask; mask >>= 1 ) { digitalWrite( SHIFTOUT_DATA_PIN, !!( cathode8_bits & mask ) ); digitalWrite( SHIFT_CLOCK_PIN, HIGH ); // rise to shift digitalWrite( SHIFT_CLOCK_PIN, LOW ); } digitalWrite( SHIFT_LATCH_PIN, HIGH ); // RCLK OFF -> ON // // 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_status.current_button.anode6 = 6; anode6_bits = 0; while(true) { anode6_bits |= digitalRead( SHIFTIN_DATA_PIN ); // 1:ButtonOFF 0:ButtonON if( button_status.current_button.anode6 < 0 ) break; digitalWrite( SHIFT_CLOCK_PIN, HIGH ); // rise to shift digitalWrite( SHIFT_CLOCK_PIN, LOW ); button_status.current_button.anode6--; anode6_bits <<= 1; } #elif CAMIDION_VERSION == 4 PORTD |= 0b11000000; PORTB |= 0b00111111; #ifdef USE_LED if( led->values8[0] & cathode8_mask ) { pinMode(4,OUTPUT); digitalWrite(4,HIGH); } else pinMode(4,INPUT); if( led->values8[1] & cathode8_mask ) { pinMode(5,OUTPUT); digitalWrite(5,HIGH); } else pinMode(5,INPUT); #endif // USE_LED byte portb_mask = cathode8_mask & 0b00111111; if( portb_mask ) PORTB &= ~portb_mask; else PORTD &= ~(cathode8_mask & 0b11000000); button_status.current_button.anode6 = -1; anode6_bits = PINC | 0b11000000; #elif CAMIDION_VERSION == 2 byte pd = button_status.current_button.cathode8 << 4; pinMode(7,INPUT); pinMode(17,INPUT); PORTD &= 0b10001111; PORTD |= pd; #ifdef USE_LED if( led->values8[0] & cathode8_mask ) { pinMode(7,OUTPUT); digitalWrite(7,HIGH); } if( led->values8[1] & cathode8_mask ) { pinMode(17,OUTPUT); digitalWrite(17,HIGH); } #endif // USE_LED button_status.current_button.anode6 = -1; anode6_bits = PINB | 0b11000000; #endif // CAMIDION_VERSION anode6_change = anode6_bits ^ *current_anode6_bits_p; if( anode6_change ) { *current_anode6_bits_p = anode6_bits; for( mask=1; mask <= 0b00100000; mask <<= 1, button_status.current_button.anode6++ ) { if( ! (anode6_change & mask) ) continue; if( ! (anode6_bits & mask) ) button_status.pressed(); else button_status.released(); } } PWM_SYNTH.updateEnvelopeStatus(); button_status.current_button.cathode8++; current_anode6_bits_p++; } #ifdef USE_MIDI_IN MIDI.read(); #endif }