Hi all. ![]()
I am building an Android controlled Arduino synth using bluetooth as a transmission medium. In putting together my Bluetooth controlled synth engine I have successfully managed to output different waveshapes according to a button press on the android GUI I am building and I can change the frequency and volume crudely using sliders on the GUI as well.
I was hoping someone could help me with how to go about implementing an ADSR or envelope shaper in my code. The synthesis method I am using is timer interrupt PWM and wavetables. The code I am using is here:
This is the wavetable:
#define INTERRUPT_PERIOD 512
#define FINT (F_CPU / INTERRUPT_PERIOD) // 16kHz?
#define FS (FINT)
#define STEPS 256
// sine lookup table pre-calculated
prog_uchar PROGMEM sinetable[256] = {
128,131,134,137,140,143,146,149,152,156,159,162,165,168,171,174,
176,179,182,185,188,191,193,196,199,201,204,206,209,211,213,216,
218,220,222,224,226,228,230,232,234,236,237,239,240,242,243,245,
246,247,248,249,250,251,252,252,253,254,254,255,255,255,255,255,
255,255,255,255,255,255,254,254,253,252,252,251,250,249,248,247,
246,245,243,242,240,239,237,236,234,232,230,228,226,224,222,220,
218,216,213,211,209,206,204,201,199,196,193,191,188,185,182,179,
176,174,171,168,165,162,159,156,152,149,146,143,140,137,134,131,
128,124,121,118,115,112,109,106,103,99, 96, 93, 90, 87, 84, 81,
79, 76, 73, 70, 67, 64, 62, 59, 56, 54, 51, 49, 46, 44, 42, 39,
37, 35, 33, 31, 29, 27, 25, 23, 21, 19, 18, 16, 15, 13, 12, 10,
9, 8, 7, 6, 5, 4, 3, 3, 2, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 6, 7, 8,
9, 10, 12, 13, 15, 16, 18, 19, 21, 23, 25, 27, 29, 31, 33, 35,
37, 39, 42, 44, 46, 49, 51, 54, 56, 59, 62, 64, 67, 70, 73, 76,
79, 81, 84, 87, 90, 93, 96, 99, 103,106,109,112,115,118,121,124
};
this is the main code:
/*----------------------------------------------------
functions to handle converting PCM to PWM and
outputting sound
----------------------------------------------------*/
// Timer Interrupt
// This is called at sampling freq to output 8-bit samples to PWM
ISR(TIMER1_COMPA_vect)
{
static unsigned int phase0;
static unsigned int sig0;
static unsigned char flag = 0;
static unsigned int tempphase;
if (soundPWM)
{
tempphase = phase0 + frequencyCoef;
sig0 = wavetable[phase0>>8];
phase0 = tempphase;
OCR2A = sig0>>vol; // output the sample
}
else { //square wave
flag ^= 1;
digitalWrite(speakerPin, flag);
}
}
void setupPWMSound()
{
// Set up Timer 2 to do pulse width modulation on the speaker pin.
// Use internal clock (datasheet p.160)
ASSR &= ~(_BV(EXCLK) | _BV(AS2));
// Set fast PWM mode (p.157)
TCCR2A |= _BV(WGM21) | _BV(WGM20);
TCCR2B &= ~_BV(WGM22);
// Do non-inverting PWM on pin OC2A (p.155)
// On the Arduino this is pin 11.
TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));
// No prescaler (p.158)
TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
// Set initial pulse width to the first sample.
OCR2A = 0;
// Set up Timer 1 to send a sample every interrupt.
cli();
// Set CTC mode (Clear Timer on Compare Match) (p.133)
// Have to set OCR1A *after*, otherwise it gets reset to 0!
TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));
// No prescaler (p.134)
TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
// Set the compare register (OCR1A).
// OCR1A is a 16-bit register, so we have to do this with
// interrupts disabled to be safe.
OCR1A = INTERRUPT_PERIOD;
// Enable interrupt when TCNT1 == OCR1A (p.136)
TIMSK1 |= _BV(OCIE1A);
sei();
soundPWM = true;
}
void startSound()
{
// Enable interrupt when TCNT1 == OCR1A (p.136)
cli();
TIMSK1 |= _BV(OCIE1A);
sei();
soundOn = true;
}
void stopSound()
{
cli();
// Disable playback per-sample interrupt.
TIMSK1 &= ~_BV(OCIE1A);
sei();
soundOn = false;
}
void setFrequency(unsigned int freq)
{
if (soundPWM) {
unsigned long templong = freq;
frequencyCoef = templong * 65536 / FS;
}
else {
unsigned long periode = F_CPU/(2*freq); //multiply by 2, because its only toggled once per cycle
cli();
OCR1A = periode;
}
}
/*----------------------------------------------------
functions to determine the wavetable content
----------------------------------------------------*/
void loadVoice(int voice)
{
if(soundOn) // if sound is on
{
stopSound(); // turn sound off
}
switch (voice)
{
// sine
case 0:
sineWave();
break;
// sawtooth
case 1:
sawtoothWave();
break;
// triangle
case 2:
triangleWave();
break;
// square
case 3:
squareWave();
break;
}
if(!soundPWM)
{
setupPWMSound();
}
startSound(); // start sound again
}
void sineWave()
{
for (int i = 0; i < 256; ++i) {
wavetable[i] = pgm_read_byte_near(sinetable + i);
}
}
void sawtoothWave()
{
for (int i = 0; i < 256; ++i) {
wavetable[i] = i; // sawtooth
}
}
void triangleWave()
{
for (int i = 0; i < 128; ++i) {
wavetable[i] = i * 2;
}
int value = 255;
for (int i = 128; i < 256; ++i) {
wavetable[i] = value;
value -= 2;
}
}
void squareWave()
{
for (int i = 0; i < 128; ++i) {
wavetable[i] = 187;
}
for (int i = 128; i < 256; ++i) {
wavetable[i] = 0;
}
}
/*----------------------------------------------------
setup and loop functions
----------------------------------------------------*/
int prevButtonValue = 0;
void setup()
{
pinMode(vcc,OUTPUT);
// pinMode(gnd,OUTPUT);
digitalWrite(vcc,HIGH);
USART_init(); //Call the USART initialization code
sei(); // global interrupts
pinMode(speakerPin, OUTPUT);
pinMode(buttonPin, INPUT);
// Choose one signal type to initially load into wavetable
// 0 - sine
// 1 - sawtooth
// 2 - triangle
// 3 - square
loadVoice(3);
}
void loop()
{
// read all sensor values
//read from potentiometer
}
int slider1read( int value1) // read the Android GUI's slider 1 and return the value
{
int valuesend=value; // read the serial
if (valuesend !='q' && valuesend !='s')
{
value1=valuesend;
}
return value1;
}
int slider2read (int value2) // read the Android GUI slider 2 and return the value
{
int valuesend=value;// read the serial
if(valuesend <114 && valuesend !=113)
{
value2=valuesend;
}
return value2;
}
int slider3read (int value3) // read the Android slider 3 and return the value
{
int valuesend=value;// read the serial
if(valuesend !=117)
{
value3=valuesend;
}
return value3;
}
void USART_init(void){
UBRR0H = (uint8_t)(BAUD_PRESCALLER>>8);
UBRR0L = (uint8_t)(BAUD_PRESCALLER);
UCSR0B = (1<<RXEN0)|(1<<TXEN0) | (1 << RXCIE0);
UCSR0C = (3<<UCSZ00);
pinMode(13,OUTPUT);
}
unsigned char USART_receive(void){
while(!(UCSR0A & (1<<RXC0)));
return UDR0;
}
void USART_send( unsigned char data){
while(!(UCSR0A & (1<<UDRE0)));
UDR0 = data;
}
void USART_putstring(char* StringPtr){
while(*StringPtr != 0x00){
USART_send(*StringPtr);
StringPtr++;}
}
int USART_putInt(int intData)
{
intData= USART_receive();
USART_send(intData);
return intData;
}
// USART interrupt
ISR (USART0_RX_vect) //USART recieve character interrupt. Much faster than polling and no clicks. :)
{
value = UDR0; // Fetch the received byte value into the variable "value"
UDR0 = value; //Put the value to UDR
switch(UDR0) // switch case for the characters sent from Android
{
case 'q':
val2=slider1read(f); // change the wave frequency according to slider 1 value
freq = map(slider1read(f), 0, 50, 440, 1320);
setFrequency(freq);
break;
case 'r':
vol=map(slider2read(f2),0,100,8,0); // change the volume according to slider 2 value
//vol=slider2read(f2);
break;
case 's' :
currentVoice = (currentVoice + 1) %4; // change the wave shape when "wave" button on GUI is pressed. Switches between sine, square, tri and saw
loadVoice(currentVoice);
break;
case 'u':
cutoff=slider3read(f3); // TODO: work out low pass filter in code.
break;
case 't' :
break;
case 'v':
break;
default:
break;
// aSin.setFreq(slider1read((value*10)+100));
}
}
void decay(void) { // experimenting with ADSR decay but how????
int i, j;
for (j=0; j<128; j++) {
for (i=0; i<STEPS; i++) {
if (wavetable[i]<127) {
wavetable[i]++;
}
if (wavetable[i]>127) {
wavetable[i]--;
}
}
delay(10);
}
}
I am wondering if it would be possible to generate an ADSR (or envelope shape) by making another wavetable of values and SOMEHOW combining the envelope wavetable with the wave wavetable... is it possible?
Could someone point me in the right direction in implementing this sort of thing using the code I have already?
I would, as per usual be really really grateful.
Steve.