diff --git a/CMakeLists.txt b/CMakeLists.txt index 555b6172f..10f559207 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -411,7 +411,7 @@ target_include_directories(furnace SYSTEM PRIVATE ${DEPENDENCIES_INCLUDE_DIRS}) target_compile_definitions(furnace PRIVATE ${DEPENDENCIES_DEFINES} IMGUI_USER_CONFIG="imconfig_fur.h") target_compile_options(furnace PRIVATE ${DEPENDENCIES_COMPILE_OPTIONS}) target_link_libraries(furnace PRIVATE ${DEPENDENCIES_LIBRARIES}) -if (PKG_CONFIG_FOUND AND (SYSTEM_FMT OR SYSTEM_LIBSNDFILE OR SYSTEM_ZLIB OR SYSTEM_SDL2 OR WITH_JACK)) +if (PKG_CONFIG_FOUND AND (SYSTEM_FMT OR SYSTEM_LIBSNDFILE OR SYSTEM_ZLIB OR SYSTEM_SDL2 OR SYSTEM_RTMIDI OR WITH_JACK)) if ("${CMAKE_VERSION}" VERSION_LESS "3.13") message(WARNING "CMake version is <3.13, using old pkg-config LDFLAGS. " diff --git a/extern/rtmidi/CMakeLists.txt b/extern/rtmidi/CMakeLists.txt index b59a69972..e0ef4ca09 100644 --- a/extern/rtmidi/CMakeLists.txt +++ b/extern/rtmidi/CMakeLists.txt @@ -3,7 +3,7 @@ # additional modifications for Furnace by tildearrow. # Set minimum CMake required version for this project. -cmake_minimum_required(VERSION 3.10 FATAL_ERROR) +cmake_minimum_required(VERSION 3.5) # Define a C++ project. project(RtMidi LANGUAGES CXX C) diff --git a/papers/format.md b/papers/format.md index aab764fd4..d3add4f17 100644 --- a/papers/format.md +++ b/papers/format.md @@ -150,6 +150,8 @@ size | description | - 0xa5: OPL3 4-op (YMF262) - 12 channels | - 0xa6: OPL3 4-op + drums (YMF262) - 14 channels | - 0xa7: OPLL drums (YM2413) - 11 channels + | - 0xa8: Atari Lynx - 4 channels + | - 0xe0: QSound - 16 channels 32 | sound chip volumes | - signed char, 64=1.0, 127=~2.0 32 | sound chip panning diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 438a965cc..5354de1a7 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -743,6 +743,7 @@ void DivEngine::getCommandStream(std::vector& where) { } void DivEngine::playSub(bool preserveDrift, int goalRow) { + for (int i=0; isetSkipRegisterWrites(false); reset(); if (preserveDrift && curOrder==0) return; bool oldRepeatPattern=repeatPattern; @@ -767,11 +768,11 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { playing=true; for (int i=0; isetSkipRegisterWrites(true); while (playing && curOrdersetSkipRegisterWrites(false); @@ -1632,7 +1633,15 @@ bool DivEngine::addWaveFromFile(const char* path) { reader.seek(0,SEEK_SET); int len=reader.readI(); wave->max=(unsigned char)reader.readC(); - if (reader.size()==(size_t)(len+5)) { + if (wave->max==255) { // new wavetable format + unsigned char waveVersion=reader.readC(); + logI("reading modern .dmw...\n"); + logD("wave version %d\n",waveVersion); + wave->max=reader.readC(); + for (int i=0; idata[i]=reader.readI(); + } + } else if (reader.size()==(size_t)(len+5)) { // read as .dmw logI("reading .dmw...\n"); if (len>256) len=256; @@ -1820,19 +1829,24 @@ void DivEngine::deepCloneOrder(bool where) { warnings=""; isBusy.lock(); for (int i=0; idata,oldPat->data,256*32*sizeof(short)); + logD("found at %d\n",j); + didNotFind=false; break; } } - if (order[i]==song.orders.ord[i][curOrder]) { + if (didNotFind) { addWarning(fmt::sprintf("no free patterns in channel %d!",i)); } } diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 3c5c68a1a..328a8401c 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -619,10 +619,12 @@ int DivPlatformArcade::dispatch(DivCommand c) { } } chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; chan[c.chan].active=false; break; case DIV_CMD_NOTE_OFF_ENV: chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; chan[c.chan].active=false; chan[c.chan].std.release(); break; @@ -851,6 +853,10 @@ void DivPlatformArcade::forceIns() { rWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|((chan[i].chVolL&1)<<6)|((chan[i].chVolR&1)<<7)); } rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3)); + if (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + } } for (int i=8; i<13; i++) { chan[i].insChanged=true; diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 505f049a6..0214d5f2b 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -171,13 +171,13 @@ void DivPlatformC64::tick() { if (!chan[i].resetMask && !isMuted[i]) { rWrite(i*7+5,0); rWrite(i*7+6,0); - rWrite(i*7+4,(isMuted[i]?0:(chan[i].wave<<4))|8|(chan[i].ring<<2)|(chan[i].sync<<1)); + rWrite(i*7+4,(isMuted[i]?8:(chan[i].wave<<4))|8|(chan[i].ring<<2)|(chan[i].sync<<1)); } } } if (chan[i].std.hadWave) { chan[i].wave=chan[i].std.wave; - rWrite(i*7+4,(isMuted[i]?0:(chan[i].wave<<4))|(chan[i].ring<<2)|(chan[i].sync<<1)|chan[i].active); + rWrite(i*7+4,(isMuted[i]?8:(chan[i].wave<<4))|(chan[i].ring<<2)|(chan[i].sync<<1)|chan[i].active); } if (chan[i].std.hadEx1) { filtControl=chan[i].std.ex1&15; @@ -199,12 +199,12 @@ void DivPlatformC64::tick() { if (chan[i].keyOn) { rWrite(i*7+5,(chan[i].attack<<4)|(chan[i].decay)); rWrite(i*7+6,(chan[i].sustain<<4)|(chan[i].release)); - rWrite(i*7+4,(isMuted[i]?0:(chan[i].wave<<4))|(chan[i].ring<<2)|(chan[i].sync<<1)|1); + rWrite(i*7+4,(isMuted[i]?8:(chan[i].wave<<4))|(chan[i].ring<<2)|(chan[i].sync<<1)|1); } if (chan[i].keyOff && !isMuted[i]) { rWrite(i*7+5,(chan[i].attack<<4)|(chan[i].decay)); rWrite(i*7+6,(chan[i].sustain<<4)|(chan[i].release)); - rWrite(i*7+4,(isMuted[i]?0:(chan[i].wave<<4))|(chan[i].ring<<2)|(chan[i].sync<<1)|0); + rWrite(i*7+4,(isMuted[i]?8:(chan[i].wave<<4))|(chan[i].ring<<2)|(chan[i].sync<<1)|0); } rWrite(i*7,chan[i].freq&0xff); rWrite(i*7+1,chan[i].freq>>8); @@ -246,8 +246,8 @@ int DivPlatformC64::dispatch(DivCommand c) { filtCut=ins->c64.cut; filtRes=ins->c64.res; filtControl=ins->c64.lp|(ins->c64.bp<<1)|(ins->c64.hp<<2)|(ins->c64.ch3off<<3); - updateFilter(); } + updateFilter(); } if (chan[c.chan].insChanged) { chan[c.chan].insChanged=false; @@ -258,11 +258,13 @@ int DivPlatformC64::dispatch(DivCommand c) { case DIV_CMD_NOTE_OFF: chan[c.chan].active=false; chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; //chan[c.chan].std.init(NULL); break; case DIV_CMD_NOTE_OFF_ENV: 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: @@ -328,7 +330,7 @@ int DivPlatformC64::dispatch(DivCommand c) { break; case DIV_CMD_WAVE: chan[c.chan].wave=c.value; - rWrite(c.chan*7+4,(chan[c.chan].wave<<4)|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|chan[c.chan].active); + rWrite(c.chan*7+4,(isMuted[c.chan]?8:(chan[c.chan].wave<<4))|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|chan[c.chan].active); break; case DIV_CMD_LEGATO: chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); @@ -409,11 +411,11 @@ int DivPlatformC64::dispatch(DivCommand c) { break; case 4: chan[c.chan].ring=c.value; - rWrite(c.chan*7+4,(chan[c.chan].wave<<4)|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|chan[c.chan].active); + rWrite(c.chan*7+4,(isMuted[c.chan]?8:(chan[c.chan].wave<<4))|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|chan[c.chan].active); break; case 5: chan[c.chan].sync=c.value; - rWrite(c.chan*7+4,(chan[c.chan].wave<<4)|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|chan[c.chan].active); + rWrite(c.chan*7+4,(isMuted[c.chan]?8:(chan[c.chan].wave<<4))|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|chan[c.chan].active); break; case 6: filtControl&=7; @@ -432,13 +434,17 @@ int DivPlatformC64::dispatch(DivCommand c) { void DivPlatformC64::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - rWrite(ch*7+4,(isMuted[ch]?0:(chan[ch].wave<<4))|(chan[ch].ring<<2)|(chan[ch].sync<<1)|chan[ch].active); + rWrite(ch*7+4,(isMuted[ch]?8:(chan[ch].wave<<4))|(chan[ch].ring<<2)|(chan[ch].sync<<1)|chan[ch].active); } void DivPlatformC64::forceIns() { for (int i=0; i<3; i++) { chan[i].insChanged=true; chan[i].testWhen=0; + if (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + } } updateFilter(); } @@ -470,7 +476,7 @@ void DivPlatformC64::reset() { rWrite(0x18,0x0f); - filtControl=0; + filtControl=7; filtRes=0; filtCut=2047; resetTime=1; diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 8787d0552..27b3df450 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -578,6 +578,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (dumpWrites) addWrite(0xffff0002,0); } chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; chan[c.chan].active=false; break; case DIV_CMD_NOTE_OFF_ENV: @@ -586,6 +587,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (dumpWrites) addWrite(0xffff0002,0); } chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; chan[c.chan].active=false; chan[c.chan].std.release(); break; @@ -781,6 +783,10 @@ void DivPlatformGenesis::forceIns() { } rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + } } if (dacMode) { rWrite(0x2b,0x80); diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 382c89f53..364505db2 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -81,6 +81,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { } case DIV_CMD_NOTE_OFF: opChan[ch].keyOff=true; + opChan[ch].keyOn=false; opChan[ch].active=false; break; case DIV_CMD_VOLUME: { @@ -315,6 +316,10 @@ void DivPlatformGenesisExt::forceIns() { DivPlatformGenesis::forceIns(); for (int i=0; i<4; i++) { opChan[i].insChanged=true; + if (opChan[i].active) { + opChan[i].keyOn=true; + opChan[i].freqChanged=true; + } } } diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 0b8b3dc26..3266b2936 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -198,7 +198,7 @@ void DivPlatformNES::tick() { int ntPos=chan[i].baseFreq; if (ntPos<0) ntPos=0; if (ntPos>252) ntPos=252; - chan[i].freq=(parent->song.properNoiseLayout)?(15-(chan[i].baseFreq&15)):(noiseTable[ntPos])-1; + chan[i].freq=(parent->song.properNoiseLayout)?(15-(chan[i].baseFreq&15)):(noiseTable[ntPos]); } else { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1; if (chan[i].freq>2047) chan[i].freq=2047; @@ -428,10 +428,7 @@ int DivPlatformNES::dispatch(DivCommand c) { void DivPlatformNES::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - rWrite(0x4015,(!isMuted[0])|((!isMuted[1])<<1)|((!isMuted[2])<<2)|((!isMuted[3])<<3)|((!isMuted[4])<<4)); - if (isMuted[4]) { - rWrite(0x4011,0); - } + nes->muted[ch]=mute; } void DivPlatformNES::forceIns() { @@ -465,7 +462,7 @@ void DivPlatformNES::reset() { nes->apu.cpu_cycles=0; nes->apu.cpu_opcode_cycle=0; - rWrite(0x4015,(!isMuted[0])|((!isMuted[1])<<1)|((!isMuted[2])<<2)|((!isMuted[3])<<3)|((!isMuted[4])<<4)); + rWrite(0x4015,0x1f); rWrite(0x4001,chan[0].sweep); rWrite(0x4005,chan[1].sweep); } @@ -510,10 +507,11 @@ int DivPlatformNES::init(DivEngine* p, int channels, int sugRate, unsigned int f apuType=flags; dumpWrites=false; skipRegisterWrites=false; + nes=new struct NESAPU; for (int i=0; i<5; i++) { isMuted[i]=false; + nes->muted[i]=false; } - nes=new struct NESAPU; setFlags(flags); init_nla_table(500,500); diff --git a/src/engine/platform/sound/nes/apu.h b/src/engine/platform/sound/nes/apu.h index 4d3649e0d..93cf72fef 100644 --- a/src/engine/platform/sound/nes/apu.h +++ b/src/engine/platform/sound/nes/apu.h @@ -338,15 +338,15 @@ enum apu_mode { APU_60HZ, APU_48HZ }; #define _apu_channel_volume_adjust(ch, index)\ ((ch)) #define s1_out(a)\ - _apu_channel_volume_adjust(a->S1.output, APU_S1) + (a->muted[0] ? 0 : _apu_channel_volume_adjust(a->S1.output, APU_S1)) #define s2_out(a)\ - _apu_channel_volume_adjust(a->S2.output, APU_S2) + (a->muted[1] ? 0 : _apu_channel_volume_adjust(a->S2.output, APU_S2)) #define tr_out(a)\ - _apu_channel_volume_adjust(a->TR.output, APU_TR) + (a->muted[2] ? 0 : _apu_channel_volume_adjust(a->TR.output, APU_TR)) #define ns_out(a)\ - _apu_channel_volume_adjust(a->NS.output, APU_NS) + (a->muted[3] ? 0 : _apu_channel_volume_adjust(a->NS.output, APU_NS)) #define dmc_out(a)\ - _apu_channel_volume_adjust(a->DMC.output, APU_DMC) + (a->muted[4] ? 0 : _apu_channel_volume_adjust(a->DMC.output, APU_DMC)) #define extra_out(ch)\ (ch * cfg->apu.channel[APU_EXTRA]) #define pulse_output(a)\ @@ -523,6 +523,7 @@ EXTERNC struct NESAPU { _apuTriangle TR; _apuNoise NS; _apuDMC DMC; + unsigned char muted[5]; }; /* apuPeriod[mode][type][cycles] */ diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 135e73fef..c51ad1f26 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -515,6 +515,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; chan[c.chan].active=false; chan[c.chan].std.init(NULL); break; @@ -528,6 +529,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; chan[c.chan].active=false; chan[c.chan].std.release(); break; @@ -829,6 +831,10 @@ void DivPlatformYM2610::forceIns() { } rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + } } for (int i=4; i<14; i++) { chan[i].insChanged=true; diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index 1cd17945b..000ab69da 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -71,6 +71,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { } case DIV_CMD_NOTE_OFF: opChan[ch].keyOff=true; + opChan[ch].keyOn=false; opChan[ch].active=false; break; case DIV_CMD_VOLUME: { @@ -282,6 +283,10 @@ void DivPlatformYM2610Ext::forceIns() { DivPlatformYM2610::forceIns(); for (int i=0; i<4; i++) { opChan[i].insChanged=true; + if (opChan[i].active) { + opChan[i].keyOn=true; + opChan[i].freqChanged=true; + } } } diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 494a2999e..cf0c1c297 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -499,7 +499,7 @@ const char* DivEngine::getSystemChips(DivSystem sys) { case DIV_SYSTEM_SMS_OPLL: return "TI SN76489 + Yamaha YM2413"; case DIV_SYSTEM_GB: - return "Game Boy"; + return "Sharp LR35902"; case DIV_SYSTEM_PCE: return "Hudson Soft HuC6280"; case DIV_SYSTEM_NES: @@ -520,7 +520,7 @@ const char* DivEngine::getSystemChips(DivSystem sys) { case DIV_SYSTEM_AY8910: return "AY-3-8910"; case DIV_SYSTEM_AMIGA: - return "Paula"; + return "MOS 8364 Paula"; case DIV_SYSTEM_YM2151: return "Yamaha YM2151 standalone"; case DIV_SYSTEM_YM2612: diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index de47a5b1d..31c3609ab 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -1002,7 +1002,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { writeLoop=true; } } - if (nextTick()) { + if (nextTick() || !playing) { done=true; if (!loop) { for (int i=0; iwriteC(i); loopSample[i]=-1; } + + if (!playing) { + writeLoop=false; + loopPos=-1; + } } // get register dumps for (int i=0; iwriteI(gd3Off-0x14); w->writeI(tickCount); if (loop) { - w->writeI(loopPos-0x1c); - w->writeI(tickCount-loopTick-1); + if (loopPos==-1) { + w->writeI(0); + w->writeI(0); + } else { + w->writeI(loopPos-0x1c); + w->writeI(tickCount-loopTick-1); + } } else { w->writeI(0); w->writeI(0); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e44fca86d..b1df6dd07 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3824,7 +3824,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save File","DefleMask 1.0/legacy module{.dmf}",workingDir,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); break; case GUI_FILE_INS_OPEN: - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Load Instrument","compatible files{.fui,.dmp},.*",workingDir); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Load Instrument","compatible files{.fui,.dmp,.tfi,.vgi},.*",workingDir); break; case GUI_FILE_INS_SAVE: ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Instrument","Furnace instrument{.fui}",workingDir,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); @@ -5492,6 +5492,11 @@ bool FurnaceGUI::init() { ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".otf",ImVec4(0.3f,1.0f,0.6f,1.0f),ICON_FA_FONT); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttc",ImVec4(0.3f,1.0f,0.6f,1.0f),ICON_FA_FONT); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".tfi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fti",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".bti",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + updateWindowTitle(); for (int i=0; i