Saturday, February 13, 2010

12 Bar Blues on an Arduino

Using a small 8ohm analogue speaker hooked up to pin 11, by strobing the pin, I generate a square wave (a kind of annoying timbre to be honest).
I generate 2 octaves of semitones starting with A-220 and index into it.
The timing is currently inaccurate as the note length for lower notes runs slightly longer (half a pulse width) than higher notes due to how the strobing currently works. It's surprisingly noticeable. I need to work on that to make it more consistent. Probably by using timer interrupts instead of delays.

To do:

Alter the volume by using analogWrite() to set the duty cycle of the pin... 255 = loud 0 = off.
I think I can use that mechanism to generate other waveforms, I'll probably try sawtooth next.
Proper ADSR waveforms.
Multichannel output (haw haw).


const int baseFreq = 220; // A
const int numHalfTones = 24; // 2 Octaves
long frequencies[numHalfTones];
long halfFreqs[numHalfTones];

/* 12 semi-tones per octave
  | Bf| | | C#| Ef| | |F# |G# |
 -+---+ | +---+---+ | +---+---+-
| A | B | C | D | E | F | G | A |
+---+---+---+---+---+---+---+---+
 
 2 octaves: 
 0  1  2  3  4  5  6  7  8  9 10 11
 A Bf  B  C C#  D Ef  E  F F#  G G# 
 12 13 14 15 16 17 18 19 20 21 22 23
 */

int seq[] = {
  3,  7, 10, 12, 13, 12, 10 , 7,
  3,  7, 10, 12, 13, 12, 10 , 7,
  8, 12, 15, 17, 18, 17, 15, 12,
  3,  7, 10, 12, 13, 12, 10 , 7,
  10, 14, 17, -1,  8, 12, 15, -1
};

int notes = 40;

long getTimeHigh(int i)
{
  return halfFreqs[i]; 
};

void ToneGen(void)
{
  float baseMult;
  int i;
  int lastTone = baseFreq;  
  baseMult = pow(2.0 , 0.083333);
  for(i = 0; i < numHalfTones; ++i)
  {
    long frequency = baseFreq;
    if(i)
    {
      frequency = (0.5 + (frequencies[i - 1] * baseMult));
    }
    frequencies[i] = frequency;
    halfFreqs[i] = (0.5 + (1000000.0 / frequency / 2.0));
  } 
};

void setup(void) {
  pinMode(11, OUTPUT);
  ToneGen();
  Serial.begin(57600);
}

int rest_count = 100;
long duration = (60 * 1000000) / 288;

void playNote(int idx)
{
  long tone = 0;
  if (idx >= 0)
  {
    tone = getTimeHigh(idx);
  }

  long endTime = micros() + duration ;
  if (tone > 0) 
  { 
    int val = HIGH;  
    while (micros() < endTime) 
    {
      digitalWrite(11,val);
      delayMicroseconds(tone);
      val = !val;
    } 

  }
  else 
  {
    delay(duration / 1000);
  }   
  digitalWrite(11, LOW); 
}

void loop(void) {
  analogWrite(11, 0);
  for(int i = 0; i < notes; ++i)
  {  
    playNote(seq[i]); 
    delay(10);
  }
}

No comments: