Playing around with timers and tones.

I was wondering on using a timer to call an interrupt and have it drive multiple output ports and multiple tones. Its easy to do it draft, but I was hoping to get some good hz with tune and fine control. Any ideas? :wink:

The code below uses a single pin and Timer1, plus analog input on pin A2 just to showcase a high to low frequency range.

I did this quickly, so don't expect much. :blush:

int sensorPin = A2;
int soundPin = 9;
int hzOSC = 0;
uint8_t pinValue = 0;
int posValue[2] = {0,0};

void setup() {
  pinMode(soundPin, OUTPUT);  
  
  TCCR1A = _BV(WGM11);
  TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS10);
  ICR1 = 200;
  TIMSK1 = _BV(TOIE1);
  
  sei(); // enable global interrupts
}

void loop() 
{
  hzOSC++;
  if (hzOSC > 800) hzOSC = 0;
  delay(map(analogRead(sensorPin), 0, 1023, 0, 10));
}

ISR(TIMER1_OVF_vect)
{
  if (pinValue == 0) PORTB &= B11111101; else PORTB |= B00000010;
  
  if (posValue[1] > hzOSC)
  {
    pinValue = !pinValue;
    posValue[1] = 0;
  }
  
  posValue[0]++;
  if (posValue[0] > 2)
  {
    posValue[0] = 0;
    posValue[1]++;
  }
}

What did you connect to the soundpin?

looking at the code:

  • for a draft exercise I would use delay(analogRead(sensorPin)/100);

  • if (pinValue == 0) pinvalue is 0 or 1; that equals true/false
    ==> if (pinvalue) PORTB |= B00000010; else PORTB &= B11111101;

as they are in an IRQ some vars need to be volatile
volatile int hzOSC = 0;
volatile int posValue[2] = {0,0};

every 3 IRQ's posValue [1] is increased. for this posValue[0] is used to count to 3.
why not:

  if (posValue > hzOSC * 3)
  {
    pinValue = !pinValue;
    posValue = 0;
  }

is it slower?
optionally let hzOSC go from 0..2400 in steps of 3 within loop() than you can do if (posValue > hzOSC) in the IRQ again

Rob

Since I need HZ with fraction format, for fine-tune, I created the non-optimized code, which works great. Just need to find a way to optimize it... :drooling_face:

#define soundPin 9
uint8_t pinValue[4] = {0,0,0,0};
float freqtab[128];
float oscPos[4] = {0.0f,0.0f,0.0f,0.0f};
float oscRate[4] = {1.0f,1.0f,1.0f,1.0f};

void setup() 
{
  Serial.begin(38400);
  
  // make frequency (Hz) table
  double k = 1.059463094359;	// 12th root of 2;
  double a = 6.875;		// a
  a *= k;	// b
  a *= k;	// bb
  a *= k;	// c, frequency of midi note 0
  for (int i = 0; i < 128; i++)
  {
    freqtab[i] = (float)a;
    Serial.print(i, DEC); Serial.print(" - "); Serial.println(freqtab[i]);
    a *= k;
  }

  pinMode(soundPin, OUTPUT);
   
  TCCR1A = _BV(WGM11);
  TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS10);
  ICR1 = 2000-1;
  TIMSK1 = _BV(TOIE1);
  
  sei(); // enable global interrupts
}

void loop() 
{
  ICR1 = 2000-1;
  oscRate[0] = oscRate[1] = oscRate[2] = (1.0f/8000.0f) * freqtab[60];
  delay(2000);
  oscRate[0] = (1.0f/8000.0f) * freqtab[72];
  delay(150);
  
  ICR1 = 1000-1;
  oscRate[0] = oscRate[1] = oscRate[2] = (1.0f/16000.0f) * freqtab[60];
  delay(2000);
  oscRate[0] = (1.0f/16000.0f) * freqtab[72];
  delay(150);
  
  ICR1 = 800-1;
  oscRate[0] = oscRate[1] = oscRate[2] = (1.0f/20000.0f) * freqtab[60];
  delay(2000);
  //oscRate[0] = (1.0f/20000.0f) * freqtab[72];
  //delay(150);
  
  //ICR1 = 400-1;
  //oscRate[0] = oscRate[1] = (1.0f/40000.0f) * freqtab[60];
  //delay(2000);
  
  oscRate[0] = (1.0f/40000.0f) * freqtab[72];
  delay(100);
  oscRate[0] = (1.0f/40000.0f) * freqtab[84];
  delay(100);
  oscRate[0] = (1.0f/40000.0f) * freqtab[96];
  delay(100);
}

