Beat Box: BeatBox.ino


//
// BeatBox -- Simple Arduino rhythm player
//

// Author:  P.J. Drongowski
// Date:    15 July 2016
// Copyright (c) 2016 Paul J. Drongowski
//               Permission granted to distribute and modify

#include <avr/pgmspace.h>
#include <avr/interrupt.h>

#include "waveforms.h"
#include "kits.h"
#include "patterns.h"

// Define input pins
int ResetButton = 0 ;       // Unused (digital)
int tempoInputPin = 0 ;     // Set BPM (analog)
int patternInputPin = 1 ;   // Choose pattern and kit (analog)

// Define output pins
int gatePin = 1 ;     // Unused
int blahPin = 5 ;     // Unused
int pwmPin = 9 ;      // PWM (DAC) output

// Rhythm instrument control variables
#define INSTRUMENTS 8
const int8_t* sampleArrays[INSTRUMENTS] ;
int16_t sampleSizes[INSTRUMENTS] ;
int sampleCounts[INSTRUMENTS] ;
int sampleIndices[INSTRUMENTS] ;

// Tempo control variables
int tempo = 120 ;
int delayValue = 125 ;

// Pattern and pattern control variables
int patternId = 0 ;
int kitId = 0 ;
int patternLength = 16 ;
int patternIndex = 0 ;
uint8_t pattern[64] ;

//
// Configure TIMER1 to produce an interupt at a ~22050 Hz
// sampling rate. TIMER1 is a 16-bit timer. No PWM.
//
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() ;
}

//
// TIMER1 PWM. Single PWM, phase correct, 22050KHz.
// PWM_FREQ = 16,000,000 / 22,050 =  726 = 0x2D5
// PWM_FREQ = 16,000,000 / 11,025 = 1451 = 0x5AB
//
#define PWM_FREQ   363

void PwmSetup() {
  // Clear OC1 on compare match, 8-bit PWM
  //TCCR1A = _BV(COM1A1) | _BV(WGM10) ;
  TCCR1A = _BV(COM1A1) ;
  // PWM TOP is OCR1A,  No prescaler
  TCCR1B = _BV(WGM13) | _BV(CS10) ;
  // Generate interrupt on input capture
  TIMSK1 = _BV(ICIE1) ;
  // Set input capture register to sampling frequency
  ICR1H = (PWM_FREQ >> 8) ;
  ICR1L = (PWM_FREQ & 0xff) ;
  // Turn on the output pin D9
  DDRB |= _BV(5) ;
  sei() ;
}

void DisableInterrupts() {
  cli() ;
  TIMSK1 &= ~_BV(OCIE1A) ; // Disable DAC interrupts
  sei() ;
}

void EnableInterrupts() {
  cli();
  TIMSK1 |= _BV(OCIE1A) ; // Enable DAC interrupts
  sei();
}

//
// Interrupt service routine (ISR)
//
ISR(TIMER1_CAPT_vect) {
  register int16_t dacValue = 0 ;
  register int16_t sample = 0 ;
  if (sampleCounts[0] > 0) {
    sample = (int8_t)pgm_read_byte_near(
      sampleArrays[0]+sampleIndices[0]) ;
    dacValue += sample ;
    sampleIndices[0]++ ;
    sampleCounts[0]-- ;
  }
  if (sampleCounts[1] > 0) {
    sample = (int8_t)pgm_read_byte_near(
      sampleArrays[1]+sampleIndices[1]) ;
    dacValue += sample ;
    sampleIndices[1]++ ;
    sampleCounts[1]-- ;
  }
  if (sampleCounts[2] > 0) {
    sample = (int8_t)pgm_read_byte_near(
      sampleArrays[2]+sampleIndices[2]) ;
    dacValue += sample ;
    sampleIndices[2]++ ;
    sampleCounts[2]-- ;
  }
  if (sampleCounts[3] > 0) {
    sample = (int8_t)pgm_read_byte_near(
      sampleArrays[3]+sampleIndices[3]) ;
    dacValue += sample ;
    sampleIndices[3]++ ;
    sampleCounts[3]-- ;
  }
  if (sampleCounts[4] > 0) {
    sample = (int8_t)pgm_read_byte_near(
      sampleArrays[4]+sampleIndices[4]) ;
    dacValue += sample ;
    sampleIndices[4]++ ;
    sampleCounts[4]-- ;
  }
  if (sampleCounts[5] > 0) {
    sample = (int8_t)pgm_read_byte_near(
      sampleArrays[5]+sampleIndices[5]) ;
    dacValue += sample ;
    sampleIndices[5]++ ;
    sampleCounts[5]-- ;
  }
  if (sampleCounts[6] > 0) {
    sample = (int8_t)pgm_read_byte_near(
      sampleArrays[6]+sampleIndices[6]) ;
    dacValue += sample ;
    sampleIndices[6]++ ;
    sampleCounts[6]-- ;
  }
  if (sampleCounts[7] > 0) {
    sample = (int8_t)pgm_read_byte_near(
      sampleArrays[7]+sampleIndices[7]) ;
    dacValue += sample ;
    sampleIndices[7]++ ;
    sampleCounts[7]-- ;
  }

  // Output through OC1A
  dacValue += 127 ;
  OCR1AH = (uint8_t) (dacValue >> 8) & 0xFF ;
  OCR1AL = (uint8_t) dacValue & 0xFF ;
}

