summaryrefslogtreecommitdiffstats
path: root/lib/rbcodec/codecs/cRSID/C64/C64.c
blob: 07e60f0e77f38cd2683a643e622c42f6463f2a95 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200

//C64 emulation (SID-playback related)


#include "../libcRSID.h"
#include "libcRSIDc64.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
#define C64_PAL_CPUCLK (985248)
#define 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;
}