#define calcOsc(a) oscPos[a] += oscRate[a]; if (oscPos[a] >= 1.0f) { pinValue[a] = !pinValue[a]; oscPos[a] -= 1.0f; }
ISR(TIMER1_OVF_vect)
{
  if (pinValue[0] == 0) PORTB &= B11111101; else PORTB |= B00000010; // D9  (PB1)
  if (pinValue[1] == 0) PORTB &= B11110111; else PORTB |= B00001000; // D11 (PB3)
  if (pinValue[2] == 0) PORTD &= B11101111; else PORTD |= B00010000; // D4  (PD4)
  
  calcOsc(0);
  calcOsc(1);
  calcOsc(2);
}

And here's a new code. Now you can modulate the angle of the square waveform, I also added a simple modulation to PWM, like the Alpha Juno had. :slight_smile: Just use 2 buttons, analog input, and analog output. So far I got 2 voices at 20khz, but I want more and at 40khz, so I need to optimize the code. I added a test-samplerate code to check if the code is overrunning or not.

#define soundPin1 9 // See below for direct port manipulation
#define soundPin2 4 // See below for direct port manipulation
#define analogInput A2
#define buttonPin 12
#define buttonPinPWM 8

float freqtab[128];
float oscPos[4] = {0.0f,0.0f,0.0f,0.0f};
float oscRate[4] = {1.0f,1.0f,1.0f,1.0f};
float oscPWM[4] = {0.5f,0.5f,0.5f,0.5f};

void setup() 
{
  Serial.begin(38400);
  
  // make frequency (Hz) table
  double k = 1.059463094359;	// 12th root of 2;
  double a = 6.875;		// a
  a *= k;	// b
  a *= k;	// bb
  a *= k;	// c, frequency of midi note 0
  for (int i = 0; i < 128; i++)
  {
    freqtab[i] = (float)a;
    a *= k;
  }

  pinMode(soundPin1, OUTPUT);
  pinMode(soundPin2, OUTPUT);
  pinMode(buttonPin, INPUT);
  pinMode(buttonPinPWM, INPUT);
   
  TCCR1A = _BV(WGM11);
  TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS10);
  ICR1 = 2000-1;
  TIMSK1 = _BV(TOIE1);
  
  sei(); // enable global interrupts  

  ICR1 = 800-1;
  oscRate[0] = oscRate[1] = oscRate[2] = (1.0f/20000.0f) * freqtab[60];
  oscPWM[0] = 0.9f;  
}

void loop() 
{
  if (!digitalRead(buttonPin)) testSampleRate(); else testPWM();
}

#define calcOSC(a) oscPos[a] += oscRate[a]; if (oscPos[a] >= 1.0f) oscPos[a] -= 1.0f;
ISR(TIMER1_OVF_vect)
{ 
  if (oscPos[0] <= oscPWM[0]) PORTB &= B11111101; else PORTB |= B00000010;
  if (oscPos[1] <= oscPWM[1]) PORTD &= B11101111; else PORTD |= B00010000;
  
  calcOSC(0);
  calcOSC(1);
}

uint8_t isUp = true;
float pwmRate = 0.0001f;
void testPWM()
{
  if (!digitalRead(buttonPinPWM)) 
  {
    oscPWM[0] = 0.1f + (float(analogRead(analogInput))/1023.0f)/2.5f;
  }
  else
  {

    pwmRate = (float(analogRead(analogInput))/1023.0f) / 1000.0f;
 
    if (isUp)
    { 
      oscPWM[0] += pwmRate;
      if (oscPWM[0] >= 0.5f) isUp = false;
    }
    else
    {
      oscPWM[0] -= pwmRate;
      if (oscPWM[0] <= 0.1f) isUp = true;
    }
  }
}


