Go Down

Topic: Playing around with timers and tones. (Read 3971 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
 


Please enter a valid email to subscribe

Confirm your email address

We need to confirm your email address.
To complete the subscription, please click the link in the email we just sent you.

Thank you for subscribing!

Arduino
via Egeo 16
Torino, 10131
Italy