Go Down

Topic: Playing around with timers and tones. (Read 3 times) previous topic - next topic

WilliamK Govinda

Well, the whole thing advanced a lot. I'm using all pins on port D, so to send note events I use another board I2C connection. The idea is to use an ATmega328 at 20Mhz on its own Shield when its done.

So far I got 50khz, 8 voices, PWM modulation and double-voice with deviation, it sounds PHAT. ;-)  8) :smiley-fat:

I removed one check and just let the position overflow by itself. So I could use MULT as 65535 and MULT2 as 65535/2 and also modulate the Pulse Width.

Its not ready yet, but once it is, a single chip will get a nice 8 voice duo-note output. Still miss filters and envelopes, but that will be added on the XT version, this is just the LE version. ;-)

Code: [Select]

#include <Wire.h>

float freqtab[128];
#define MULT 65535
#define MULT2 (65535/2)
#define MTYPE unsigned int
#define MAX_V 8
#define I2C_ADDRESS 4
MTYPE oscPos[MAX_V];
MTYPE oscRate[MAX_V];
MTYPE oscPWM = MULT2;
uint8_t outPins = 0;
uint8_t voiceNote[MAX_V];
uint8_t voiceOrder[MAX_V];
uint8_t voiceOrderCounter = 0;
uint8_t voiceActive[MAX_V];

// ======================================================================================= //
void setup()
{
  memset(oscPos,0,sizeof(oscPos));
  memset(oscRate,0,sizeof(oscRate));
  memset(voiceNote,0,sizeof(voiceNote));
  memset(voiceOrder,0,sizeof(voiceOrder));
  memset(voiceActive,0,sizeof(voiceActive));
 
  double k = 1.059463094359; // Frequency (Hz) Table
  double a = 6.875;
  a *= k; a *= k; a *= k;
  for (int i = 0; i < 128; i++) { freqtab[i] = (float)a; a *= k; }
   
  TCCR1A = _BV(WGM11);
  TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS10);
  ICR1 = 300-1;
   
  pinMode(0, OUTPUT);
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
 
  TIMSK1 = _BV(TOIE1);
  sei();
 
  Wire.begin(I2C_ADDRESS);
  Wire.onReceive(receiveEvent);
}

// ======================================================================================= //
uint8_t isDecay = 1;
uint8_t pwmModRate = 1;
uint8_t pwmModRateCounter = 0;
uint8_t pwmModAdder = 8;
#define calcOSC(a) oscPos[a] += oscRate[a]; if (voiceActive[a] == 1 && oscPos[a] < oscPWM) bitSet(outPins,a);

// ======================================================================================= //
ISR(TIMER1_OVF_vect)
{
  PORTD = outPins;
  outPins = 0;
   
  calcOSC(0);
  calcOSC(1);
  calcOSC(2);
  calcOSC(3);
  calcOSC(4);
  calcOSC(5);
  calcOSC(6);
  calcOSC(7);
 
  pwmModRateCounter++;
  if (pwmModRateCounter > pwmModRate)
  {
    pwmModRateCounter = 0;
   
    if (isDecay == 1)
    {
      oscPWM -= pwmModAdder; if (oscPWM < (MULT2/100)) isDecay = 0;
    }
    else
    {
      oscPWM += pwmModAdder; if (oscPWM >= MULT2) isDecay = 1;
    }
  }
}

// ======================================================================================= //
void loop()

  //
}

// ======================================================================================= //
void receiveEvent(int howMany)
{
  while(Wire.available() > 0) // loop through all but the last
  {
    char c = Wire.receive(); // receive byte as a character
    if (c > 0) setNoteOn(c); else if (c < 0) setNoteOff(c*-1);
  }
}

// ======================================================================================= //
void setNoteOn(uint8_t note)
{
  uint8_t voice = 255;
  for (char v=0; v<MAX_V/2; v++)
  {
    if (voiceNote[v] == 0)
    {
      voice = v;
      break;
    }
  }
 
  // All voices used, steal oldest one //
  if (voice == 255)
  {
    uint8_t order = 255;
    for (char v=0; v<MAX_V/2; v++)
    {
      if (voiceOrder[v] < order)
      {
        order = voiceOrder[v];
        voice = v;
      }
    }
  }
 
  // Store note //
  voiceOrderCounter++;
  voiceNote[voice] = note;
  voiceOrder[voice] = voiceOrderCounter;
 
  // Set Output //
  oscRate[voice] = (MTYPE) MULT * (1.0f/53333.333f) * freqtab[note];
  voiceActive[voice] = 1;

  uint8_t deviation = 1;
  if (note > 48) deviation = ((note-48)/4)+1;
  oscRate[voice+4] = oscRate[voice] + deviation;
  voiceActive[voice+4] = 1;
}