void testSampleRate()
{
  oscPWM[0] = 0.5f;
  
  ICR1 = 2000-1;
  oscRate[0] = oscRate[1] = oscRate[2] = (1.0f/8000.0f) * freqtab[60];
  delay(2000);
  if (digitalRead(buttonPin)) return;
  oscRate[0] = (1.0f/8000.0f) * freqtab[72];
  delay(150);
  
  ICR1 = 1000-1;
  oscRate[0] = oscRate[1] = oscRate[2] = (1.0f/16000.0f) * freqtab[60];
  delay(2000);
  if (digitalRead(buttonPin)) return;
  oscRate[0] = (1.0f/16000.0f) * freqtab[72];
  delay(150);
  
  ICR1 = 800-1;
  oscRate[0] = oscRate[1] = oscRate[2] = (1.0f/20000.0f) * freqtab[60];
  delay(2000);
  if (digitalRead(buttonPin)) return;
  oscRate[0] = (1.0f/20000.0f) * freqtab[72];
  delay(150);
  
  ICR1 = 400-1;
  oscRate[0] = oscRate[1] = (1.0f/40000.0f) * freqtab[60];
  delay(2000);
  if (digitalRead(buttonPin)) return;
  
  oscRate[0] = (1.0f/40000.0f) * freqtab[72];
  delay(100);
  oscRate[0] = (1.0f/40000.0f) * freqtab[84];
  delay(100);
  oscRate[0] = (1.0f/40000.0f) * freqtab[96];
  delay(100);
}

These comments are focussed on the freq table:

For generating a frequency table you better begin at the high end and do divisions - maybe not as fast, but errors don't add up.

Furthermore if you have the highest octave, you only need divisions by 2.
C8 = C8 /1;
C7 = C8 /2;
C6 = C8 /4;
C5 = C8 /8;
etc. ..

This means a smaller table is possible - 12 elements instead of 128 floats (464 bytes of precious RAM) - comparison sketch below

Top down generation is 0.1 hz better compared to - Frequencies of Musical Notes, A4 = 440 Hz -

float ft[12];
float freqtab[128];

float FREQ(uint8_t t)
{
  uint8_t n = t/12;
  uint8_t o = t - n*12;
  return (ft[o]/(1 << (10-(n))));
}

void setup() 
{
  Serial.begin(38400);

  // make frequency (Hz) table
  double k = 1.059463094359;	// 12th root of 2;
  double A9 = 14080;
  A9*=k;
  A9*=k;		// a
  for (int i = 11; i >=0; i--)
  {
    ft[i] = A9;
    A9 /= k;
  }

  // make frequency (Hz) table
  double a = 6.875;		// a
  a *= k;	// b
  a *= k;	// bb
  a *= k;	// c, frequency of midi note 0
  for (int i = 0; i < 128; i++)
  {
    freqtab[i] = (float)a;
    a *= k;
  }

  for (int i = 0; i<128; i++)
  {
    Serial.print(i, DEC); 
    Serial.print(" - "); 
    Serial.print(FREQ(i));
    Serial.print(" - "); 
    Serial.println(freqtab[i]);
  }
}

void loop() 
{
}

Disadvantage will be that the access takes more time than with full table, don't know if that is acceptable

another trick to make the table smaller is to use uint16_t to hold the frequency multiplied by 8.0 (divide by 8.0 when read)

  • formula : f = (freqtab * 8 + 0.5); // 0.5 for rounding
    * Error in frequency is max 0.63% (lowest octave)
    * works for freqtab(0..119) so not the full range - 127
    * => to get full range use factor 5.0 but error goes up to 0.96%*
    timing in micros

    accessing all 128 elements in a loop assigning to a float.
    * total per item*
    full table: 196 1.53 factor 1
    calc table: 5688 44.44 factor 29.0
    int table: 1652 12.90 factor 8.44 (multiplier 8 - other multipliers tested did worse)
    Conclusion:
    It is clear that memory and its access has its price.

