diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 4a5fafab5..0da8735d2 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -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)); diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index f068015cd..4da6cbaef 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -37,6 +37,7 @@ bool DivInstrumentFM::operator==(const DivInstrumentFM& other) { _C(ams2) && _C(ops) && _C(opllPreset) && + _C(block) && _C(fixedDrums) && _C(kickFreq) && _C(snareHatFreq) && diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 92f9d1b99..346af821a 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -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].samplesong.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=(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].samplesong.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; diff --git a/src/engine/platform/c64.h b/src/engine/platform/c64.h index f2290c805..9bb449145 100644 --- a/src/engine/platform/c64.h +++ b/src/engine/platform/c64.h @@ -33,8 +33,8 @@ class DivPlatformC64: public DivDispatch { struct Channel: public SharedChannel { 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); diff --git a/src/engine/song.h b/src/engine/song.h index a6da6b3d4..e2f8f95be 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -145,6 +145,7 @@ enum DivSystem { DIV_SYSTEM_SUPERVISION, DIV_SYSTEM_UPD1771C, DIV_SYSTEM_SID3, + DIV_SYSTEM_C64_PCM, DIV_SYSTEM_MAX }; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 3753986ce..160b24fd2 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -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<