I'm looking for some help with my first Arduino project.
The aim is to build a tap tempo flashing LED, that acts as a master tempo, with a synth section that can go from rhythmic subdivisions of the master tempo all the way up to pitched tones.
I've managed to build something by cobbling together two existing projects from here and here.
/*
Tap Tempo
PTB button connected to digital pin 12.
LED on pin 8 displays the beat pulse
LED on pin 13 shows the current button state (dim when button is pressed)
100K pot on Analog Input 0 alters tapped speed.
Digital 8 <--------/\/ 220 ohm /\/----[LED]----> GND
+------> +5 power
|
[ switch ]
|
Digital 12 <-----+---------/\/ 10k ohm /\/-----> GND
*/// Digital 3: Audio out, synth LED out
// Analog 1 = Filter
// Analog 2 = Base pitch
// Analog 3 = Tone control
#include <avr/io.h>
#include <avr/interrupt.h>
float theTime = 0; //what is current tap count??
float diviDer = 0; // tone divider
uint16_t syncPhaseAcc;
uint16_t syncPhaseInc;
uint16_t grainPhaseAcc;
uint16_t grainPhaseInc;
uint16_t grainAmp;
uint8_t grainDecay;
uint16_t grain2PhaseAcc;
uint16_t grain2PhaseInc;
uint16_t grain2Amp;
uint8_t grain2Decay;
// Map Analogue channels
#define SYNC_CONTROL (1)
#define GRAIN2_FREQ_CONTROL (2)
#define GRAIN_TONE_CONTROL (3)
// Changing these will also requires rewriting audioOn()
// For modern ATmega168 and ATmega328 boards -- see original code for different boards
// Output is on pin 3
//
#define PWM_PIN 3
#define PWM_VALUE OCR2B
#define LED_PORT PORTB
#define LED_BIT 5
#define PWM_INTERRUPT TIMER2_OVF_vect
// Smooth logarithmic mapping
//
uint16_t antilogTable[] = {
64830,64132,63441,62757,62081,61413,60751,60097,59449,58809,58176,57549,56929,56316,55709,55109,
54515,53928,53347,52773,52204,51642,51085,50535,49991,49452,48920,48393,47871,47356,46846,46341,
45842,45348,44859,44376,43898,43425,42958,42495,42037,41584,41136,40693,40255,39821,39392,38968,
38548,38133,37722,37316,36914,36516,36123,35734,35349,34968,34591,34219,33850,33486,33125,32768
};
uint16_t mapPhaseInc(uint16_t input) {
return (antilogTable[input & 0x3f]) >> (input >> 6);
}
// Stepped Minor multiply beat for mapping, not finished
//
uint16_t minorTable[20] = {
1,2,3,4,6,8,12,16,24,32,48,64,65,77,82,103,109,129,154,163
};
uint16_t mapMinor(uint16_t input) {
uint8_t value = (1023-input) / (1024/20);
return (minorTable[value]);
}
void audioOn() {
// Set up PWM to 31.25kHz, phase accurate
TCCR2A = _BV(COM2B1) | _BV(WGM20);
TCCR2B = _BV(CS20);
TIMSK2 = _BV(TOIE2);
}
void setup()
{
pinMode( 12, INPUT ); /* tap button - press it to set the tempo */
pinMode( 13, OUTPUT ); /* button state display - not necessary */
pinMode( 8, OUTPUT ); /* tempo display light - shows the current tempo */
pinMode(PWM_PIN,OUTPUT);
audioOn();
// pinMode(LED_PIN,OUTPUT);
// initialize serial communication for debugging:
Serial.begin(9600);
}
int lastTapState = LOW; /* the last tap button state */
unsigned long currentTimer[2] = { 500, 500 }; /* array of most recent tap counts */
unsigned long timeoutTime = 0; /* this is when the timer will trigger next */
unsigned long indicatorTimeout; /* for our fancy "blink" tempo indicator */
void loop()
{
// read the value from the sensor:
/* read the button on pin 12, and only pay attention to the
HIGH-LOW transition so that we only register when the
button is first pressed down */
int tapState = digitalRead( 12 );
if( tapState == LOW && tapState != lastTapState )
{
tap(); /* we got a HIGH-LOW transition, call our tap() function */
}
lastTapState = tapState; /* keep track of the state */
/* check for timer timeout */
if( millis() >= timeoutTime )
{
/* timeout happened. clock tick! */
indicatorTimeout = millis() + 30; /* this sets the time when LED 13 goes off */
/* and reschedule the timer to keep the pace */
rescheduleTimer();
}
/* display the tap button state on LED 13 */
digitalWrite( 13, tapState );
if( millis() < indicatorTimeout ) {
digitalWrite( 8, HIGH );
} else {
digitalWrite( 8, LOW );
}
// This is the synth, the loop is pretty simple - it just updates the parameters for the oscillators.
//
// Smooth frequency mapping
syncPhaseInc = mapPhaseInc(analogRead(SYNC_CONTROL)) / 4;
// Stepped pentatonic mapping: D, E, G, A, B
syncPhaseInc = (1000 / theTime) * mapPhaseInc(analogRead(SYNC_CONTROL));
diviDer = ((analogRead(GRAIN_TONE_CONTROL)) / 8) + 1;
grainPhaseInc = mapPhaseInc(analogRead(GRAIN2_FREQ_CONTROL)) / diviDer;
grainDecay = 1000 / theTime;
grain2PhaseInc = mapPhaseInc(analogRead(GRAIN2_FREQ_CONTROL)) / diviDer;
grain2Decay = 1000 / theTime;
}
SIGNAL(PWM_INTERRUPT)
{
uint8_t value;
uint16_t output;
syncPhaseAcc += syncPhaseInc;
if (syncPhaseAcc < syncPhaseInc) {
// Time to start the next grain
grainPhaseAcc = 0;
grainAmp = 0x7fff;
grain2PhaseAcc = 0;
grain2Amp = 0x7fff;
LED_PORT ^= 1 << LED_BIT; // Faster than using digitalWrite
}
// Increment the phase of the grain oscillators
grainPhaseAcc += grainPhaseInc;
grain2PhaseAcc += grain2PhaseInc;
// Convert phase into a triangle wave
value = (grainPhaseAcc >> 7) & 0xff;
if (grainPhaseAcc & 0x8000) value = ~value;
// Multiply by current grain amplitude to get sample
output = value * (grainAmp >> 8);
// Repeat for second grain
value = (grain2PhaseAcc >> 7) & 0xff;
if (grain2PhaseAcc & 0x8000) value = ~value;
output += value * (grain2Amp >> 8);
// Make the grain amplitudes decay by a factor every sample (exponential decay)
grainAmp -= (grainAmp >> 8) * grainDecay;
grain2Amp -= (grain2Amp >> 8) * grain2Decay;
// Scale output to the available range, clipping if necessary
output >>= 9;
if (output > 255) output = 255;
// Output to PWM (this is faster than using analogWrite)
PWM_VALUE = output;
}
unsigned long lastTap = 0; /* when the last tap happened */
void tap()
{
/* we keep two of these around to average together later */
currentTimer[1] = currentTimer[0];
currentTimer[0] = millis() - lastTap;
lastTap = millis();
timeoutTime = 0; /* force the trigger to happen immediately - sync and blink! */
}
void rescheduleTimer()
{
/* set the timer to go off again when the time reaches the
timeout. The timeout is all of the "currentTimer" values averaged
together, then added onto the current time. When that time has been
reached, the next tick will happen...
*/
// these constants won't change:
const int sensorMin = 0; // sensor minimum, discovered through experiment
const int sensorMax = 1023; // sensor maximum, discovered through experiment
int sensorPin = analogRead(A0); // select the input pin for the potentiometer
int range = map(sensorPin, sensorMin, sensorMax, 0, 9);
float pot = 0; // multiplier for sensor
// do something different depending on the
// range value:
switch (range) {
case 0: //
pot = 8;
break;
case 1: //
pot = 6;
break;
case 2: //
pot = 4;
break;
case 3: //
pot = 3;
break;
case 4: //
pot = 2;
break;
case 5: //
pot = 1;
break;
case 6: //
pot = 0.5;
break;
case 7: //
pot = 0.33;
break;
case 8: // 2 decimal places is the limit
pot = 0.25;
break;
}
timeoutTime = millis() + (((currentTimer[0] + currentTimer[1])/2) * pot);
theTime = (((currentTimer[0] + currentTimer[1])/2) * pot);
}
I'm really happy with what I have so far, but I'm wondering if it's possible to add a switch to synchronise the master flashing to the audio. Perhaps by reseting the phase? I've tried various approaches using the LED value e.g. while() and multiplying the phase by the inverse of its state, but no luck so far.
Any help much appreciated!