but I want more and at 40khz,

(OK my freq table remarks make no sense for speed up :wink:

To optimize speed you need to get rid of the float math or at least optimize it.

some oneliners:

oscPWM[0] = 0.1f + (float(analogRead(analogInput))/1023.0f)/2.5f;
==>
oscPWM[0] = 0.1 + analogRead(analogInput) * 3,9100e-4; // multiplication is cheaper than division

oscRate[2] = (1.0f/20000.0f) * freqtab[60];
==>
oscRate[2] = freqtab[60] * 0,00005;

pwmRate = (float(analogRead(analogInput))/1023.0f) / 1000.0f;
==>
pwmRate = analogRead(analogInput) * 9,775171e-7;

oscRate[0] = oscRate[1] = oscRate[2] = (1.0f/8000.0f) * freqtab[60];
==>
oscRate[0] = oscRate[1] = oscRate[2] = 0,000125 * freqtab[60];

etc


as I see in your code that entries of the freq table are divided by 4000,8000,16000, 20000, 40000.

Why not multiplying the freq table with the Greatest Common Divider 80000? // do this in advance when generating the table
That would give a multiplications by 20, 10, 5, 4, and 2 which might be faster


You have some PWM calculation with values between 0 and 1.
Try to make unsigned longs of it and do the math in microseconds iso seconds
Maybe working in milliseconds is good enough - multiply all floats with 512 and the error will be in the order of 0.5% max.

my 2 cents,
Rob

Thanks for the ideas, I will check each individually tomorrow. 8)

The real-time interrupt is where things needs optimization, everything else is not real-time, so its ok if its slow.

#define calcOSC(a) oscPos[a] += oscRate[a]; if (oscPos[a] >= 1.0f) oscPos[a] -= 1.0f;
ISR(TIMER1_OVF_vect)
{ 
  if (oscPos[0] <= oscPWM[0]) PORTB &= B11111101; else PORTB |= B00000010;
  if (oscPos[1] <= oscPWM[1]) PORTD &= B11101111; else PORTD |= B00010000;
  
  calcOSC(0);
  calcOSC(1);
}

This is where I need to optimize as much as possible. So far I can get 2 voices at 20khz, which is not bad, but I want 4 voices at 40khz instead. :drooling_face: Them I can go crazy and see if I could do another thing with this, but that's for later on. :wink:

Wk

#define calcOSC(a) oscPos[a] += oscRate[a]; if (oscPos[a] >= 1.0f) oscPos[a] -= 1.0f;
ISR(TIMER1_OVF_vect)
{ 
  if (oscPos[0] <= oscPWM[0]) PORTB &= B11111101; else PORTB |= B00000010;
  if (oscPos[1] <= oscPWM[1]) PORTD &= B11101111; else PORTD |= B00010000;
  
  calcOSC(0);
  calcOSC(1);
}

you are comparing floats, that takes time. See sketch below:

volatile float x = 1.0f;
volatile float y = 2.0f;
volatile long  k = 1;
volatile long  l = 2;
volatile int  p = 1;
volatile int  q = 2;
int a;

void setup()
{
  Serial.begin(115200);

  uint32_t before = millis();
  for(int i=0; i< 10000; i++)
  {
    if (x < y) a = 15; 
    else a = 10;
  }
  Serial.println(millis()- before);

  before = millis();
  for(int i=0; i< 10000; i++)
  {
    if (k < l) a = 15; 
    else a = 10;
  }
  Serial.println(millis()- before);

  before = millis();
  for(int i=0; i< 10000; i++)
  {
    if (p < q) a = 15; 
    else a = 10;
  }
  Serial.println(millis()- before);
}

void loop()
{
}

my output (A2009)
47 - float compare
20 - long compare
13 - int compare

So there is a factor 2 or 3 to be gained.

OK, here is your code patched so the critical part is using int iso float. Hope I got all points patched, give it a try...
Rob

