|
| 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 | +} |
0 commit comments