Hey guys,
Currently I am busy making my own arduino digital/hybrid synth (digital; oscillators, LFO and ADSR and an analog filter and amplification section). The synth uses a 256 byte wavetable for its oscillator, which acts as input for an 8-bit pwm with a refresh rate of 31250 Hz. The phase of the oscillator is tracked by means of a long and is incremented each time in an intterupt routine, by a phasedelta long variable.
Everything works perfectly excepts when I try to add frequency modulation. When frequency modulation is added the frequency of the oscillator drops.
The way I calculate the frequency modulation is the following: first I use the value of my modulation source (in this case the lfo) as an index in a fixedpoint lookup table for frequency modulation (unsigned int 6.10 format (10 bytes for the decimal value)) and multiply the phasedelta with this value trough a special function which deals with this specific fixed point math multiplication. The fixedmathmultiply function.
To counter the frequency drop I tried to do the calculation outside my main interrupt routine and do it in a different interrupt with a lower refresh rate (about 4000hz), but the oscillator still exhibits an unintended drop in frequency, probably because the interrupt routines still influence each other (have to wait before one is completed).
So my question is, does anybody know to optimize the multiplication process or knows how i can prioritize the interrupt routines in such a way that the do not influence each other?
Here is the increddibly slimmed down code [note: LFOwavetable is not included, because the code would other wise be to large for this post]:
#include <avr/interrupt.h> //add interrupts
#include <avr/io.h> //no idea
#include <avr/pgmspace.h> //to store values in progmem
#include "Arduino.h"
//Array filled with values used for modulating the oscillator number with as conversion base 1024, so fixed point math can be used. contains 256 unsigned ints
prog_uint16_t freqModValArray[] PROGMEM = {
1024,1027,1030,1032,1035,1038,1041,1044,1047,1049,1052,1055,1058,1061,1064,1067,1070,1072,1075,1078,1081,1084,1087,1090,1093,1096,1099,1102,1105,1108,1111,1114,1117,1120,1123,1126,1129,1132,1135,1139,1142,1145,1148,1151,1154,1157,1160,1164,1167,1170,1173,1176,1179,1183,1186,1189,1192,1196,1199,1202,1205,1209,1212,1215,1219,1222,1225,1229,1232,1235,1239,1242,1245,1249,1252,1256,1259,1262,1266,1269,1273,1276,1280,1283,1287,1290,1294,1297,1301,1304,1308,1311,1315,1319,1322,1326,1329,1333,1337,1340,1344,1348,1351,1355,1359,1362,1366,1370,1373,1377,1381,1385,1388,1392,1396,1400,1404,1407,1411,1415,1419,1423,1427,1431,1434,1438,1442,1446,1450,1454,1458,1462,1466,1470,1474,1478,1482,1486,1490,1494,1498,1502,1506,1510,1515,1519,1523,1527,1531,1535,1539,1544,1548,1552,1556,1561,1565,1569,1573,1578,1582,1586,1591,1595,1599,1604,1608,1612,1617,1621,1625,1630,1634,1639,1643,1648,1652,1657,1661,1666,1670,1675,1679,1684,1689,1693,1698,1702,1707,1712,1716,1721,1726,1730,1735,1740,1745,1749,1754,1759,1764,1768,1773,1778,1783,1788,1793,1797,1802,1807,1812,1817,1822,1827,1832,1837,1842,1847,1852,1857,1862,1867,1872,1877,1882,1888,1893,1898,1903,1908,1913,1919,1924,1929,1934,1940,1945,1950,1956,1961,1966,1972,1977,1982,1988,1993,1999,2004,2009,2015,2020,2026,2031,2037,2042,2048
};
//byte oscwaveform[2][256];
byte osc1waveform[256];
volatile unsigned long accumulatorosc1;
volatile unsigned long accumulatorLFO;
volatile int LFOval;
volatile unsigned long osc1count;
volatile unsigned long osc1phasedelta;
volatile unsigned long LFOphasedelta;
unsigned long standardclkfreq = 16000000L;
unsigned long samplingfreq = 31250L;
float conversionunit = (pow(2,32)/samplingfreq);
float a = 1.05946309; //number used for calculating frequency of a note from a specific value
float frequency = 110; //setting the standard frequency for now
void setup(){
noInterrupts();
setuposc();
setuppins();
setuptimers();
interrupts();
}
ISR(TIMER0_COMPA_vect){
//routine that fires at a rate of 4000hz
osc1count = fixedmathmultiply(osc1phasedelta,pgm_read_word(&(freqModValArray[LFOval/2])));
}
ISR(TIMER1_OVF_vect){
OCR1B = updateLFO();
OCR2A = updateosc1();
}
void setuposc(){
accumulatorosc1 = 0;
accumulatorLFO = 0;
osc1count = 0;
LFOval = 0;
//preload a 50 percent square wave for osc1
generateSquarewaveosc(50);
osc1count = conversionunit*frequency;
osc1phasedelta = osc1count;
LFOphasedelta = 1 * conversionunit; //operates at 1hz;
}
void setuppins(){
for(int i = 2; i<14;i++){
pinMode(i,OUTPUT);
}
}
void setuptimers(){
//to sync the timers
//GTCCR = (1<<TSM)|(1<<PSRASY)|(1<<PSRSYNC); // halt all timers
//set timer0 interrupt at 4kHz
TCCR0A = 0;// set entire TCCR2A register to 0
TCCR0B = 0;// same for TCCR2B
TCNT0 = 0;//initialize counter value to 0
// set compare match register for 2khz increments
OCR0A = 61;// = (16*10^6) / (4000*64) - 1 (must be <256)
// turn on CTC mode
TCCR0A |= (1 << WGM01);
// Set CS01 bit for 64 prescaler
TCCR0B |= (1 << CS01) | (1 << CS00);
// enable timer compare interrupt
TIMSK0 |= (1 << OCIE0A);
//setting up timer 1 with 9-bit fast pwm mode
TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM12) | _BV(WGM11); //setting the timer to 9-bit fast pwm mode with OCR1A/B as top
//TCCR2B = _BV(CS20); //setting the prescaler to 1 (for future purposes might want to divide it by 8, so i can make more of a bass synthesizer, or even lower, to open up more calculation space.
TCCR1B = _BV(WGM12)|_BV(CS10); //setting the prescaler to 1
TIMSK1 = 0x01; //Timer1 INT Reg: Timer1 Overflow Interrupt Enable
OCR1A = 500; //set overflow compare register to a random value, this one will be used for the LFO generation
OCR1B = 50;
//setting up timer 2 with 8 bit fast pwm mode
TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20 ); //setting the timer to 8-bit fast pwm mode with OCR2A/B as top
TCCR2B = _BV(CS20); //setting the prescaler to
OCR2A = 127; //set overflow compare register to a random value, this one will be used for the LFO generation
OCR2B = 127;
//syncing the timers
// set all timers to the same value
TCNT0 = 0; // set timer0 to 0
TCNT1H = 0; // set timer1 high byte to 0
TCNT1L = 0; // set timer1 low byte to 0
TCNT2 = 0; // set timer2 to 0
GTCCR = 0; // release all timers
}
void loop(){
}
void generateSquarewaveosc(int pw){
//pw is a value between 1 and 99, best to limit this to 10 to 90
byte switchpoint = 255*pw/100;
for(int i =0; i<256 ; i++){
if(i>switchpoint){
osc1waveform[i] = 0;
}else{
osc1waveform[i] = 255;
}
}
int updateLFO(){
//function for updating the LFO
//should have selectable waveshapes
//has a 9-bit waveshape
//should be able to cope with slow oscillations
int pos;
accumulatorLFO += LFOphasedelta; //increment the LFO
pos= accumulatorLFO>>23; //position is a position between 0 and 511 (a 9-bit values)
LFOval = pgm_read_word(&(LFOWavetables[pos]));
return LFOval;
}
byte updateosc1(){
//function for updating oscillator 1.
byte val; //value for storing the value of the oscillator
byte pos; //stores the phase position for the lookup table
accumulatorosc1 += osc1count; //increment the phase
pos = accumulatorosc1>>24; //get the phase position for the lookup table
val = osc1waveform[pos]; //store the value of the lookup table at the pos position and return it
return val;
}
unsigned long fixedmathmultiply(unsigned long number, int multiplier){
unsigned long numberlsb = number & 65535ul; //get the lsb 16-bits of the number variable
unsigned long numbermsb = number>>16 ; // get the msb 16-bits of the number variable
numberlsb = numberlsb*multiplier;
numbermsb = numbermsb*multiplier;
numberlsb = numberlsb>>10;
numbermsb = numbermsb<<(6);
unsigned long result = numbermsb+numberlsb;
return result;
}