Skip to content

Commit

Permalink
C64: add a sample channel
Browse files Browse the repository at this point in the history
but don't get too excited! it's just $D418 PCM for now...
  • Loading branch information
tildearrow committed Jan 28, 2025
1 parent f16b237 commit 0d8b97b
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 14 deletions.
1 change: 1 addition & 0 deletions src/engine/dispatchContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
((DivPlatformNES*)dispatch)->set5E01(false);
break;
case DIV_SYSTEM_C64_6581:
case DIV_SYSTEM_C64_PCM:
dispatch=new DivPlatformC64;
if (isRender) {
((DivPlatformC64*)dispatch)->setCore(eng->getConfInt("c64CoreRender",1));
Expand Down
1 change: 1 addition & 0 deletions src/engine/instrument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ bool DivInstrumentFM::operator==(const DivInstrumentFM& other) {
_C(ams2) &&
_C(ops) &&
_C(opllPreset) &&
_C(block) &&
_C(fixedDrums) &&
_C(kickFreq) &&
_C(snareHatFreq) &&
Expand Down
153 changes: 143 additions & 10 deletions src/engine/platform/c64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,50 @@ short DivPlatformC64::runFakeFilter(unsigned char ch, int in) {
return CLAMP(fout,-32768,32767);
}

void DivPlatformC64::processDAC(int sRate) {
bool didWrite=false;
if (chan[3].sample>=0 && chan[3].sample<parent->song.sampleLen) {
chan[3].pcmPeriod-=chan[3].pcmRate*4;
while (chan[3].pcmPeriod<0) {
chan[3].pcmPeriod+=sRate;
DivSample* s=parent->getSample(chan[3].sample);
if (s!=NULL) {
if (chan[3].pcmPos<0 || chan[3].pcmPos>=(int)s->samples) {
chan[3].pcmOut=15;
didWrite=true;
} else {
chan[3].pcmOut=(0x80+s->data8[chan[3].pcmPos])>>4;
didWrite=true;
}
chan[3].pcmPos++;

if (s->isLoopable() && chan[3].pcmPos>=s->loopEnd) {
chan[3].pcmPos=s->loopStart;
} else if (chan[3].pcmPos>=(int)s->samples) {
chan[3].sample=-1;
didWrite=true;
}
} else {
chan[3].sample=-1;
didWrite=true;
}
}
}

if (didWrite && !isMuted[3]) updateVolume();
}

void DivPlatformC64::acquire(short** buf, size_t len) {
int dcOff=(sidCore)?0:sid->get_dc(0);
for (size_t i=0; i<len; i++) {
// run PCM
pcmCycle+=lineRate;
while (pcmCycle>=(rate*2)) {
pcmCycle-=(rate*2);
processDAC(lineRate);
}

// the rest
if (!writes.empty()) {
QueuedWrite w=writes.front();
if (sidCore==2) {
Expand Down Expand Up @@ -149,7 +190,15 @@ void DivPlatformC64::updateFilter() {
rWrite(0x15,filtCut&7);
rWrite(0x16,filtCut>>3);
rWrite(0x17,(filtRes<<4)|(chan[2].filter<<2)|(chan[1].filter<<1)|(int)(chan[0].filter));
rWrite(0x18,(filtControl<<4)|vol);
updateVolume();
}

void DivPlatformC64::updateVolume() {
if (chan[3].sample>=0 && !isMuted[3]) {
rWrite(0x18,(filtControl<<4)|chan[3].pcmOut);
} else {
rWrite(0x18,(filtControl<<4)|vol);
}
}

void DivPlatformC64::tick(bool sysTick) {
Expand Down Expand Up @@ -308,14 +357,64 @@ void DivPlatformC64::tick(bool sysTick) {
chan[i].freqChanged=false;
}
}

if (chan[3].freqChanged) {
int i=3;
double off=1.0;
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[i].sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=(double)s->centerRate/8363.0;
}
}
chan[i].pcmRate=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,2,1);
if (dumpWrites) addWrite(0xffff0001+(i<<8),chan[i].pcmRate);
}

if (willUpdateFilter) updateFilter();
}

int DivPlatformC64::dispatch(DivCommand c) {
if (c.chan>2) return 0;
if (c.chan>3) return 0;
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_C64);

if (chan[c.chan].pcm || c.chan>2) {
if (skipRegisterWrites) break;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
} else if (chan[c.chan].sampleNote!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(chan[c.chan].sampleNote);
c.value=ins->amiga.getFreq(chan[c.chan].sampleNote);
}
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
chan[c.chan].sample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
break;
}
if (chan[c.chan].setPos) {
chan[c.chan].setPos=false;
} else {
chan[c.chan].pcmPos=0;
}
chan[c.chan].pcmPeriod=0;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=parent->calcBaseFreq(2,1,c.value,false);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].macroInit(ins);
chan[c.chan].keyOn=true;
break;
}

