summaryrefslogtreecommitdiffstats
path: root/lib/rbcodec/codecs/cRSID/C64/C64.c
diff options
context:
space:
mode:
authorWolfram Sang <wsa@the-dreams.de>2022-03-04 18:37:30 +0100
committerSolomon Peachy <pizza@shaftnet.org>2023-02-07 09:19:32 -0500
commite8135fea5a10dba25fefe991bf6bc36f351e2919 (patch)
treea17571dc0c1bc25a824f639be88d461245d62e89 /lib/rbcodec/codecs/cRSID/C64/C64.c
parent1c26f565bf0ae50495dc8d2139f76bd367728cd6 (diff)
downloadrockbox-e8135fea5a.tar.gz
rockbox-e8135fea5a.zip
codec: sid: add cRSID-1.0 for 21st century SID playback
Plain import of the library parts first. Adaptions to Rockbox will follow. A *lot* of kudos go to Mihaly Horvath for creating this library from his already lightweight cSID-light, mainly for Rockbox. Besides a lot of other things, he made his algorithms integer-only and significantly improved the C64 emulation, so finally RSIDs could be played as well as PSIDs. TinySID was nice for what it is, but this is a quantum leap in SID playback quality for Rockbox. Check for example: https://hvsc.csdb.dk/MUSICIANS/P/Page_Jason/Eighth.sid https://hvsc.csdb.dk/MUSICIANS/J/Jeff/Blowing.sid Change-Id: I353e12fbfd7cd8696b834616e55743e7b844a73e
Diffstat (limited to 'lib/rbcodec/codecs/cRSID/C64/C64.c')
-rw-r--r--lib/rbcodec/codecs/cRSID/C64/C64.c197
1 files changed, 197 insertions, 0 deletions
diff --git a/lib/rbcodec/codecs/cRSID/C64/C64.c b/lib/rbcodec/codecs/cRSID/C64/C64.c
new file mode 100644
index 0000000000..81dca35542
--- /dev/null
+++ b/lib/rbcodec/codecs/cRSID/C64/C64.c
@@ -0,0 +1,197 @@
+
+//C64 emulation (SID-playback related)
+
+
+#include "../libcRSID.h"
+
+#include "MEM.c"
+#include "CPU.c"
+#include "CIA.c"
+#include "VIC.c"
+#include "SID.c"
+
+
+cRSID_C64instance* cRSID_createC64 (cRSID_C64instance* C64, unsigned short samplerate) { //init a basic PAL C64 instance
+ if(samplerate) C64->SampleRate = samplerate;
+ else C64->SampleRate = samplerate = 44100;
+ C64->SampleClockRatio = (985248<<4)/samplerate; //shifting (multiplication) enhances SampleClockRatio precision
+ C64->Attenuation = 26;
+ C64->CPU.C64 = C64;
+ cRSID_createSIDchip ( C64, &C64->SID[1], 8580, 0xD400 ); //default C64 setup with only 1 SID and 2 CIAs and 1 VIC
+ cRSID_createCIAchip ( C64, &C64->CIA[1], 0xDC00 );
+ cRSID_createCIAchip ( C64, &C64->CIA[2], 0xDD00 );
+ cRSID_createVICchip ( C64, &C64->VIC, 0xD000 );
+ //if(C64->RealSIDmode) {
+ cRSID_setROMcontent ( C64 );
+ //}
+ cRSID_initC64(C64);
+ return C64;
+}
+
+
+void cRSID_setC64 (cRSID_C64instance* C64) { //set hardware-parameters (Models, SIDs) for playback of loaded SID-tune
+ enum C64clocks { C64_PAL_CPUCLK=985248, C64_NTSC_CPUCLK=1022727 };
+ enum C64scanlines { C64_PAL_SCANLINES = 312, C64_NTSC_SCANLINES = 263 };
+ enum C64scanlineCycles { C64_PAL_SCANLINE_CYCLES = 63, C64_NTSC_SCANLINE_CYCLES = 65 };
+ //enum C64framerates { PAL_FRAMERATE = 50, NTSC_FRAMERATE = 60 }; //Hz
+
+ static const unsigned int CPUspeeds[] = { C64_NTSC_CPUCLK, C64_PAL_CPUCLK };
+ static const unsigned short ScanLines[] = { C64_NTSC_SCANLINES, C64_PAL_SCANLINES };
+ static const unsigned char ScanLineCycles[] = { C64_NTSC_SCANLINE_CYCLES, C64_PAL_SCANLINE_CYCLES };
+ //unsigned char FrameRates[] = { NTSC_FRAMERATE, PAL_FRAMERATE };
+
+ static const short Attenuations[]={0,26,43,137}; //increase for 2SID (to 43) and 3SID (to 137)
+ short SIDmodel;
+ unsigned char SIDchipCount;
+
+
+ C64->VideoStandard = ( (C64->SIDheader->ModelFormatStandard & 0x0C) >> 2 ) != 2;
+ if (C64->SampleRate==0) C64->SampleRate = 44100;
+ C64->CPUfrequency = CPUspeeds[C64->VideoStandard];
+ C64->SampleClockRatio = ( C64->CPUfrequency << 4 ) / C64->SampleRate; //shifting (multiplication) enhances SampleClockRatio precision
+
+ C64->VIC.RasterLines = ScanLines[C64->VideoStandard];
+ C64->VIC.RasterRowCycles = ScanLineCycles[C64->VideoStandard];
+ C64->FrameCycles = C64->VIC.RasterLines * C64->VIC.RasterRowCycles; ///C64->SampleRate / PAL_FRAMERATE; //1x speed tune with VIC Vertical-blank timing
+
+ C64->PrevRasterLine=-1; //so if $d012 is set once only don't disturb FrameCycleCnt
+
+ C64->SID[1].ChipModel = (C64->SIDheader->ModelFormatStandard&0x30) >= 0x20? 8580:6581;
+
+ SIDmodel = C64->SIDheader->ModelFormatStandard & 0xC0;
+ if (SIDmodel) SIDmodel = (SIDmodel >= 0x80) ? 8580:6581; else SIDmodel = C64->SID[1].ChipModel;
+ cRSID_createSIDchip ( C64, &C64->SID[2], SIDmodel, 0xD000 + C64->SIDheader->SID2baseAddress*16 );
+
+ SIDmodel = C64->SIDheader->ModelFormatStandardH & 0x03;
+ if (SIDmodel) SIDmodel = (SIDmodel >= 0x02) ? 8580:6581; else SIDmodel = C64->SID[1].ChipModel;
+ cRSID_createSIDchip ( C64, &C64->SID[3], SIDmodel, 0xD000 + C64->SIDheader->SID3baseAddress*16 );
+
+ SIDchipCount = 1 + (C64->SID[2].BaseAddress > 0) + (C64->SID[3].BaseAddress > 0);
+ C64->Attenuation = Attenuations[SIDchipCount];
+}
+
+
+void cRSID_initC64 (cRSID_C64instance* C64) { //C64 Reset
+ cRSID_initSIDchip( &C64->SID[1] );
+ cRSID_initCIAchip( &C64->CIA[1] ); cRSID_initCIAchip( &C64->CIA[2] );
+ cRSID_initMem(C64);
+ cRSID_initCPU( &C64->CPU, (cRSID_readMemC64(C64,0xFFFD)<<8) + cRSID_readMemC64(C64,0xFFFC) );
+ C64->IRQ = C64->NMI = 0;
+}
+
+
+int cRSID_emulateC64 (cRSID_C64instance *C64) {
+ static unsigned char InstructionCycles;
+ static int Output;
+
+
+ //Cycle-based part of emulations:
+
+
+ while (C64->SampleCycleCnt <= C64->SampleClockRatio) {
+
+ if (!C64->RealSIDmode) {
+ if (C64->FrameCycleCnt >= C64->FrameCycles) {
+ C64->FrameCycleCnt -= C64->FrameCycles;
+ if (C64->Finished) { //some tunes (e.g. Barbarian, A-Maze-Ing) doesn't always finish in 1 frame
+ cRSID_initCPU ( &C64->CPU, C64->PlayAddress ); //(PSID docs say bank-register should always be set for each call's region)
+ C64->Finished=0; //C64->SampleCycleCnt=0; //PSID workaround for some tunes (e.g. Galdrumway):
+ if (C64->TimerSource==0) C64->IObankRD[0xD019] = 0x81; //always simulate to player-calls that VIC-IRQ happened
+ else C64->IObankRD[0xDC0D] = 0x83; //always simulate to player-calls that CIA TIMERA/TIMERB-IRQ happened
+ }}
+ if (C64->Finished==0) {
+ if ( (InstructionCycles = cRSID_emulateCPU()) >= 0xFE ) { InstructionCycles=6; C64->Finished=1; }
+ }
+ else InstructionCycles=7; //idle between player-calls
+ C64->FrameCycleCnt += InstructionCycles;
+ C64->IObankRD[0xDC04] += InstructionCycles; //very simple CIA1 TimerA simulation for PSID (e.g. Delta-Mix_E-Load_loader)
+ }
+
+ else { //RealSID emulations:
+ if ( cRSID_handleCPUinterrupts(&C64->CPU) ) { C64->Finished=0; InstructionCycles=7; }
+ else if (C64->Finished==0) {
+ if ( (InstructionCycles = cRSID_emulateCPU()) >= 0xFE ) {
+ InstructionCycles=6; C64->Finished=1;
+ }
+ }
+ else InstructionCycles=7; //idle between IRQ-calls
+ C64->IRQ = C64->NMI = 0; //prepare for collecting IRQ sources
+ C64->IRQ |= cRSID_emulateCIA (&C64->CIA[1], InstructionCycles);
+ C64->NMI |= cRSID_emulateCIA (&C64->CIA[2], InstructionCycles);
+ C64->IRQ |= cRSID_emulateVIC (&C64->VIC, InstructionCycles);
+ }
+
+ C64->SampleCycleCnt += (InstructionCycles<<4);
+
+ cRSID_emulateADSRs (&C64->SID[1], InstructionCycles);
+ if ( C64->SID[2].BaseAddress != 0 ) cRSID_emulateADSRs (&C64->SID[2], InstructionCycles);
+ if ( C64->SID[3].BaseAddress != 0 ) cRSID_emulateADSRs (&C64->SID[3], InstructionCycles);
+
+ }
+ C64->SampleCycleCnt -= C64->SampleClockRatio;
+
+
+ //Samplerate-based part of emulations:
+
+
+ if (!C64->RealSIDmode) { //some PSID tunes use CIA TOD-clock (e.g. Kawasaki Synthesizer Demo)
+ --C64->TenthSecondCnt;
+ if (C64->TenthSecondCnt <= 0) {
+ C64->TenthSecondCnt = C64->SampleRate / 10;
+ ++(C64->IObankRD[0xDC08]);
+ if(C64->IObankRD[0xDC08]>=10) {
+ C64->IObankRD[0xDC08]=0; ++(C64->IObankRD[0xDC09]);
+ //if(C64->IObankRD[0xDC09]%
+ }
+ }
+ }
+
+ Output = cRSID_emulateWaves (&C64->SID[1]);
+ if ( C64->SID[2].BaseAddress != 0 ) Output += cRSID_emulateWaves (&C64->SID[2]);
+ if ( C64->SID[3].BaseAddress != 0 ) Output += cRSID_emulateWaves (&C64->SID[3]);
+
+ return Output;
+}
+
+
+static inline short cRSID_playPSIDdigi(cRSID_C64instance* C64) {
+ enum PSIDdigiSpecs { DIGI_VOLUME = 1200 }; //80 };
+ static unsigned char PlaybackEnabled=0, NybbleCounter=0, RepeatCounter=0, Shifts;
+ static unsigned short SampleAddress, RatePeriod;
+ static short Output=0;
+ static int PeriodCounter;
+
+ if (C64->IObankWR[0xD41D]) {
+ PlaybackEnabled = (C64->IObankWR[0xD41D] >= 0xFE);
+ PeriodCounter = 0; NybbleCounter = 0;
+ SampleAddress = C64->IObankWR[0xD41E] + (C64->IObankWR[0xD41F]<<8);
+ RepeatCounter = C64->IObankWR[0xD43F];
+ }
+ C64->IObankWR[0xD41D] = 0;
+
+ if (PlaybackEnabled) {
+ RatePeriod = C64->IObankWR[0xD45D] + (C64->IObankWR[0xD45E]<<8);
+ if (RatePeriod) PeriodCounter += C64->CPUfrequency / RatePeriod;
+ if ( PeriodCounter >= C64->SampleRate ) {
+ PeriodCounter -= C64->SampleRate;
+
+ if ( SampleAddress < C64->IObankWR[0xD43D] + (C64->IObankWR[0xD43E]<<8) ) {
+ if (NybbleCounter) {
+ Shifts = C64->IObankWR[0xD47D] ? 4:0;
+ ++SampleAddress;
+ }
+ else Shifts = C64->IObankWR[0xD47D] ? 0:4;
+ Output = ( ( (C64->RAMbank[SampleAddress]>>Shifts) & 0xF) - 8 ) * DIGI_VOLUME; //* (C64->IObankWR[0xD418]&0xF);
+ NybbleCounter^=1;
+ }
+ else if (RepeatCounter) {
+ SampleAddress = C64->IObankWR[0xD47F] + (C64->IObankWR[0xD47E]<<8);
+ RepeatCounter--;
+ }
+
+ }
+ }
+
+ return Output;
+}
+