MidiVOX organ: ComboOrgan.ino

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