if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].freqChanged=true;
Expand Down Expand Up @@ -364,18 +463,22 @@ int DivPlatformC64::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].sample=-1;
chan[c.chan].pcm=false;
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
//chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
if (c.chan>2) break;
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].std.release();
break;
case DIV_CMD_ENV_RELEASE:
if (c.chan>2) break;
chan[c.chan].std.release();
break;
case DIV_CMD_INSTRUMENT:
Expand All @@ -385,6 +488,7 @@ int DivPlatformC64::dispatch(DivCommand c) {
}
break;
case DIV_CMD_VOLUME:
if (c.chan>2) break;
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.has) {
Expand All @@ -405,6 +509,9 @@ int DivPlatformC64::dispatch(DivCommand c) {
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_FREQUENCY(c.value2);
if (c.chan>2 || chan[c.chan].pcm) {
destFreq=parent->calcBaseFreq(2,1,c.value2+chan[c.chan].sampleNoteDelta,false);
}
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
Expand All @@ -427,21 +534,28 @@ int DivPlatformC64::dispatch(DivCommand c) {
break;
}
case DIV_CMD_STD_NOISE_MODE:
if (c.chan>2) break;
chan[c.chan].duty=(c.value*4095)/100;
rWrite(c.chan*7+2,chan[c.chan].duty&0xff);
rWrite(c.chan*7+3,chan[c.chan].duty>>8);
break;
case DIV_CMD_C64_FINE_DUTY:
if (c.chan>2) break;
chan[c.chan].duty=c.value;
rWrite(c.chan*7+2,chan[c.chan].duty&0xff);
rWrite(c.chan*7+3,chan[c.chan].duty>>8);
break;
case DIV_CMD_WAVE:
if (c.chan>2) break;
chan[c.chan].wave=c.value;
rWrite(c.chan*7+4,(chan[c.chan].wave<<4)|(chan[c.chan].test<<3)|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active && chan[c.chan].gate));
break;
case DIV_CMD_LEGATO:
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(0)));
if (c.chan>2 || chan[c.chan].pcm) {
chan[c.chan].baseFreq=parent->calcBaseFreq(2,1,c.value+chan[c.chan].sampleNoteDelta+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(0)),false);
} else {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(0)));
}
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
break;
Expand All @@ -456,36 +570,44 @@ int DivPlatformC64::dispatch(DivCommand c) {
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_PRE_NOTE:
if (c.chan>2) break;
if (resetTime) chan[c.chan].testWhen=c.value-resetTime+1;
break;
case DIV_CMD_GET_VOLMAX:
return 15;
break;
case DIV_CMD_C64_CUTOFF:
if (c.chan>2) break;
if (c.value>100) c.value=100;
filtCut=(c.value+2)*2047/102;
updateFilter();
break;
case DIV_CMD_C64_FINE_CUTOFF:
if (c.chan>2) break;
filtCut=c.value;
updateFilter();
break;
case DIV_CMD_C64_RESONANCE:
if (c.chan>2) break;
if (c.value>15) c.value=15;
filtRes=c.value;
updateFilter();
break;
case DIV_CMD_C64_FILTER_MODE:
if (c.chan>2) break;
filtControl=c.value&7;
updateFilter();
break;
case DIV_CMD_C64_RESET_TIME:
if (c.chan>2) break;
resetTime=c.value;
break;
case DIV_CMD_C64_RESET_MASK:
if (c.chan>2) break;
chan[c.chan].resetMask=c.value;
break;
case DIV_CMD_C64_FILTER_RESET:
if (c.chan>2) break;
if (c.value&15) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_C64);
if (ins->c64.initFilter) {
Expand All @@ -496,6 +618,7 @@ int DivPlatformC64::dispatch(DivCommand c) {
chan[c.chan].resetFilter=c.value>>4;
break;
case DIV_CMD_C64_DUTY_RESET:
if (c.chan>2) break;
if (c.value&15) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_C64);
chan[c.chan].duty=ins->c64.duty;
Expand All @@ -505,6 +628,7 @@ int DivPlatformC64::dispatch(DivCommand c) {
chan[c.chan].resetDuty=c.value>>4;
break;
case DIV_CMD_C64_EXTENDED:
if (c.chan>2) break;
switch (c.value>>4) {
case 0:
chan[c.chan].attack=c.value&15;
Expand Down Expand Up @@ -545,19 +669,23 @@ int DivPlatformC64::dispatch(DivCommand c) {
}
break;
case DIV_CMD_C64_AD:
if (c.chan>2) break;
chan[c.chan].attack=c.value>>4;
chan[c.chan].decay=c.value&15;
rWrite(c.chan*7+5,(chan[c.chan].attack<<4)|(chan[c.chan].decay));
break;
case DIV_CMD_C64_SR:
if (c.chan>2) break;
chan[c.chan].sustain=c.value>>4;
chan[c.chan].release=c.value&15;
rWrite(c.chan*7+6,(chan[c.chan].sustain<<4)|(chan[c.chan].release));
break;
case DIV_CMD_C64_PW_SLIDE:
if (c.chan>2) break;
chan[c.chan].pw_slide=c.value*c.value2;
break;
case DIV_CMD_C64_CUTOFF_SLIDE:
if (c.chan>2) break;
cutoff_slide=c.value*c.value2;
break;
case DIV_CMD_MACRO_OFF:
Expand Down Expand Up @@ -589,10 +717,11 @@ void DivPlatformC64::muteChannel(int ch, bool mute) {
} else {
sid->set_is_muted(ch,mute);
}
if (ch==3) updateVolume();
}

void DivPlatformC64::forceIns() {
for (int i=0; i<3; i++) {
for (int i=0; i<4; i++) {
chan[i].insChanged=true;
chan[i].testWhen=0;
if (chan[i].active) {
Expand All @@ -604,15 +733,15 @@ void DivPlatformC64::forceIns() {
}

void DivPlatformC64::notifyInsChange(int ins) {
for (int i=0; i<3; i++) {
for (int i=0; i<4; i++) {
if (chan[i].ins==ins) {
chan[i].insChanged=true;
}
}
}

void DivPlatformC64::notifyInsDeletion(void* ins) {
for (int i=0; i<3; i++) {
for (int i=0; i<4; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
Expand Down Expand Up @@ -690,7 +819,7 @@ float DivPlatformC64::getPostAmp() {

void DivPlatformC64::reset() {
while (!writes.empty()) writes.pop();
for (int i=0; i<3; i++) {
for (int i=0; i<4; i++) {
chan[i]=DivPlatformC64::Channel();
chan[i].std.setEngine(parent);
fakeLow[i]=0;
Expand All @@ -699,6 +828,7 @@ void DivPlatformC64::reset() {
}

cutoff_slide=0;
pcmCycle=0;

if (sidCore==2) {
dSID_init(sid_d,chipClock,rate,sidIs6581?6581:8580,needInitTables);
Expand Down Expand Up @@ -761,18 +891,21 @@ void DivPlatformC64::setFlags(const DivConfig& flags) {
switch (flags.getInt("clockSel",0)) {
case 0x0: // NTSC C64
chipClock=COLOR_NTSC*2.0/7.0;
lineRate=15734;
break;
case 0x1: // PAL C64
chipClock=COLOR_PAL*2.0/9.0;
lineRate=15625;
break;
case 0x2: // SSI 2001
default:
chipClock=14318180.0/16.0;
lineRate=15734;
break;
}
CHECK_CUSTOM_CLOCK;
rate=chipClock;
for (int i=0; i<3; i++) {
for (int i=0; i<4; i++) {
oscBuf[i]->rate=rate/16;
}
if (sidCore>0) {
Expand Down Expand Up @@ -839,7 +972,7 @@ int DivPlatformC64::init(DivEngine* p, int channels, int sugRate, const DivConfi
skipRegisterWrites=false;
needInitTables=true;
writeOscBuf=0;
for (int i=0; i<3; i++) {
for (int i=0; i<4; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
Expand Down Expand Up @@ -884,7 +1017,7 @@ int DivPlatformC64::init(DivEngine* p, int channels, int sugRate, const DivConfi
}

void DivPlatformC64::quit() {
for (int i=0; i<3; i++) {
for (int i=0; i<4; i++) {
delete oscBuf[i];
}
if (sid!=NULL) delete sid;
Expand Down
Loading

2 comments on commit 0d8b97b

@freq-mod
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 8580-comaptible PCM play method planned at one point?

@tildearrow
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really feeling like it at the moment but in the far future it could happen.

Please sign in to comment.