C64: add a sample channel

but don't get too excited! it's just $D418 PCM for now...
This commit is contained in:
tildearrow 2025-01-28 17:33:08 -05:00
parent f16b23772e
commit 0d8b97b1a3
8 changed files with 168 additions and 14 deletions

View file

@ -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));

View file

@ -37,6 +37,7 @@ bool DivInstrumentFM::operator==(const DivInstrumentFM& other) {
_C(ams2) &&
_C(ops) &&
_C(opllPreset) &&
_C(block) &&
_C(fixedDrums) &&
_C(kickFreq) &&
_C(snareHatFreq) &&

View file

@ -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) {
@ -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) {
@ -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;
@ -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:
@ -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) {
@ -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;
@ -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;
@ -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) {
@ -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;
@ -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;
@ -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:
@ -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) {
@ -604,7 +733,7 @@ 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;
}
@ -612,7 +741,7 @@ void DivPlatformC64::notifyInsChange(int ins) {
}
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);
}
}
@ -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;
@ -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);
@ -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) {
@ -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;
}
@ -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;

View file

@ -33,8 +33,8 @@
class DivPlatformC64: public DivDispatch {
struct Channel: public SharedChannel<signed char> {
int prevFreq, testWhen;
unsigned int audPos, pcmPos;
int sample, pcmPeriod, pcmRate, pcmOut;
unsigned int audPos;
int pcmPos, sample, pcmPeriod, pcmRate, pcmOut;
unsigned char sweep, wave, attack, decay, sustain, release;
short duty;
bool sweepChanged, filter, setPos, pcm;
@ -49,7 +49,7 @@ class DivPlatformC64: public DivDispatch {
sample(-1),
pcmPeriod(0),
pcmRate(0),
pcmOut(0),
pcmOut(15),
sweep(0),
wave(0),
attack(0),
@ -88,6 +88,7 @@ class DivPlatformC64: public DivDispatch {
unsigned char writeOscBuf;
unsigned char sidCore;
int filtCut, resetTime, initResetTime;
int pcmCycle, lineRate;
short cutoff_slide;
bool keyPriority, sidIs6581, needInitTables, no1EUpdate, multiplyRel, macroRace;
@ -105,10 +106,12 @@ class DivPlatformC64: public DivDispatch {
inline short runFakeFilter(unsigned char ch, int in);
void processDAC(int sRate);
void acquire_classic(short* bufL, short* bufR, size_t start, size_t len);
void acquire_fp(short* bufL, short* bufR, size_t start, size_t len);
void updateFilter();
void updateVolume();
public:
void acquire(short** buf, size_t len);
int dispatch(DivCommand c);

View file

@ -145,6 +145,7 @@ enum DivSystem {
DIV_SYSTEM_SUPERVISION,
DIV_SYSTEM_UPD1771C,
DIV_SYSTEM_SID3,
DIV_SYSTEM_C64_PCM,
DIV_SYSTEM_MAX
};

View file

@ -2316,6 +2316,18 @@ void DivEngine::registerSystems() {
SID3PostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_C64_PCM]=new DivSysDef(
_("Commodore 64 (SID 6581) with software PCM"), NULL, 0xe2, 0, 4, false, true, 0, false, (1U<<DIV_SAMPLE_DEPTH_8BIT)|(1U<<DIV_SAMPLE_DEPTH_16BIT), 0, 0,
_("the 6581 had a quirk which allowed playback of 4-bit samples by writing PCM data to the volume register."),
{_("Channel 1"), _("Channel 2"), _("Channel 3"), _("PCM")},
{"CH1", "CH2", "CH3", "P"},
{DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM},
{DIV_INS_C64, DIV_INS_C64, DIV_INS_C64, DIV_INS_AMIGA},
{},
{},
c64PostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_DUMMY]=new DivSysDef(
_("Dummy System"), NULL, 0xfd, 0, 8, false, true, 0, false, 0, 0, 0,
_("this is a system designed for testing purposes."),

View file

@ -1228,6 +1228,7 @@ const int availableSystems[]={
DIV_SYSTEM_NES,
DIV_SYSTEM_C64_8580,
DIV_SYSTEM_C64_6581,
DIV_SYSTEM_C64_PCM,
DIV_SYSTEM_YM2151,
DIV_SYSTEM_SEGAPCM,
DIV_SYSTEM_SEGAPCM_COMPAT,
@ -1402,6 +1403,7 @@ const int chipsSpecial[]={
DIV_SYSTEM_NES,
DIV_SYSTEM_C64_8580,
DIV_SYSTEM_C64_6581,
DIV_SYSTEM_C64_PCM,
DIV_SYSTEM_SFX_BEEPER,
DIV_SYSTEM_SFX_BEEPER_QUADTONE,
DIV_SYSTEM_DUMMY,

View file

@ -680,7 +680,8 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
break;
}
case DIV_SYSTEM_C64_8580:
case DIV_SYSTEM_C64_6581: {
case DIV_SYSTEM_C64_6581:
case DIV_SYSTEM_C64_PCM: {
int clockSel=flags.getInt("clockSel",0);
bool keyPriority=flags.getBool("keyPriority",true);
bool no1EUpdate=flags.getBool("no1EUpdate",false);