// ======================================================================================= //
void setNoteOff(uint8_t note)
{
  for (char v=0; v<MAX_V/2; v++)
  {
    if (voiceNote[v] == note)
    {
      voiceActive[v] = voiceActive[v+4] = 0;
      voiceNote[v] = 0;
      oscRate[v] = 0;
      oscRate[v+4] = 0;
      break;
    }
  } 
}

WilliamK Govinda

And here's a very simple sketch to read the MIDI Shield (Rugged Circuits) and send I2C data to the other board.

Code: [Select]
#include <Wire.h>

#define I2C_ADDRESS 4
#define MIDIchannel 0        // Regular MIDI Channel - 1 (0 to 15)
#define MIDIactivityPin 13
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
#  define MIDI_EN 57     // PF3 = 57
#else
#  define MIDI_EN 17
#endif
byte incomingByte;
byte note;
byte noteOn = 0;
byte state = 0;
unsigned long MIDIactivityMillis = millis()+25;
byte MIDIactivity = 0;

void setup()
{
  pinMode(MIDIactivityPin,OUTPUT);
  digitalWrite(MIDIactivityPin,LOW);
 
  Wire.begin(); // join i2c bus (address optional for master)

  Serial.begin(31250);
  pinMode(MIDI_EN, OUTPUT); digitalWrite(MIDI_EN, HIGH);
}

void loop()
{
  if (MIDIactivity && MIDIactivityMillis < millis())
  {
    MIDIactivity = 0;
    digitalWrite(MIDIactivityPin,LOW);
  }
 
  if (Serial.available() > 0)
  { 
    incomingByte = Serial.read();
    switch (state)
    {
      case 0:
        if (incomingByte >= 128)
        {
          digitalWrite(MIDIactivityPin,HIGH);
          MIDIactivityMillis = millis()+25;
          MIDIactivity = 1;
         
          if (incomingByte <= 143) // Note Off //
          {
            //if ((incomingByte-128) == MIDIchannel)
            noteOn = 0;
            state = 1;
          }
          else if (incomingByte <= 159) // Note On //
          {
            //if ((incomingByte-144) == MIDIchannel)
            noteOn = 1;
            state = 1;
          }
        }
        break;
       
       case 1:
         if(incomingByte < 128) // Note Number //
         {
            note = incomingByte;
            state = 2;
         }
         else state = 0;  // reset //
         break;
       
       case 2:
         if(incomingByte < 128) // Velocity //
         {
           if (noteOn == 1 && incomingByte > 0) sendNote(note); else sendNote(note*-1);
           noteOn = 0;
         }
         state = 0;  // reset //
         break;
     }
  }
}

void sendNote(char note)
{
  Wire.beginTransmission(I2C_ADDRESS);
  Wire.send(note);
  Wire.endTransmission();
}

robtillaart

BIG progress...
small question, can't the 2 loops be merged into one? if so is it faster/smaller footprint?
Code: [Select]

  uint8_t voice = 255;
  uint8_t order = 255;
  for (char v=0; v<MAX_V/2; v++)
  {
    if (voiceNote[v] == 0)
    {
      voice = v;
      break;
    }
    if (voiceOrder[v] < order)
    {
      order = voiceOrder[v];
      voice = v;
    }   
  }
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

robtillaart

Quote
oscRate[voice] = (MTYPE) MULT * (1.0f/53333.333f) * freqtab[note];


What if you build a second table with precalculated values....

oscTable[128] = (MTYPE) MULT * (1.0f/53333.333f) * freqtab[..];

then you could just state:

oscRate[voice] = oscTable[note];

away with those floating points ... ;)
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

WilliamK Govinda

