Permalink
Cannot retrieve contributors at this time
#ifndef _SYNTH | |
#define _SYNTH | |
#define MAX(a,b) ((a)<(b)?(b):(a)) | |
#define MIN(a,b) ((a)>(b)?(b):(a)) | |
//************************************************************************************* | |
// ADAPTATION OF: | |
// Arduino synth V4.1 | |
// Optimized audio driver, modulation engine, envelope engine. | |
// | |
// Dzl/Illutron 2014 | |
// | |
// See https://github.com/dzlonline/the_synth for the original code. | |
// | |
// This modification turns a single PWM output into 3 outputs, each with a 120' offset, | |
// to simultaneously drive 3 speakers and a single brushless DC motor. | |
// This only makes sense if you read: | |
// http://cassettepunk.com/blog/2016/03/21/nielsen-speaker | |
// Adaptation by Alec Smecher. | |
//************************************************************************************* | |
#include <avr/pgmspace.h> | |
#include <avr/interrupt.h> | |
#include "tables.h" | |
#include <Arduino.h> | |
#define DIFF 1 | |
#define CHA 2 | |
#define CHB 3 | |
#define SINE 0 | |
#define TRIANGLE 1 | |
#define SQUARE 2 | |
#define SAW 3 | |
#define RAMP 4 | |
#define NOISE 5 | |
#define ENVELOPE0 0 | |
#define ENVELOPE1 1 | |
#define ENVELOPE2 2 | |
#define ENVELOPE3 3 | |
#define FS 20000.0 //-Sample rate (NOTE: must match tables.h) | |
#define SET(x,y) (x |=(1<<y)) //-Bit set/clear macros | |
#define CLR(x,y) (x &= (~(1<<y))) // | | |
#define CHK(x,y) (x & (1<<y)) // | | |
#define TOG(x,y) (x^=(1<<y)) //-+ | |
volatile unsigned int PCW[4] = { | |
0, 0, 0, 0}; //-Wave phase accumolators | |
volatile unsigned int FTW[4] = { | |
1000, 200, 300, 400}; //-Wave frequency tuning words | |
volatile unsigned char AMP[4] = { | |
255, 255, 255, 255}; //-Wave amplitudes [0-255] | |
volatile unsigned int PITCH[4] = { | |
500, 500, 500, 500}; //-Voice pitch | |
volatile int MOD[4] = { | |
20, 0, 64, 127}; //-Voice envelope modulation [0-1023 512=no mod. <512 pitch down >512 pitch up] | |
volatile unsigned int wavs[4]; //-Wave table selector [address of wave in memory] | |
volatile unsigned int envs[4]; //-Envelopte selector [address of envelope in memory] | |
volatile unsigned int EPCW[4] = { | |
0x8000, 0x8000, 0x8000, 0x8000}; //-Envelope phase accumolator | |
volatile unsigned int EFTW[4] = { | |
10, 10, 10, 10}; //-Envelope speed tuning word | |
volatile unsigned char divider = 4; //-Sample rate decimator for envelope | |
volatile unsigned int tim = 0; | |
volatile unsigned char tik = 0; | |
volatile unsigned char output_mode; | |
//********************************************************************************************* | |
// Audio driver interrupt | |
//********************************************************************************************* | |
SIGNAL(TIMER1_COMPA_vect) | |
{ | |
//------------------------------- | |
// Time division | |
//------------------------------- | |
divider++; | |
if(!(divider&=0x03)) | |
tik=1; | |
//------------------------------- | |
// Volume envelope generator | |
//------------------------------- | |
if (!(((unsigned char*)&EPCW[divider])[1]&0x80)) | |
AMP[divider] = pgm_read_byte(envs[divider] + (((unsigned char*)&(EPCW[divider]+=EFTW[divider]))[1])); | |
else | |
AMP[divider] = 0; | |
//------------------------------- | |
// Synthesizer/audio mixer | |
//------------------------------- | |
PCW[0] += FTW[0]; PCW[1] += FTW[1]; PCW[2] += FTW[2]; PCW[3] += FTW[3]; | |
volatile unsigned int a = ((signed char *)&(PCW[0]))[1]; | |
volatile unsigned int b = ((signed char *)&(PCW[1]))[1]; | |
// volatile unsigned int c = ((signed char *)&(PCW[2]))[1]; | |
// volatile unsigned int d = ((signed char *)&(PCW[3]))[1]; | |
volatile unsigned int ocr2a = 127 + ( // Pin 3 | |
(( | |
(((signed char)pgm_read_byte(wavs[0] + a) * AMP[0]) >> 8) + | |
(((signed char)pgm_read_byte(wavs[1] + b) * AMP[1]) >> 8)/* + | |
(((signed char)pgm_read_byte(wavs[2] + c) * AMP[2]) >> 8) + | |
(((signed char)pgm_read_byte(wavs[3] + d) * AMP[3]) >> 8)*/ | |
) >> 2)); | |
OCR2A = (MAX(127, ocr2a)-127)*8; // Chop & scale for the motor | |
volatile unsigned int ocr2b = 127 + ( // Pin 11 | |
(( | |
(((signed char)pgm_read_byte(wavs[0] + ((a+85)%256)) * AMP[0]) >> 8) + | |
(((signed char)pgm_read_byte(wavs[1] + ((b+85)%256)) * AMP[1]) >> 8)/* + | |
(((signed char)pgm_read_byte(wavs[2] + ((c+85)%256)) * AMP[2]) >> 8) + | |
(((signed char)pgm_read_byte(wavs[3] + ((d+85)%256)) * AMP[3]) >> 8)*/ | |
) >> 2)); | |
OCR2B = (MAX(127, ocr2b)-127)*8; // Chop & scale for the motor | |
volatile unsigned int ocr0b = 127 + ( // Pin 5 | |
(( | |
(((signed char)pgm_read_byte(wavs[0] + ((a+171)%256)) * AMP[0]) >> 8) + | |
(((signed char)pgm_read_byte(wavs[1] + ((b+171)%256)) * AMP[1]) >> 8)/* + | |
(((signed char)pgm_read_byte(wavs[2] + ((c+171)%256)) * AMP[2]) >> 8) + | |
(((signed char)pgm_read_byte(wavs[3] + ((d+171)%256)) * AMP[3]) >> 8)*/ | |
) >> 2)); | |
OCR0B = (MAX(127, ocr0b)-127)*8; // Chop & scale for the motor | |
//************************************************ | |
// Modulation engine | |
//************************************************ | |
// FTW[divider] = PITCH[divider] + (int) (((PITCH[divider]/64)*(EPCW[divider]/64)) /128)*MOD[divider]; | |
FTW[divider] = PITCH[divider] + (int) (((PITCH[divider]>>6)*(EPCW[divider]>>6))/128)*MOD[divider]; | |
tim++; | |
} | |
class synth | |
{ | |
private: | |
public: | |
synth() | |
{ | |
} | |
//********************************************************************* | |
// Startup default | |
//********************************************************************* | |
void begin() | |
{ | |
output_mode=CHA; | |
TCCR1A = 0x00; //-Start audio interrupt | |
TCCR1B = 0x09; | |
TCCR1C = 0x00; | |
OCR1A=16000000.0 / FS; //-Auto sample rate | |
SET(TIMSK1, OCIE1A); //-Start audio interrupt | |
sei(); //-+ | |
TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); | |
TCCR2B = _BV(CS20); | |
OCR2A = OCR2B = 127; | |
pinMode(3, OUTPUT); | |
pinMode(11, OUTPUT); | |
TCCR0A = _BV(COM0A1) | _BV(COM0B1) | _BV(WGM01) | _BV(WGM00); | |
TCCR0B = _BV(CS20); | |
OCR0A = OCR0B = 127; | |
pinMode(5, OUTPUT); | |
} | |
//********************************************************************* | |
// Startup fancy selecting varoius output modes | |
//********************************************************************* | |
void begin(unsigned char d) | |
{ | |
TCCR1A = 0x00; //-Start audio interrupt | |
TCCR1B = 0x09; | |
TCCR1C = 0x00; | |
OCR1A=16000000.0 / FS; //-Auto sample rate | |
SET(TIMSK1, OCIE1A); //-Start audio interrupt | |
sei(); //-+ | |
output_mode=d; | |
switch(d) | |
{ | |
case DIFF: //-Differntial signal on CHA and CHB pins (11,3) | |
TCCR2A = 0xB3; //-8 bit audio PWM | |
TCCR2B = 0x01; // | | |
OCR2A = OCR2B = 127; //-+ | |
SET(DDRB, 3); //-PWM pin | |
SET(DDRD, 3); //-PWM pin | |
break; | |
case CHB: //-Single ended signal on CHB pin (3) | |
TCCR2A = 0x23; //-8 bit audio PWM | |
TCCR2B = 0x01; // | | |
OCR2A = OCR2B = 127; //-+ | |
SET(DDRD, 3); //-PWM pin | |
break; | |
case CHA: | |
default: | |
output_mode=CHA; //-Single ended signal in CHA pin (11) | |
TCCR2A = 0x83; //-8 bit audio PWM | |
TCCR2B = 0x01; // | | |
OCR2A = OCR2B = 127; //-+ | |
SET(DDRB, 3); //-PWM pin | |
break; | |
} | |
} | |
//********************************************************************* | |
// Timing/sequencing functions | |
//********************************************************************* | |
unsigned char synthTick(void) | |
{ | |
if(tik) | |
{ | |
tik=0; | |
return 1; //-True every 4 samples | |
} | |
return 0; | |
} | |
unsigned char voiceFree(unsigned char voice) | |
{ | |
if (!(((unsigned char*)&EPCW[voice])[1]&0x80)) | |
return 0; | |
return 1; | |
} | |
//********************************************************************* | |
// Setup all voice parameters in MIDI range | |
// voice[0-3],wave[0-6],pitch[0-127],envelope[0-4],length[0-127],mod[0-127:64=no mod] | |
//********************************************************************* | |
void setupVoice(unsigned char voice, unsigned char wave, unsigned char pitch, unsigned char env, unsigned char length, unsigned int mod) | |
{ | |
setWave(voice,wave); | |
setPitch(voice,pitch); | |
setEnvelope(voice,env); | |
setLength(voice,length); | |
setMod(voice,mod); | |
} | |
//********************************************************************* | |
// Setup wave [0-6] | |
//********************************************************************* | |
void setWave(unsigned char voice, unsigned char wave) | |
{ | |
switch (wave) | |
{ | |
case TRIANGLE: | |
wavs[voice] = (unsigned int)TriangleTable; | |
break; | |
case SQUARE: | |
wavs[voice] = (unsigned int)SquareTable; | |
break; | |
case SAW: | |
wavs[voice] = (unsigned int)SawTable; | |
break; | |
case RAMP: | |
wavs[voice] = (unsigned int)RampTable; | |
break; | |
case NOISE: | |
wavs[voice] = (unsigned int)NoiseTable; | |
break; | |
default: | |
wavs[voice] = (unsigned int)SinTable; | |
break; | |
} | |
} | |
//********************************************************************* | |
// Setup Pitch [0-127] | |
//********************************************************************* | |
void setPitch(unsigned char voice,unsigned char MIDInote) | |
{ | |
PITCH[voice]=pgm_read_word(&PITCHS[MIDInote]); | |
} | |
//********************************************************************* | |
// Setup Envelope [0-4] | |
//********************************************************************* | |
void setEnvelope(unsigned char voice, unsigned char env) | |
{ | |
switch (env) | |
{ | |
case 1: | |
envs[voice] = (unsigned int)Env0; | |
break; | |
case 2: | |
envs[voice] = (unsigned int)Env1; | |
break; | |
case 3: | |
envs[voice] = (unsigned int)Env2; | |
break; | |
case 4: | |
envs[voice] = (unsigned int)Env3; | |
break; | |
default: | |
envs[voice] = (unsigned int)Env0; | |
break; | |
} | |
} | |
//********************************************************************* | |
// Setup Length [0-128] | |
//********************************************************************* | |
void setLength(unsigned char voice,unsigned char length) | |
{ | |
EFTW[voice]=pgm_read_word(&EFTWS[length]); | |
} | |
//********************************************************************* | |
// Setup mod | |
//********************************************************************* | |
void setMod(unsigned char voice,unsigned char mod) | |
{ | |
// MOD[voice]=(unsigned int)mod*8;//0-1023 512=no mod | |
MOD[voice]=(int)mod-64;//0-1023 512=no mod | |
} | |
//********************************************************************* | |
// Midi trigger | |
//********************************************************************* | |
void mTrigger(unsigned char voice,unsigned char MIDInote) | |
{ | |
PITCH[voice]=pgm_read_word(&PITCHS[MIDInote]); | |
EPCW[voice]=0; | |
FTW[divider] = PITCH[voice] + (int) (((PITCH[voice]>>6)*(EPCW[voice]>>6))/128)*MOD[voice]; | |
} | |
//********************************************************************* | |
// Set frequency direct | |
//********************************************************************* | |
void setFrequency(unsigned char voice,float f) | |
{ | |
PITCH[voice]=f/(FS/65535.0); | |
} | |
//********************************************************************* | |
// Set time | |
//********************************************************************* | |
void setTime(unsigned char voice,float t) | |
{ | |
EFTW[voice]=(1.0/t)/(FS/(32767.5*10.0));//[s]; | |
} | |
//********************************************************************* | |
// Simple trigger | |
//********************************************************************* | |
void trigger(unsigned char voice) | |
{ | |
EPCW[voice]=0; | |
FTW[voice]=PITCH[voice]; | |
// FTW[voice]=PITCH[voice]+(PITCH[voice]*(EPCW[voice]/(32767.5*128.0 ))*((int)MOD[voice]-512)); | |
} | |
//********************************************************************* | |
// Suspend/resume synth | |
//********************************************************************* | |
void suspend() | |
{ | |
CLR(TIMSK1, OCIE1A); //-Stop audio interrupt | |
} | |
void resume() | |
{ | |
SET(TIMSK1, OCIE1A); //-Start audio interrupt | |
} | |
}; | |
#endif |