Skip to content

Commit 53dfc2b

Browse files
committed
Add first iteration of modified cRSID by Hermit
1 parent 2d76a4e commit 53dfc2b

8 files changed

Lines changed: 1424 additions & 0 deletions

File tree

src/cRSID/C64/C64.c

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
2+
// cRSID lightweight (integer-only) RealSID library (with API-calls) by Hermit (Mihaly Horvath), Year 2024
3+
// License: WTF - do what the fuck you want with the code, but please mention me as the original author
4+
// C64 emulation (SID-playback related)
5+
6+
#include <stdio.h>
7+
#include "pico/time.h"
8+
#include "../libcRSID.h"
9+
10+
#include "MEM.c"
11+
#include "CPU.c"
12+
#include "CIA.c"
13+
#include "VIC.c"
14+
#include "SID.c"
15+
16+
extern void (*writeFuncPtr)(uint8_t,uint8_t,uint16_t);
17+
extern void USBSID_FrameHandler(cRSID_C64instance *C64);
18+
extern unsigned long long main_cpu_clk, prev_cpu_clk, flush_cpu_clk, vic_cpu_clk, usid_main_clk;
19+
unsigned long long emulation_clk;
20+
extern unsigned int us_writecycles;
21+
22+
cRSID_C64instance* cRSID_createC64 (cRSID_C64instance* C64) { //init a basic PAL C64 instance
23+
if (C64->ram_init_done == 0) {
24+
/* ISSUE: THIS TAKES UP TOO MUCH SPACE FOR PICO1 */
25+
C64->RAMbank = malloc(0x10100);
26+
C64->IObankWR = malloc(0x10100);
27+
C64->IObankRD = malloc(0x10100);
28+
C64->ROMbanks = malloc(0x10100);
29+
C64->ram_init_done = 1;
30+
/* TODO: FIX free() */
31+
}
32+
enum C64clocks { C64_PAL_CPUCLK=985248, DEFAULT_SAMPLERATE=44100 };
33+
unsigned short samplerate;
34+
C64->SampleRate = samplerate = DEFAULT_SAMPLERATE;
35+
C64->SampleClockRatio = (C64_PAL_CPUCLK<<4)/samplerate; //shifting (multiplication) enhances SampleClockRatio precision
36+
C64->Attenuation = 26; C64->SIDchipCount=1;
37+
C64->CPU.C64 = C64;
38+
cRSID_createSIDchip ( C64, &C64->SID[0], 8580, CRSID_CHANNEL_BOTH, 0xD400 ); //default C64 setup with only 1 SID and 2 CIAs and 1 VIC
39+
cRSID_createCIAchip ( C64, &C64->CIA[0], 0xDC00 );
40+
cRSID_createCIAchip ( C64, &C64->CIA[1], 0xDD00 );
41+
cRSID_createVICchip ( C64, &C64->VIC, 0xD000 );
42+
//if(C64->RealSIDmode) {
43+
cRSID_setROMcontent ( C64 );
44+
//}
45+
cRSID_initC64(C64);
46+
return C64;
47+
}
48+
49+
void cRSID_setC64 (cRSID_C64instance* C64) { //set hardware-parameters (Models, SIDs) for playback of loaded SID-tune
50+
enum C64clocks { C64_PAL_CPUCLK=985248, C64_NTSC_CPUCLK=1022727 };
51+
enum C64scanlines { C64_PAL_SCANLINES = 312, C64_NTSC_SCANLINES = 263 };
52+
enum C64scanlineCycles { C64_PAL_SCANLINE_CYCLES = 63, C64_NTSC_SCANLINE_CYCLES = 65 };
53+
//enum C64framerates { PAL_FRAMERATE = 50, NTSC_FRAMERATE = 60 }; //Hz
54+
55+
static const unsigned int CPUspeeds[] = { C64_NTSC_CPUCLK, C64_PAL_CPUCLK };
56+
static const unsigned short ScanLines[] = { C64_NTSC_SCANLINES, C64_PAL_SCANLINES };
57+
static const unsigned char ScanLineCycles[] = { C64_NTSC_SCANLINE_CYCLES, C64_PAL_SCANLINE_CYCLES };
58+
//unsigned char FrameRates[] = { NTSC_FRAMERATE, PAL_FRAMERATE };
59+
60+
static const short Attenuations[]={0,26,43,137,200}; //increase for 2SID (to 43) and 3SID (to 137)
61+
short SIDmodel; char SIDchannel;
62+
63+
64+
C64->VideoStandard = ( (C64->SIDheader->ModelFormatStandard & 0x0C) >> 2 ) != 2;
65+
if (C64->SampleRate==0) C64->SampleRate = 44100;
66+
C64->CPUfrequency = CPUspeeds[C64->VideoStandard];
67+
C64->SampleClockRatio = ( C64->CPUfrequency << 4 ) / C64->SampleRate; //shifting (multiplication) enhances SampleClockRatio precision
68+
69+
C64->VIC.RasterLines = ScanLines[C64->VideoStandard];
70+
C64->VIC.RasterRowCycles = ScanLineCycles[C64->VideoStandard];
71+
C64->FrameCycles = C64->VIC.RasterLines * C64->VIC.RasterRowCycles; ///C64->SampleRate / PAL_FRAMERATE; //1x speed tune with VIC Vertical-blank timing
72+
73+
C64->PrevRasterLine=-1; //so if $d012 is set once only don't disturb FrameCycleCnt
74+
75+
SIDmodel = (C64->SIDheader->ModelFormatStandard&0x30) >= 0x20 ? 8580:6581;
76+
C64->SID[0].ChipModel = C64->SelectedSIDmodel? C64->SelectedSIDmodel : SIDmodel;
77+
78+
79+
if (C64->SIDheader->Version != CRSID_FILEVERSION_WEBSID) {
80+
81+
C64->SID[0].Channel = CRSID_CHANNEL_LEFT;
82+
83+
SIDmodel = C64->SIDheader->ModelFormatStandard & 0xC0;
84+
if (SIDmodel) SIDmodel = (SIDmodel >= 0x80) ? 8580:6581; else SIDmodel = C64->SID[0].ChipModel;
85+
if (C64->SelectedSIDmodel) SIDmodel = C64->SelectedSIDmodel;
86+
cRSID_createSIDchip ( C64, &C64->SID[1], SIDmodel, CRSID_CHANNEL_RIGHT, 0xD000 + C64->SIDheader->SID2baseAddress*16 );
87+
88+
SIDmodel = C64->SIDheader->ModelFormatStandardH & 0x03;
89+
if (SIDmodel) SIDmodel = (SIDmodel >= 0x02) ? 8580:6581; else SIDmodel = C64->SID[0].ChipModel;
90+
if (C64->SelectedSIDmodel) SIDmodel = C64->SelectedSIDmodel;
91+
cRSID_createSIDchip ( C64, &C64->SID[2], SIDmodel, CRSID_CHANNEL_BOTH, 0xD000 + C64->SIDheader->SID3baseAddress*16 );
92+
93+
C64->SID[3].BaseAddress=0x0000; C64->SID[3].BasePtr = NULL; //ensure disabling SID4 in non-WebSID format
94+
95+
}
96+
else {
97+
98+
C64->SID[0].Channel = (C64->SIDheader->ModelFormatStandardH & 0x40)? CRSID_CHANNEL_RIGHT:CRSID_CHANNEL_LEFT;
99+
if (C64->SIDheader->ModelFormatStandardH & 0x80) C64->SID[0].Channel = CRSID_CHANNEL_BOTH; //my own proposal for 'middle' channel
100+
101+
SIDmodel = C64->SIDheader->SID2flagsL & 0x30;
102+
SIDchannel = (C64->SIDheader->SID2flagsL & 0x40) ? CRSID_CHANNEL_RIGHT:CRSID_CHANNEL_LEFT;
103+
if (C64->SIDheader->SID2flagsL & 0x80) SIDchannel = CRSID_CHANNEL_BOTH;
104+
if (SIDmodel) SIDmodel = (SIDmodel >= 0x20) ? 8580:6581; else SIDmodel = C64->SID[0].ChipModel;
105+
if (C64->SelectedSIDmodel) SIDmodel = C64->SelectedSIDmodel;
106+
cRSID_createSIDchip ( C64, &C64->SID[1], SIDmodel, SIDchannel, 0xD000 + C64->SIDheader->SID2baseAddress*16 );
107+
108+
SIDmodel = C64->SIDheader->SID3flagsL & 0x30;
109+
SIDchannel = (C64->SIDheader->SID3flagsL & 0x40) ? CRSID_CHANNEL_RIGHT:CRSID_CHANNEL_LEFT;
110+
if (C64->SIDheader->SID3flagsL & 0x80) SIDchannel = CRSID_CHANNEL_BOTH;
111+
if (SIDmodel) SIDmodel = (SIDmodel >= 0x20) ? 8580:6581; else SIDmodel = C64->SID[0].ChipModel;
112+
if (C64->SelectedSIDmodel) SIDmodel = C64->SelectedSIDmodel;
113+
cRSID_createSIDchip ( C64, &C64->SID[2], SIDmodel, SIDchannel, 0xD000 + C64->SIDheader->SID3flagsH*16 );
114+
115+
SIDmodel = C64->SIDheader->SID4flagsL & 0x30;
116+
SIDchannel = (C64->SIDheader->SID4flagsL & 0x40) ? CRSID_CHANNEL_RIGHT:CRSID_CHANNEL_LEFT;
117+
if (C64->SIDheader->SID4flagsL & 0x80) SIDchannel = CRSID_CHANNEL_BOTH;
118+
if (SIDmodel) SIDmodel = (SIDmodel >= 0x20) ? 8580:6581; else SIDmodel = C64->SID[0].ChipModel;
119+
if (C64->SelectedSIDmodel) SIDmodel = C64->SelectedSIDmodel;
120+
cRSID_createSIDchip ( C64, &C64->SID[3], SIDmodel, SIDchannel, 0xD000 + C64->SIDheader->SID4baseAddress*16 );
121+
122+
}
123+
124+
125+
C64->SIDchipCount = 1 + (C64->SID[1].BaseAddress > 0) + (C64->SID[2].BaseAddress > 0) + (C64->SID[3].BaseAddress > 0);
126+
if (C64->SIDchipCount == 1) C64->SID[0].Channel = CRSID_CHANNEL_BOTH;
127+
C64->Attenuation = Attenuations[C64->SIDchipCount];
128+
129+
}
130+
131+
void cRSID_initC64 (cRSID_C64instance* C64) { //C64 Reset
132+
cRSID_initSIDchip( &C64->SID[0] );
133+
cRSID_initCIAchip( &C64->CIA[0] );
134+
cRSID_initCIAchip( &C64->CIA[1] );
135+
cRSID_initMem(C64);
136+
cRSID_initCPU( &C64->CPU, (cRSID_readMemC64(C64,0xFFFD)<<8) + cRSID_readMemC64(C64,0xFFFC) );
137+
C64->IRQ = C64->NMI = 0;
138+
C64->SampleCycleCnt = C64->OverSampleCycleCnt = 0;
139+
140+
main_cpu_clk = prev_cpu_clk = flush_cpu_clk = vic_cpu_clk = usid_main_clk = 0;
141+
us_writecycles = 0;
142+
emulation_clk = 0;
143+
}
144+
145+
unsigned long long cRSID_emulateC64 (cRSID_C64instance *C64) {
146+
static unsigned char InstructionCycles;
147+
emulation_clk = main_cpu_clk;
148+
//Cycle-based part of emulations:
149+
while (main_cpu_clk - emulation_clk == 0) {
150+
if (!C64->RealSIDmode) {
151+
if (C64->FrameCycleCnt >= C64->FrameCycles) {
152+
USBSID_FrameHandler(C64);
153+
C64->FrameCycleCnt -= C64->FrameCycles;
154+
if (C64->Finished) { //some tunes (e.g. Barbarian, A-Maze-Ing) doesn't always finish in 1 frame
155+
cRSID_initCPU(&C64->CPU, C64->PlayAddress); //(PSID docs say bank-register should always be set for each call's region)
156+
C64->Finished = 0; //C64->SampleCycleCnt=0; //PSID workaround for some tunes (e.g. Galdrumway):
157+
if (C64->TimerSource == 0) {
158+
C64->IObankRD[0xD019] = 0x81; //always simulate to player-calls that VIC-IRQ happened
159+
} else {
160+
C64->IObankRD[0xDC0D] = 0x83; //always simulate to player-calls that CIA TIMERA/TIMERB-IRQ happened
161+
}
162+
}
163+
}
164+
if (C64->Finished==0) {
165+
if ((InstructionCycles = cRSID_emulateCPU()) >= 0xFE) {
166+
InstructionCycles=6;
167+
C64->Finished=1;
168+
}
169+
} else {
170+
InstructionCycles=7; //idle between player-calls
171+
}
172+
C64->FrameCycleCnt += InstructionCycles;
173+
C64->IObankRD[0xDC04] += InstructionCycles; //very simple CIA1 TimerA simulation for PSID (e.g. Delta-Mix_E-Load_loader)
174+
175+
} else { //RealSID emulations:
176+
177+
if (cRSID_handleCPUinterrupts(&C64->CPU)) {
178+
C64->Finished = 0;
179+
InstructionCycles = 7;
180+
} else if (C64->Finished == 0) {
181+
if ((InstructionCycles = cRSID_emulateCPU()) >= 0xFE) {
182+
InstructionCycles = 6;
183+
C64->Finished = 1;
184+
}
185+
} else {
186+
InstructionCycles = 7; //idle between IRQ-calls
187+
}
188+
C64->IRQ = C64->NMI = 0; //prepare for collecting IRQ sources
189+
C64->IRQ |= cRSID_emulateCIA (&C64->CIA[0], InstructionCycles);
190+
C64->NMI |= cRSID_emulateCIA (&C64->CIA[1], InstructionCycles);
191+
C64->IRQ |= cRSID_emulateVIC (&C64->VIC, InstructionCycles);
192+
}
193+
}
194+
195+
if (!C64->RealSIDmode) { //some PSID tunes use CIA TOD-clock (e.g. Kawasaki Synthesizer Demo)
196+
--C64->TenthSecondCnt;
197+
if (C64->TenthSecondCnt <= 0) {
198+
C64->TenthSecondCnt = C64->SampleRate / 10;
199+
++(C64->IObankRD[0xDC08]);
200+
if(C64->IObankRD[0xDC08]>=10) {
201+
C64->IObankRD[0xDC08]=0; ++(C64->IObankRD[0xDC09]);
202+
}
203+
}
204+
}
205+
206+
if (C64->SecondCnt < C64->SampleRate) {
207+
++C64->SecondCnt;
208+
} else {
209+
C64->SecondCnt = 0;
210+
if(C64->PlayTime < 3600) {
211+
++C64->PlayTime;
212+
}
213+
}
214+
prev_cpu_clk = main_cpu_clk;
215+
return (main_cpu_clk - emulation_clk);
216+
}