#define soundPin1 9 // See below for direct port manipulation
#define soundPin2 4 // See below for direct port manipulation
#define analogInput A2
#define buttonPin 12
#define buttonPinPWM 8

float freqtab[128];
// all multiplied by 10000
int oscPos[4] = {0,0,0,0};
int oscRate[4] = {10000,10000,10000,10000};
int oscPWM[4] = {5000,5000,5000,5000};

void setup() 
{
  Serial.begin(38400);
  
  // make frequency (Hz) table
  double k = 1.059463094359;	// 12th root of 2;
  double a = 6.875;		// a
  a *= k;	// b
  a *= k;	// bb
  a *= k;	// c, frequency of midi note 0
  for (int i = 0; i < 128; i++)
  {
    freqtab[i] = (float)a;
    a *= k;
  }

  pinMode(soundPin1, OUTPUT);
  pinMode(soundPin2, OUTPUT);
  pinMode(buttonPin, INPUT);
  pinMode(buttonPinPWM, INPUT);
   
  TCCR1A = _BV(WGM11);
  TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS10);
  ICR1 = 2000-1;
  TIMSK1 = _BV(TOIE1);
  
  sei(); // enable global interrupts  

  ICR1 = 800-1;
  oscRate[0] = oscRate[1] = oscRate[2] = (int) 10000 * (1.0f/20000.0f) * freqtab[60];
  oscPWM[0] = (int) 9000; // * 0.9f;  
}

void loop() 
{
  if (!digitalRead(buttonPin)) testSampleRate(); else testPWM();
}

#define calcOSC(a) oscPos[a] += oscRate[a]; if (oscPos[a] >= 10000) oscPos[a] -= 10000;
ISR(TIMER1_OVF_vect)
{ 
  if (oscPos[0] <= oscPWM[0]) PORTB &= B11111101; else PORTB |= B00000010;
  if (oscPos[1] <= oscPWM[1]) PORTD &= B11101111; else PORTD |= B00010000;
  
  calcOSC(0);
  calcOSC(1);
}

uint8_t isUp = true;
//float pwmRate = 0.0001f;
int pwmRate = 1; // 10000 * 0.0001f;
void testPWM()
{
  if (!digitalRead(buttonPinPWM)) 
  {
    oscPWM[0] = (int) 10000 * (0.1f + (float(analogRead(analogInput))/1023.0f)/2.5f);
  }
  else
  {

    pwmRate = (int) 10000 * (float(analogRead(analogInput))/1023.0f) / 1000.0f;
 
    if (isUp)
    { 
      oscPWM[0] += pwmRate;
      // if (oscPWM[0] >= 0.5f) isUp = false;
      if (oscPWM[0] >= 5000) isUp = false;
    }
    else
    {
      oscPWM[0] -= pwmRate;
      // if (oscPWM[0] <= 0.1f) isUp = true;
      if (oscPWM[0] <= 1000) isUp = true;
    }
  }
}


void testSampleRate()
{
  oscPWM[0] = 5000; //0.5f;
  
  ICR1 = 2000-1;
  oscRate[0] = oscRate[1] = oscRate[2] = (int) 10000 * (1.0f/8000.0f) * freqtab[60];
  delay(2000);
  if (digitalRead(buttonPin)) return;
  oscRate[0] = (int) 10000 * (1.0f/8000.0f) * freqtab[72];
  delay(150);
  
  ICR1 = 1000-1;
  oscRate[0] = oscRate[1] = oscRate[2] = (int) 10000 * (1.0f/16000.0f) * freqtab[60];
  delay(2000);
  if (digitalRead(buttonPin)) return;
  oscRate[0] = (int) 10000 * (1.0f/16000.0f) * freqtab[72];
  delay(150);
  
  ICR1 = 800-1;
  oscRate[0] = oscRate[1] = oscRate[2] = (int) 10000 * (1.0f/20000.0f) * freqtab[60];
  delay(2000);
  if (digitalRead(buttonPin)) return;
  oscRate[0] = (int) 10000 * (1.0f/20000.0f) * freqtab[72];
  delay(150);
  
  ICR1 = 400-1;
  oscRate[0] = oscRate[1] = (int) 10000 * (1.0f/40000.0f) * freqtab[60];
  delay(2000);
  if (digitalRead(buttonPin)) return;
  
  oscRate[0] = (int) 10000 * (1.0f/40000.0f) * freqtab[72];
  delay(100);
  oscRate[0] = (int) 10000 * (1.0f/40000.0f) * freqtab[84];
  delay(100);
  oscRate[0] = (int) 10000 * (1.0f/40000.0f) * freqtab[96];
  delay(100);
}

