// // MidiVox: Combo organ (F-style or V-style) // // Author: P.J. Drongowski // Date: 1 May 2015 // Copyright (c) 2015 Paul J. Drongowski #include <avi/pgmspace.h> #include <avr/interrupt.h> #include "SPI.h" // // Make sure that you have the latest Arduino MIDI library // installed on your machine. // https://github.com/FortySevenEffects/arduino_midi_library/releases/tag/4.2 // Arduino_MIDI_Library_v4.2.zip // #include <MIDI.h> MIDI_CREATE_DEFAULT_INSTANCE(); // // Pin definitions // // SPI pins: // D13: SCK Slave Clock // D12: MISO Master In, Slave Out // D11: MOSI Master Out, Slave In // D9: SS Slave Select int ResetButton = 6 ; int DataLED = 7 ; int SlaveSelect = 9 ; // // Include all of the single cycle waves to be stored in // PROGMEM. // #include "Farf.h" #include "Vox.h" #include "Tables.h" // // Sound generation global variables/parameters // #define FARF_MODEL 0 #define VOX_MODEL 1 int organModel ; // // Per-generator (note) variables // // An Arduino UNO can support 5 note generators (as determined // by stress testing). // #define NUM_OF_GENERATORS 8 boolean generating[NUM_OF_GENERATORS] ; int genNote[NUM_OF_GENERATORS] ; int index[NUM_OF_GENERATORS] ; int numOfSamples[NUM_OF_GENERATORS] ; int stepSize[NUM_OF_GENERATORS] ; prog_int16_t *sampleArray[NUM_OF_GENERATORS] ; //////////////////////////////////////////////////////// // Interrupt, timer and hardware functions //////////////////////////////////////////////////////// // // Write a 12-bit value to the MCP 4921 DAC. // #define SILENCE 2047 void writeDac(int16_t dacValue) { byte data ; digitalWrite(SlaveSelect, LOW) ; data = highByte(dacValue) ; data = 0x0F & data ; data = 0x30 | data ; SPI.transfer(data) ; data = lowByte(dacValue) ; SPI.transfer(data) ; digitalWrite(SlaveSelect, HIGH) ; } // // Configure TIMER1 to produce an interupt at a ~22050 Hz // sampling rate. TIMER1 is a 16-bit timer. // void TimerSetup() { cli() ; // Normal operation - no PWM TCCR1A = 0 ; // No prescaling (CS10), CTC mode (WGM12) TCCR1B = _BV(WGM12) | _BV(CS10) ; // Sample rate (CPU clock: 16MHz, Sample rate: 22050Hz) OCR1A = 16000000 / 22050 ; // SD fill interrupt happens at TCNT1 == 1 // OCR1B = 1 ; // Enable timer interrupt for DAC ISR TIMSK1 |= _BV(OCIE1A) ; sei() ; } void DisableInterrupts() { cli() ; TIMSK1 &= ~_BV(OCIE1A) ; // Disable DAC interrupts sei() ; } void EnableInterrupts() { cli(); TIMSK1 |= _BV(OCIE1A) ; // Enable DAC interrupts sei(); } #define GENERATEX(I) \ if (generating[I]) { \ dacValue += ((int16_t)pgm_read_word_near(sampleArray[I] + index[I]) >> 5) ; \ if ((index[I] += stepSize[I]) >= numOfSamples[I]) { \ index[I] = index[I] - numOfSamples[I] ; \ } \ } #define GENERATE(I) \ if (generating[I]) { \ samValue = pgm_read_word_near(sampleArray[I] + index[I]) ; \ samValue = (samValue >> 5) ; \ dacValue += samValue ; \ index[I] = index[I] + stepSize[I] ; \ if (index[I] >= numOfSamples[I]) { \ index[I] = index[I] - numOfSamples[I] ; \ } \ } ISR(TIMER1_COMPA_vect) { int16_t dacValue, samValue ; dacValue = 0 ; GENERATE(0) ; GENERATE(1) ; GENERATE(2) ; GENERATE(3) ; GENERATE(4) ; writeDac(dacValue + 2048) ; } // // Blink the data LED on the MidiVox board // void blinkDataLED() { digitalWrite(DataLED, HIGH) ; delay(500) ; digitalWrite(DataLED, LOW) ; delay(500) ; } //////////////////////////////////////////////////////// // MIDI handling //////////////////////////////////////////////////////// // // Find an available generator and configure it // to play the specific MIDI note. // void ConfigGenerator(int i, int note) { byte noteInfo ; int key ; index[i] = 0 ; noteInfo = pgm_read_byte_near(MidiNotes + note); key = (noteInfo >> 4) & 0x0F ; stepSize[i] = (noteInfo & 0x0F) + 1 ; if (organModel == FARF_MODEL) { numOfSamples[i] = pgm_read_word_near(FarfNumOfSamples + key) ; sampleArray[i] = (prog_int16_t*)pgm_read_word_near(FarfWaves + key) ; } else { numOfSamples[i] = pgm_read_word_near(VoxNumOfSamples + key) ; sampleArray[i] = (prog_int16_t*)pgm_read_word_near(VoxWaves + key) ; } } // // MIDI message handlers (callback functions) // void handleNoteOn(byte channel, byte note, byte velocity) { // Find an idle generator and allocate to the new note. // Ignore the MIDI channel (at least for now). if (! generating[0]) { genNote[0] = note ; ConfigGenerator(0, note) ; // Start the generator (must be last after configuration!) generating[0] = true ; return ; } if (! generating[1]) { genNote[1] = note ; ConfigGenerator(1, note) ; generating[1] = true ; return ; } if (! generating[2]) { genNote[2] = note ; ConfigGenerator(2, note) ; generating[2] = true ; return ; } if (! generating[3]) { genNote[3] = note ; ConfigGenerator(3, note) ; generating[3] = true ; return ; } if (! generating[4]) { genNote[4] = note ; ConfigGenerator(4, note) ; generating[4] = true ; return ; } } // // In case you haven't noticed, I essentially unrolled the loops // for scanning the generator status information! // void handleNoteOff(byte channel, byte note, byte velocity) { // Find the generator playing the note and turn it off. // Ignore the MIDI channel. if (generating[0] && (genNote[0] == note)) { genNote[0] = 0 ; generating[0] = false ; return ; } if (generating[1] && (genNote[1] == note)) { genNote[1] = 0 ; generating[1] = false ; return ; } if (generating[2] && (genNote[2] == note)) { genNote[2] = 0 ; generating[2] = false ; return ; } if (generating[3] && (genNote[3] == note)) { genNote[3] = 0 ; generating[3] = false ; return ; } if (generating[4] && (genNote[4] == note)) { genNote[4] = 0 ; generating[4] = false ; return ; } } void setup() { int key ; byte noteInfo ; // Set up pin modes pinMode(ResetButton, INPUT_PULLUP) ; pinMode(DataLED, OUTPUT) ; pinMode(SlaveSelect, OUTPUT) ; // Turn the data LED off digitalWrite(DataLED, LOW) ; // Initialize the SPI interface SPI.begin() ; SPI.setBitOrder(MSBFIRST) ; writeDac(SILENCE) ; // Initialize sound generator(s) ******* Experimental ***** organModel = FARF_MODEL ; for (int i = 0 ; i < NUM_OF_GENERATORS ; i++) { generating[i] = false ; genNote[i] = 0 ; index[i] = 0 ; noteInfo = pgm_read_byte_near(MidiNotes + 60); stepSize[i] = (noteInfo & 0x0F) + 1 ; key = (noteInfo >> 4) & 0x0F ; if (organModel == FARF_MODEL) { numOfSamples[i] = pgm_read_word_near(FarfNumOfSamples + key) ; sampleArray[i] = (prog_int16_t*)pgm_read_word_near(FarfWaves + key) ; } else { numOfSamples[i] = pgm_read_word_near(VoxNumOfSamples + key) ; sampleArray[i] = (prog_int16_t*)pgm_read_word_near(VoxWaves + key) ; } } // Blink data LED on and off four times at start-up for (int i = 4 ; i > 0 ; i--) { blinkDataLED() ; } TimerSetup() ; // Initialize the MIDI interface // Register the message handlers (callback functions) and // then start MIDI OMNI mode MIDI.setHandleNoteOn(handleNoteOn) ; MIDI.setHandleNoteOff(handleNoteOff) ; MIDI.begin(MIDI_CHANNEL_OMNI) ; } void loop() { MIDI.read() ; if (digitalRead(ResetButton) == LOW) { for (int i = 0 ; i < NUM_OF_GENERATORS ; i++) { generating[i] = false ; } organModel = VOX_MODEL ; digitalWrite(DataLED, LOW) ; delay(500) ; } }