diff --git a/quantum/audio/SID.cpp b/quantum/audio/SID.cpp new file mode 100644 index 000000000..167fd2096 --- /dev/null +++ b/quantum/audio/SID.cpp @@ -0,0 +1,419 @@ +/* + SID.cpp - Atmega8 MOS6581 SID Emulator + Copyright (c) 2007 Christoph Haberer, christoph(at)roboterclub-freiburg.de + Arduino Library Conversion by Mario Patino, cybernesto(at)gmail.com + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/************************************************************************ + + Atmega8 MOS6581 SID Emulator + + SID = Sound Interface Device + + This program tries to emulate the sound chip SID of the famous + historical C64 Commodore computer. + The SID emulator includes all registers of the original SID, but + some functions may not be implemented yet. + If you want to program the SID registers to generate your own sound, + please refer to the MOS6581 datasheet. The emulator tries to be as + compatible as possible. + + In the main program there is an interrupt routine which sets the + ouptut values for the PWM-Output at 62.5kHz. Therefore the high + frequency noise of the PWM should not be audible to normal people. + The output is calculated with a 16kHz sample frequency to save + processing cycles. + + The envelope generators are updated every 1ms. + + The amplitude value is output as an 8Bit PWM value. + The PWM-Output may be directly connected to an audio amplifier. + +************************************************************************ + + Hardware + + processor: ATMEGA8, ATMEGA168 + clock: 16MHz Crystal + + PIN15 PB1/OC1A 8Bit PWM sound output + PIN19 PB0 test LED + +***************************************************************************/ + +#include + +#include "SID.h" + +// attack, decay, release envelope timings +const static uint16_t AttackRate[16]={2,4,16,24,38,58,68,80,100,250,500,800,1000,3000,5000,8000}; +const static uint16_t DecayReleaseRate[16]={6,24,48,72,114,168,204,240,300,750,1500,2400,3000,9000,15000,24000}; + +static uint8_t output; +static Sid_t Sid; +static Oscillator_t osc[OSCILLATORS]; + +void initialize() +{ + // TIMER1 used to generate sound output + // TIMER1: Fast PWM 8-bit + TCCR1A = (1 << WGM10) | (1 << COM1A1) ; + // TIMER1: no prescaling + TCCR1B = (1 << WGM12) | (1 << CS10); + +#if defined(__AVR_ATmega8__)|| defined(__AVR_ATmega128__) + // TIMER2 used to generate sample and ms interrupts + // TIMER2: Normal Mode + TCCR2 = 0; + // TIMER2: clock/8 prescaling + TCCR2 |= (1 << CS21); + // TIMER2: set compare value to generate a 16kHz sample rate + OCR2 = SAMPLERATECOUNT; + // interrupt mask register: enable timer2 OCR2A interrupt + TIMSK = (1 << OCIE2); + + // interrupt mask register: enable timer1 overflow + TIMSK |= (1 << TOIE1); +#else + // TIMER2 used to generate sample and ms interrupts + // TIMER2: Normal Mode + TCCR2A = 0 ; + // TIMER2: clock/8 prescaling + TCCR2B = (1 << CS21); + // TIMER2: set compare value to generate a 16kHz sample rate + OCR2A = SAMPLERATECOUNT; + // interrupt mask register: enable timer2 OCR2A interrupt + TIMSK2 = (1 << OCIE2A); + + // interrupt mask register: enable timer1 overflow + TIMSK1 = (1 << TOIE1); +#endif +} + +static int8_t wave(Voice_t *voice, uint16_t phase) +{ + int8_t out; + uint8_t n = phase >> 8; + uint8_t wavetype = voice->ControlReg; + + if(wavetype & SAWTOOTH) + { + out = n - 128; + } + + if(wavetype & TRIANGLE) + { + if(n&0x80) + out = ((n^0xFF)<<1)-128; + else + out = (n<<1)-128; + } + + if(wavetype & RECTANGLE) + { + if(n > (voice->PW >> 4)) // SID has 12Bit pwm, here we use only 8Bit + out = 127; + else + out = -127; + } + + return out; +} + +static void waveforms() +{ + static uint16_t phase[3], sig[3]; + static int16_t temp,temp1; + static uint8_t i,j,k; + static uint16_t noise = 0xACE1; + static uint8_t noise8; + static uint16_t tempphase; + + // noise generator based on Galois LFSR + noise = (noise >> 1) ^ (-(noise & 1) & 0xB400u); + noise8 = noise>>8; + + for(i = 0; i< 3; i++) + { + j = (i == 0 ? 2 : i - 1); + tempphase=phase[i]+osc[i].freq_coefficient; //0.88us + if(Sid.block.voice[i].ControlReg&NOISE) + { + if((tempphase^phase[i])&0x4000) sig[i]=noise8*osc[i].envelope; + } + else + { + if(Sid.block.voice[i].ControlReg&RINGMOD) + { + if(phase[j]&0x8000) + sig[i]=osc[i].envelope*-wave(&Sid.block.voice[i],phase[i]); + else + sig[i]=osc[i].envelope*wave(&Sid.block.voice[i],phase[i]); + } + else + { + if(Sid.block.voice[i].ControlReg&SYNC) + { + if(tempphase < phase[j]) + phase[i] = 0; + } + else + sig[i]=osc[i].envelope*wave(&Sid.block.voice[i],phase[i]); //2.07us + } + } + phase[i]=tempphase; + } + + // voice filter selection + temp=0; // direct output variable + temp1=0; // filter output variable + if(Sid.block.RES_Filt&FILT1) temp1+=sig[0]; + else temp+=sig[0]; + if(Sid.block.RES_Filt&FILT2) temp1+=sig[1]; + else temp+=sig[1]; + if(Sid.block.RES_Filt&FILT3) temp1+=sig[2]; + else if(!(Sid.block.Mode_Vol&VOICE3OFF))temp+=sig[2]; // voice 3 with special turn off bit + + //filterOutput = IIR2((struct IIR_filter*)&filter04_06, filterInput); + //IIR2(filter04_06, temp1); + k=(temp>>8)+128; + k+=temp1>>10; // no real filter implemeted yet + + output = k; // Output to PWM +} + + +static void envelopes() +{ + uint8_t n; + uint8_t controll_regadr[3]={4,11,18}; + // if gate is ONE then the attack,decay,sustain cycle begins + // if gate switches to zero the sound decays + for(n=0;nMAXLEVEL) + { + osc[n].amp=MAXLEVEL; + osc[n].attackdecay_flag=false; // if level reached, then switch to decay + } + } + else // decay cycle + { + if(osc[n].amp>osc[n].level_sustain) + { + osc[n].amp-=osc[n].m_decay; + if(osc[n].amp0) + { + osc[n].amp-=osc[n].m_release; + if(osc[n].amp<0) osc[n].amp=0; + } + } + osc[n].envelope=osc[n].amp>>8; + } +} + +/************************************************************************ + + interrupt routine timer 1 overflow + - set PWM output + +************************************************************************/ +ISR(TIMER1_OVF_vect) +{ + OCR1A = output; // Output to PWM +} + + +/************************************************************************ + + interrupt routine timer 2 16kHz + - calculate waverform phases + - calculate waveforms + - calculate attack decay release (1kHz) + +************************************************************************/ +#if defined(__AVR_ATmega8__)|| defined(__AVR_ATmega128__) +ISR(TIMER2_COMP_vect) +{ + static uint8_t mscounter = 0; + OCR2 += SAMPLERATECOUNT; // Output to PWM + waveforms(); //~22us + + if(mscounter++ >= MSCOUNT) + { + envelopes(); //~16us + mscounter = 0; + } +} +#else + +ISR(TIMER2_COMPA_vect) +{ + static uint8_t mscounter = 0; + OCR2A += SAMPLERATECOUNT; // Output to PWM + waveforms(); //~36us + + if(mscounter++ >= MSCOUNT) + { + envelopes(); //~16us + mscounter = 0; + } +} +#endif + +// Constructor ///////////////////////////////////////////////////////////////// +// Function that handles the creation and setup of instances + +void SID::begin() +{ + pinMode(9, OUTPUT); + initialize(); + + //initialize SID-registers + Sid.sidregister[6]=0xF0; + Sid.sidregister[13]=0xF0; + Sid.sidregister[20]=0xF0; + + + // set all amplitudes to zero + for(int n=0;nNUMREGISTERS-1) + return 0; + + Sid.sidregister[regnum]=value; + + switch(regnum) + { + //voice1 + case 1: + osc[0].freq_coefficient=((uint16_t)Sid.sidregister[0]+((uint16_t)Sid.sidregister[1]<<8))>>2; + break; + case 5: setenvelope(&Sid.block.voice[0]);break; + case 6: setenvelope(&Sid.block.voice[0]);break; + + //voice2 + case 8: + osc[1].freq_coefficient=((uint16_t)Sid.sidregister[7]+((uint16_t)Sid.sidregister[8]<<8))>>2; + break; + case 12: setenvelope(&Sid.block.voice[1]);break; + case 13: setenvelope(&Sid.block.voice[1]);break; + + //voice3 + case 15: + osc[2].freq_coefficient=((uint16_t)Sid.sidregister[14]+((uint16_t)Sid.sidregister[15]<<8))>>2; + break; + case 19: setenvelope(&Sid.block.voice[2]);break; + case 20: setenvelope(&Sid.block.voice[2]);break; + } + return 1; +} + +/************************************************************************ + + uint8_t get_sidregister(uint8_t regnum) + + The registers of the virtual SID are read by this routine. + If an invalid register is requested it returns zero. + +************************************************************************/ +uint8_t SID::get_register(uint8_t regnum) +{ + if(regnum>NUMREGISTERS-1) + return 0; + return Sid.sidregister[regnum]; +} + +// Private Methods ///////////////////////////////////////////////////////////// +// Functions only available to other functions in this library + +uint8_t SID::get_wavenum(Voice_t *voice) +{ + uint8_t n; + + if(voice==&Sid.block.voice[0]) n=0; + if(voice==&Sid.block.voice[1]) n=1; + if(voice==&Sid.block.voice[2]) n=2; + + return n; +} + +void SID::setfreq(Voice_t *voice,uint16_t freq) +{ + uint32_t templong; + uint8_t n; + + n=get_wavenum(voice); + + templong=freq; + osc[n].freq_coefficient=templong*4000/SAMPLEFREQ; +} + + +void SID::setenvelope(Voice_t *voice) +{ + uint8_t n; + + n=get_wavenum(voice); + osc[n].attackdecay_flag=true; + + osc[n].level_sustain=(voice->SustainRelease>>4)*SUSTAINFACTOR; + osc[n].m_attack=MAXLEVEL/AttackRate[voice->AttackDecay>>4]; + osc[n].m_decay=(MAXLEVEL-osc[n].level_sustain*SUSTAINFACTOR)/DecayReleaseRate[voice->AttackDecay&0x0F]; + osc[n].m_release=(osc[n].level_sustain)/DecayReleaseRate[voice->SustainRelease&0x0F]; +} + + diff --git a/quantum/audio/SID.h b/quantum/audio/SID.h new file mode 100644 index 000000000..6c1c2416c --- /dev/null +++ b/quantum/audio/SID.h @@ -0,0 +1,120 @@ +/* + SID.h - Atmega8 MOS6581 SID Emulator + Copyright (c) 2007 Christoph Haberer, christoph(at)roboterclub-freiburg.de + Arduino Library Conversion by Mario Patino, cybernesto(at)gmail.com + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// ensure this library description is only included once +#ifndef SID_h +#define SID_h + +#include + +#define NUMREGISTERS 29 +#define OSCILLATORS 3 +#define MAXLEVEL ( 0xFFFF / OSCILLATORS ) +#define SUSTAINFACTOR ( MAXLEVEL / 15 ) + +#define SAMPLEFREQ 16000L +#define SAMPLERATECOUNT (F_CPU/(8*SAMPLEFREQ)-1) + +#define ENVELOPE_FREQ 1000L +#define MSCOUNT (SAMPLEFREQ/ENVELOPE_FREQ-1) + + +// SID Registers +#define VOICE1 0 +#define VOICE2 7 +#define VOICE3 14 +#define CONTROLREG 4 +#define ATTACKDECAY 5 +#define SUSTAINRELEASE 6 + +// SID voice control register bits +#define GATE (1<<0) +#define SYNC (1<<1) +#define RINGMOD (1<<2) +#define TEST (1<<3) // not implemented +#define TRIANGLE (1<<4) +#define SAWTOOTH (1<<5) +#define RECTANGLE (1<<6) +#define NOISE (1<<7) + +// SID RES/FILT ( reg.23 ) +#define FILT1 (1<<0) +#define FILT2 (1<<1) +#define FILT3 (1<<2) +// SID MODE/VOL ( reg.24 ) +#define VOICE3OFF (1<<7) + +typedef struct +{ + uint16_t Freq; // Frequency: FreqLo/FreqHi + uint16_t PW; // PulseWidth: PW LO/HI only 12 bits used in SID + uint8_t ControlReg; // NOISE,RECTANGLE,SAWTOOTH,TRIANGLE,TEST,RINGMOD,SYNC,GATE + uint8_t AttackDecay; // bit0-3 decay, bit4-7 attack + uint8_t SustainRelease; // bit0-3 release, bit4-7 sustain +} Voice_t; + +typedef struct +{ + Voice_t voice[3]; + uint16_t FC; // not implemented + uint8_t RES_Filt; // partly implemented + uint8_t Mode_Vol; // partly implemented + uint8_t POTX; // not implemented + uint8_t POTY; // not implemented + uint8_t OSC3_Random;// not implemented + uint8_t ENV3; // not implemented +} Blocks_t; + +typedef union +{ + Blocks_t block; + uint8_t sidregister[NUMREGISTERS]; +} Sid_t; + +typedef struct +{ + uint16_t freq_coefficient; + uint8_t envelope; + uint16_t m_attack; + uint16_t m_decay; + uint16_t m_release; + uint8_t attackdecay_flag; + int16_t level_sustain; + int16_t amp; +} Oscillator_t; + +// library interface description +class SID +{ + // user-accessible "public" interface + public: + void begin(); + uint8_t set_register(uint8_t regnum, uint8_t value); + uint8_t get_register(uint8_t regnum); + // library-accessible "private" interface + private: + uint8_t get_wavenum(Voice_t *voice); + void setfreq(Voice_t *voice,uint16_t freq); + void init_waveform(Voice_t *voice); + void setenvelope(Voice_t *voice); +}; + +#endif +