Ah, good ideas, I will take care of those once everything is set in stone, right now I'm still experimenting with rates and other things. I plan on running it at 20Mhz and the higher rate possible, to get a better sound on higher octaves.

Wk

robtillaart

#20
Aug 15, 2011, 11:23 pm Last Edit: Aug 16, 2011, 12:32 am by robtillaart Reason: 1
Maybe it is possible to generate oscTable directly in Setup() iso indirect though freqTab[] ... That would remove all floating points.

update --
A quick look showed it was rather simple to make a oscTable generator, a separate sketch that generates a function that fills the oscTable for a given value of MULT. To do that is uses float operations and the freq table but the function generated uses only integer math !  Includes a check for the values in the table.

Have a look.
Code: [Select]

float freqtab[128];
#define MULT 65535
#define MTYPE unsigned int

MTYPE oscTable1[128];
MTYPE oscTable[128];

void setup()
{
  Serial.begin(115200);
  Serial.println("Start");

  f();
  gen();
}

void f()
{
  double k = 1.059463094359; // Frequency (Hz) Table
  double a = 6.875;
  a *= k;
  a *= k;
  a *= k;
  for (int i = 0; i < 128; i++)
  {
    freqtab[i] = (float)a;
    oscTable1[i] = (MTYPE) MULT * (1.0f/53333.333f) * freqtab[i];
    a *= k;
  }

  Serial.println();
  Serial.println("void gen()");
  Serial.println("{");
  for (int i= 127; i> 115; i--)
  {
    Serial.print("  oscTable[");
    Serial.print(i);
    Serial.print("] = ");
    Serial.print(oscTable1[i]);
    Serial.println(";");
  }
  Serial.println("  for (int i = 115; i>= 0; i--) oscTable[i] = oscTable[i+12]/2;");
  Serial.println("}");
  Serial.println();
  Serial.println(); 
}


void gen()
{
  oscTable[127] = 15413;
  oscTable[126] = 14548;
  oscTable[125] = 13732;
  oscTable[124] = 12961;
  oscTable[123] = 12233;
  oscTable[122] = 11547;
  oscTable[121] = 10899;
  oscTable[120] = 10287;
  oscTable[119] = 9710;
  oscTable[118] = 9165;
  oscTable[117] = 8650;
  oscTable[116] = 8165;

  for (int i = 115; i>= 0; i--) oscTable[i] = oscTable[i+12]/2;

  for (int i = 0; i < 128; i++)
  {
    Serial.print( i );
    Serial.print(" \t ");
    Serial.print(oscTable1[i]);
    Serial.print(" \t ");
    Serial.print(oscTable[i]);
    Serial.print(" \t ");
    Serial.println(oscTable1[i] - oscTable[i]);
  }
}


void loop()
{
}


Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

WilliamK Govinda

The final project is up, thank you so much for all your help!  8)

I just posted on a new thread:

http://arduino.cc/forum/index.php/topic,69874.0.html

sciguy

Hmmm, I'm working on a very similar thing, but I've decided I'm going to try and figure it all out myself, so I won't look at your technique.   :smiley-sweat:

I'm aiming for 4 voices, but with 16 step 4 bit waveforms.  I'm also using a separate timer to control fast PWM to run thru a lowpass filter in order to get an analog output. 
Monophonic is working great, I posted it in exhibition, but still figuring out polyphonic sound.
Soundcloud page: http://soundcloud.com/beefinator-2
Youtube channel: http://www.youtube.com/user/beefinator14
Old soundcloud page (ran out o

WilliamK Govinda

Dude, check out the BeatVox code at the following link. It does 6 voices 8-bit sample playback. ;-)

https://github.com/Beat707/BeatVox

Wk

sciguy

Oh, I forgot to mention I'm trying to do this on a duemilanove...    :smiley-roll-sweat:

Thanks anyway, though
Soundcloud page: http://soundcloud.com/beefinator-2
Youtube channel: http://www.youtube.com/user/beefinator14
Old soundcloud page (ran out o

WilliamK Govinda

Yup, I also did it on a 2009 and also Uno boards, they are both the same Flash/Ram anyway.

It works great, but so far I only did one-shot playback. Never had the time to implement interpolated playback, but should be easy.

I would say that 4 voices should be ok to do, even 6 if you take some shortcuts, like no interpolation. ;-)

Wk

Go Up