Pages: 1 [2]   Go Down
Author Topic: Playing around with timers and tones.  (Read 2995 times)
0 Members and 1 Guest are viewing this topic.
Brazil
Offline Offline
God Member
*****
Karma: 3
Posts: 616
Wusik Dot Com
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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. ;-)  smiley-cool 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:
#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;
    }
  } 
}
Logged


Brazil
Offline Offline
God Member
*****
Karma: 3
Posts: 616
Wusik Dot Com
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Code:
#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();
}
Logged


Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 224
Posts: 13915
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

BIG progress...
small question, can't the 2 loops be merged into one? if so is it faster/smaller footprint?
Code:
  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;
    }   
  }
Logged

Rob Tillaart

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

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 224
Posts: 13915
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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 ... smiley-wink
Logged

Rob Tillaart

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

Brazil
Offline Offline
God Member
*****
Karma: 3
Posts: 616
Wusik Dot Com
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged


Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 224
Posts: 13915
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
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()
{
}

« Last Edit: August 15, 2011, 05:32:45 pm by robtillaart » Logged

Rob Tillaart

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

Brazil
Offline Offline
God Member
*****
Karma: 3
Posts: 616
Wusik Dot Com
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

The final project is up, thank you so much for all your help!  smiley-cool

I just posted on a new thread:

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


Nowhere
Offline Offline
God Member
*****
Karma: 3
Posts: 852
|-\ |\|\
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

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

Brazil
Offline Offline
God Member
*****
Karma: 3
Posts: 616
Wusik Dot Com
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

https://github.com/Beat707/BeatVox

Wk
Logged


Nowhere
Offline Offline
God Member
*****
Karma: 3
Posts: 852
|-\ |\|\
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Thanks anyway, though
Logged

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

Brazil
Offline Offline
God Member
*****
Karma: 3
Posts: 616
Wusik Dot Com
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged


Pages: 1 [2]   Go Up
Jump to: