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?
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.
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:
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...
#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. 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
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. Them I can go crazy and see if I could do another thing with this, but that's for later on.
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.
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?
Wk
Should I put your name on the code now?
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. 8)
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.
#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 ...
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