Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
Jack Humbert | 56c47f2aed | 7 years ago |
@ -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 <avr/interrupt.h>
|
||||
|
||||
#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;n<OSCILLATORS;n++)
|
||||
{
|
||||
if(Sid.sidregister[controll_regadr[n]]&GATE) // if gate set then attack,decay,sustain
|
||||
{
|
||||
if(osc[n].attackdecay_flag)
|
||||
{ // if attack cycle
|
||||
osc[n].amp+=osc[n].m_attack;
|
||||
if(osc[n].amp>MAXLEVEL)
|
||||
{
|
||||
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].amp<osc[n].level_sustain) osc[n].amp=osc[n].level_sustain;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else // if gate flag is not set then release
|
||||
{
|
||||
osc[n].attackdecay_flag=true; // at next attack/decay cycle start wiht attack
|
||||
if(osc[n].amp>0)
|
||||
{
|
||||
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;n<OSCILLATORS;n++) {
|
||||
osc[n].attackdecay_flag=true;
|
||||
setenvelope(&Sid.block.voice[n]);
|
||||
osc[n].amp=0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Public Methods //////////////////////////////////////////////////////////////
|
||||
// Functions available in Wiring sketches, this library, and other libraries
|
||||
/************************************************************************
|
||||
|
||||
uint8_t set_sidregister(uint8_t regnum, uint8_t value)
|
||||
|
||||
The registers of the virtual SID are set by this routine.
|
||||
For some registers it is necessary to transform the SID-register
|
||||
values to some internal settings of the emulator.
|
||||
To select this registers and to start the calculation, the switch/
|
||||
case statement is used.
|
||||
For instance: If setting the SID envelope register, new attach, decay
|
||||
sustain times are calculated.
|
||||
If an invalid register is requested the returned value will be 0.
|
||||
|
||||
4.2007 ch
|
||||
|
||||
************************************************************************/
|
||||
uint8_t SID::set_register(uint8_t regnum, uint8_t value)
|
||||
{
|
||||
if(regnum>NUMREGISTERS-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];
|
||||
}
|
||||
|
||||
|
@ -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 <inttypes.h>
|
||||
|
||||
#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
|
||||
|
Loading…
Reference in new issue