Oh, thank you so much, it works perfectly at 40khz and 4 Voices!!!! 8)

I have to check the final waveform if its good and the resolution of the fine-tune, but I understood what you did, and I agree with the math, should work.

I was googling like nuts on float to int differences, I know what each is, but to be honest, I never converted a float to int code before, this is the first time. I'm impressed on how simple that is, thanks again!

My project is open-source, so as soon as I have it ready, I will post videos and info here at the forum. 8)

Wk

Sadly Int won't do, I need to use Long, otherwise I can't make smooth fine-tune transitions. :wink:

Here's the code I'm testing, I bump up the resolution and still can get 40khz and 4 voices. But now there's a heck load of aliasing on some situations, something I will need to address at some other time.

Keep in mind that I didn't add the other 3 outputs correctly, but they are computed, so I can check for speed. Its just a change on the interrupt area. I will now check which PWM outputs I will use for the envelope and them decide the outputs for the 4 voices. I need to leave something to use for communication with another chip, so I have to check my options: I2C, SPI, UART, ...

#define soundPin1 9 // See below for direct port manipulation
#define soundPin2 4 // See below for direct port manipulation
#define analogInput A2
#define buttonPin 12
#define buttonPinPWM 8

float freqtab[128];
#define MULT 1000000
#define MULT2 500000
#define MTYPE long
MTYPE oscPos[4] = {0,0,0,0};
MTYPE oscRate[4] = {MULT,MULT,MULT,MULT};
MTYPE oscPWM[4] = {MULT2,MULT2,MULT2,MULT2};

void setup() 
{ 
  // make frequency (Hz) table
  double k = 1.059463094359;	// 12th root of 2;
  double a = 6.875;		// a
  a *= k;	// b
  a *= k;	// bb
  a *= k;	// c, frequency of midi note 0
  for (int i = 0; i < 128; i++)
  {
    freqtab[i] = (float)a;
    a *= k;
  }

  pinMode(soundPin1, OUTPUT);
  pinMode(soundPin2, OUTPUT);
  pinMode(buttonPin, INPUT);
  pinMode(buttonPinPWM, INPUT);
   
  TCCR1A = _BV(WGM11);
  TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS10);
  ICR1 = 2000-1;
  TIMSK1 = _BV(TOIE1);
  sei();

  ICR1 = 800-1;
  oscRate[0] = oscRate[1] = oscRate[2] = oscRate[3] = (MTYPE) MULT * (1.0f/20000.0f) * freqtab[60];
  oscPWM[0] = MULT2;
}

void loop() 
{
  if (!digitalRead(buttonPin)) testSampleRate(); else testPWM();
}

#define calcOSC(a) oscPos[a] += oscRate[a]; if (oscPos[a] >= MULT) oscPos[a] -= MULT;
ISR(TIMER1_OVF_vect)
{ 
  if (oscPos[0] <= oscPWM[0]) PORTB &= B11111101; else PORTB |= B00000010;
  if (oscPos[1] <= oscPWM[1]) PORTD &= B11101111; else PORTD |= B00010000;
  if (oscPos[2] <= oscPWM[2]) PORTD &= B11101111; else PORTD |= B00010000;
  if (oscPos[3] <= oscPWM[3]) PORTD &= B11101111; else PORTD |= B00010000;
  
  calcOSC(0);
  calcOSC(1);
  calcOSC(2);
  calcOSC(3);
}

