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