diff --git a/extern/opl/MODIFIED.md b/extern/opl/MODIFIED.md index 78341ad48..d5a39e9f7 100644 --- a/extern/opl/MODIFIED.md +++ b/extern/opl/MODIFIED.md @@ -1,4 +1,4 @@ # modification disclaimer -this is a modified version of Nuked-OPL3 which implements channel muting in the core. +this is a modified version of Nuked-OPL3 which implements channel muting in the core, and resampling function. see [this issue](https://github.com/tildearrow/furnace/issues/414) for more information. diff --git a/extern/opl/opl3.c b/extern/opl/opl3.c index 3358f0335..5acdca250 100644 --- a/extern/opl/opl3.c +++ b/extern/opl/opl3.c @@ -1362,6 +1362,11 @@ void OPL3_Reset(opl3_chip *chip, uint32_t samplerate) #endif } +void OPL3_Resample(opl3_chip *chip, uint32_t samplerate) +{ + chip->rateratio = (samplerate << RSM_FRAC) / 49716; +} + void OPL3_WriteReg(opl3_chip *chip, uint16_t reg, uint8_t v) { uint8_t high = (reg >> 8) & 0x01; diff --git a/extern/opl/opl3.h b/extern/opl/opl3.h index 8d5153237..1e88a8018 100644 --- a/extern/opl/opl3.h +++ b/extern/opl/opl3.h @@ -158,6 +158,7 @@ struct _opl3_chip { void OPL3_Generate(opl3_chip *chip, int16_t *buf); void OPL3_GenerateResampled(opl3_chip *chip, int16_t *buf); void OPL3_Reset(opl3_chip *chip, uint32_t samplerate); +void OPL3_Resample(opl3_chip *chip, uint32_t samplerate); void OPL3_WriteReg(opl3_chip *chip, uint16_t reg, uint8_t v); void OPL3_WriteRegBuffered(opl3_chip *chip, uint16_t reg, uint8_t v); void OPL3_GenerateStream(opl3_chip *chip, int16_t *sndptr, uint32_t numsamples); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 3578dbd9c..6c3fa416d 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -275,11 +275,20 @@ void DivPlatformOPL::acquire_nuked(short** buf, size_t len) { if (os[3]>32767) os[3]=32767; buf[0][h]=os[0]; - if (oplType==3 || oplType==759) { + if (totalOutputs>1) { buf[1][h]=os[1]; + } + if (totalOutputs>2) { buf[2][h]=os[2]; + } + if (totalOutputs>3) { buf[3][h]=os[3]; } + if (totalOutputs==6) { + // placeholder for OPL4 + buf[4][h]=0; + buf[5][h]=0; + } } } @@ -1590,7 +1599,7 @@ void DivPlatformOPL::reset() { } */ if (downsample) { - const unsigned int downsampledRate=(unsigned int)((double)rate*rate/chipRateBase); + const unsigned int downsampledRate=(unsigned int)((double)rate*round(COLOR_NTSC/72.0)/(double)chipRateBase); OPL3_Reset(&fm,downsampledRate); } else { OPL3_Reset(&fm,rate); @@ -1671,7 +1680,7 @@ void DivPlatformOPL::reset() { } int DivPlatformOPL::getOutputCount() { - return (oplType==3 || oplType==759)?4:1; + return totalOutputs; } bool DivPlatformOPL::keyOffAffectsArp(int ch) { @@ -1730,6 +1739,7 @@ void DivPlatformOPL::setOPLType(int type, bool drums) { if (type==8950) { adpcmChan=drums?11:9; } + totalOutputs=1; break; case 3: case 4: case 759: slotsNonDrums=slotsOPL3; @@ -1748,6 +1758,7 @@ void DivPlatformOPL::setOPLType(int type, bool drums) { chipFreqBase=32768*684; downsample=true; } + totalOutputs=(type==4)?6:4; break; } chipType=type; @@ -1829,14 +1840,36 @@ void DivPlatformOPL::setFlags(const DivConfig& flags) { case 0x04: chipClock=15000000.0; break; + case 0x05: + chipClock=33868800.0; + break; default: chipClock=COLOR_NTSC*4.0; break; } CHECK_CUSTOM_CLOCK; - rate=chipClock/288; - chipRateBase=rate; - compatPan=flags.getBool("compatPan",false); + switch (flags.getInt("chipType",0)) { + case 1: // YMF289B + chipFreqBase=32768*684; + rate=chipClock/768; + chipRateBase=chipClock/684; + downsample=true; + totalOutputs=2; // Stereo output only + break; + default: // YMF262 + chipFreqBase=32768*288; + rate=chipClock/288; + chipRateBase=rate; + downsample=false; + totalOutputs=4; + break; + } + if (downsample) { + const unsigned int downsampledRate=(unsigned int)((double)rate*round(COLOR_NTSC/72.0)/(double)chipRateBase); + OPL3_Resample(&fm,downsampledRate); + } else { + OPL3_Resample(&fm,rate); + } break; case 4: switch (flags.getInt("clockSel",0)) { @@ -1860,6 +1893,7 @@ void DivPlatformOPL::setFlags(const DivConfig& flags) { chipClock=rate*288; break; } + compatPan=flags.getBool("compatPan",false); for (int i=0; i<20; i++) { oscBuf[i]->rate=rate; diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index f4881c29d..a417b5088 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -82,7 +82,7 @@ class DivPlatformOPL: public DivDispatch { const unsigned short* chanMap; const unsigned char* outChanMap; int chipFreqBase, chipRateBase; - int delay, chipType, oplType, chans, melodicChans, totalChans, adpcmChan, sampleBank; + int delay, chipType, oplType, chans, melodicChans, totalChans, adpcmChan, sampleBank, totalOutputs; unsigned char lastBusy; unsigned char drumState; unsigned char drumVol[5]; diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index d23622ad2..94ad6eee5 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -2334,6 +2334,22 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_OPL3_DRUMS, 1.0f, 0, "") } ); + ENTRY( + "Yamaha YMF289B (OPL3-L)", { + CH(DIV_SYSTEM_OPL3, 1.0f, 0, + "clockSel=5\n" + "chipType=1\n" + ) + } + ); + ENTRY( + "Yamaha YMF289B (drums mode)", { + CH(DIV_SYSTEM_OPL3_DRUMS, 1.0f, 0, + "clockSel=5\n" + "chipType=1\n" + ) + } + ); if (settings.hiddenSystems) { ENTRY( "Yamaha YMU759 (MA-2)", { diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index cc95e4099..c111e2bad 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -1478,6 +1478,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo case DIV_SYSTEM_OPL3: case DIV_SYSTEM_OPL3_DRUMS: { int clockSel=flags.getInt("clockSel",0); + int chipType=flags.getInt("chipType",0); bool compatPan=flags.getBool("compatPan",false); ImGui::Text("Clock rate:"); @@ -1502,6 +1503,19 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo clockSel=4; altered=true; } + if (ImGui::RadioButton("33.8688MHz (OPL3-L)",clockSel==5)) { + clockSel=5; + altered=true; + } + ImGui::Text("Chip type:"); + if (ImGui::RadioButton("OPL3 (YMF262)",chipType==0)) { + chipType=0; + altered=true; + } + if (ImGui::RadioButton("OPL3-L (YMF289B)",chipType==1)) { + chipType=1; + altered=true; + } ImGui::Unindent(); if (ImGui::Checkbox("Compatible panning (0800)",&compatPan)) { @@ -1511,6 +1525,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo if (altered) { e->lockSave([&]() { flags.set("clockSel",clockSel); + flags.set("chipType",chipType); flags.set("compatPan",compatPan); }); } diff --git a/src/gui/sysPartNumber.cpp b/src/gui/sysPartNumber.cpp index 94dc822b8..8a9cd4416 100644 --- a/src/gui/sysPartNumber.cpp +++ b/src/gui/sysPartNumber.cpp @@ -156,9 +156,15 @@ const char* FurnaceGUI::getSystemPartNumber(DivSystem sys, DivConfig& flags) { return "YM3812"; break; case DIV_SYSTEM_OPL3: - case DIV_SYSTEM_OPL3_DRUMS: - return "YMF262"; + case DIV_SYSTEM_OPL3_DRUMS:{ + int chipType=flags.getInt("chipType",0); + if (chipType==1) { + return "YMF289B"; + } else { + return "YMF262"; + } break; + } case DIV_SYSTEM_OPL4: case DIV_SYSTEM_OPL4_DRUMS: return "YMF278";