#define SHKIT 0
#define CTKIT 1
#define ADKIT 2
#define VRKIT 3

void loadKit(int kitId) {
  register int i ;
  register int8_t** samples ;
  register int16_t** sizes ;
  
  switch(kitId) {
    case SHKIT:
    default: {
      // SH kit
      samples = (int8_t**)shWaveforms ;
      sizes = (int16_t**)shWaveformSizes ;
      break ;
    }
    case CTKIT: {
      // CT kit
      samples = (int8_t**)ctWaveforms ;
      sizes = (int16_t**)ctWaveformSizes ;
      break ;
    }
    case ADKIT: {
      // AD kit
      samples = (int8_t**)adWaveforms ;
      sizes = (int16_t**)adWaveformSizes ;
      break ;
    }
    case VRKIT: {
      // VR kit
      samples = (int8_t**)vrWaveforms ;
      sizes = (int16_t**)vrWaveformSizes ;
      break ;
    }
  }
  for (i = 0 ; i < 8 ; i++) {
    sampleArrays[i] = (int8_t*)pgm_read_word_near(samples+i) ;
    sampleSizes[i] = (int16_t)pgm_read_word_near(sizes+i) ;
  }
}

void changePatternAndKit(int patternInput) {
  register int i ;
  register int kit ;
  register uint8_t* newpattern ;

  if (patternInput < 6) {
    // healingA pattern
    patternLength = HEALINGASIZE ;
    newpattern = (uint8_t*)healingA ;
    kit = SHKIT ;
  } else if (patternInput < 12) {
    // cybotranA pattern
    patternLength = CYBOTRONASIZE ;
    newpattern = (uint8_t*)cybotronA ;
    kit = CTKIT ;
  } else if (patternInput < 18) {
    // planetA pattern
    patternLength = PLANETASIZE ;
    newpattern = (uint8_t*)planetA ;
    kit = CTKIT ;
  } else if (patternInput < 24) {
    // adonisA pattern
    patternLength = ADONISASIZE ;
    newpattern = (uint8_t*)adonisA ;
    kit = ADKIT ;
  } else {
    // voodoo pattern
    patternLength = VOODOOSIZE ;
    newpattern = (uint8_t*)voodoo ;
    kit = VRKIT ;
  }
  loadKit(kit) ;
  if (patternLength > 64) patternLength = 64 ;
  for (i = 0 ; i < patternLength ; i++) {
    pattern[i] = (uint8_t)pgm_read_byte_near(newpattern+i) ;
  }
}

void setup() {
  register int i ;
  // Initialize the serial port (only for debugging)
  Serial.begin(9600) ;
  // Set up pin modes
  pinMode(ResetButton, INPUT_PULLUP) ;
  //pinMode(gatePin, OUTPUT) ;
  // Initialize pin values
  //digitalWrite(gatePin, LOW) ;

  changePatternAndKit(analogRead(patternInputPin) >> 5) ;

  tempo = 120 ;
  delayValue = (60000 / tempo) / 4 ;

  patternIndex = 0 ;
  for (i = 0 ; i < INSTRUMENTS ; i++) sampleCounts[i] = 0 ;

  PwmSetup() ;
}

void loop() {
  register uint8_t pbits ;
  register int difference ;
  static int oldTempoInput = 0 ;
  static int tempoInput = 0 ;
  static int oldPatternInput = 0 ;
  static int patternInput = 0 ;
  static int bpmCount = 3 ;

  // Turn TXLED on during the first eighth note of every beat
  if (bpmCount & 0x0002) {
    TXLED1 ;
  } else {
    TXLED0 ;
  } 
  if (bpmCount == 0) bpmCount = 3; else bpmCount-- ;

  // Change tempo if necessary
  // Dimmer connected to pin A0 sweeps tempo from 60 to 188 BPM
  tempoInput = analogRead(tempoInputPin) >> 3 ;
  difference = abs(tempoInput - oldTempoInput) ;
  if (difference > 0) {
    // Change the tempo
    tempo = 60 + tempoInput ;
    delayValue = (60000 / tempo) / 4 ;
    oldTempoInput = tempoInput ;
  }

  // Change pattern/kit if necessary
  // Dimmer connected to pin A1 selects one of 5 patterns
  patternInput = analogRead(patternInputPin) >> 5 ;
  difference = abs(patternInput - oldPatternInput) ;
  if (difference > 0) {
    // Change the pattern and its associated kit
    changePatternAndKit(patternInput) ;
    oldPatternInput = patternInput ;
  }

  // Delay for the equivalent of one sixteenth note
  delay(delayValue) ;

  // Wrap around the pattern index if necessary and
  // get the bit pattern for the current step
  if (patternIndex >= patternLength) patternIndex = 0 ;
  pbits = pattern[patternIndex] ;

  for (int i = 0 ; i < 8 ; i++) { 
    if (pbits & 0x01) {
      sampleCounts[i] = sampleSizes[i] ;
      sampleIndices[i] = 0 ;
    }
    pbits = pbits >> 1 ;
  }

 patternIndex++ ;
}