uint8_t isUp = true;
MTYPE pwmRate = 1;
void testPWM()
{
  if (!digitalRead(buttonPinPWM)) 
  {
    oscPWM[0] = (MTYPE) MULT * (0.1f + (float(analogRead(analogInput))/1023.0f)/2.5f);
  }
  else
  {
    pwmRate = (MTYPE) MULT * (float(analogRead(analogInput))/1023.0f) / 1000.0f;
    if (isUp)
    { 
      oscPWM[0] += pwmRate;
      if (oscPWM[0] >= MULT2) isUp = false;
    }
    else
    {
      oscPWM[0] -= pwmRate;
      if (oscPWM[0] <= 1000) isUp = true;
    }
  }
}

uint8_t firstPitchSwp = true;
void testSampleRate()
{
  oscPWM[0] = MULT2;
  
  if (!digitalRead(buttonPinPWM))
  {
    if (firstPitchSwp)
    {
      ICR1 = 400-1;
      firstPitchSwp = false;
      oscRate[0] = (MTYPE) MULT * (1.0f/40000.0f) * freqtab[60];
    }
    
    oscRate[0]++;
    delay(1);
  }
  else
  {  
    firstPitchSwp = true;
    
    ICR1 = 2000-1;
    oscRate[0] = oscRate[1] = oscRate[2] = oscRate[3] = (MTYPE) MULT * (1.0f/8000.0f) * freqtab[60];
    delay(2000);
    if (digitalRead(buttonPin)) return;
    oscRate[0] = (MTYPE) MULT * (1.0f/8000.0f) * freqtab[72];
    delay(150);
    
    ICR1 = 1000-1;
    oscRate[0] = oscRate[1] = oscRate[2] = oscRate[3] = (MTYPE) MULT * (1.0f/16000.0f) * freqtab[60];
    delay(2000);
    if (digitalRead(buttonPin)) return;
    oscRate[0] = (MTYPE) MULT * (1.0f/16000.0f) * freqtab[72];
    delay(150);
    
    ICR1 = 800-1;
    oscRate[0] = oscRate[1] = oscRate[2] = oscRate[3] = (MTYPE) MULT * (1.0f/20000.0f) * freqtab[60];
    delay(2000);
    if (digitalRead(buttonPin)) return;
    oscRate[0] = (MTYPE) MULT * (1.0f/20000.0f) * freqtab[72];
    delay(150);
    
    ICR1 = 400-1;
    oscRate[0] = oscRate[1] = oscRate[2] = oscRate[3] = (MTYPE) MULT * (1.0f/40000.0f) * freqtab[60];
    delay(2000);
    
    oscRate[0] = (MTYPE) MULT * (1.0f/40000.0f) * freqtab[72];
    delay(100);
    oscRate[0] = (MTYPE) MULT * (1.0f/40000.0f) * freqtab[84];
    delay(100);
    oscRate[0] = (MTYPE) MULT * (1.0f/40000.0f) * freqtab[96];
    delay(100);
  }
}

You are welcome,

Think you can get rid of all the floating point math as MULT = 1.000.000 you can savely rewrite this:

pwmRate = (MTYPE) MULT * (float(analogRead(analogInput))/1023.0f) / 1000.0f;
to
pwmRate = (1000 * analogRead())/1023;

// and as pwmRate will allways be between 0..1000 an int would be large enough.
etc

And it will be even faster too..

update --
a float has 6 or 7 digits of precision, a long has 9....

Oh, indeed, tomorrow I will try some more, thank you so much. Should I put your name on the code now? :wink:

Wk

Should I put your name on the code now? :wink:

No definitely not, it's your idea, I only gave some optimizing tips

Now you use long and use a factor 1.000.000 as an int and factor 10.000 was not enough for finetuning

Consider the option of using an unsigned int. (0..65535) and scale a factor 50.000 -
==> gives you 5x as much room for finetuning and still the speed of int. (faster and smaller footprint than longs)

I was wondering about that too, need to run some tests, txs again.

Wk

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. :wink: 8) :drooling_face:

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. :wink:

#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;
    }
  }  
}

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

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

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

  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;
    }    
  }

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 ... :wink:

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