src/cRSID/C64/CIA.c

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// cRSID lightweight (integer-only) RealSID library (with API-calls) by Hermit (Mihaly Horvath), Year 2024
2+
// License: WTF - do what the fuck you want with the code, but please mention me as the original author
3+
// cRSID CIA emulation
4+
5+
extern unsigned long long main_cpu_clk, prev_cpu_clk, flush_cpu_clk;
6+
unsigned long long cia1_cpu_clk = 0, cia2_cpu_clk = 0;
7+
8+
9+
void cRSID_createCIAchip (cRSID_C64instance* C64, cRSID_CIAinstance* CIA, unsigned short baseaddress) {
10+
CIA->C64 = C64;
11+
CIA->ChipModel = 0;
12+
CIA->BaseAddress = baseaddress;
13+
CIA->BasePtrWR = &C64->IObankWR[baseaddress]; CIA->BasePtrRD = &C64->IObankRD[baseaddress];
14+
CIA->id = (baseaddress == 0xDC00) ? 1 : 2;
15+
cRSID_initCIAchip(CIA);
16+
}
17+
18+
19+
void cRSID_initCIAchip (cRSID_CIAinstance* CIA) {
20+
unsigned char i;
21+
for (i=0; i<0x10; ++i) CIA->BasePtrWR[i] = CIA->BasePtrRD[i] = 0x00;
22+
}
23+
24+
25+
static inline char cRSID_emulateCIA (cRSID_CIAinstance* CIA, char cycles) {
26+
static int Tmp;
27+
28+
enum CIAregisters { PORTA=0, PORTB=1, DDRA=2, DDRB=3,
29+
TIMERAL=4, TIMERAH=5, TIMERBL=6, TIMERBH=7, //Write:Set Timer-latch, Read: read Timer
30+
TOD_TENTHSECONDS=8, TOD_SECONDS=9, TOD_MINUTES=0xA, TOD_HOURS=0xB,
31+
SERIAL_DATA=0xC, INTERRUPTS=0xD, CONTROLA=0xE, CONTROLB=0xF };
32+
33+
enum InterruptBitVal { INTERRUPT_HAPPENED=0x80, SET_OR_CLEAR_FLAGS=0x80, //(Read or Write operation determines which one:)
34+
FLAGn=0x10, SERIALPORT=0x08, ALARM=0x04, TIMERB=0x02, TIMERA=0x01 }; //flags/masks of interrupt-sources
35+
36+
enum ControlAbitVal { ENABLE_TIMERA=0x01, PORTB6_TIMERA=0x02, TOGGLED_PORTB6=0x04, ONESHOT_TIMERA=0x08,
37+
FORCELOADA_STROBE=0x10, TIMERA_FROM_CNT=0x20, SERIALPORT_IS_OUTPUT=0x40, TIMEOFDAY_50Hz=0x80 };
38+
39+
enum ControlBbitVal { ENABLE_TIMERB=0x01, PORTB7_TIMERB=0x02, TOGGLED_PORTB7=0x04, ONESHOT_TIMERB=0x08,
40+
FORCELOADB_STROBE=0x10, TIMERB_FROM_CPUCLK=0x00, TIMERB_FROM_CNT=0x20, TIMERB_FROM_TIMERA=0x40,
41+
TIMERB_FROM_TIMERA_AND_CNT = 0x60, TIMEOFDAY_WRITE_SETS_ALARM = 0x80 };
42+
43+
//TimerA
44+
if (CIA->BasePtrWR[CONTROLA] & FORCELOADA_STROBE) { //force latch into counter (strobe-input)
45+
CIA->BasePtrRD[TIMERAH] = CIA->BasePtrWR[TIMERAH]; CIA->BasePtrRD[TIMERAL] = CIA->BasePtrWR[TIMERAL];
46+
}
47+
else if ( (CIA->BasePtrWR[CONTROLA] & (ENABLE_TIMERA|TIMERA_FROM_CNT)) == ENABLE_TIMERA ) { //Enabled, counts Phi2
48+
Tmp = ( (CIA->BasePtrRD[TIMERAH]<<8) + CIA->BasePtrRD[TIMERAL] ) - cycles; //count timer
49+
if (Tmp <= 0) { //Timer counted down
50+
Tmp += (CIA->BasePtrWR[TIMERAH]<<8) + CIA->BasePtrWR[TIMERAL]; //reload timer
51+
if (CIA->BasePtrWR[CONTROLA] & ONESHOT_TIMERA) CIA->BasePtrWR[CONTROLA] &= ~ENABLE_TIMERA; //disable if one-shot
52+
CIA->BasePtrRD[INTERRUPTS] |= TIMERA;
53+
if (CIA->BasePtrWR[INTERRUPTS] & TIMERA) { //generate interrupt if mask allows
54+
CIA->BasePtrRD[INTERRUPTS] |= INTERRUPT_HAPPENED;
55+
}
56+
}
57+
CIA->BasePtrRD[TIMERAH] = (Tmp >> 8); CIA->BasePtrRD[TIMERAL] = Tmp & 0xFF;
58+
}
59+
CIA->BasePtrWR[CONTROLA] &= ~FORCELOADA_STROBE; //strobe is edge-sensitive
60+
CIA->BasePtrRD[CONTROLA] = CIA->BasePtrWR[CONTROLA]; //control-registers are readable
61+
62+
//TimerB
63+
if (CIA->BasePtrWR[CONTROLB] & FORCELOADB_STROBE) { //force latch into counter (strobe-input)
64+
CIA->BasePtrRD[TIMERBH] = CIA->BasePtrWR[TIMERBH]; CIA->BasePtrRD[TIMERBL] = CIA->BasePtrWR[TIMERBL];
65+
} //what about clocking TimerB by TimerA? (maybe not used in any music)
66+
else if ( (CIA->BasePtrWR[CONTROLB] & (ENABLE_TIMERB|TIMERB_FROM_TIMERA)) == ENABLE_TIMERB ) { //Enabled, counts Phi2
67+
Tmp = ( (CIA->BasePtrRD[TIMERBH]<<8) + CIA->BasePtrRD[TIMERBL] ) - cycles;//count timer
68+
if (Tmp <= 0) { //Timer counted down
69+
Tmp += (CIA->BasePtrWR[TIMERBH]<<8) + CIA->BasePtrWR[TIMERBL]; //reload timer
70+
if (CIA->BasePtrWR[CONTROLB] & ONESHOT_TIMERB) CIA->BasePtrWR[CONTROLB] &= ~ENABLE_TIMERB; //disable if one-shot
71+
CIA->BasePtrRD[INTERRUPTS] |= TIMERB;
72+
if (CIA->BasePtrWR[INTERRUPTS] & TIMERB) { //generate interrupt if mask allows
73+
CIA->BasePtrRD[INTERRUPTS] |= INTERRUPT_HAPPENED;
74+
}
75+
}
76+
CIA->BasePtrRD[TIMERBH] = (Tmp >> 8); CIA->BasePtrRD[TIMERBL] = Tmp & 0xFF;
77+
}
78+
CIA->BasePtrWR[CONTROLB] &= ~FORCELOADB_STROBE; //strobe is edge-sensitive
79+
CIA->BasePtrRD[CONTROLB] = CIA->BasePtrWR[CONTROLB]; //control-registers are readable
80+
81+
return (CIA->BasePtrRD[INTERRUPTS] & INTERRUPT_HAPPENED);
82+
}
83+
84+
85+
static inline void cRSID_writeCIAIRQmask (cRSID_CIAinstance* CIA, unsigned char value) {
86+
if (value&0x80) CIA->BasePtrWR[0xD] |= (value&0x1F);
87+
else CIA->BasePtrWR[0xD] &= ~(value&0x1F);
88+
}
89+
90+
91+
static inline void cRSID_acknowledgeCIAIRQ (cRSID_CIAinstance* CIA) {
92+
CIA->BasePtrRD[0xD] = 0x00; //reading a CIA interrupt-register clears its read-part and IRQ-flag
93+
if (CIA->id == 1) {
94+
// static int kak = 0;
95+
// if(kak == 0) {printf("CIA IRQ @ %llu cycles\n", (main_cpu_clk - cia1_cpu_clk));kak=1;}
96+
// printf("CIA IRQ @ %llu cycles\n", (main_cpu_clk - cia1_cpu_clk));
97+
cia1_cpu_clk = main_cpu_clk;
98+
}
99+
if (CIA->id == 2) {
100+
// static int kak = 0;
101+
// if(kak == 0) {printf("CIA NMI @ %llu cycles\n", (main_cpu_clk - cia2_cpu_clk));kak=1;}
102+
// printf("CIA NMI @ %llu cycles\n", (main_cpu_clk - cia2_cpu_clk));
103+
cia2_cpu_clk = main_cpu_clk;
104+
}
105+
}
106+
107+
108+
//static inline void cRSID_writeCIARWreg () {
109+
//mirroring write-latch to read-latch for Readable-Writeable registers?
110+
//}

0 commit comments

Comments
 (0)