diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 293c4cd0f..13ef95c74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -187,6 +187,8 @@ jobs: export USE_WAE=ON export CMAKE_EXTRA_ARGS=() if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then + # 1. Go to hell + export USE_WAE=OFF CMAKE_EXTRA_ARGS+=('-DCMAKE_GENERATOR_PLATFORM=${{ steps.windows-identify.outputs.msvc-target }}') # Force static linking diff --git a/.gitignore b/.gitignore index df492a378..636c827fc 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ test/songs/ test/delta/ test/result/ .vs/ +CMakeSettings.json \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index d63fd70b5..435f43c75 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,6 +19,3 @@ [submodule "extern/adpcm"] path = extern/adpcm url = https://github.com/superctr/adpcm -[submodule "extern/Nuked-OPL3"] - path = extern/Nuked-OPL3 - url = https://github.com/nukeykt/Nuked-OPL3.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 498f3df69..eaa1781d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -237,7 +237,7 @@ extern/adpcm/ymz_codec.c extern/Nuked-OPN2/ym3438.c extern/opm/opm.c extern/Nuked-OPLL/opll.c -extern/Nuked-OPL3/opl3.c +extern/opl/opl3.c src/engine/platform/sound/sn76496.cpp src/engine/platform/sound/ay8910.cpp src/engine/platform/sound/saa1099.cpp @@ -250,6 +250,13 @@ src/engine/platform/sound/nes/mmc5.c src/engine/platform/sound/vera_psg.c src/engine/platform/sound/vera_pcm.c +src/engine/platform/sound/nes_nsfplay/nes_apu.cpp +src/engine/platform/sound/nes_nsfplay/nes_dmc.cpp +src/engine/platform/sound/nes_nsfplay/nes_fds.cpp +src/engine/platform/sound/nes_nsfplay/nes_mmc5.cpp +src/engine/platform/sound/nes_nsfplay/nes_n106.cpp +src/engine/platform/sound/nes_nsfplay/nes_vrc6.cpp + src/engine/platform/sound/c64/sid.cc src/engine/platform/sound/c64/voice.cc src/engine/platform/sound/c64/wave.cc @@ -284,6 +291,8 @@ src/engine/platform/sound/x1_010/x1_010.cpp src/engine/platform/sound/swan.cpp +src/engine/platform/sound/su.cpp + src/engine/platform/sound/k005289/k005289.cpp src/engine/platform/sound/n163/n163.cpp @@ -341,6 +350,7 @@ src/engine/platform/segapcm.cpp src/engine/platform/qsound.cpp src/engine/platform/x1_010.cpp src/engine/platform/lynx.cpp +src/engine/platform/su.cpp src/engine/platform/swan.cpp src/engine/platform/vera.cpp src/engine/platform/bubsyswsg.cpp @@ -386,6 +396,7 @@ src/gui/guiConst.cpp src/gui/about.cpp src/gui/channels.cpp +src/gui/chanOsc.cpp src/gui/compatFlags.cpp src/gui/cursor.cpp src/gui/dataList.cpp @@ -393,6 +404,7 @@ src/gui/debugWindow.cpp src/gui/doAction.cpp src/gui/editing.cpp src/gui/editControls.cpp +src/gui/effectList.cpp src/gui/insEdit.cpp src/gui/log.cpp src/gui/mixer.cpp diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ad608e4cb..eb25675d0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,12 +49,27 @@ the coding style is described here: - `size_t` are 32-bit or 64-bit, depending on architecture. - in float/double operations, always use decimal and `f` if single-precision. - e.g. `1.0f` or `1.0` instead of `1`. +- prefer `NULL` over `nullptr` or any other proprietary null. - don't use `auto` unless needed. +- use `String` for `std::string` (this is typedef'd in ta-utils.h). +- prefer using operator for String (std::string) comparisons (a==""). some files (particularly the ones in `src/engine/platform/sound` and `extern/`) don't follow this style. you don't have to follow this style. I will fix it after I accept your contribution. +additional guidelines: + +- in general **strongly** avoid breaking compatibility. + - do not touch loadFur/saveFur unless you know what you're doing! + - new fields must be at the end of each block to ensure forward compatibility + - likewise, the instrument read/write functions in DivInstrument have to be handled carefully + - any change to the format requires a version bump (see `src/engine/engine.h`). + - do not bump the version number under any circumstances! + - if you are making major changes to the playback routine, make sure to test with older songs to ensure nothing breaks. + - I will run a test suite to make sure this is the case. + - if something breaks, you might want to add a compatibility flag (this requires changing the format though). + ## Demo Songs just put your demo song in `demos/`! diff --git a/README.md b/README.md index f8ea1aec5..1eac106d2 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ this is a multi-system chiptune tracker. check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage). +[see here](https://nightly.link/tildearrow/furnace/workflows/build/master) for unstable developer builds. + ## features - supports the following systems: @@ -64,6 +66,8 @@ some people have provided packages for Unix/Unix-like distributions. here's a li [![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml) +if you can't download these artifacts (because GitHub requires you to be logged in), [go here](https://nightly.link/tildearrow/furnace/workflows/build/master) instead. + **NOTE: do not download the project's source as a .zip or .tar.gz as these do not include the project's submodules which are necessary to proceed with building. please instead use Git as shown below.** ## dependencies diff --git a/TODO.md b/TODO.md new file mode 100644 index 000000000..907e1aeef --- /dev/null +++ b/TODO.md @@ -0,0 +1,32 @@ +# to-do for 0.6pre1 + +- piano/input pad + - note input via piano + - input pad + - settings +- RF5C68 system +- OPN system +- OPNA system +- ZX beeper system +- Y8950 system +- SCC/SCC+ system +- maybe YMU759 ADPCM channel +- ADPCM chips +- Game Boy envelope macro/sequence +- rewrite the system name detection function anyway +- scroll instrument/wave/sample list when selecting item +- unified data view +- volume commands should work on Game Boy +- macro editor menu +- add another FM editor layout +- try to find out why does VSlider not accept keyboard input +- finish lock layout +- if macros have release, note off should release them +- add ability to select entire row when clicking on row number +- store edit/followOrders/followPattern state in config +- add ability to select a column by double clicking +- add ability to move selection by dragging +- Apply button in settings +- find and replace +- finish wave synth +- add mono/poly note preview button \ No newline at end of file diff --git a/extern/Nuked-OPL3 b/extern/Nuked-OPL3 deleted file mode 160000 index bb5c8d08a..000000000 --- a/extern/Nuked-OPL3 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bb5c8d08a85779c42b75c79d7b84f365a1b93b66 diff --git a/extern/SAASound/src/SAADevice.cpp b/extern/SAASound/src/SAADevice.cpp index 5cfb17b22..9064dcab9 100644 --- a/extern/SAASound/src/SAADevice.cpp +++ b/extern/SAASound/src/SAADevice.cpp @@ -307,7 +307,7 @@ BYTE CSAADevice::_ReadData(void) } #endif -void CSAADevice::_TickAndOutputStereo(unsigned int& left_mixed, unsigned int& right_mixed) +void CSAADevice::_TickAndOutputStereo(unsigned int& left_mixed, unsigned int& right_mixed, DivDispatchOscBuffer** oscBuf) { unsigned int temp_left, temp_right; unsigned int accum_left = 0, accum_right = 0; @@ -316,21 +316,27 @@ void CSAADevice::_TickAndOutputStereo(unsigned int& left_mixed, unsigned int& ri m_Noise0.Tick(); m_Noise1.Tick(); m_Amp0.TickAndOutputStereo(temp_left, temp_right); + oscBuf[0]->data[oscBuf[0]->needle++]=(temp_left+temp_right)<<4; accum_left += temp_left; accum_right += temp_right; m_Amp1.TickAndOutputStereo(temp_left, temp_right); + oscBuf[1]->data[oscBuf[1]->needle++]=(temp_left+temp_right)<<4; accum_left += temp_left; accum_right += temp_right; m_Amp2.TickAndOutputStereo(temp_left, temp_right); + oscBuf[2]->data[oscBuf[2]->needle++]=(temp_left+temp_right)<<4; accum_left += temp_left; accum_right += temp_right; m_Amp3.TickAndOutputStereo(temp_left, temp_right); + oscBuf[3]->data[oscBuf[3]->needle++]=(temp_left+temp_right)<<4; accum_left += temp_left; accum_right += temp_right; m_Amp4.TickAndOutputStereo(temp_left, temp_right); + oscBuf[4]->data[oscBuf[4]->needle++]=(temp_left+temp_right)<<4; accum_left += temp_left; accum_right += temp_right; m_Amp5.TickAndOutputStereo(temp_left, temp_right); + oscBuf[5]->data[oscBuf[5]->needle++]=(temp_left+temp_right)<<4; accum_left += temp_left; accum_right += temp_right; } diff --git a/extern/SAASound/src/SAADevice.h b/extern/SAASound/src/SAADevice.h index 639d90620..fe4339305 100644 --- a/extern/SAASound/src/SAADevice.h +++ b/extern/SAASound/src/SAADevice.h @@ -53,7 +53,7 @@ public: void _SetClockRate(unsigned int nClockRate); void _SetSampleRate(unsigned int nSampleRate); void _SetOversample(unsigned int nOversample); - void _TickAndOutputStereo(unsigned int& left_mixed, unsigned int& right_mixed); + void _TickAndOutputStereo(unsigned int& left_mixed, unsigned int& right_mixed, DivDispatchOscBuffer** oscBuf); void _TickAndOutputSeparate(unsigned int& left_mixed, unsigned int& right_mixed, unsigned int& left0, unsigned int& right0, unsigned int& left1, unsigned int& right1, diff --git a/extern/SAASound/src/SAAImpl.cpp b/extern/SAASound/src/SAAImpl.cpp index 455a6a18b..6602feee9 100644 --- a/extern/SAASound/src/SAAImpl.cpp +++ b/extern/SAASound/src/SAAImpl.cpp @@ -298,7 +298,7 @@ void scale_for_output(unsigned int left_input, unsigned int right_input, *pBuffer++ = (right_output >> 8) & 0x00ff; } -void CSAASoundInternal::GenerateMany(BYTE* pBuffer, unsigned long nSamples) +void CSAASoundInternal::GenerateMany(BYTE* pBuffer, unsigned long nSamples, DivDispatchOscBuffer** oscBuf) { unsigned int left_mixed, right_mixed; static double filterout_z1_left_mixed = 0, filterout_z1_right_mixed = 0; @@ -376,7 +376,7 @@ void CSAASoundInternal::GenerateMany(BYTE* pBuffer, unsigned long nSamples) #endif while (nSamples--) { - m_chip._TickAndOutputStereo(left_mixed, right_mixed); + m_chip._TickAndOutputStereo(left_mixed, right_mixed, oscBuf); scale_for_output(left_mixed, right_mixed, oversample, m_bHighpass, nBoost, filterout_z1_left_mixed, filterout_z1_right_mixed, pBuffer); } diff --git a/extern/SAASound/src/SAAImpl.h b/extern/SAASound/src/SAAImpl.h index 61fa79c58..f5f58d556 100755 --- a/extern/SAASound/src/SAAImpl.h +++ b/extern/SAASound/src/SAAImpl.h @@ -68,7 +68,7 @@ public: unsigned short GetCurrentBytesPerSample(void); static unsigned short GetBytesPerSample(SAAPARAM uParam); - void GenerateMany(BYTE * pBuffer, unsigned long nSamples); + void GenerateMany(BYTE * pBuffer, unsigned long nSamples, DivDispatchOscBuffer** oscBuf); }; diff --git a/extern/SAASound/src/SAASndC.cpp b/extern/SAASound/src/SAASndC.cpp index f03e82114..747c70a73 100755 --- a/extern/SAASound/src/SAASndC.cpp +++ b/extern/SAASound/src/SAASndC.cpp @@ -81,7 +81,7 @@ unsigned long SAAAPI SAASNDGetSampleRate(SAAPARAM uParam) void SAAAPI SAASNDGenerateMany(SAASND object, BYTE * pBuffer, unsigned long nSamples) { - ((LPCSAASOUND)(object))->GenerateMany(pBuffer, nSamples); + ((LPCSAASOUND)(object))->GenerateMany(pBuffer, nSamples, NULL); } void SAAAPI SAASNDSetSampleRate(SAASND object, unsigned int nSampleRate) diff --git a/extern/SAASound/src/SAASound.h b/extern/SAASound/src/SAASound.h index c80831484..7496cc360 100644 --- a/extern/SAASound/src/SAASound.h +++ b/extern/SAASound/src/SAASound.h @@ -61,6 +61,8 @@ typedef unsigned long SAAPARAM; #ifdef __cplusplus +#include "../../src/engine/dispatch.h" + class CSAASound { public: @@ -79,7 +81,7 @@ public: virtual unsigned short GetCurrentBytesPerSample () = 0; static unsigned short GetBytesPerSample (SAAPARAM uParam); - virtual void GenerateMany (BYTE * pBuffer, unsigned long nSamples) = 0; + virtual void GenerateMany (BYTE * pBuffer, unsigned long nSamples, DivDispatchOscBuffer** oscBuf) = 0; virtual void SetClockRate(unsigned int nClockRate) = 0; virtual void SetSampleRate(unsigned int nSampleRate) = 0; diff --git a/extern/SAASound/src/types.h b/extern/SAASound/src/types.h index 4eb62f485..8cce21247 100755 --- a/extern/SAASound/src/types.h +++ b/extern/SAASound/src/types.h @@ -12,8 +12,10 @@ defined(__arm__) || \ (defined(__mips__) && defined(__MIPSEL__)) #else +#ifndef __BIG_ENDIAN #define __BIG_ENDIAN #endif +#endif #ifndef NULL diff --git a/extern/igfd/ImGuiFileDialog.cpp b/extern/igfd/ImGuiFileDialog.cpp index 3980bcd3e..98d6c909a 100644 --- a/extern/igfd/ImGuiFileDialog.cpp +++ b/extern/igfd/ImGuiFileDialog.cpp @@ -3295,7 +3295,8 @@ namespace IGFD const std::string& vFileName, const int& vCountSelectionMax, UserDatas vUserDatas, - ImGuiFileDialogFlags vFlags) + ImGuiFileDialogFlags vFlags, + SelectFun vSelectFun) { if (prFileDialogInternal.puShowDialog) // if already opened, quit return; @@ -3306,6 +3307,7 @@ namespace IGFD prFileDialogInternal.puDLGtitle = vTitle; prFileDialogInternal.puDLGuserDatas = vUserDatas; prFileDialogInternal.puDLGflags = vFlags; + prFileDialogInternal.puDLGselFun = vSelectFun; prFileDialogInternal.puDLGoptionsPane = nullptr; prFileDialogInternal.puDLGoptionsPaneWidth = 0.0f; prFileDialogInternal.puDLGmodal = false; @@ -3335,7 +3337,8 @@ namespace IGFD const std::string& vFilePathName, const int& vCountSelectionMax, UserDatas vUserDatas, - ImGuiFileDialogFlags vFlags) + ImGuiFileDialogFlags vFlags, + SelectFun vSelectFun) { if (prFileDialogInternal.puShowDialog) // if already opened, quit return; @@ -3348,6 +3351,7 @@ namespace IGFD prFileDialogInternal.puDLGoptionsPaneWidth = 0.0f; prFileDialogInternal.puDLGuserDatas = vUserDatas; prFileDialogInternal.puDLGflags = vFlags; + prFileDialogInternal.puDLGselFun = vSelectFun; prFileDialogInternal.puDLGmodal = false; auto ps = IGFD::Utils::ParsePathFileName(vFilePathName); @@ -3390,7 +3394,8 @@ namespace IGFD const float& vSidePaneWidth, const int& vCountSelectionMax, UserDatas vUserDatas, - ImGuiFileDialogFlags vFlags) + ImGuiFileDialogFlags vFlags, + SelectFun vSelectFun) { if (prFileDialogInternal.puShowDialog) // if already opened, quit return; @@ -3401,6 +3406,7 @@ namespace IGFD prFileDialogInternal.puDLGtitle = vTitle; prFileDialogInternal.puDLGuserDatas = vUserDatas; prFileDialogInternal.puDLGflags = vFlags; + prFileDialogInternal.puDLGselFun = vSelectFun; prFileDialogInternal.puDLGoptionsPane = vSidePane; prFileDialogInternal.puDLGoptionsPaneWidth = vSidePaneWidth; prFileDialogInternal.puDLGmodal = false; @@ -3435,7 +3441,8 @@ namespace IGFD const float& vSidePaneWidth, const int& vCountSelectionMax, UserDatas vUserDatas, - ImGuiFileDialogFlags vFlags) + ImGuiFileDialogFlags vFlags, + SelectFun vSelectFun) { if (prFileDialogInternal.puShowDialog) // if already opened, quit return; @@ -3448,6 +3455,7 @@ namespace IGFD prFileDialogInternal.puDLGoptionsPaneWidth = vSidePaneWidth; prFileDialogInternal.puDLGuserDatas = vUserDatas; prFileDialogInternal.puDLGflags = vFlags; + prFileDialogInternal.puDLGselFun = vSelectFun; prFileDialogInternal.puDLGmodal = false; auto ps = IGFD::Utils::ParsePathFileName(vFilePathName); @@ -3489,7 +3497,8 @@ namespace IGFD const std::string& vFileName, const int& vCountSelectionMax, UserDatas vUserDatas, - ImGuiFileDialogFlags vFlags) + ImGuiFileDialogFlags vFlags, + SelectFun vSelectFun) { if (prFileDialogInternal.puShowDialog) // if already opened, quit return; @@ -3497,7 +3506,7 @@ namespace IGFD OpenDialog( vKey, vTitle, vFilters, vPath, vFileName, - vCountSelectionMax, vUserDatas, vFlags); + vCountSelectionMax, vUserDatas, vFlags, vSelectFun); prFileDialogInternal.puDLGmodal = true; } @@ -3509,7 +3518,8 @@ namespace IGFD const std::string& vFilePathName, const int& vCountSelectionMax, UserDatas vUserDatas, - ImGuiFileDialogFlags vFlags) + ImGuiFileDialogFlags vFlags, + SelectFun vSelectFun) { if (prFileDialogInternal.puShowDialog) // if already opened, quit return; @@ -3517,7 +3527,7 @@ namespace IGFD OpenDialog( vKey, vTitle, vFilters, vFilePathName, - vCountSelectionMax, vUserDatas, vFlags); + vCountSelectionMax, vUserDatas, vFlags, vSelectFun); prFileDialogInternal.puDLGmodal = true; } @@ -3534,7 +3544,8 @@ namespace IGFD const float& vSidePaneWidth, const int& vCountSelectionMax, UserDatas vUserDatas, - ImGuiFileDialogFlags vFlags) + ImGuiFileDialogFlags vFlags, + SelectFun vSelectFun) { if (prFileDialogInternal.puShowDialog) // if already opened, quit return; @@ -3543,7 +3554,7 @@ namespace IGFD vKey, vTitle, vFilters, vPath, vFileName, vSidePane, vSidePaneWidth, - vCountSelectionMax, vUserDatas, vFlags); + vCountSelectionMax, vUserDatas, vFlags, vSelectFun); prFileDialogInternal.puDLGmodal = true; } @@ -3559,7 +3570,8 @@ namespace IGFD const float& vSidePaneWidth, const int& vCountSelectionMax, UserDatas vUserDatas, - ImGuiFileDialogFlags vFlags) + ImGuiFileDialogFlags vFlags, + SelectFun vSelectFun) { if (prFileDialogInternal.puShowDialog) // if already opened, quit return; @@ -3568,7 +3580,7 @@ namespace IGFD vKey, vTitle, vFilters, vFilePathName, vSidePane, vSidePaneWidth, - vCountSelectionMax, vUserDatas, vFlags); + vCountSelectionMax, vUserDatas, vFlags, vSelectFun); prFileDialogInternal.puDLGmodal = true; } @@ -3940,6 +3952,9 @@ namespace IGFD return 2; } else { fdi.SelectFileName(prFileDialogInternal, vInfos); + if (prFileDialogInternal.puDLGselFun!=NULL) { + prFileDialogInternal.puDLGselFun(GetFilePathName().c_str()); + } } } } diff --git a/extern/igfd/ImGuiFileDialog.h b/extern/igfd/ImGuiFileDialog.h index 2944bfbf4..016bd9a10 100644 --- a/extern/igfd/ImGuiFileDialog.h +++ b/extern/igfd/ImGuiFileDialog.h @@ -1080,6 +1080,7 @@ namespace IGFD typedef void* UserDatas; typedef std::function PaneFun; // side pane function binding + typedef std::function SelectFun; // click on file function binding class FileDialogInternal { public: @@ -1103,6 +1104,7 @@ namespace IGFD ImGuiFileDialogFlags puDLGflags = ImGuiFileDialogFlags_None; UserDatas puDLGuserDatas = nullptr; PaneFun puDLGoptionsPane = nullptr; + SelectFun puDLGselFun = nullptr; float puDLGoptionsPaneWidth = 0.0f; bool puDLGmodal = false; bool puNeedToExitDialog = false; @@ -1155,7 +1157,8 @@ namespace IGFD const std::string& vFileName, // defaut file name const int& vCountSelectionMax = 1, // count selection max UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags + ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags + SelectFun vSelectFun = nullptr); // function to be called on file click void OpenDialog( // open simple dialog (path and filename are obtained from filePathName) const std::string& vKey, // key dialog @@ -1164,7 +1167,8 @@ namespace IGFD const std::string& vFilePathName, // file path name (will be decompsoed in path and fileName) const int& vCountSelectionMax = 1, // count selection max UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags + ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags + SelectFun vSelectFun = nullptr); // function to be called on file click // with pane void OpenDialog( // open dialog with custom right pane (path and fileName can be specified) @@ -1177,7 +1181,8 @@ namespace IGFD const float& vSidePaneWidth = 250.0f, // side pane width const int& vCountSelectionMax = 1, // count selection max UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags + ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags + SelectFun vSelectFun = nullptr); // function to be called on file click void OpenDialog( // open dialog with custom right pane (path and filename are obtained from filePathName) const std::string& vKey, // key dialog @@ -1188,7 +1193,8 @@ namespace IGFD const float& vSidePaneWidth = 250.0f, // side pane width const int& vCountSelectionMax = 1, // count selection max UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags + ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags + SelectFun vSelectFun = nullptr); // function to be called on file click // modal dialog void OpenModal( // open simple modal (path and fileName can be specified) @@ -1199,7 +1205,8 @@ namespace IGFD const std::string& vFileName, // defaut file name const int& vCountSelectionMax = 1, // count selection max UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags + ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags + SelectFun vSelectFun = nullptr); // function to be called on file click void OpenModal( // open simple modal (path and fielname are obtained from filePathName) const std::string& vKey, // key dialog @@ -1208,7 +1215,8 @@ namespace IGFD const std::string& vFilePathName, // file path name (will be decompsoed in path and fileName) const int& vCountSelectionMax = 1, // count selection max UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags + ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags + SelectFun vSelectFun = nullptr); // function to be called on file click // with pane void OpenModal( // open modal with custom right pane (path and filename are obtained from filePathName) @@ -1221,7 +1229,8 @@ namespace IGFD const float& vSidePaneWidth = 250.0f, // side pane width const int& vCountSelectionMax = 1, // count selection max UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags + ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags + SelectFun vSelectFun = nullptr); // function to be called on file click void OpenModal( // open modal with custom right pane (path and fielname are obtained from filePathName) const std::string& vKey, // key dialog @@ -1232,7 +1241,8 @@ namespace IGFD const float& vSidePaneWidth = 250.0f, // side pane width const int& vCountSelectionMax = 1, // count selection max UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags + ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags + SelectFun vSelectFun = nullptr); // function to be called on file click // Display / Close dialog form bool Display( // Display the dialog. return true if a result was obtained (Ok or not) diff --git a/extern/imgui_patched/imgui_impl_sdl.cpp b/extern/imgui_patched/imgui_impl_sdl.cpp index 2870d2170..c1b2649ca 100644 --- a/extern/imgui_patched/imgui_impl_sdl.cpp +++ b/extern/imgui_patched/imgui_impl_sdl.cpp @@ -300,6 +300,9 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event) { float wheel_x = (event->wheel.x > 0) ? 1.0f : (event->wheel.x < 0) ? -1.0f : 0.0f; float wheel_y = (event->wheel.y > 0) ? 1.0f : (event->wheel.y < 0) ? -1.0f : 0.0f; +#ifdef __APPLE__ + wheel_x = -wheel_x; +#endif io.AddMouseWheelEvent(wheel_x, wheel_y); return true; } diff --git a/extern/opl/.github/FUNDING.yml b/extern/opl/.github/FUNDING.yml new file mode 100644 index 000000000..613b2dd88 --- /dev/null +++ b/extern/opl/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: nukeykt # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: ['https://www.buymeacoffee.com/nukeykt', 'https://paypal.me/nukeykt'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/extern/opl/LICENSE b/extern/opl/LICENSE new file mode 100644 index 000000000..8000a6faa --- /dev/null +++ b/extern/opl/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random + Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/extern/opl/MODIFIED.md b/extern/opl/MODIFIED.md new file mode 100644 index 000000000..78341ad48 --- /dev/null +++ b/extern/opl/MODIFIED.md @@ -0,0 +1,4 @@ +# modification disclaimer + +this is a modified version of Nuked-OPL3 which implements channel muting in the core. +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 new file mode 100644 index 000000000..b0a090c5f --- /dev/null +++ b/extern/opl/opl3.c @@ -0,0 +1,1476 @@ +/* Nuked OPL3 + * Copyright (C) 2013-2020 Nuke.YKT + * + * This file is part of Nuked OPL3. + * + * Nuked OPL3 is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 + * of the License, or (at your option) any later version. + * + * Nuked OPL3 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Nuked OPL3. If not, see . + + * Nuked OPL3 emulator. + * Thanks: + * MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): + * Feedback and Rhythm part calculation information. + * forums.submarine.org.uk(carbon14, opl3): + * Tremolo and phase generator calculation information. + * OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): + * OPL2 ROMs. + * siliconpr0n.org(John McMaster, digshadow): + * YMF262 and VRC VII decaps and die shots. + * + * version: 1.8 + */ + +#include +#include +#include +#include "opl3.h" + +#if OPL_ENABLE_STEREOEXT && !defined OPL_SIN +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES 1 +#endif +#include +/* input: [0, 256), output: [0, 65536] */ +#define OPL_SIN(x) ((int32_t)(sin((x) * M_PI / 512.0) * 65536.0)) +#endif + +/* Quirk: Some FM channels are output one sample later on the left side than the right. */ +#ifndef OPL_QUIRK_CHANNELSAMPLEDELAY +#define OPL_QUIRK_CHANNELSAMPLEDELAY (!OPL_ENABLE_STEREOEXT) +#endif + +#define RSM_FRAC 10 + +/* Channel types */ + +enum { + ch_2op = 0, + ch_4op = 1, + ch_4op2 = 2, + ch_drum = 3 +}; + +/* Envelope key types */ + +enum { + egk_norm = 0x01, + egk_drum = 0x02 +}; + + +/* + logsin table +*/ + +static const uint16_t logsinrom[256] = { + 0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471, + 0x443, 0x41a, 0x3f5, 0x3d3, 0x3b5, 0x398, 0x37e, 0x365, + 0x34e, 0x339, 0x324, 0x311, 0x2ff, 0x2ed, 0x2dc, 0x2cd, + 0x2bd, 0x2af, 0x2a0, 0x293, 0x286, 0x279, 0x26d, 0x261, + 0x256, 0x24b, 0x240, 0x236, 0x22c, 0x222, 0x218, 0x20f, + 0x206, 0x1fd, 0x1f5, 0x1ec, 0x1e4, 0x1dc, 0x1d4, 0x1cd, + 0x1c5, 0x1be, 0x1b7, 0x1b0, 0x1a9, 0x1a2, 0x19b, 0x195, + 0x18f, 0x188, 0x182, 0x17c, 0x177, 0x171, 0x16b, 0x166, + 0x160, 0x15b, 0x155, 0x150, 0x14b, 0x146, 0x141, 0x13c, + 0x137, 0x133, 0x12e, 0x129, 0x125, 0x121, 0x11c, 0x118, + 0x114, 0x10f, 0x10b, 0x107, 0x103, 0x0ff, 0x0fb, 0x0f8, + 0x0f4, 0x0f0, 0x0ec, 0x0e9, 0x0e5, 0x0e2, 0x0de, 0x0db, + 0x0d7, 0x0d4, 0x0d1, 0x0cd, 0x0ca, 0x0c7, 0x0c4, 0x0c1, + 0x0be, 0x0bb, 0x0b8, 0x0b5, 0x0b2, 0x0af, 0x0ac, 0x0a9, + 0x0a7, 0x0a4, 0x0a1, 0x09f, 0x09c, 0x099, 0x097, 0x094, + 0x092, 0x08f, 0x08d, 0x08a, 0x088, 0x086, 0x083, 0x081, + 0x07f, 0x07d, 0x07a, 0x078, 0x076, 0x074, 0x072, 0x070, + 0x06e, 0x06c, 0x06a, 0x068, 0x066, 0x064, 0x062, 0x060, + 0x05e, 0x05c, 0x05b, 0x059, 0x057, 0x055, 0x053, 0x052, + 0x050, 0x04e, 0x04d, 0x04b, 0x04a, 0x048, 0x046, 0x045, + 0x043, 0x042, 0x040, 0x03f, 0x03e, 0x03c, 0x03b, 0x039, + 0x038, 0x037, 0x035, 0x034, 0x033, 0x031, 0x030, 0x02f, + 0x02e, 0x02d, 0x02b, 0x02a, 0x029, 0x028, 0x027, 0x026, + 0x025, 0x024, 0x023, 0x022, 0x021, 0x020, 0x01f, 0x01e, + 0x01d, 0x01c, 0x01b, 0x01a, 0x019, 0x018, 0x017, 0x017, + 0x016, 0x015, 0x014, 0x014, 0x013, 0x012, 0x011, 0x011, + 0x010, 0x00f, 0x00f, 0x00e, 0x00d, 0x00d, 0x00c, 0x00c, + 0x00b, 0x00a, 0x00a, 0x009, 0x009, 0x008, 0x008, 0x007, + 0x007, 0x007, 0x006, 0x006, 0x005, 0x005, 0x005, 0x004, + 0x004, 0x004, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002, + 0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000 +}; + +/* + exp table +*/ + +static const uint16_t exprom[256] = { + 0x7fa, 0x7f5, 0x7ef, 0x7ea, 0x7e4, 0x7df, 0x7da, 0x7d4, + 0x7cf, 0x7c9, 0x7c4, 0x7bf, 0x7b9, 0x7b4, 0x7ae, 0x7a9, + 0x7a4, 0x79f, 0x799, 0x794, 0x78f, 0x78a, 0x784, 0x77f, + 0x77a, 0x775, 0x770, 0x76a, 0x765, 0x760, 0x75b, 0x756, + 0x751, 0x74c, 0x747, 0x742, 0x73d, 0x738, 0x733, 0x72e, + 0x729, 0x724, 0x71f, 0x71a, 0x715, 0x710, 0x70b, 0x706, + 0x702, 0x6fd, 0x6f8, 0x6f3, 0x6ee, 0x6e9, 0x6e5, 0x6e0, + 0x6db, 0x6d6, 0x6d2, 0x6cd, 0x6c8, 0x6c4, 0x6bf, 0x6ba, + 0x6b5, 0x6b1, 0x6ac, 0x6a8, 0x6a3, 0x69e, 0x69a, 0x695, + 0x691, 0x68c, 0x688, 0x683, 0x67f, 0x67a, 0x676, 0x671, + 0x66d, 0x668, 0x664, 0x65f, 0x65b, 0x657, 0x652, 0x64e, + 0x649, 0x645, 0x641, 0x63c, 0x638, 0x634, 0x630, 0x62b, + 0x627, 0x623, 0x61e, 0x61a, 0x616, 0x612, 0x60e, 0x609, + 0x605, 0x601, 0x5fd, 0x5f9, 0x5f5, 0x5f0, 0x5ec, 0x5e8, + 0x5e4, 0x5e0, 0x5dc, 0x5d8, 0x5d4, 0x5d0, 0x5cc, 0x5c8, + 0x5c4, 0x5c0, 0x5bc, 0x5b8, 0x5b4, 0x5b0, 0x5ac, 0x5a8, + 0x5a4, 0x5a0, 0x59c, 0x599, 0x595, 0x591, 0x58d, 0x589, + 0x585, 0x581, 0x57e, 0x57a, 0x576, 0x572, 0x56f, 0x56b, + 0x567, 0x563, 0x560, 0x55c, 0x558, 0x554, 0x551, 0x54d, + 0x549, 0x546, 0x542, 0x53e, 0x53b, 0x537, 0x534, 0x530, + 0x52c, 0x529, 0x525, 0x522, 0x51e, 0x51b, 0x517, 0x514, + 0x510, 0x50c, 0x509, 0x506, 0x502, 0x4ff, 0x4fb, 0x4f8, + 0x4f4, 0x4f1, 0x4ed, 0x4ea, 0x4e7, 0x4e3, 0x4e0, 0x4dc, + 0x4d9, 0x4d6, 0x4d2, 0x4cf, 0x4cc, 0x4c8, 0x4c5, 0x4c2, + 0x4be, 0x4bb, 0x4b8, 0x4b5, 0x4b1, 0x4ae, 0x4ab, 0x4a8, + 0x4a4, 0x4a1, 0x49e, 0x49b, 0x498, 0x494, 0x491, 0x48e, + 0x48b, 0x488, 0x485, 0x482, 0x47e, 0x47b, 0x478, 0x475, + 0x472, 0x46f, 0x46c, 0x469, 0x466, 0x463, 0x460, 0x45d, + 0x45a, 0x457, 0x454, 0x451, 0x44e, 0x44b, 0x448, 0x445, + 0x442, 0x43f, 0x43c, 0x439, 0x436, 0x433, 0x430, 0x42d, + 0x42a, 0x428, 0x425, 0x422, 0x41f, 0x41c, 0x419, 0x416, + 0x414, 0x411, 0x40e, 0x40b, 0x408, 0x406, 0x403, 0x400 +}; + +/* + freq mult table multiplied by 2 + + 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 12, 12, 15, 15 +*/ + +static const uint8_t mt[16] = { + 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30 +}; + +/* + ksl table +*/ + +static const uint8_t kslrom[16] = { + 0, 32, 40, 45, 48, 51, 53, 55, 56, 58, 59, 60, 61, 62, 63, 64 +}; + +static const uint8_t kslshift[4] = { + 8, 1, 2, 0 +}; + +/* + envelope generator constants +*/ + +static const uint8_t eg_incstep[4][4] = { + { 0, 0, 0, 0 }, + { 1, 0, 0, 0 }, + { 1, 0, 1, 0 }, + { 1, 1, 1, 0 } +}; + +/* + address decoding +*/ + +static const int8_t ad_slot[0x20] = { + 0, 1, 2, 3, 4, 5, -1, -1, 6, 7, 8, 9, 10, 11, -1, -1, + 12, 13, 14, 15, 16, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +static const uint8_t ch_slot[18] = { + 0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32 +}; + +#if OPL_ENABLE_STEREOEXT +/* + stereo extension panning table +*/ + +static int32_t panpot_lut[256]; +static uint8_t panpot_lut_build = 0; +#endif + +/* + Envelope generator +*/ + +typedef int16_t(*envelope_sinfunc)(uint16_t phase, uint16_t envelope); +typedef void(*envelope_genfunc)(opl3_slot *slott); + +static int16_t OPL3_EnvelopeCalcExp(uint32_t level) +{ + if (level > 0x1fff) + { + level = 0x1fff; + } + return (exprom[level & 0xff] << 1) >> (level >> 8); +} + +static int16_t OPL3_EnvelopeCalcSin0(uint16_t phase, uint16_t envelope) +{ + uint16_t out = 0; + uint16_t neg = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + neg = 0xffff; + } + if (phase & 0x100) + { + out = logsinrom[(phase & 0xff) ^ 0xff]; + } + else + { + out = logsinrom[phase & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)) ^ neg; +} + +static int16_t OPL3_EnvelopeCalcSin1(uint16_t phase, uint16_t envelope) +{ + uint16_t out = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + out = 0x1000; + } + else if (phase & 0x100) + { + out = logsinrom[(phase & 0xff) ^ 0xff]; + } + else + { + out = logsinrom[phase & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)); +} + +static int16_t OPL3_EnvelopeCalcSin2(uint16_t phase, uint16_t envelope) +{ + uint16_t out = 0; + phase &= 0x3ff; + if (phase & 0x100) + { + out = logsinrom[(phase & 0xff) ^ 0xff]; + } + else + { + out = logsinrom[phase & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)); +} + +static int16_t OPL3_EnvelopeCalcSin3(uint16_t phase, uint16_t envelope) +{ + uint16_t out = 0; + phase &= 0x3ff; + if (phase & 0x100) + { + out = 0x1000; + } + else + { + out = logsinrom[phase & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)); +} + +static int16_t OPL3_EnvelopeCalcSin4(uint16_t phase, uint16_t envelope) +{ + uint16_t out = 0; + uint16_t neg = 0; + phase &= 0x3ff; + if ((phase & 0x300) == 0x100) + { + neg = 0xffff; + } + if (phase & 0x200) + { + out = 0x1000; + } + else if (phase & 0x80) + { + out = logsinrom[((phase ^ 0xff) << 1) & 0xff]; + } + else + { + out = logsinrom[(phase << 1) & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)) ^ neg; +} + +static int16_t OPL3_EnvelopeCalcSin5(uint16_t phase, uint16_t envelope) +{ + uint16_t out = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + out = 0x1000; + } + else if (phase & 0x80) + { + out = logsinrom[((phase ^ 0xff) << 1) & 0xff]; + } + else + { + out = logsinrom[(phase << 1) & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)); +} + +static int16_t OPL3_EnvelopeCalcSin6(uint16_t phase, uint16_t envelope) +{ + uint16_t neg = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + neg = 0xffff; + } + return OPL3_EnvelopeCalcExp(envelope << 3) ^ neg; +} + +static int16_t OPL3_EnvelopeCalcSin7(uint16_t phase, uint16_t envelope) +{ + uint16_t out = 0; + uint16_t neg = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + neg = 0xffff; + phase = (phase & 0x1ff) ^ 0x1ff; + } + out = phase << 3; + return OPL3_EnvelopeCalcExp(out + (envelope << 3)) ^ neg; +} + +static const envelope_sinfunc envelope_sin[8] = { + OPL3_EnvelopeCalcSin0, + OPL3_EnvelopeCalcSin1, + OPL3_EnvelopeCalcSin2, + OPL3_EnvelopeCalcSin3, + OPL3_EnvelopeCalcSin4, + OPL3_EnvelopeCalcSin5, + OPL3_EnvelopeCalcSin6, + OPL3_EnvelopeCalcSin7 +}; + +enum envelope_gen_num +{ + envelope_gen_num_attack = 0, + envelope_gen_num_decay = 1, + envelope_gen_num_sustain = 2, + envelope_gen_num_release = 3 +}; + +static void OPL3_EnvelopeUpdateKSL(opl3_slot *slot) +{ + int16_t ksl = (kslrom[slot->channel->f_num >> 6] << 2) + - ((0x08 - slot->channel->block) << 5); + if (ksl < 0) + { + ksl = 0; + } + slot->eg_ksl = (uint8_t)ksl; +} + +static void OPL3_EnvelopeCalc(opl3_slot *slot) +{ + uint8_t nonzero; + uint8_t rate; + uint8_t rate_hi; + uint8_t rate_lo; + uint8_t reg_rate = 0; + uint8_t ks; + uint8_t eg_shift, shift; + uint16_t eg_rout; + int16_t eg_inc; + uint8_t eg_off; + uint8_t reset = 0; + slot->eg_out = slot->eg_rout + (slot->reg_tl << 2) + + (slot->eg_ksl >> kslshift[slot->reg_ksl]) + *slot->trem; + if (slot->key && slot->eg_gen == envelope_gen_num_release) + { + reset = 1; + reg_rate = slot->reg_ar; + } + else + { + switch (slot->eg_gen) + { + case envelope_gen_num_attack: + reg_rate = slot->reg_ar; + break; + case envelope_gen_num_decay: + reg_rate = slot->reg_dr; + break; + case envelope_gen_num_sustain: + if (!slot->reg_type) + { + reg_rate = slot->reg_rr; + } + break; + case envelope_gen_num_release: + reg_rate = slot->reg_rr; + break; + } + } + slot->pg_reset = reset; + ks = slot->channel->ksv >> ((slot->reg_ksr ^ 1) << 1); + nonzero = (reg_rate != 0); + rate = ks + (reg_rate << 2); + rate_hi = rate >> 2; + rate_lo = rate & 0x03; + if (rate_hi & 0x10) + { + rate_hi = 0x0f; + } + eg_shift = rate_hi + slot->chip->eg_add; + shift = 0; + if (nonzero) + { + if (rate_hi < 12) + { + if (slot->chip->eg_state) + { + switch (eg_shift) + { + case 12: + shift = 1; + break; + case 13: + shift = (rate_lo >> 1) & 0x01; + break; + case 14: + shift = rate_lo & 0x01; + break; + default: + break; + } + } + } + else + { + shift = (rate_hi & 0x03) + eg_incstep[rate_lo][slot->chip->timer & 0x03]; + if (shift & 0x04) + { + shift = 0x03; + } + if (!shift) + { + shift = slot->chip->eg_state; + } + } + } + eg_rout = slot->eg_rout; + eg_inc = 0; + eg_off = 0; + /* Instant attack */ + if (reset && rate_hi == 0x0f) + { + eg_rout = 0x00; + } + /* Envelope off */ + if ((slot->eg_rout & 0x1f8) == 0x1f8) + { + eg_off = 1; + } + if (slot->eg_gen != envelope_gen_num_attack && !reset && eg_off) + { + eg_rout = 0x1ff; + } + switch (slot->eg_gen) + { + case envelope_gen_num_attack: + if (!slot->eg_rout) + { + slot->eg_gen = envelope_gen_num_decay; + } + else if (slot->key && shift > 0 && rate_hi != 0x0f) + { + eg_inc = ~slot->eg_rout >> (4 - shift); + } + break; + case envelope_gen_num_decay: + if ((slot->eg_rout >> 4) == slot->reg_sl) + { + slot->eg_gen = envelope_gen_num_sustain; + } + else if (!eg_off && !reset && shift > 0) + { + eg_inc = 1 << (shift - 1); + } + break; + case envelope_gen_num_sustain: + case envelope_gen_num_release: + if (!eg_off && !reset && shift > 0) + { + eg_inc = 1 << (shift - 1); + } + break; + } + slot->eg_rout = (eg_rout + eg_inc) & 0x1ff; + /* Key off */ + if (reset) + { + slot->eg_gen = envelope_gen_num_attack; + } + if (!slot->key) + { + slot->eg_gen = envelope_gen_num_release; + } +} + +static void OPL3_EnvelopeKeyOn(opl3_slot *slot, uint8_t type) +{ + slot->key |= type; +} + +static void OPL3_EnvelopeKeyOff(opl3_slot *slot, uint8_t type) +{ + slot->key &= ~type; +} + +/* + Phase Generator +*/ + +static void OPL3_PhaseGenerate(opl3_slot *slot) +{ + opl3_chip *chip; + uint16_t f_num; + uint32_t basefreq; + uint8_t rm_xor, n_bit; + uint32_t noise; + uint16_t phase; + + chip = slot->chip; + f_num = slot->channel->f_num; + if (slot->reg_vib) + { + int8_t range; + uint8_t vibpos; + + range = (f_num >> 7) & 7; + vibpos = slot->chip->vibpos; + + if (!(vibpos & 3)) + { + range = 0; + } + else if (vibpos & 1) + { + range >>= 1; + } + range >>= slot->chip->vibshift; + + if (vibpos & 4) + { + range = -range; + } + f_num += range; + } + basefreq = (f_num << slot->channel->block) >> 1; + phase = (uint16_t)(slot->pg_phase >> 9); + if (slot->pg_reset) + { + slot->pg_phase = 0; + } + slot->pg_phase += (basefreq * mt[slot->reg_mult]) >> 1; + /* Rhythm mode */ + noise = chip->noise; + slot->pg_phase_out = phase; + if (slot->slot_num == 13) /* hh */ + { + chip->rm_hh_bit2 = (phase >> 2) & 1; + chip->rm_hh_bit3 = (phase >> 3) & 1; + chip->rm_hh_bit7 = (phase >> 7) & 1; + chip->rm_hh_bit8 = (phase >> 8) & 1; + } + if (slot->slot_num == 17 && (chip->rhy & 0x20)) /* tc */ + { + chip->rm_tc_bit3 = (phase >> 3) & 1; + chip->rm_tc_bit5 = (phase >> 5) & 1; + } + if (chip->rhy & 0x20) + { + rm_xor = (chip->rm_hh_bit2 ^ chip->rm_hh_bit7) + | (chip->rm_hh_bit3 ^ chip->rm_tc_bit5) + | (chip->rm_tc_bit3 ^ chip->rm_tc_bit5); + switch (slot->slot_num) + { + case 13: /* hh */ + slot->pg_phase_out = rm_xor << 9; + if (rm_xor ^ (noise & 1)) + { + slot->pg_phase_out |= 0xd0; + } + else + { + slot->pg_phase_out |= 0x34; + } + break; + case 16: /* sd */ + slot->pg_phase_out = (chip->rm_hh_bit8 << 9) + | ((chip->rm_hh_bit8 ^ (noise & 1)) << 8); + break; + case 17: /* tc */ + slot->pg_phase_out = (rm_xor << 9) | 0x80; + break; + default: + break; + } + } + n_bit = ((noise >> 14) ^ noise) & 0x01; + chip->noise = (noise >> 1) | (n_bit << 22); +} + +/* + Slot +*/ + +static void OPL3_SlotWrite20(opl3_slot *slot, uint8_t data) +{ + if ((data >> 7) & 0x01) + { + slot->trem = &slot->chip->tremolo; + } + else + { + slot->trem = (uint8_t*)&slot->chip->zeromod; + } + slot->reg_vib = (data >> 6) & 0x01; + slot->reg_type = (data >> 5) & 0x01; + slot->reg_ksr = (data >> 4) & 0x01; + slot->reg_mult = data & 0x0f; +} + +static void OPL3_SlotWrite40(opl3_slot *slot, uint8_t data) +{ + slot->reg_ksl = (data >> 6) & 0x03; + slot->reg_tl = data & 0x3f; + OPL3_EnvelopeUpdateKSL(slot); +} + +static void OPL3_SlotWrite60(opl3_slot *slot, uint8_t data) +{ + slot->reg_ar = (data >> 4) & 0x0f; + slot->reg_dr = data & 0x0f; +} + +static void OPL3_SlotWrite80(opl3_slot *slot, uint8_t data) +{ + slot->reg_sl = (data >> 4) & 0x0f; + if (slot->reg_sl == 0x0f) + { + slot->reg_sl = 0x1f; + } + slot->reg_rr = data & 0x0f; +} + +static void OPL3_SlotWriteE0(opl3_slot *slot, uint8_t data) +{ + slot->reg_wf = data & 0x07; + if (slot->chip->newm == 0x00) + { + slot->reg_wf &= 0x03; + } +} + +static void OPL3_SlotGenerate(opl3_slot *slot) +{ + slot->out = envelope_sin[slot->reg_wf](slot->pg_phase_out + *slot->mod, slot->eg_out); +} + +static void OPL3_SlotCalcFB(opl3_slot *slot) +{ + if (slot->channel->fb != 0x00) + { + slot->fbmod = (slot->prout + slot->out) >> (0x09 - slot->channel->fb); + } + else + { + slot->fbmod = 0; + } + slot->prout = slot->out; +} + +/* + Channel +*/ + +static void OPL3_ChannelSetupAlg(opl3_channel *channel); + +static void OPL3_ChannelUpdateRhythm(opl3_chip *chip, uint8_t data) +{ + opl3_channel *channel6; + opl3_channel *channel7; + opl3_channel *channel8; + uint8_t chnum; + + chip->rhy = data & 0x3f; + if (chip->rhy & 0x20) + { + channel6 = &chip->channel[6]; + channel7 = &chip->channel[7]; + channel8 = &chip->channel[8]; + channel6->out[0] = &channel6->slots[1]->out; + channel6->out[1] = &channel6->slots[1]->out; + channel6->out[2] = &chip->zeromod; + channel6->out[3] = &chip->zeromod; + channel7->out[0] = &channel7->slots[0]->out; + channel7->out[1] = &channel7->slots[0]->out; + channel7->out[2] = &channel7->slots[1]->out; + channel7->out[3] = &channel7->slots[1]->out; + channel8->out[0] = &channel8->slots[0]->out; + channel8->out[1] = &channel8->slots[0]->out; + channel8->out[2] = &channel8->slots[1]->out; + channel8->out[3] = &channel8->slots[1]->out; + for (chnum = 6; chnum < 9; chnum++) + { + chip->channel[chnum].chtype = ch_drum; + } + OPL3_ChannelSetupAlg(channel6); + OPL3_ChannelSetupAlg(channel7); + OPL3_ChannelSetupAlg(channel8); + /* hh */ + if (chip->rhy & 0x01) + { + OPL3_EnvelopeKeyOn(channel7->slots[0], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel7->slots[0], egk_drum); + } + /* tc */ + if (chip->rhy & 0x02) + { + OPL3_EnvelopeKeyOn(channel8->slots[1], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel8->slots[1], egk_drum); + } + /* tom */ + if (chip->rhy & 0x04) + { + OPL3_EnvelopeKeyOn(channel8->slots[0], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel8->slots[0], egk_drum); + } + /* sd */ + if (chip->rhy & 0x08) + { + OPL3_EnvelopeKeyOn(channel7->slots[1], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel7->slots[1], egk_drum); + } + /* bd */ + if (chip->rhy & 0x10) + { + OPL3_EnvelopeKeyOn(channel6->slots[0], egk_drum); + OPL3_EnvelopeKeyOn(channel6->slots[1], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel6->slots[0], egk_drum); + OPL3_EnvelopeKeyOff(channel6->slots[1], egk_drum); + } + } + else + { + for (chnum = 6; chnum < 9; chnum++) + { + chip->channel[chnum].chtype = ch_2op; + OPL3_ChannelSetupAlg(&chip->channel[chnum]); + OPL3_EnvelopeKeyOff(chip->channel[chnum].slots[0], egk_drum); + OPL3_EnvelopeKeyOff(chip->channel[chnum].slots[1], egk_drum); + } + } +} + +static void OPL3_ChannelWriteA0(opl3_channel *channel, uint8_t data) +{ + if (channel->chip->newm && channel->chtype == ch_4op2) + { + return; + } + channel->f_num = (channel->f_num & 0x300) | data; + channel->ksv = (channel->block << 1) + | ((channel->f_num >> (0x09 - channel->chip->nts)) & 0x01); + OPL3_EnvelopeUpdateKSL(channel->slots[0]); + OPL3_EnvelopeUpdateKSL(channel->slots[1]); + if (channel->chip->newm && channel->chtype == ch_4op) + { + channel->pair->f_num = channel->f_num; + channel->pair->ksv = channel->ksv; + OPL3_EnvelopeUpdateKSL(channel->pair->slots[0]); + OPL3_EnvelopeUpdateKSL(channel->pair->slots[1]); + } +} + +static void OPL3_ChannelWriteB0(opl3_channel *channel, uint8_t data) +{ + if (channel->chip->newm && channel->chtype == ch_4op2) + { + return; + } + channel->f_num = (channel->f_num & 0xff) | ((data & 0x03) << 8); + channel->block = (data >> 2) & 0x07; + channel->ksv = (channel->block << 1) + | ((channel->f_num >> (0x09 - channel->chip->nts)) & 0x01); + OPL3_EnvelopeUpdateKSL(channel->slots[0]); + OPL3_EnvelopeUpdateKSL(channel->slots[1]); + if (channel->chip->newm && channel->chtype == ch_4op) + { + channel->pair->f_num = channel->f_num; + channel->pair->block = channel->block; + channel->pair->ksv = channel->ksv; + OPL3_EnvelopeUpdateKSL(channel->pair->slots[0]); + OPL3_EnvelopeUpdateKSL(channel->pair->slots[1]); + } +} + +static void OPL3_ChannelSetupAlg(opl3_channel *channel) +{ + if (channel->chtype == ch_drum) + { + if (channel->ch_num == 7 || channel->ch_num == 8) + { + channel->slots[0]->mod = &channel->chip->zeromod; + channel->slots[1]->mod = &channel->chip->zeromod; + return; + } + switch (channel->alg & 0x01) + { + case 0x00: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->slots[0]->out; + break; + case 0x01: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->chip->zeromod; + break; + } + return; + } + if (channel->alg & 0x08) + { + return; + } + if (channel->alg & 0x04) + { + channel->pair->out[0] = &channel->chip->zeromod; + channel->pair->out[1] = &channel->chip->zeromod; + channel->pair->out[2] = &channel->chip->zeromod; + channel->pair->out[3] = &channel->chip->zeromod; + switch (channel->alg & 0x03) + { + case 0x00: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->pair->slots[0]->out; + channel->slots[0]->mod = &channel->pair->slots[1]->out; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->slots[1]->out; + channel->out[1] = &channel->chip->zeromod; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x01: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->pair->slots[0]->out; + channel->slots[0]->mod = &channel->chip->zeromod; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->pair->slots[1]->out; + channel->out[1] = &channel->slots[1]->out; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x02: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->chip->zeromod; + channel->slots[0]->mod = &channel->pair->slots[1]->out; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->pair->slots[0]->out; + channel->out[1] = &channel->slots[1]->out; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x03: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->chip->zeromod; + channel->slots[0]->mod = &channel->pair->slots[1]->out; + channel->slots[1]->mod = &channel->chip->zeromod; + channel->out[0] = &channel->pair->slots[0]->out; + channel->out[1] = &channel->slots[0]->out; + channel->out[2] = &channel->slots[1]->out; + channel->out[3] = &channel->chip->zeromod; + break; + } + } + else + { + switch (channel->alg & 0x01) + { + case 0x00: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->slots[1]->out; + channel->out[1] = &channel->chip->zeromod; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x01: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->chip->zeromod; + channel->out[0] = &channel->slots[0]->out; + channel->out[1] = &channel->slots[1]->out; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + } + } +} + +static void OPL3_ChannelWriteC0(opl3_channel *channel, uint8_t data) +{ + channel->fb = (data & 0x0e) >> 1; + channel->con = data & 0x01; + channel->alg = channel->con; + if (channel->chip->newm) + { + if (channel->chtype == ch_4op) + { + channel->pair->alg = 0x04 | (channel->con << 1) | (channel->pair->con); + channel->alg = 0x08; + OPL3_ChannelSetupAlg(channel->pair); + } + else if (channel->chtype == ch_4op2) + { + channel->alg = 0x04 | (channel->pair->con << 1) | (channel->con); + channel->pair->alg = 0x08; + OPL3_ChannelSetupAlg(channel); + } + else + { + OPL3_ChannelSetupAlg(channel); + } + } + else + { + OPL3_ChannelSetupAlg(channel); + } + if (channel->chip->newm) + { + channel->cha = ((data >> 4) & 0x01) ? ~0 : 0; + channel->chb = ((data >> 5) & 0x01) ? ~0 : 0; + } + else + { + channel->cha = channel->chb = (uint16_t)~0; + } +#if OPL_ENABLE_STEREOEXT + if (!channel->chip->stereoext) + { + channel->leftpan = channel->cha << 16; + channel->rightpan = channel->chb << 16; + } +#endif +} + +#if OPL_ENABLE_STEREOEXT +static void OPL3_ChannelWriteD0(opl3_channel* channel, uint8_t data) +{ + if (channel->chip->stereoext) + { + channel->leftpan = panpot_lut[data ^ 0xff]; + channel->rightpan = panpot_lut[data]; + } +} +#endif + +static void OPL3_ChannelKeyOn(opl3_channel *channel) +{ + if (channel->chip->newm) + { + if (channel->chtype == ch_4op) + { + OPL3_EnvelopeKeyOn(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOn(channel->slots[1], egk_norm); + OPL3_EnvelopeKeyOn(channel->pair->slots[0], egk_norm); + OPL3_EnvelopeKeyOn(channel->pair->slots[1], egk_norm); + } + else if (channel->chtype == ch_2op || channel->chtype == ch_drum) + { + OPL3_EnvelopeKeyOn(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOn(channel->slots[1], egk_norm); + } + } + else + { + OPL3_EnvelopeKeyOn(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOn(channel->slots[1], egk_norm); + } +} + +static void OPL3_ChannelKeyOff(opl3_channel *channel) +{ + if (channel->chip->newm) + { + if (channel->chtype == ch_4op) + { + OPL3_EnvelopeKeyOff(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOff(channel->slots[1], egk_norm); + OPL3_EnvelopeKeyOff(channel->pair->slots[0], egk_norm); + OPL3_EnvelopeKeyOff(channel->pair->slots[1], egk_norm); + } + else if (channel->chtype == ch_2op || channel->chtype == ch_drum) + { + OPL3_EnvelopeKeyOff(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOff(channel->slots[1], egk_norm); + } + } + else + { + OPL3_EnvelopeKeyOff(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOff(channel->slots[1], egk_norm); + } +} + +static void OPL3_ChannelSet4Op(opl3_chip *chip, uint8_t data) +{ + uint8_t bit; + uint8_t chnum; + for (bit = 0; bit < 6; bit++) + { + chnum = bit; + if (bit >= 3) + { + chnum += 9 - 3; + } + if ((data >> bit) & 0x01) + { + chip->channel[chnum].chtype = ch_4op; + chip->channel[chnum + 3].chtype = ch_4op2; + } + else + { + chip->channel[chnum].chtype = ch_2op; + chip->channel[chnum + 3].chtype = ch_2op; + } + } +} + +static int16_t OPL3_ClipSample(int32_t sample) +{ + if (sample > 32767) + { + sample = 32767; + } + else if (sample < -32768) + { + sample = -32768; + } + return (int16_t)sample; +} + +static void OPL3_ProcessSlot(opl3_slot *slot) +{ + OPL3_SlotCalcFB(slot); + OPL3_EnvelopeCalc(slot); + OPL3_PhaseGenerate(slot); + OPL3_SlotGenerate(slot); +} + +void OPL3_Generate(opl3_chip *chip, int16_t *buf) +{ + opl3_channel *channel; + opl3_writebuf *writebuf; + int16_t **out; + int32_t mix; + uint8_t ii; + int16_t accm; + uint8_t shift = 0; + + buf[1] = OPL3_ClipSample(chip->mixbuff[1]); + +#if OPL_QUIRK_CHANNELSAMPLEDELAY + for (ii = 0; ii < 15; ii++) +#else + for (ii = 0; ii < 36; ii++) +#endif + { + OPL3_ProcessSlot(&chip->slot[ii]); + } + + mix = 0; + for (ii = 0; ii < 18; ii++) + { + channel = &chip->channel[ii]; + if (channel->muted) continue; + out = channel->out; + accm = *out[0] + *out[1] + *out[2] + *out[3]; +#if OPL_ENABLE_STEREOEXT + mix += (int16_t)((accm * channel->leftpan) >> 16); +#else + mix += (int16_t)(accm & channel->cha); +#endif + } + chip->mixbuff[0] = mix; + +#if OPL_QUIRK_CHANNELSAMPLEDELAY + for (ii = 15; ii < 18; ii++) + { + OPL3_ProcessSlot(&chip->slot[ii]); + } +#endif + + buf[0] = OPL3_ClipSample(chip->mixbuff[0]); + +#if OPL_QUIRK_CHANNELSAMPLEDELAY + for (ii = 18; ii < 33; ii++) + { + OPL3_ProcessSlot(&chip->slot[ii]); + } +#endif + + mix = 0; + for (ii = 0; ii < 18; ii++) + { + channel = &chip->channel[ii]; + if (channel->muted) continue; + out = channel->out; + accm = *out[0] + *out[1] + *out[2] + *out[3]; +#if OPL_ENABLE_STEREOEXT + mix += (int16_t)((accm * channel->rightpan) >> 16); +#else + mix += (int16_t)(accm & channel->chb); +#endif + } + chip->mixbuff[1] = mix; + +#if OPL_QUIRK_CHANNELSAMPLEDELAY + for (ii = 33; ii < 36; ii++) + { + OPL3_ProcessSlot(&chip->slot[ii]); + } +#endif + + if ((chip->timer & 0x3f) == 0x3f) + { + chip->tremolopos = (chip->tremolopos + 1) % 210; + } + if (chip->tremolopos < 105) + { + chip->tremolo = chip->tremolopos >> chip->tremoloshift; + } + else + { + chip->tremolo = (210 - chip->tremolopos) >> chip->tremoloshift; + } + + if ((chip->timer & 0x3ff) == 0x3ff) + { + chip->vibpos = (chip->vibpos + 1) & 7; + } + + chip->timer++; + + chip->eg_add = 0; + if (chip->eg_timer) + { + while (shift < 36 && ((chip->eg_timer >> shift) & 1) == 0) + { + shift++; + } + if (shift > 12) + { + chip->eg_add = 0; + } + else + { + chip->eg_add = shift + 1; + } + } + + if (chip->eg_timerrem || chip->eg_state) + { + if (chip->eg_timer == 0xfffffffff) + { + chip->eg_timer = 0; + chip->eg_timerrem = 1; + } + else + { + chip->eg_timer++; + chip->eg_timerrem = 0; + } + } + + chip->eg_state ^= 1; + + while ((writebuf = &chip->writebuf[chip->writebuf_cur]), writebuf->time <= chip->writebuf_samplecnt) + { + if (!(writebuf->reg & 0x200)) + { + break; + } + writebuf->reg &= 0x1ff; + OPL3_WriteReg(chip, writebuf->reg, writebuf->data); + chip->writebuf_cur = (chip->writebuf_cur + 1) % OPL_WRITEBUF_SIZE; + } + chip->writebuf_samplecnt++; +} + +void OPL3_GenerateResampled(opl3_chip *chip, int16_t *buf) +{ + while (chip->samplecnt >= chip->rateratio) + { + chip->oldsamples[0] = chip->samples[0]; + chip->oldsamples[1] = chip->samples[1]; + OPL3_Generate(chip, chip->samples); + chip->samplecnt -= chip->rateratio; + } + buf[0] = (int16_t)((chip->oldsamples[0] * (chip->rateratio - chip->samplecnt) + + chip->samples[0] * chip->samplecnt) / chip->rateratio); + buf[1] = (int16_t)((chip->oldsamples[1] * (chip->rateratio - chip->samplecnt) + + chip->samples[1] * chip->samplecnt) / chip->rateratio); + chip->samplecnt += 1 << RSM_FRAC; +} + +void OPL3_Reset(opl3_chip *chip, uint32_t samplerate) +{ + opl3_slot *slot; + opl3_channel *channel; + uint8_t slotnum; + uint8_t channum; + uint8_t local_ch_slot; + + memset(chip, 0, sizeof(opl3_chip)); + for (slotnum = 0; slotnum < 36; slotnum++) + { + slot = &chip->slot[slotnum]; + slot->chip = chip; + slot->mod = &chip->zeromod; + slot->eg_rout = 0x1ff; + slot->eg_out = 0x1ff; + slot->eg_gen = envelope_gen_num_release; + slot->trem = (uint8_t*)&chip->zeromod; + slot->slot_num = slotnum; + } + for (channum = 0; channum < 18; channum++) + { + channel = &chip->channel[channum]; + local_ch_slot = ch_slot[channum]; + channel->slots[0] = &chip->slot[local_ch_slot]; + channel->slots[1] = &chip->slot[local_ch_slot + 3]; + chip->slot[local_ch_slot].channel = channel; + chip->slot[local_ch_slot + 3].channel = channel; + if ((channum % 9) < 3) + { + channel->pair = &chip->channel[channum + 3]; + } + else if ((channum % 9) < 6) + { + channel->pair = &chip->channel[channum - 3]; + } + channel->chip = chip; + channel->out[0] = &chip->zeromod; + channel->out[1] = &chip->zeromod; + channel->out[2] = &chip->zeromod; + channel->out[3] = &chip->zeromod; + channel->muted = 0; + channel->chtype = ch_2op; + channel->cha = 0xffff; + channel->chb = 0xffff; +#if OPL_ENABLE_STEREOEXT + channel->leftpan = 0x10000; + channel->rightpan = 0x10000; +#endif + channel->ch_num = channum; + OPL3_ChannelSetupAlg(channel); + } + chip->noise = 1; + chip->rateratio = (samplerate << RSM_FRAC) / 49716; + chip->tremoloshift = 4; + chip->vibshift = 1; + +#if OPL_ENABLE_STEREOEXT + if (!panpot_lut_build) + { + int32_t i; + for (i = 0; i < 256; i++) + { + panpot_lut[i] = OPL_SIN(i); + } + panpot_lut_build = 1; + } +#endif +} + +void OPL3_WriteReg(opl3_chip *chip, uint16_t reg, uint8_t v) +{ + uint8_t high = (reg >> 8) & 0x01; + uint8_t regm = reg & 0xff; + switch (regm & 0xf0) + { + case 0x00: + if (high) + { + switch (regm & 0x0f) + { + case 0x04: + OPL3_ChannelSet4Op(chip, v); + break; + case 0x05: + chip->newm = v & 0x01; +#if OPL_ENABLE_STEREOEXT + chip->stereoext = (v >> 1) & 0x01; +#endif + break; + } + } + else + { + switch (regm & 0x0f) + { + case 0x08: + chip->nts = (v >> 6) & 0x01; + break; + } + } + break; + case 0x20: + case 0x30: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWrite20(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0x40: + case 0x50: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWrite40(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0x60: + case 0x70: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWrite60(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0x80: + case 0x90: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWrite80(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0xe0: + case 0xf0: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWriteE0(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0xa0: + if ((regm & 0x0f) < 9) + { + OPL3_ChannelWriteA0(&chip->channel[9 * high + (regm & 0x0f)], v); + } + break; + case 0xb0: + if (regm == 0xbd && !high) + { + chip->tremoloshift = (((v >> 7) ^ 1) << 1) + 2; + chip->vibshift = ((v >> 6) & 0x01) ^ 1; + OPL3_ChannelUpdateRhythm(chip, v); + } + else if ((regm & 0x0f) < 9) + { + OPL3_ChannelWriteB0(&chip->channel[9 * high + (regm & 0x0f)], v); + if (v & 0x20) + { + OPL3_ChannelKeyOn(&chip->channel[9 * high + (regm & 0x0f)]); + } + else + { + OPL3_ChannelKeyOff(&chip->channel[9 * high + (regm & 0x0f)]); + } + } + break; + case 0xc0: + if ((regm & 0x0f) < 9) + { + OPL3_ChannelWriteC0(&chip->channel[9 * high + (regm & 0x0f)], v); + } + break; +#if OPL_ENABLE_STEREOEXT + case 0xd0: + if ((regm & 0x0f) < 9) + { + OPL3_ChannelWriteD0(&chip->channel[9 * high + (regm & 0x0f)], v); + } + break; +#endif + } +} + +void OPL3_WriteRegBuffered(opl3_chip *chip, uint16_t reg, uint8_t v) +{ + uint64_t time1, time2; + opl3_writebuf *writebuf; + uint32_t writebuf_last; + + writebuf_last = chip->writebuf_last; + writebuf = &chip->writebuf[writebuf_last]; + + if (writebuf->reg & 0x200) + { + OPL3_WriteReg(chip, writebuf->reg & 0x1ff, writebuf->data); + + chip->writebuf_cur = (writebuf_last + 1) % OPL_WRITEBUF_SIZE; + chip->writebuf_samplecnt = writebuf->time; + } + + writebuf->reg = reg | 0x200; + writebuf->data = v; + time1 = chip->writebuf_lasttime + OPL_WRITEBUF_DELAY; + time2 = chip->writebuf_samplecnt; + + if (time1 < time2) + { + time1 = time2; + } + + writebuf->time = time1; + chip->writebuf_lasttime = time1; + chip->writebuf_last = (writebuf_last + 1) % OPL_WRITEBUF_SIZE; +} + +void OPL3_GenerateStream(opl3_chip *chip, int16_t *sndptr, uint32_t numsamples) +{ + uint32_t i; + + for(i = 0; i < numsamples; i++) + { + OPL3_GenerateResampled(chip, sndptr); + sndptr += 2; + } +} diff --git a/extern/opl/opl3.h b/extern/opl/opl3.h new file mode 100644 index 000000000..6ad33f3e0 --- /dev/null +++ b/extern/opl/opl3.h @@ -0,0 +1,168 @@ +/* Nuked OPL3 + * Copyright (C) 2013-2020 Nuke.YKT + * + * This file is part of Nuked OPL3. + * + * Nuked OPL3 is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 + * of the License, or (at your option) any later version. + * + * Nuked OPL3 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Nuked OPL3. If not, see . + + * Nuked OPL3 emulator. + * Thanks: + * MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): + * Feedback and Rhythm part calculation information. + * forums.submarine.org.uk(carbon14, opl3): + * Tremolo and phase generator calculation information. + * OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): + * OPL2 ROMs. + * siliconpr0n.org(John McMaster, digshadow): + * YMF262 and VRC VII decaps and die shots. + * + * version: 1.8 + */ + +#ifndef OPL_OPL3_H +#define OPL_OPL3_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#ifndef OPL_ENABLE_STEREOEXT +#define OPL_ENABLE_STEREOEXT 0 +#endif + +#define OPL_WRITEBUF_SIZE 1024 +#define OPL_WRITEBUF_DELAY 2 + +typedef struct _opl3_slot opl3_slot; +typedef struct _opl3_channel opl3_channel; +typedef struct _opl3_chip opl3_chip; + +struct _opl3_slot { + opl3_channel *channel; + opl3_chip *chip; + int16_t out; + int16_t fbmod; + int16_t *mod; + int16_t prout; + uint16_t eg_rout; + uint16_t eg_out; + uint8_t eg_inc; + uint8_t eg_gen; + uint8_t eg_rate; + uint8_t eg_ksl; + uint8_t *trem; + uint8_t reg_vib; + uint8_t reg_type; + uint8_t reg_ksr; + uint8_t reg_mult; + uint8_t reg_ksl; + uint8_t reg_tl; + uint8_t reg_ar; + uint8_t reg_dr; + uint8_t reg_sl; + uint8_t reg_rr; + uint8_t reg_wf; + uint8_t key; + uint32_t pg_reset; + uint32_t pg_phase; + uint16_t pg_phase_out; + uint8_t slot_num; +}; + +struct _opl3_channel { + opl3_slot *slots[2]; + opl3_channel *pair; + opl3_chip *chip; + int16_t *out[4]; + +#if OPL_ENABLE_STEREOEXT + int32_t leftpan; + int32_t rightpan; +#endif + + uint8_t chtype; + uint16_t f_num; + uint8_t block; + uint8_t fb; + uint8_t con; + uint8_t alg; + uint8_t ksv; + uint16_t cha, chb; + uint8_t ch_num; + uint8_t muted; +}; + +typedef struct _opl3_writebuf { + uint64_t time; + uint16_t reg; + uint8_t data; +} opl3_writebuf; + +struct _opl3_chip { + opl3_channel channel[18]; + opl3_slot slot[36]; + uint16_t timer; + uint64_t eg_timer; + uint8_t eg_timerrem; + uint8_t eg_state; + uint8_t eg_add; + uint8_t newm; + uint8_t nts; + uint8_t rhy; + uint8_t vibpos; + uint8_t vibshift; + uint8_t tremolo; + uint8_t tremolopos; + uint8_t tremoloshift; + uint32_t noise; + int16_t zeromod; + int32_t mixbuff[2]; + uint8_t rm_hh_bit2; + uint8_t rm_hh_bit3; + uint8_t rm_hh_bit7; + uint8_t rm_hh_bit8; + uint8_t rm_tc_bit3; + uint8_t rm_tc_bit5; + +#if OPL_ENABLE_STEREOEXT + uint8_t stereoext; +#endif + + /* OPL3L */ + int32_t rateratio; + int32_t samplecnt; + int16_t oldsamples[2]; + int16_t samples[2]; + + uint64_t writebuf_samplecnt; + uint32_t writebuf_cur; + uint32_t writebuf_last; + uint64_t writebuf_lasttime; + opl3_writebuf writebuf[OPL_WRITEBUF_SIZE]; +}; + +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_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); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/extern/opm/opm.c b/extern/opm/opm.c index 7a776fc06..79f6414cb 100644 --- a/extern/opm/opm.c +++ b/extern/opm/opm.c @@ -1256,6 +1256,14 @@ static inline void OPM_Mixer(opm_t *chip) } chip->mix[0] += chip->op_mix * chip->op_mixl; chip->mix[1] += chip->op_mix * chip->op_mixr; + + if (slot<8) { + chip->op_chmix[slot&7]=0; + } + chip->op_chmix[slot&7]+=chip->op_mix*(chip->op_mixl|chip->op_mixr); + if (slot>=24) { + chip->ch_out[slot&7]=chip->op_chmix[slot&7]; + } } static inline void OPM_Noise(opm_t *chip) diff --git a/extern/opm/opm.h b/extern/opm/opm.h index fe5a1518b..732f020fe 100644 --- a/extern/opm/opm.h +++ b/extern/opm/opm.h @@ -141,6 +141,7 @@ typedef struct { int16_t op_fb[2]; uint8_t op_mixl; uint8_t op_mixr; + uint16_t op_chmix[8]; // Mixer @@ -161,6 +162,7 @@ typedef struct { uint8_t smp_so; uint8_t smp_sh1; uint8_t smp_sh2; + uint16_t ch_out[8]; // Noise uint32_t noise_lfsr; diff --git a/papers/doc/3-pattern/effects.md b/papers/doc/3-pattern/effects.md index 773d30373..f72be5013 100644 --- a/papers/doc/3-pattern/effects.md +++ b/papers/doc/3-pattern/effects.md @@ -14,6 +14,15 @@ however, effects are continuous, which means you only need to type it once and t - maximum tremolo depth is -60 volume steps. - `08xy`: set panning. `x` is the left channel and `y` is the right one. - not all systems support this effect. +- `80xx`: set panning (linear). this effect behaves more like other trackers: + - `00` is left. + - `80` is center. + - `FF` is right. + - not all systems support this effect. +- `81xx`: set volume of left channel (from `00` to `FF`). + - not all systems support this effect. +- `82xx`: set volume of right channel (from `00` to `FF`). + - not all systems support this effect. - `09xx`: set speed 1. - `0Axy`: volume slide. - if `x` is 0 then this is a slide down. diff --git a/papers/doc/7-systems/amiga.md b/papers/doc/7-systems/amiga.md index 97c1151f6..cbc73f7ae 100644 --- a/papers/doc/7-systems/amiga.md +++ b/papers/doc/7-systems/amiga.md @@ -6,8 +6,10 @@ in this very computer music trackers were born... # effects -- `10xx`: toggle low-pass filter. `0` turns it off and `1` turns it on. +- `10xx`: change wave. + - only works when "Mode" is set to "Wavetable" in the instrument. - `11xx`: toggle amplitude modulation with the next channel. - does not work on the last channel. - `12xx`: toggle period (frequency) modulation with the next channel. - - does not work on the last channel. \ No newline at end of file + - does not work on the last channel. +- `13xx`: toggle low-pass filter. `0` turns it off and `1` turns it on. \ No newline at end of file diff --git a/papers/doc/7-systems/nes.md b/papers/doc/7-systems/nes.md index 3de0e3076..3ccca2647 100644 --- a/papers/doc/7-systems/nes.md +++ b/papers/doc/7-systems/nes.md @@ -2,10 +2,13 @@ the console from Nintendo that plays Super Mario Bros. and helped revive the agonizing video game market in the US during mid-80s. -also known as Famicom. It is a five-channel PSG: first two channels play pulse wave with three different duty cycles, third is a fixed-volume triangle channel, fourth is a noise channel (can work in both pseudo-random and periodic modes) and fifth is a (D)PCM sample channel +also known as Famicom. It is a five-channel PSG: first two channels play pulse wave with three different duty cycles, third is a fixed-volume triangle channel, fourth is a noise channel (can work in both pseudo-random and periodic modes) and fifth is a (D)PCM sample channel. # effects +- `11xx`: write to delta modulation counter. + - this may be used to attenuate the triangle and noise channels. + - will not work if a sample is playing. - `12xx`: set duty cycle or noise mode of channel. - may be 0-3 for the pulse channels and 0-1 for the noise channel. - `13xy`: setup sweep up. @@ -16,3 +19,7 @@ also known as Famicom. It is a five-channel PSG: first two channels play pulse w - `x` is the time. - `y` is the shift. - set to 0 to disable it. +- `18xx`: set PCM channel mode. + - `00`: PCM (software). + - `01`: DPCM (hardware). + - when in DPCM mode, samples will sound muffled (due to its nature), availables pitches are limited and loop point is ignored. \ No newline at end of file diff --git a/papers/doc/7-systems/opl.md b/papers/doc/7-systems/opl.md index b019e3f98..683be51e6 100644 --- a/papers/doc/7-systems/opl.md +++ b/papers/doc/7-systems/opl.md @@ -44,3 +44,37 @@ afterwards everyone moved to Windows and software mixed PCM streaming... - only in 4-op mode (OPL3). - `1Dxx`: set attack of operator 4. - only in 4-op mode (OPL3). +- `2Axy`: set waveform of operator. + - `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators". + - `y` is the value. + - only in OPL2 or higher. +- `30xx`: enable envelope hard reset. + - this works by inserting a quick release and tiny delay before a new note. +- `50xy`: set AM of operator. + - `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators". + - `y` determines whether AM is on. +- `51xy` set SL of operator. + - `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators". + - `y` is the value. +- `52xy` set RR of operator. + - `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators". + - `y` is the value. +- `53xy`: set VIB of operator. + - `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators". + - `y` determines whether VIB is on. +- `54xy` set KSL of operator. + - `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators". + - `y` is the value. +- `55xy` set SUS of operator. + - `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators". + - `y` determines whether SUS is on. +- `56xx`: set DR of all operators. +- `57xx`: set DR of operator 1. +- `58xx`: set DR of operator 2. +- `59xx`: set DR of operator 3. + - only in 4-op mode (OPL3). +- `5Axx`: set DR of operator 4. + - only in 4-op mode (OPL3). +- `5Bxy`: set KSR of operator. + - `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators". + - `y` determines whether KSR is on. \ No newline at end of file diff --git a/papers/doc/7-systems/opll.md b/papers/doc/7-systems/opll.md index 2e92171ba..d0e6475c7 100644 --- a/papers/doc/7-systems/opll.md +++ b/papers/doc/7-systems/opll.md @@ -2,17 +2,17 @@ the YM2413, otherwise known as OPLL, is a cost-reduced FM synthesis sound chip, based on the Yamaha YM3812 (OPL2). thought OPL was downgraded enough? :p -OPLL spawned also a few derivative chips, the best known of these is: -- the myth. the legend. THE VRC7. 6 channels, *rather interesting* instruments sound bank, no drums mode +OPLL also spawned a few derivative chips, the best known of these is: +- the famous Konami VRC7. used in the Japan-only video game Lagrange Point, it was **another** cost reduction on top of the OPLL! this time just 6 channels... - Yamaha YM2423, same chip as YM2413, just a different patch set -- Yamaha YMF281, ditto +- Yamaha YMF281, ditto # technical specifications the YM2413 is equipped with the following features: - 9 channels of 2 operator FM synthesis -- A drum/percussion mode, replacing the last 3 voices with 3 rhythm channels +- A drum/percussion mode, replacing the last 3 voices with 5 rhythm channels - 1 user-definable patch (this patch can be changed throughout the course of the song) - 15 pre-defined patches which can all be used at the same time - Support for ADSR on both the modulator and the carrier @@ -37,3 +37,27 @@ the YM2413 is equipped with the following features: - `19xx`: set attack of all operators. - `1Axx`: set attack of operator 1. - `1Bxx`: set attack of operator 2. +- `50xy`: set AM of operator. + - `x` is the operator (1-2). a value of 0 means "all operators". + - `y` determines whether AM is on. +- `51xy` set SL of operator. + - `x` is the operator (1-2). a value of 0 means "all operators". + - `y` is the value. +- `52xy` set RR of operator. + - `x` is the operator (1-2). a value of 0 means "all operators". + - `y` is the value. +- `53xy`: set VIB of operator. + - `x` is the operator (1-2). a value of 0 means "all operators". + - `y` determines whether VIB is on. +- `54xy` set KSL of operator. + - `x` is the operator (1-2). a value of 0 means "all operators". + - `y` is the value. +- `55xy` set EGT of operator. + - `x` is the operator (1-2). a value of 0 means "all operators". + - `y` determines whether EGT is on. +- `56xx`: set DR of all operators. +- `57xx`: set DR of operator 1. +- `58xx`: set DR of operator 2. +- `5Bxy`: set KSR of operator. + - `x` is the operator (1-2). a value of 0 means "all operators". + - `y` determines whether KSR is on. \ No newline at end of file diff --git a/papers/doc/7-systems/opz.md b/papers/doc/7-systems/opz.md new file mode 100644 index 000000000..61e931394 --- /dev/null +++ b/papers/doc/7-systems/opz.md @@ -0,0 +1,103 @@ +# Yamaha OPZ (YM2414) + +this is the YM2151's little-known successor, used in the Yamaha TX81Z and a few other Yamaha synthesizers. oh, and the Korg Z3 too. + +it adds these features on top of the YM2151: +- 8 waveforms (but they're different from the OPL ones) +- per-channel (possibly) linear volume control separate from TL +- increased multiplier precision (in 1/16ths) +- 4-step envelope generator shift (minimum TL) +- another LFO +- no per-operator key on/off +- fixed frequency mode per operator (kind of like OPN family's extended channel mode but with a bit less precision and for all 8 channels) +- "reverb" effect (actually extends release) + +unlike the YM2151, this chip is officially undocumented. very few efforts have been made to study the chip and document it... +therefore emulation of this chip in Furnace is incomplete and uncertain. + +no plans have been made for TX81Z MIDI passthrough, because: +- Furnace works with register writes rather than MIDI commands +- the MIDI protocol is slow (would not be enough). +- the TX81Z is very slow to process a note on/off or parameter change event. +- the TL range has been reduced to 0-99, but the chip goes from 0-127. + +# effects + +- `10xx`: set noise frequency of channel 8 operator 4. 00 disables noise while 01 to 20 enables it. +- `11xx`: set feedback of channel. +- `12xx`: set operator 1 level. +- `13xx`: set operator 2 level. +- `14xx`: set operator 3 level. +- `15xx`: set operator 4 level. +- `16xy`: set multiplier of operator. + - `x` is the operator (1-4). + - `y` is the mutliplier. +- `17xx`: set LFO speed. +- `18xx`: set LFO waveform. `xx` may be one of the following: + - `00`: saw + - `01`: square + - `02`: triangle + - `03`: noise +- `19xx`: set attack of all operators. +- `1Axx`: set attack of operator 1. +- `1Bxx`: set attack of operator 2. +- `1Cxx`: set attack of operator 3. +- `1Dxx`: set attack of operator 4. +- `28xy`: set reverb of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `2Axy`: set waveform of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `2Bxy`: set EG shift of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `2Cxy` set fine multiplier of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `2Fxx`: enable envelope hard reset. + - this works by inserting a quick release and tiny delay before a new note. +- `3xyy`: set fixed frequency of operator 1/2. + - `x` is the block (0-7 for operator 1; 8-F for operator 2). + - `y` is the frequency. fixed frequency mode will be disabled if this is less than 8. + - the actual frequency is: `y*(2^x)`. +- `4xyy`: set fixed frequency of operator 3/4. + - `x` is the block (0-7 for operator 3; 8-F for operator 4). + - `y` is the frequency. fixed frequency mode will be disabled if this is less than 8. + - the actual frequency is: `y*(2^x)`. +- `50xy`: set AM of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` determines whether AM is on. +- `51xy` set SL of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `52xy` set RR of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `53xy` set DT of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value: + - 0: +0 + - 1: +1 + - 2: +2 + - 3: +3 + - 4: -0 + - 5: -3 + - 6: -2 + - 7: -1 +- `54xy` set RS of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `55xy` set DT2 of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `56xx`: set DR of all operators. +- `57xx`: set DR of operator 1. +- `58xx`: set DR of operator 2. +- `59xx`: set DR of operator 3. +- `5Axx`: set DR of operator 4. +- `5Bxx`: set D2R/SR of all operators. +- `5Cxx`: set D2R/SR of operator 1. +- `5Dxx`: set D2R/SR of operator 2. +- `5Exx`: set D2R/SR of operator 3. +- `5Fxx`: set D2R/SR of operator 4. diff --git a/papers/doc/7-systems/qsound.md b/papers/doc/7-systems/qsound.md index 9da79d822..5d6078aa7 100644 --- a/papers/doc/7-systems/qsound.md +++ b/papers/doc/7-systems/qsound.md @@ -12,7 +12,8 @@ There are also 3 ADPCM channels, however they cannot be used in Furnace yet. The # effects -- `08xx`: Set panning. Valid range is 00-20. 00 for full left, 10 for center and 20 for full right. It is also possible to bypass the QSound algorithm by using the range 30-50. -- `10xx`: Set echo feedback level. This effect will apply to all channels. -- `11xx`: Set echo level. -- `3xxx`: Set the length of the echo delay buffer. +- `10xx`: set echo feedback level. + - this effect will apply to all channels. +- `11xx`: set echo level. +- `12xx`: toggle QSound algorithm (on by default). +- `3xxx`: set the length of the echo delay buffer. diff --git a/papers/doc/7-systems/ym2151.md b/papers/doc/7-systems/ym2151.md index 69eba0fb3..2291088e1 100644 --- a/papers/doc/7-systems/ym2151.md +++ b/papers/doc/7-systems/ym2151.md @@ -26,3 +26,41 @@ it also was present on several pinball machines and synthesizers of the era, and - `1Bxx`: set attack of operator 2. - `1Cxx`: set attack of operator 3. - `1Dxx`: set attack of operator 4. +- `30xx`: enable envelope hard reset. + - this works by inserting a quick release and tiny delay before a new note. +- `50xy`: set AM of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` determines whether AM is on. +- `51xy` set SL of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `52xy` set RR of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `53xy` set DT of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value: + - 0: +0 + - 1: +1 + - 2: +2 + - 3: +3 + - 4: -0 + - 5: -3 + - 6: -2 + - 7: -1 +- `54xy` set RS of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `55xy` set DT2 of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `56xx`: set DR of all operators. +- `57xx`: set DR of operator 1. +- `58xx`: set DR of operator 2. +- `59xx`: set DR of operator 3. +- `5Axx`: set DR of operator 4. +- `5Bxx`: set D2R/SR of all operators. +- `5Cxx`: set D2R/SR of operator 1. +- `5Dxx`: set D2R/SR of operator 2. +- `5Exx`: set D2R/SR of operator 3. +- `5Fxx`: set D2R/SR of operator 4. \ No newline at end of file diff --git a/papers/doc/7-systems/ym2610.md b/papers/doc/7-systems/ym2610.md index 4515fde23..ec405efef 100644 --- a/papers/doc/7-systems/ym2610.md +++ b/papers/doc/7-systems/ym2610.md @@ -57,3 +57,43 @@ its soundchip is a 4-in-1: 4ch 4-op FM, YM2149 (AY-3-8910 clone) and 2 different - `x` is the numerator. - `y` is the denominator. - if `x` or `y` are 0 this will disable auto-envelope mode. +- `30xx`: enable envelope hard reset. + - this works by inserting a quick release and tiny delay before a new note. +- `50xy`: set AM of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` determines whether AM is on. +- `51xy` set SL of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `52xy` set RR of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `53xy` set DT of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value: + - 0: +0 + - 1: +1 + - 2: +2 + - 3: +3 + - 4: -0 + - 5: -3 + - 6: -2 + - 7: -1 +- `54xy` set RS of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `55xy` set SSG-EG of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value (0-8). + - values between 0 and 7 set SSG-EG. + - value 8 disables it. +- `56xx`: set DR of all operators. +- `57xx`: set DR of operator 1. +- `58xx`: set DR of operator 2. +- `59xx`: set DR of operator 3. +- `5Axx`: set DR of operator 4. +- `5Bxx`: set D2R/SR of all operators. +- `5Cxx`: set D2R/SR of operator 1. +- `5Dxx`: set D2R/SR of operator 2. +- `5Exx`: set D2R/SR of operator 3. +- `5Fxx`: set D2R/SR of operator 4. \ No newline at end of file diff --git a/papers/doc/7-systems/ym2610b.md b/papers/doc/7-systems/ym2610b.md index e251122cd..17f274559 100644 --- a/papers/doc/7-systems/ym2610b.md +++ b/papers/doc/7-systems/ym2610b.md @@ -56,3 +56,43 @@ it is backward compatible with the original chip. - `x` is the numerator. - `y` is the denominator. - if `x` or `y` are 0 this will disable auto-envelope mode. +- `30xx`: enable envelope hard reset. + - this works by inserting a quick release and tiny delay before a new note. +- `50xy`: set AM of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` determines whether AM is on. +- `51xy` set SL of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `52xy` set RR of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `53xy` set DT of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value: + - 0: +0 + - 1: +1 + - 2: +2 + - 3: +3 + - 4: -0 + - 5: -3 + - 6: -2 + - 7: -1 +- `54xy` set RS of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `55xy` set SSG-EG of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value (0-8). + - values between 0 and 7 set SSG-EG. + - value 8 disables it. +- `56xx`: set DR of all operators. +- `57xx`: set DR of operator 1. +- `58xx`: set DR of operator 2. +- `59xx`: set DR of operator 3. +- `5Axx`: set DR of operator 4. +- `5Bxx`: set D2R/SR of all operators. +- `5Cxx`: set D2R/SR of operator 1. +- `5Dxx`: set D2R/SR of operator 2. +- `5Exx`: set D2R/SR of operator 3. +- `5Fxx`: set D2R/SR of operator 4. \ No newline at end of file diff --git a/papers/doc/7-systems/ym2612.md b/papers/doc/7-systems/ym2612.md index b7dd8a16e..e86dde9da 100644 --- a/papers/doc/7-systems/ym2612.md +++ b/papers/doc/7-systems/ym2612.md @@ -25,3 +25,43 @@ one of two chips that powered the Sega Genesis. It is a six-channel, four-operat - `1Bxx`: set attack of operator 2. - `1Cxx`: set attack of operator 3. - `1Dxx`: set attack of operator 4. +- `30xx`: enable envelope hard reset. + - this works by inserting a quick release and tiny delay before a new note. +- `50xy`: set AM of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` determines whether AM is on. +- `51xy` set SL of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `52xy` set RR of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `53xy` set DT of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value: + - 0: -3 + - 1: -2 + - 2: -1 + - 3: 0 + - 4: 1 + - 5: 2 + - 6: 3 + - 7: -0 +- `54xy` set RS of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `55xy` set SSG-EG of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value (0-8). + - values between 0 and 7 set SSG-EG. + - value 8 disables it. +- `56xx`: set DR of all operators. +- `57xx`: set DR of operator 1. +- `58xx`: set DR of operator 2. +- `59xx`: set DR of operator 3. +- `5Axx`: set DR of operator 4. +- `5Bxx`: set D2R/SR of all operators. +- `5Cxx`: set D2R/SR of operator 1. +- `5Dxx`: set D2R/SR of operator 2. +- `5Exx`: set D2R/SR of operator 3. +- `5Fxx`: set D2R/SR of operator 4. \ No newline at end of file diff --git a/papers/format.md b/papers/format.md index ce1685c63..7ab678002 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,16 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 90: Furnace dev90 +- 89: Furnace dev89 +- 88: Furnace dev88 +- 87: Furnace dev87 +- 86: Furnace dev86 +- 85: Furnace dev85 +- 84: Furnace dev84 +- 83: Furnace dev83 +- 82: Furnace dev82 +- 81: Furnace dev81 - 80: Furnace dev80 - 79: Furnace dev79 - 78: Furnace dev78 @@ -167,7 +177,7 @@ size | description | - 0x8f: OPL (YM3526) - 9 channels | - 0x90: OPL2 (YM3812) - 9 channels | - 0x91: OPL3 (YMF262) - 18 channels - | - 0x92: MultiPCM - 24 channels + | - 0x92: MultiPCM - 28 channels | - 0x93: Intel 8253 (beeper) - 1 channel | - 0x94: POKEY - 4 channels | - 0x95: RF5C68 - 8 channels @@ -190,14 +200,26 @@ size | description | - 0xa6: Neo Geo extended (YM2610) - 17 channels | - 0xa7: OPLL drums (YM2413) - 11 channels | - 0xa8: Atari Lynx - 4 channels - | - 0xa9: SegaPCM (for Deflemask Compatibility) - 5 channels + | - 0xa9: SegaPCM (for DefleMask compatibility) - 5 channels | - 0xaa: MSM6295 - 4 channels | - 0xab: MSM6258 - 1 channel | - 0xac: Commander X16 (VERA) - 17 channels | - 0xad: Bubble System WSG - 2 channels + | - 0xae: OPL4 (YMF278B) - 42 channels + | - 0xaf: OPL4 drums (YMF278B) - 44 channels | - 0xb0: Seta/Allumer X1-010 - 16 channels + | - 0xb1: Ensoniq ES5506 - 32 channels + | - 0xb2: Yamaha Y8950 - 10 channels + | - 0xb3: Yamaha Y8950 drums - 12 channels + | - 0xb4: Konami SCC+ - 5 channels + | - 0xb5: tildearrow Sound Unit - 8 channels + | - 0xb6: OPN extended - 9 channels + | - 0xb7: PC-98 extended - 19 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels + | - 0xfd: Dummy System - 8 channels + | - 0xfe: reserved for development + | - 0xff: reserved for development | - (compound!) means that the system is composed of two or more chips, | and has to be flattened. 32 | sound chip volumes @@ -258,11 +280,30 @@ size | description 1 | buggy portamento after slide (>=72) or reserved 1 | new ins affects envelope (Game Boy) (>=72) or reserved 1 | ExtCh channel state is shared (>=78) or reserved - 25 | reserved + 1 | ignore DAC mode change outside of intended channel (>=83) or reserved + 1 | E1xx and E2xx also take priority over Slide00 (>=83) or reserved + 1 | new Sega PCM (with macros and proper vol/pan) (>=84) or reserved + 1 | weird f-num/block-based chip pitch slides (>=85) or reserved + 1 | SN duty macro always resets phase (>=86) or reserved + 1 | pitch macro is linear (>=90) or reserved + 19 | reserved ``` # instrument +notes: + +- the entire instrument is stored, regardless of instrument type. +- the macro range varies depending on the instrument type. +- "macro open" indicates whether the macro is collapsed or not in the instrument editor. +- FM operator order is: + - 1/3/2/4 (internal order) for OPN, OPM, OPZ and OPL 4-op + - 1/2/?/? (? = unused) for OPL 2-op and OPLL +- meaning of extended macros varies depending on instrument type. +- meaning of panning macros varies depending on instrument type: + - for hard-panned chips (e.g. FM and Game Boy): left panning is 2-bit panning macro (left/right) + - otherwise both left and right panning macros are used + ``` size | description -----|------------------------------------ @@ -271,17 +312,43 @@ size | description 2 | format version (see header) 1 | instrument type | - 0: standard - | - 1: FM + | - 1: FM (OPM/OPN) | - 2: Game Boy | - 3: C64 | - 4: Amiga/sample + | - 5: PC Engine + | - 6: AY-3-8910 + | - 7: AY8930 + | - 8: TIA + | - 9: SAA1099 + | - 10: VIC + | - 11: PET + | - 12: VRC6 + | - 13: OPLL + | - 14: OPL + | - 15: FDS + | - 16: Virtual Boy + | - 17: Namco 163 + | - 18: SCC + | - 19: OPZ + | - 20: POKEY + | - 21: PC Speaker + | - 22: WonderSwan + | - 23: Lynx + | - 24: VERA + | - 25: X1-010 + | - 26: VRC6 (saw) + | - 27: ES5506 + | - 28: MultiPCM + | - 29: SNES + | - 30: Sound Unit 1 | reserved STR | instrument name --- | **FM instrument data** - 1 | alg + 1 | alg (SUS on OPLL) 1 | feedback - 1 | fms - 1 | ams + 1 | fms (DC on OPLL) + 1 | ams (DM on OPLL) 1 | operator count | - this is either 2 or 4, and is ignored on non-OPL systems. | - always read 4 ops regardless of this value. @@ -303,10 +370,12 @@ size | description 1 | dt 1 | d2r 1 | ssgEnv - 1 | dam - 1 | dvb - 1 | egt - 1 | ksl + | - bit 4: on (EG-S on OPLL) + | - bit 0-3: envelope type + 1 | dam (for YMU759 compat; REV on OPZ) + 1 | dvb (for YMU759 compat; FINE on OPZ) + 1 | egt (for YMU759 compat; FixedFreq on OPZ) + 1 | ksl (EGShift on OPZ) 1 | sus 1 | vib 1 | ws @@ -342,7 +411,11 @@ size | description 1 | filter macro is absolute --- | **Amiga instrument data** 2 | initial sample - 14 | reserved + 1 | mode (>=82) or reserved + | - 0: sample + | - 1: wavetable + 1 | wavetable length (-1) (>=82) or reserved + 12 | reserved --- | **standard instrument data** 4 | volume macro length 4 | arp macro length @@ -365,9 +438,11 @@ size | description 1 | reserved (>=17) or duty macro height (>=15) or reserved 1 | reserved (>=17) or wave macro height (>=15) or reserved 4?? | volume macro + | - before version 87, if this is the C64 relative cutoff macro, its values were stored offset by 18. 4?? | arp macro | - before version 31, this macro's values were stored offset by 12. 4?? | duty macro + | - before version 87, if this is the C64 relative duty macro, its values were stored offset by 12. 4?? | wave macro 4?? | pitch macro (>=17) 4?? | extra 1 macro (>=17) @@ -600,6 +675,39 @@ size | description 1 | parameter 2 1 | parameter 3 1 | parameter 4 + --- | **additional macro mode flags** (>=84) + 1 | volume macro mode + 1 | duty macro mode + 1 | wave macro mode + 1 | pitch macro mode + 1 | extra 1 macro mode + 1 | extra 2 macro mode + 1 | extra 3 macro mode + 1 | alg macro mode + 1 | fb macro mode + 1 | fms macro mode + 1 | ams macro mode + 1 | left panning macro mode + 1 | right panning macro mode + 1 | phase reset macro mode + 1 | extra 4 macro mode + 1 | extra 5 macro mode + 1 | extra 6 macro mode + 1 | extra 7 macro mode + 1 | extra 8 macro mode + --- | **extra C64 data** (>=89) + 1 | don't test/gate before new note + --- | **MultiPCM data** (>=93) + 1 | attack rate + 1 | decay 1 rate + 1 | decay level + 1 | decay 2 rate + 1 | release rate + 1 | rate correction + 1 | lfo rate + 1 | vib depth + 1 | am depth + 23 | reserved ``` # wavetable @@ -675,15 +783,18 @@ size | description | - 10: A# | - 11: B | - 12: C (of next octave) + | - this is actually a leftover of the .dmf format. | - 100: note off | - 100: note release | - 100: macro release | - octave | - this is an signed char stored in a short. | - therefore octave value 255 is actually octave -1. + | - yep, another leftover of the .dmf format... | - instrument | - volume - | - effect and effect data... + | - effect and effect data (× effect columns) + | - for note/octave, if both values are 0 then it means empty. | - for instrument, volume, effect and effect data, a value of -1 means empty. STR | pattern name (>=51) ``` diff --git a/src/audio/taAudio.h b/src/audio/taAudio.h index c1ef519dd..818122bf9 100644 --- a/src/audio/taAudio.h +++ b/src/audio/taAudio.h @@ -25,13 +25,13 @@ struct SampleRateChangeEvent { double rate; - SampleRateChangeEvent(double r): + explicit SampleRateChangeEvent(double r): rate(r) {} }; struct BufferSizeChangeEvent { unsigned int bufsize; - BufferSizeChangeEvent(unsigned int bs): + explicit BufferSizeChangeEvent(unsigned int bs): bufsize(bs) {} }; @@ -183,6 +183,7 @@ class TAAudio { inBufs(NULL), outBufs(NULL), audioProcCallback(NULL), + audioProcCallbackUser(NULL), sampleRateChanged(NULL), bufferSizeChanged(NULL), midiIn(NULL), diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 37e36a2bf..bf80c645e 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -21,6 +21,7 @@ #define _DISPATCH_H #include +#include #include #define ONE_SEMITONE 2200 @@ -35,74 +36,95 @@ // names as strings for the commands (and other debug stuff). // // if you miss it, the program will crash or misbehave at some point. +// +// the comments are: (arg1, arg2) -> val +// not all commands have a return value enum DivDispatchCmds { - DIV_CMD_NOTE_ON=0, + DIV_CMD_NOTE_ON=0, // (note) DIV_CMD_NOTE_OFF, DIV_CMD_NOTE_OFF_ENV, DIV_CMD_ENV_RELEASE, - DIV_CMD_INSTRUMENT, - DIV_CMD_VOLUME, - DIV_CMD_GET_VOLUME, - DIV_CMD_GET_VOLMAX, - DIV_CMD_NOTE_PORTA, - DIV_CMD_PITCH, - DIV_CMD_PANNING, - DIV_CMD_LEGATO, - DIV_CMD_PRE_PORTA, - DIV_CMD_PRE_NOTE, // used in C64 + DIV_CMD_INSTRUMENT, // (ins, force) + DIV_CMD_VOLUME, // (vol) + DIV_CMD_GET_VOLUME, // () -> vol + DIV_CMD_GET_VOLMAX, // () -> volMax + DIV_CMD_NOTE_PORTA, // (target, speed) -> 2 if target reached + DIV_CMD_PITCH, // (pitch) + DIV_CMD_PANNING, // (left, right) + DIV_CMD_LEGATO, // (note) + DIV_CMD_PRE_PORTA, // (inPorta, isPortaOrSlide) + DIV_CMD_PRE_NOTE, // used in C64 (note) - DIV_CMD_SAMPLE_MODE, - DIV_CMD_SAMPLE_FREQ, - DIV_CMD_SAMPLE_BANK, - DIV_CMD_SAMPLE_POS, + DIV_CMD_SAMPLE_MODE, // (enabled) + DIV_CMD_SAMPLE_FREQ, // (frequency) + DIV_CMD_SAMPLE_BANK, // (bank) + DIV_CMD_SAMPLE_POS, // (pos) - DIV_CMD_FM_HARD_RESET, - DIV_CMD_FM_LFO, - DIV_CMD_FM_LFO_WAVE, - DIV_CMD_FM_TL, - DIV_CMD_FM_AR, - DIV_CMD_FM_FB, - DIV_CMD_FM_MULT, - DIV_CMD_FM_EXTCH, - DIV_CMD_FM_AM_DEPTH, - DIV_CMD_FM_PM_DEPTH, + DIV_CMD_FM_HARD_RESET, // (enabled) + DIV_CMD_FM_LFO, // (speed) + DIV_CMD_FM_LFO_WAVE, // (waveform) + DIV_CMD_FM_TL, // (op, value) + DIV_CMD_FM_AM, // (op, value) + DIV_CMD_FM_AR, // (op, value) + DIV_CMD_FM_DR, // (op, value) + DIV_CMD_FM_SL, // (op, value) + DIV_CMD_FM_D2R, // (op, value) + DIV_CMD_FM_RR, // (op, value) + DIV_CMD_FM_DT, // (op, value) + DIV_CMD_FM_DT2, // (op, value) + DIV_CMD_FM_RS, // (op, value) + DIV_CMD_FM_KSR, // (op, value) + DIV_CMD_FM_VIB, // (op, value) + DIV_CMD_FM_SUS, // (op, value) + DIV_CMD_FM_WS, // (op, value) + DIV_CMD_FM_SSG, // (op, value) + DIV_CMD_FM_REV, // (op, value) + DIV_CMD_FM_EG_SHIFT, // (op, value) + DIV_CMD_FM_FB, // (value) + DIV_CMD_FM_MULT, // (op, value) + DIV_CMD_FM_FINE, // (op, value) + DIV_CMD_FM_FIXFREQ, // (op, value) + DIV_CMD_FM_EXTCH, // (enabled) + DIV_CMD_FM_AM_DEPTH, // (depth) + DIV_CMD_FM_PM_DEPTH, // (depth) - DIV_CMD_GENESIS_LFO, + DIV_CMD_GENESIS_LFO, // unused? - DIV_CMD_ARCADE_LFO, + DIV_CMD_ARCADE_LFO, // unused? - DIV_CMD_STD_NOISE_FREQ, - DIV_CMD_STD_NOISE_MODE, + DIV_CMD_STD_NOISE_FREQ, // (freq) + DIV_CMD_STD_NOISE_MODE, // (mode) - DIV_CMD_WAVE, + DIV_CMD_WAVE, // (waveform) - DIV_CMD_GB_SWEEP_TIME, - DIV_CMD_GB_SWEEP_DIR, + DIV_CMD_GB_SWEEP_TIME, // (time) + DIV_CMD_GB_SWEEP_DIR, // (direction) - DIV_CMD_PCE_LFO_MODE, - DIV_CMD_PCE_LFO_SPEED, + DIV_CMD_PCE_LFO_MODE, // (mode) + DIV_CMD_PCE_LFO_SPEED, // (speed) - DIV_CMD_NES_SWEEP, + DIV_CMD_NES_SWEEP, // (direction, value) + DIV_CMD_NES_DMC, // (value) - DIV_CMD_C64_CUTOFF, - DIV_CMD_C64_RESONANCE, - DIV_CMD_C64_FILTER_MODE, - DIV_CMD_C64_RESET_TIME, - DIV_CMD_C64_RESET_MASK, - DIV_CMD_C64_FILTER_RESET, - DIV_CMD_C64_DUTY_RESET, - DIV_CMD_C64_EXTENDED, - DIV_CMD_C64_FINE_DUTY, - DIV_CMD_C64_FINE_CUTOFF, + DIV_CMD_C64_CUTOFF, // (value) + DIV_CMD_C64_RESONANCE, // (value) + DIV_CMD_C64_FILTER_MODE, // (value) + DIV_CMD_C64_RESET_TIME, // (value) + DIV_CMD_C64_RESET_MASK, // (mask) + DIV_CMD_C64_FILTER_RESET, // (value) + DIV_CMD_C64_DUTY_RESET, // (value) + DIV_CMD_C64_EXTENDED, // (value) + DIV_CMD_C64_FINE_DUTY, // (value) + DIV_CMD_C64_FINE_CUTOFF, // (value) DIV_CMD_AY_ENVELOPE_SET, DIV_CMD_AY_ENVELOPE_LOW, DIV_CMD_AY_ENVELOPE_HIGH, DIV_CMD_AY_ENVELOPE_SLIDE, - DIV_CMD_AY_NOISE_MASK_AND, - DIV_CMD_AY_NOISE_MASK_OR, - DIV_CMD_AY_AUTO_ENVELOPE, - DIV_CMD_AY_IO_WRITE, + DIV_CMD_AY_NOISE_MASK_AND, // (value) + DIV_CMD_AY_NOISE_MASK_OR, // (value) + DIV_CMD_AY_AUTO_ENVELOPE, // (value) + DIV_CMD_AY_IO_WRITE, // (port, value) DIV_CMD_AY_AUTO_PWM, DIV_CMD_FDS_MOD_DEPTH, @@ -111,17 +133,18 @@ enum DivDispatchCmds { DIV_CMD_FDS_MOD_POS, DIV_CMD_FDS_MOD_WAVE, - DIV_CMD_SAA_ENVELOPE, + DIV_CMD_SAA_ENVELOPE, // (value) - DIV_CMD_AMIGA_FILTER, - DIV_CMD_AMIGA_AM, - DIV_CMD_AMIGA_PM, + DIV_CMD_AMIGA_FILTER, // (enabled) + DIV_CMD_AMIGA_AM, // (enabled) + DIV_CMD_AMIGA_PM, // (enabled) - DIV_CMD_LYNX_LFSR_LOAD, + DIV_CMD_LYNX_LFSR_LOAD, // (value) DIV_CMD_QSOUND_ECHO_FEEDBACK, DIV_CMD_QSOUND_ECHO_DELAY, DIV_CMD_QSOUND_ECHO_LEVEL, + DIV_CMD_QSOUND_SURROUND, DIV_CMD_X1_010_ENVELOPE_SHAPE, DIV_CMD_X1_010_ENVELOPE_ENABLE, @@ -146,7 +169,7 @@ enum DivDispatchCmds { DIV_CMD_N163_GLOBAL_WAVE_LOADLEN, DIV_CMD_N163_GLOBAL_WAVE_LOADMODE, - DIV_ALWAYS_SET_VOLUME, + DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol DIV_CMD_MAX }; @@ -200,7 +223,26 @@ struct DivRegWrite { addr(a), val(v) {} }; +struct DivDispatchOscBuffer { + bool follow; + unsigned int rate; + unsigned short needle; + unsigned short readNeedle; + unsigned short followNeedle; + short data[65536]; + + DivDispatchOscBuffer(): + follow(true), + rate(65536), + needle(0), + readNeedle(0), + followNeedle(0) { + memset(data,0,65536*sizeof(short)); + } +}; + class DivEngine; +class DivMacroInt; class DivDispatch { protected: @@ -214,11 +256,13 @@ class DivDispatch { /** * the rate the samples are provided. * the engine shall resample to the output rate. + * you have to initialize this one during init() or setFlags(). */ int rate; /** * the actual chip's clock. + * you have to initialize this one during init() or setFlags(). */ int chipClock; @@ -245,14 +289,27 @@ class DivDispatch { /** * ticks this dispatch. + * @param sysTick whether the engine has ticked (if not then this may be a sub-tick used in low-latency mode). */ - virtual void tick(); + virtual void tick(bool sysTick=true); /** * get the state of a channel. * @return a pointer, or NULL. */ virtual void* getChanState(int chan); + + /** + * get the DivMacroInt of a chanmel. + * @return a pointer, or NULL. + */ + virtual DivMacroInt* getChanMacroInt(int chan); + + /** + * get an oscilloscope buffer for a channel. + * @return a pointer to a DivDispatchOscBuffer, or NULL if not supported. + */ + virtual DivDispatchOscBuffer* getOscBuffer(int chan); /** * get the register pool of this dispatch. @@ -404,6 +461,26 @@ class DivDispatch { */ virtual const char** getRegisterSheet(); + /** + * Get sample memory buffer. + */ + virtual const void* getSampleMem(int index = 0); + + /** + * Get sample memory capacity. + */ + virtual size_t getSampleMemCapacity(int index = 0); + + /** + * Get sample memory usage. + */ + virtual size_t getSampleMemUsage(int index = 0); + + /** + * Render samples into sample memory. + */ + virtual void renderSamples(); + /** * initialize this DivDispatch. * @param parent the parent DivEngine. @@ -417,16 +494,35 @@ class DivDispatch { /** * quit the DivDispatch. */ - virtual void quit(); + virtual void quit(); - virtual ~DivDispatch(); + virtual ~DivDispatch(); }; +// pitch calculation: +// - a DivDispatch usually contains four variables per channel: +// - baseFreq: this changes on new notes, legato, arpeggio and slides. +// - pitch: this changes with DIV_CMD_PITCH (E5xx/04xy). +// - freq: this is the result of combining baseFreq and pitch using DivEngine::calcFreq(). +// - freqChanged: whether baseFreq and/or pitch have changed, and a frequency recalculation is required on the next tick. +// - the following definitions will help you calculate baseFreq. +// - to use them, define CHIP_DIVIDER and/or CHIP_FREQBASE in your code (not in the header though!). #define NOTE_PERIODIC(x) round(parent->calcBaseFreq(chipClock,CHIP_DIVIDER,x,true)) #define NOTE_PERIODIC_NOROUND(x) parent->calcBaseFreq(chipClock,CHIP_DIVIDER,x,true) #define NOTE_FREQUENCY(x) parent->calcBaseFreq(chipClock,CHIP_FREQBASE,x,false) +// this is a special case definition. only use it for f-num/block-based chips. +#define NOTE_FNUM_BLOCK(x,bits) parent->calcBaseFreqFNumBlock(chipClock,CHIP_FREQBASE,x,bits) + +// these are here for convenience. +// it is encouraged to use these, since you get an exact value this way. +// - NTSC colorburst: 3.58MHz +// - PAL colorburst: 4.43MHz #define COLOR_NTSC (315000000.0/88.0) #define COLOR_PAL (283.75*15625.0+25.0) +#define CLAMP_VAR(x,xMin,xMax) \ + if (xxMax) x=xMax; + #endif diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 4f4ecf398..f44ed0d5c 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -44,6 +44,7 @@ #include "platform/qsound.h" #include "platform/vera.h" #include "platform/x1_010.h" +#include "platform/su.h" #include "platform/swan.h" #include "platform/lynx.h" #include "platform/bubsyswsg.h" @@ -188,6 +189,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do break; case DIV_SYSTEM_NES: dispatch=new DivPlatformNES; + ((DivPlatformNES*)dispatch)->setNSFPlay(eng->getConfInt("nesCore",0)==1); break; case DIV_SYSTEM_C64_6581: dispatch=new DivPlatformC64; @@ -226,6 +228,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do break; case DIV_SYSTEM_FDS: dispatch=new DivPlatformFDS; + ((DivPlatformFDS*)dispatch)->setNSFPlay(eng->getConfInt("fdsCore",0)==1); break; case DIV_SYSTEM_TIA: dispatch=new DivPlatformTIA; @@ -311,6 +314,12 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_MMC5: dispatch=new DivPlatformMMC5; break; + case DIV_SYSTEM_SOUND_UNIT: + dispatch=new DivPlatformSoundUnit; + break; + case DIV_SYSTEM_DUMMY: + dispatch=new DivPlatformDummy; + break; default: logW("this system is not supported yet! using dummy platform."); dispatch=new DivPlatformDummy; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 14577b40e..364b0d135 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -41,7 +41,7 @@ void process(void* u, float** in, float** out, int inChans, int outChans, unsign ((DivEngine*)u)->nextBuf(in,out,inChans,outChans,size); } -const char* DivEngine::getEffectDesc(unsigned char effect, int chan) { +const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNull) { switch (effect) { case 0x00: return "00xy: Arpeggio"; @@ -69,6 +69,12 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan) { return "0Dxx: Jump to next pattern"; case 0x0f: return "0Fxx: Set speed 2"; + case 0x80: + return "80xx: Set panning (00: left; 80: center; FF: right)"; + case 0x81: + return "81xx: Set panning (left channel)"; + case 0x82: + return "82xx: Set panning (right channel)"; case 0xc0: case 0xc1: case 0xc2: case 0xc3: return "Cxxx: Set tick rate (hz)"; case 0xe0: @@ -116,14 +122,13 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan) { default: if ((effect&0xf0)==0x90) { return "9xxx: Set sample offset*256"; - } - else if (chan>=0 && chan=0 && changetEffectName(effect); if (ret!=NULL) return ret; } break; } - return "Invalid effect"; + return notNull?"Invalid effect":NULL; } void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) { @@ -141,7 +146,7 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) { for (int j=nextRow; jdata[j][5+(l<<1)]; if (effectVal<0) effectVal=0; if (pat[k]->data[j][4+(l<<1)]==0x0d) { @@ -358,6 +363,14 @@ void DivEngine::runExportThread() { for (int j=0; jmuteChannel(dispatchChanOfChan[j],isMuted[j]); } @@ -385,6 +398,17 @@ void DivEngine::runExportThread() { if (sf_close(sf)!=0) { logE("could not close audio file!"); } + + if (getChannelType(i)==5) { + i++; + while (true) { + if (i>=chans) break; + if (getChannelType(i)!=5) break; + } + i--; + } + + if (stopExport) break; } exporting=false; @@ -412,12 +436,25 @@ void DivEngine::runExportThread() { break; } } + stopExport=false; } bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode) { exportPath=path; exportMode=mode; + if (exportMode!=DIV_EXPORT_MODE_ONE) { + // remove extension + String lowerCase=exportPath; + for (char& i: lowerCase) { + if (i>='A' && i<='Z') i+='a'-'A'; + } + size_t extPos=lowerCase.rfind(".wav"); + if (extPos!=String::npos) { + exportPath=exportPath.substr(0,extPos); + } + } exporting=true; + stopExport=false; stop(); repeatPattern=false; setOrder(0); @@ -433,6 +470,7 @@ void DivEngine::waitAudioFile() { } bool DivEngine::haltAudioFile() { + stopExport=true; stop(); return true; } @@ -468,118 +506,107 @@ void DivEngine::renderSamples() { song.sample[i]->render(); } - // step 2: allocate ADPCM-A samples - if (adpcmAMem==NULL) adpcmAMem=new unsigned char[16777216]; - - size_t memPos=0; - for (int i=0; ilengthA+255)&(~0xff); - if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) { - memPos=(memPos+0xfffff)&0xf00000; + // step 2: render samples to dispatch + for (int i=0; irenderSamples(); } - if (memPos>=16777216) { - logW("out of ADPCM-A memory for sample %d!",i); - break; - } - if (memPos+paddedLen>=16777216) { - memcpy(adpcmAMem+memPos,s->dataA,16777216-memPos); - logW("out of ADPCM-A memory for sample %d!",i); - } else { - memcpy(adpcmAMem+memPos,s->dataA,paddedLen); - } - s->offA=memPos; - memPos+=paddedLen; } - adpcmAMemLen=memPos+256; +} - // step 2: allocate ADPCM-B samples - if (adpcmBMem==NULL) adpcmBMem=new unsigned char[16777216]; - - memPos=0; - for (int i=0; ilengthB+255)&(~0xff); - if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) { - memPos=(memPos+0xfffff)&0xf00000; +String DivEngine::encodeSysDesc(std::vector& desc) { + String ret; + if (desc[0]!=0) { + int index=0; + for (size_t i=0; i=32) break; } - if (memPos>=16777216) { - logW("out of ADPCM-B memory for sample %d!",i); - break; - } - if (memPos+paddedLen>=16777216) { - memcpy(adpcmBMem+memPos,s->dataB,16777216-memPos); - logW("out of ADPCM-B memory for sample %d!",i); - } else { - memcpy(adpcmBMem+memPos,s->dataB,paddedLen); - } - s->offB=memPos; - memPos+=paddedLen; } - adpcmBMemLen=memPos+256; + return ret; +} - // step 4: allocate qsound pcm samples - if (qsoundMem==NULL) qsoundMem=new unsigned char[16777216]; - memset(qsoundMem,0,16777216); +std::vector DivEngine::decodeSysDesc(String desc) { + std::vector ret; + bool hasVal=false; + bool negative=false; + int val=0; + int curStage=0; + int sysID=0; + int sysVol=0; + int sysPan=0; + int sysFlags=0; + desc+=' '; // ha + for (char i: desc) { + switch (i) { + case ' ': + if (hasVal) { + if (negative) val=-val; + switch (curStage) { + case 0: + sysID=val; + curStage++; + break; + case 1: + sysVol=val; + curStage++; + break; + case 2: + sysPan=val; + curStage++; + break; + case 3: + sysFlags=val; - memPos=0; - for (int i=0; ilength8; - if (length>65536-16) { - length=65536-16; + if (systemFromFileFur(sysID)!=0) { + if (sysVol<-128) sysVol=-128; + if (sysVol>127) sysVol=127; + if (sysPan<-128) sysPan=-128; + if (sysPan>127) sysPan=127; + ret.push_back(systemFromFileFur(sysID)); + ret.push_back(sysVol); + ret.push_back(sysPan); + ret.push_back(sysFlags); + } + + curStage=0; + break; + } + hasVal=false; + negative=false; + val=0; + } + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + val=(val*10)+(i-'0'); + hasVal=true; + break; + case '-': + if (!hasVal) negative=true; + break; } - if ((memPos&0xff0000)!=((memPos+length)&0xff0000)) { - memPos=(memPos+0xffff)&0xff0000; - } - if (memPos>=16777216) { - logW("out of QSound PCM memory for sample %d!",i); - break; - } - if (memPos+length>=16777216) { - for (unsigned int i=0; i<16777216-(memPos+length); i++) { - qsoundMem[(memPos+i)^0x8000]=s->data8[i]; - } - logW("out of QSound PCM memory for sample %d!",i); - } else { - for (int i=0; idata8[i]; - } - } - s->offQSound=memPos^0x8000; - memPos+=length+16; } - qsoundMemLen=memPos+256; + return ret; +} - // step 4: allocate x1-010 pcm samples - if (x1_010Mem==NULL) x1_010Mem=new unsigned char[1048576]; - memset(x1_010Mem,0,1048576); - - memPos=0; - for (int i=0; ilength8+4095)&(~0xfff); - // fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!) - if (paddedLen>131072) { - paddedLen=131072; +void DivEngine::initSongWithDesc(const int* description) { + int chanCount=0; + if (description[0]!=0) { + int index=0; + for (int i=0; description[i]; i+=4) { + song.system[index]=(DivSystem)description[i]; + song.systemVol[index]=description[i+1]; + song.systemPan[index]=description[i+2]; + song.systemFlags[index]=description[i+3]; + index++; + chanCount+=getChannelCount(song.system[index]); + if (chanCount>=63) break; + if (index>=32) break; } - if ((memPos&0xfe0000)!=((memPos+paddedLen)&0xfe0000)) { - memPos=(memPos+0x1ffff)&0xfe0000; - } - if (memPos>=1048576) { - logW("out of X1-010 memory for sample %d!",i); - break; - } - if (memPos+paddedLen>=1048576) { - memcpy(x1_010Mem+memPos,s->data8,1048576-memPos); - logW("out of X1-010 memory for sample %d!",i); - } else { - memcpy(x1_010Mem+memPos,s->data8,paddedLen); - } - s->offX1_010=memPos; - memPos+=paddedLen; + song.systemLen=index; } - x1_010MemLen=memPos+256; } void DivEngine::createNew(const int* description) { @@ -589,33 +616,109 @@ void DivEngine::createNew(const int* description) { song.unload(); song=DivSong(); if (description!=NULL) { - if (description[0]!=0) { - int index=0; - for (int i=0; description[i]; i+=4) { - song.system[index]=(DivSystem)description[i]; - song.systemVol[index]=description[i+1]; - song.systemPan[index]=description[i+2]; - song.systemFlags[index]=description[i+3]; - index++; - if (index>=32) break; - } - song.systemLen=index; - } + initSongWithDesc(description); } recalcChans(); - renderSamples(); saveLock.unlock(); BUSY_END; initDispatch(); BUSY_BEGIN; + renderSamples(); reset(); BUSY_END; } -void DivEngine::changeSystem(int index, DivSystem which) { +void DivEngine::swapChannels(int src, int dest) { + logV("swapping channel %d with %d",src,dest); + if (src==dest) { + logV("not swapping channels because it's the same channel!",src,dest); + return; + } + + for (int i=0; i<256; i++) { + song.orders.ord[dest][i]^=song.orders.ord[src][i]; + song.orders.ord[src][i]^=song.orders.ord[dest][i]; + song.orders.ord[dest][i]^=song.orders.ord[src][i]; + + DivPattern* prev=song.pat[src].data[i]; + song.pat[src].data[i]=song.pat[dest].data[i]; + song.pat[dest].data[i]=prev; + } + + song.pat[src].effectCols^=song.pat[dest].effectCols; + song.pat[dest].effectCols^=song.pat[src].effectCols; + song.pat[src].effectCols^=song.pat[dest].effectCols; + + String prevChanName=song.chanName[src]; + String prevChanShortName=song.chanShortName[src]; + bool prevChanShow=song.chanShow[src]; + bool prevChanCollapse=song.chanCollapse[src]; + + song.chanName[src]=song.chanName[dest]; + song.chanShortName[src]=song.chanShortName[dest]; + song.chanShow[src]=song.chanShow[dest]; + song.chanCollapse[src]=song.chanCollapse[dest]; + song.chanName[dest]=prevChanName; + song.chanShortName[dest]=prevChanShortName; + song.chanShow[dest]=prevChanShow; + song.chanCollapse[dest]=prevChanCollapse; +} + +void DivEngine::stompChannel(int ch) { + logV("stomping channel %d",ch); + for (int i=0; i<256; i++) { + song.orders.ord[ch][i]=0; + } + song.pat[ch].wipePatterns(); + song.pat[ch].effectCols=1; + song.chanName[ch]=""; + song.chanShortName[ch]=""; + song.chanShow[ch]=true; + song.chanCollapse[ch]=false; +} + +void DivEngine::swapChannelsP(int src, int dest) { + if (src<0 || src>=chans) return; + if (dest<0 || dest>=chans) return; + BUSY_BEGIN; + saveLock.lock(); + swapChannels(src,dest); + saveLock.unlock(); + BUSY_END; +} + +void DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) { + int chanCount=chans; quitDispatch(); BUSY_BEGIN; saveLock.lock(); + + if (!preserveOrder) { + int firstChan=0; + int chanMovement=getChannelCount(which)-getChannelCount(song.system[index]); + while (dispatchOfChan[firstChan]!=index) firstChan++; + int lastChan=firstChan+getChannelCount(song.system[index]); + if (chanMovement!=0) { + if (chanMovement>0) { + // add channels + for (int i=chanCount+chanMovement-1; i>=lastChan+chanMovement; i--) { + swapChannels(i,i-chanMovement); + } + for (int i=lastChan; i=song.insLen) return &song.nullIns; +DivInstrument* DivEngine::getIns(int index, DivInstrumentType fallbackType) { + if (index==-2 && tempIns!=NULL) { + return tempIns; + } + if (index<0 || index>=song.insLen) { + switch (fallbackType) { + case DIV_INS_OPLL: + return &song.nullInsOPLL; + break; + case DIV_INS_OPL: + return &song.nullInsOPL; + break; + default: + break; + } + return &song.nullIns; + } return song.ins[index]; } @@ -727,6 +861,11 @@ DivSample* DivEngine::getSample(int index) { return song.sample[index]; } +DivDispatch* DivEngine::getDispatch(int index) { + if (index<0 || index>=song.systemLen) return NULL; + return disCont[index].dispatch; +} + void DivEngine::setLoops(int loops) { remainingLoops=loops; } @@ -749,6 +888,16 @@ unsigned char* DivEngine::getRegisterPool(int sys, int& size, int& depth) { return disCont[sys].dispatch->getRegisterPool(); } +DivMacroInt* DivEngine::getMacroInt(int chan) { + if (chan<0 || chan>=chans) return NULL; + return disCont[dispatchOfChan[chan]].dispatch->getChanMacroInt(dispatchChanOfChan[chan]); +} + +DivDispatchOscBuffer* DivEngine::getOscBuffer(int chan) { + if (chan<0 || chan>=chans) return NULL; + return disCont[dispatchOfChan[chan]].dispatch->getOscBuffer(dispatchChanOfChan[chan]); +} + void DivEngine::enableCommandStream(bool enable) { cmdStreamEnabled=enable; } @@ -820,6 +969,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { } if (!preserveDrift) { ticks=1; + subticks=1; } skipping=false; cmdStream.clear(); @@ -842,21 +992,78 @@ double DivEngine::calcBaseFreq(double clock, double divider, int note, bool peri base*(divider/clock); } -int DivEngine::calcFreq(int base, int pitch, bool period, int octave) { +unsigned short DivEngine::calcBaseFreqFNumBlock(double clock, double divider, int note, int bits) { + int bf=calcBaseFreq(clock,divider,note,false); + int block=note/12; + if (block<0) block=0; + if (block>7) block=7; + bf>>=block; + if (bf<0) bf=0; + // octave boundaries + while (bf>0 && bf<644 && block>0) { + bf<<=1; + block--; + } + if (bf>1288) { + while (block<7) { + bf>>=1; + block++; + } + if (bf>((1<4095) pitch=4095; - return period? - ((base*(reversePitchTable[pitch]))/whatTheFuck): - (((base*(pitchTable[pitch]))>>10)*whatTheFuck)/1024; + int ret=period? + ((base*(reversePitchTable[pitch]))/whatTheFuck): + (((base*(pitchTable[pitch]))>>10)*whatTheFuck)/1024; + if (!song.pitchMacroIsLinear) { + ret+=period?(-pitch2):pitch2; + } + return ret; } return period? - base-pitch: - base+((pitch*octave)>>1); + base-pitch-pitch2: + base+((pitch*octave)>>1)+pitch2; +} + +int DivEngine::convertPanSplitToLinear(unsigned int val, unsigned char bits, int range) { + int panL=val>>bits; + int panR=val&((1<range) val=range; + int maxV=(1<maxV) panL=maxV; + if (panR>maxV) panR=maxV; + return (panL<chanInsType[j][0]!=DIV_INS_NULL) { + isInsTypePossible[sysDefs[song.system[i]]->chanInsType[j][0]]=true; + } + + if (sysDefs[song.system[i]]->chanInsType[j][1]!=DIV_INS_NULL) { + isInsTypePossible[sysDefs[song.system[i]]->chanInsType[j][1]]=true; + } + } } } + + possibleInsTypes.clear(); + for (int i=0; iname=fmt::sprintf("Instrument %d",insCount); - ins->type=getPreferInsType(refChan); + ins->type=prefType; saveLock.lock(); song.ins.push_back(ins); song.insLen=insCount+1; @@ -1283,6 +1523,15 @@ int DivEngine::addInstrumentPtr(DivInstrument* which) { return song.insLen; } +void DivEngine::loadTempIns(DivInstrument* which) { + BUSY_BEGIN; + if (tempIns==NULL) { + tempIns=new DivInstrument; + } + *tempIns=*which; + BUSY_END; +} + void DivEngine::delInstrument(int index) { BUSY_BEGIN; saveLock.lock(); @@ -1336,6 +1585,10 @@ bool DivEngine::addWaveFromFile(const char* path) { fclose(f); return false; } + if (len==(SIZE_MAX>>1)) { + fclose(f); + return false; + } if (len==0) { fclose(f); return false; @@ -1468,6 +1721,105 @@ int DivEngine::addSample() { int DivEngine::addSampleFromFile(const char* path) { BUSY_BEGIN; + warnings=""; + + const char* pathRedux=strrchr(path,DIR_SEPARATOR); + if (pathRedux==NULL) { + pathRedux=path; + } else { + pathRedux++; + } + String stripPath; + const char* pathReduxEnd=strrchr(pathRedux,'.'); + if (pathReduxEnd==NULL) { + stripPath=pathRedux; + } else { + for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) { + stripPath+=*i; + } + } + + const char* ext=strrchr(path,'.'); + if (ext!=NULL) { + String extS; + for (; *ext; ext++) { + char i=*ext; + if (i>='A' && i<='Z') { + i+='a'-'A'; + } + extS+=i; + } + if (extS==String(".dmc")) { // read as .dmc + size_t len=0; + DivSample* sample=new DivSample; + int sampleCount=(int)song.sample.size(); + sample->name=stripPath; + + FILE* f=fopen(path,"rb"); + if (f==NULL) { + BUSY_END; + lastError=fmt::sprintf("could not open file! (%s)",strerror(errno)); + delete sample; + return -1; + } + + if (fseek(f,0,SEEK_END)<0) { + fclose(f); + BUSY_END; + lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno)); + delete sample; + return -1; + } + + len=ftell(f); + + if (len==0) { + fclose(f); + BUSY_END; + lastError="file is empty!"; + delete sample; + return -1; + } + + if (len==(SIZE_MAX>>1)) { + fclose(f); + BUSY_END; + lastError="file is invalid!"; + delete sample; + return -1; + } + + if (fseek(f,0,SEEK_SET)<0) { + fclose(f); + BUSY_END; + lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno)); + delete sample; + return -1; + } + + sample->rate=33144; + sample->centerRate=33144; + sample->depth=1; + sample->init(len*8); + + if (fread(sample->dataDPCM,1,len,f)==0) { + fclose(f); + BUSY_END; + lastError=fmt::sprintf("could not read file! (%s)",strerror(errno)); + delete sample; + return -1; + } + + saveLock.lock(); + song.sample.push_back(sample); + song.sampleLen=sampleCount+1; + saveLock.unlock(); + renderSamples(); + BUSY_END; + return sampleCount; + } + } + SF_INFO si; SNDFILE* f=sf_open(path,SFM_READ,&si); if (f==NULL) { @@ -1492,13 +1844,7 @@ int DivEngine::addSampleFromFile(const char* path) { } DivSample* sample=new DivSample; int sampleCount=(int)song.sample.size(); - const char* sName=strrchr(path,DIR_SEPARATOR); - if (sName==NULL) { - sName=path; - } else { - sName++; - } - sample->name=sName; + sample->name=stripPath; int index=0; if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) { @@ -1840,10 +2186,13 @@ void DivEngine::noteOff(int chan) { } void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) { - //if (ch<0 || ch>=chans) return; + bool isViable[DIV_MAX_CHANS]; + bool canPlayAnyway=false; + bool notInViableChannel=false; if (midiBaseChan<0) midiBaseChan=0; if (midiBaseChan>=chans) midiBaseChan=chans-1; int finalChan=midiBaseChan; + int finalChanType=getChannelType(finalChan); if (!playing) { reset(); @@ -1851,16 +2200,56 @@ void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) { playing=true; } + // 1. check which channels are viable for this instrument + DivInstrument* insInst=getIns(ins); + if (getPreferInsType(finalChan)!=insInst->type && getPreferInsSecondType(finalChan)!=insInst->type) notInViableChannel=true; + for (int i=0; i=song.insLen || getPreferInsType(i)==insInst->type || getPreferInsSecondType(i)==insInst->type) { + if (insInst->type==DIV_INS_OPL) { + if (insInst->fm.ops==2 || getChannelType(i)==DIV_CH_OP) { + isViable[i]=true; + canPlayAnyway=true; + } else { + isViable[i]=false; + } + } else { + isViable[i]=true; + canPlayAnyway=true; + } + } else { + isViable[i]=false; + } + } + + if (!canPlayAnyway) return; + + // 2. find a free channel do { - if ((ins==-1 || getPreferInsType(finalChan)==getIns(ins)->type) && chan[finalChan].midiNote==-1) { + if (isViable[finalChan] && chan[finalChan].midiNote==-1 && (insInst->type==DIV_INS_OPL || getChannelType(finalChan)==finalChanType || notInViableChannel)) { chan[finalChan].midiNote=note; + chan[finalChan].midiAge=midiAgeCounter++; pendingNotes.push(DivNoteEvent(finalChan,ins,note,vol,true)); - break; + return; } if (++finalChan>=chans) { finalChan=0; } } while (finalChan!=midiBaseChan); + + // 3. find the oldest channel + int candidate=finalChan; + do { + if (isViable[finalChan] && (insInst->type==DIV_INS_OPL || getChannelType(finalChan)==finalChanType || notInViableChannel) && chan[finalChan].midiAge=chans) { + finalChan=0; + } + } while (finalChan!=midiBaseChan); + + chan[candidate].midiNote=note; + chan[candidate].midiAge=midiAgeCounter++; + pendingNotes.push(DivNoteEvent(candidate,ins,note,vol,true)); } void DivEngine::autoNoteOff(int ch, int note, int vol) { @@ -1878,6 +2267,20 @@ void DivEngine::autoNoteOff(int ch, int note, int vol) { } } +void DivEngine::autoNoteOffAll() { + if (!playing) { + reset(); + freelance=true; + playing=true; + } + for (int i=0; i2.0f) metroVol=2.0f; + + if (lowLatency) logI("using low latency mode."); switch (audioEngine) { case DIV_AUDIO_JACK: @@ -2212,6 +2626,9 @@ bool DivEngine::deinitAudioBackend() { #endif bool DivEngine::init() { + // register systems + if (!systemsRegistered) registerSystems(); + // init config #ifdef _WIN32 configPath=getWinConfigPath(); @@ -2245,6 +2662,18 @@ bool DivEngine::init() { loadConf(); + // set default system preset + if (!hasLoadedSomething) { + logD("setting default preset"); + std::vector preset=decodeSysDesc(getConfString("initialSys","")); + logD("preset size %ld",preset.size()); + if (preset.size()>0 && (preset.size()&3)==0) { + preset.push_back(0); + initSongWithDesc(preset.data()); + } + hasLoadedSomething=true; + } + // init the rest of engine bool haveAudio=false; if (!initAudioBackend()) { @@ -2283,6 +2712,7 @@ bool DivEngine::init() { oscBuf[1]=new float[32768]; initDispatch(); + renderSamples(); reset(); active=true; diff --git a/src/engine/engine.h b/src/engine/engine.h index 89f6ff732..e9a5db2e0 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -19,13 +19,16 @@ #ifndef _ENGINE_H #define _ENGINE_H +#include "instrument.h" #include "song.h" #include "dispatch.h" #include "dataErrors.h" #include "safeWriter.h" #include "../audio/taAudio.h" #include "blip_buf.h" +#include #include +#include #include #include #include @@ -42,8 +45,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev81" -#define DIV_ENGINE_VERSION 81 +#define DIV_VERSION "dev93" +#define DIV_ENGINE_VERSION 93 // for imports #define DIV_VERSION_MOD 0xff01 @@ -82,11 +85,12 @@ struct DivChannelState { int delayOrder, delayRow, retrigSpeed, retrigTick; int vibratoDepth, vibratoRate, vibratoPos, vibratoDir, vibratoFine; int tremoloDepth, tremoloRate, tremoloPos; - unsigned char arp, arpStage, arpTicks; + unsigned char arp, arpStage, arpTicks, panL, panR; bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff; bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, noteOnInhibit, resetArp; int midiNote, curMidiNote, midiPitch; + size_t midiAge; bool midiAftertouch; DivChannelState(): @@ -116,6 +120,8 @@ struct DivChannelState { arp(0), arpStage(-1), arpTicks(1), + panL(255), + panR(255), doNote(false), legato(false), portaStop(false), @@ -133,6 +139,7 @@ struct DivChannelState { midiNote(-1), curMidiNote(-1), midiPitch(-1), + midiAge(0), midiAftertouch(false) {} }; @@ -176,6 +183,94 @@ struct DivDispatchContainer { dcOffCompensation(false) {} }; +typedef std::function EffectProcess; + +struct DivSysDef { + const char* name; + const char* nameJ; + unsigned char id; + unsigned char id_DMF; + int channels; + bool isFM, isSTD, isCompound; + unsigned int vgmVersion; + const char* chanNames[DIV_MAX_CHANS]; + const char* chanShortNames[DIV_MAX_CHANS]; + int chanTypes[DIV_MAX_CHANS]; + // 0: primary + // 1: alternate (usually PCM) + DivInstrumentType chanInsType[DIV_MAX_CHANS][2]; + EffectProcess effectFunc; + EffectProcess postEffectFunc; + DivSysDef( + const char* sysName, const char* sysNameJ, unsigned char fileID, unsigned char fileID_DMF, int chans, + bool isFMChip, bool isSTDChip, unsigned int vgmVer, bool compound, + std::initializer_list chNames, + std::initializer_list chShortNames, + std::initializer_list chTypes, + std::initializer_list chInsType1, + std::initializer_list chInsType2={}, + EffectProcess fxHandler=[](int,unsigned char,unsigned char) -> bool {return false;}, + EffectProcess postFxHandler=[](int,unsigned char,unsigned char) -> bool {return false;}): + name(sysName), + nameJ(sysNameJ), + id(fileID), + id_DMF(fileID_DMF), + channels(chans), + isFM(isFMChip), + isSTD(isSTDChip), + isCompound(compound), + vgmVersion(vgmVer), + effectFunc(fxHandler), + postEffectFunc(postFxHandler) { + memset(chanNames,0,DIV_MAX_CHANS*sizeof(void*)); + memset(chanShortNames,0,DIV_MAX_CHANS*sizeof(void*)); + memset(chanTypes,0,DIV_MAX_CHANS*sizeof(int)); + for (int i=0; i=DIV_MAX_CHANS) break; + } + + index=0; + for (const char* i: chShortNames) { + chanShortNames[index++]=i; + if (index>=DIV_MAX_CHANS) break; + } + + index=0; + for (int i: chTypes) { + chanTypes[index++]=i; + if (index>=DIV_MAX_CHANS) break; + } + + index=0; + for (DivInstrumentType i: chInsType1) { + chanInsType[index++][0]=i; + if (index>=DIV_MAX_CHANS) break; + } + + index=0; + for (DivInstrumentType i: chInsType2) { + chanInsType[index++][1]=i; + if (index>=DIV_MAX_CHANS) break; + } + } +}; + +enum DivChanTypes { + DIV_CH_FM=0, + DIV_CH_PULSE=1, + DIV_CH_NOISE=2, + DIV_CH_WAVE=3, + DIV_CH_PCM=4, + DIV_CH_OP=5 +}; + class DivEngine { DivDispatchContainer disCont[32]; TAAudio* output; @@ -194,6 +289,7 @@ class DivEngine { bool repeatPattern; bool metronome; bool exporting; + bool stopExport; bool halted; bool forceMono; bool cmdStreamEnabled; @@ -201,8 +297,11 @@ class DivEngine { bool firstTick; bool skipping; bool midiIsDirect; + bool lowLatency; + bool systemsRegistered; + bool hasLoadedSomething; int softLockCount; - int ticks, curRow, curOrder, remainingLoops, nextSpeed; + int subticks, ticks, curRow, curOrder, remainingLoops, nextSpeed; double divider; int cycles; double clockDrift; @@ -227,6 +326,10 @@ class DivEngine { std::vector midiIns; std::vector midiOuts; std::vector cmdStream; + std::vector possibleInsTypes; + DivSysDef* sysDefs[256]; + DivSystem sysFileMapFur[256]; + DivSystem sysFileMapDMF[256]; struct SamplePreview { int sample; @@ -242,6 +345,7 @@ class DivEngine { int reversePitchTable[4096]; int pitchTable[4096]; int midiBaseChan; + size_t midiAgeCounter; blip_buffer_t* samp_bb; size_t samp_bbInLen; @@ -252,6 +356,7 @@ class DivEngine { size_t metroTickLen; float metroFreq, metroPos; float metroAmp; + float metroVol; size_t totalProcessed; @@ -268,7 +373,7 @@ class DivEngine { void nextRow(); void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool isSecond); // returns true if end of song. - bool nextTick(bool noAccum=false); + bool nextTick(bool noAccum=false, bool inhibitLowLat=false); bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal); bool perSystemPostEffect(int ch, unsigned char effect, unsigned char effectVal); void recalcChans(); @@ -278,21 +383,33 @@ class DivEngine { bool loadDMF(unsigned char* file, size_t len); bool loadFur(unsigned char* file, size_t len); bool loadMod(unsigned char* file, size_t len); + bool loadFTM(unsigned char* file, size_t len); void loadDMP(SafeReader& reader, std::vector& ret, String& stripPath); void loadTFI(SafeReader& reader, std::vector& ret, String& stripPath); void loadVGI(SafeReader& reader, std::vector& ret, String& stripPath); void loadS3I(SafeReader& reader, std::vector& ret, String& stripPath); void loadSBI(SafeReader& reader, std::vector& ret, String& stripPath); + void loadOPLI(SafeReader& reader, std::vector& ret, String& stripPath); + void loadOPNI(SafeReader& reader, std::vector& ret, String& stripPath); + void loadY12(SafeReader& reader, std::vector& ret, String& stripPath); + void loadBNK(SafeReader& reader, std::vector& ret, String& stripPath); void loadOPM(SafeReader& reader, std::vector& ret, String& stripPath); + void loadFF(SafeReader& reader, std::vector& ret, String& stripPath); bool initAudioBackend(); bool deinitAudioBackend(); + void registerSystems(); + void initSongWithDesc(const int* description); + void exchangeIns(int one, int two); + void swapChannels(int src, int dest); + void stompChannel(int ch); public: DivSong song; + DivInstrument* tempIns; DivSystem sysOfChan[DIV_MAX_CHANS]; int dispatchOfChan[DIV_MAX_CHANS]; int dispatchChanOfChan[DIV_MAX_CHANS]; @@ -300,12 +417,18 @@ class DivEngine { float* oscBuf[2]; float oscSize; int oscReadPos, oscWritePos; + int tickMult; + std::atomic processTime; void runExportThread(); void nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size); - DivInstrument* getIns(int index); + DivInstrument* getIns(int index, DivInstrumentType fallbackType=DIV_INS_FM); DivWavetable* getWave(int index); DivSample* getSample(int index); + DivDispatch* getDispatch(int index); + // parse system setup description + String encodeSysDesc(std::vector& desc); + std::vector decodeSysDesc(String desc); // start fresh void createNew(const int* description); // load a file. @@ -357,8 +480,16 @@ class DivEngine { // calculate base frequency/period double calcBaseFreq(double clock, double divider, int note, bool period); + // calculate base frequency in f-num/block format + unsigned short calcBaseFreqFNumBlock(double clock, double divider, int note, int bits); + // calculate frequency/period - int calcFreq(int base, int pitch, bool period=false, int octave=0); + int calcFreq(int base, int pitch, bool period=false, int octave=0, int pitch2=0); + + // convert panning formats + int convertPanSplitToLinear(unsigned int val, unsigned char bits, int range); + int convertPanSplitToLinearLR(unsigned char left, unsigned char right, int range); + unsigned int convertPanLinearToSplit(int val, unsigned char bits, int range); // find song loop position void walkSong(int& loopOrder, int& loopRow, int& loopEnd); @@ -395,8 +526,11 @@ class DivEngine { // get channel count int getTotalChannelCount(); + // get instrument types available for use + std::vector& getPossibleInsTypes(); + // get effect description - const char* getEffectDesc(unsigned char effect, int chan); + const char* getEffectDesc(unsigned char effect, int chan, bool notNull=false); // get channel type // - 0: FM @@ -410,15 +544,15 @@ class DivEngine { // get preferred instrument type DivInstrumentType getPreferInsType(int ch); + // get alternate instrument type + DivInstrumentType getPreferInsSecondType(int ch); + // get song system name - const char* getSongSystemName(); + String getSongSystemName(bool isMultiSystemAcceptable=true); // get sys name const char* getSystemName(DivSystem sys); - // get sys chips - const char* getSystemChips(DivSystem sys); - // get japanese system name const char* getSystemNameJ(DivSystem sys); @@ -512,6 +646,9 @@ class DivEngine { // if the returned vector is empty then there was an error. std::vector instrumentFromFile(const char* path); + // load temporary instrument + void loadTempIns(DivInstrument* which); + // delete instrument void delInstrument(int index); @@ -566,6 +703,7 @@ class DivEngine { void autoNoteOn(int chan, int ins, int note, int vol=-1); void autoNoteOff(int chan, int note, int vol=-1); + void autoNoteOffAll(); // go to order void setOrder(unsigned char order); @@ -588,6 +726,12 @@ class DivEngine { // get register pool unsigned char* getRegisterPool(int sys, int& size, int& depth); + // get macro interpreter + DivMacroInt* getMacroInt(int chan); + + // get osc buffer + DivDispatchOscBuffer* getOscBuffer(int chan); + // enable command stream dumping void enableCommandStream(bool enable); @@ -621,6 +765,9 @@ class DivEngine { // set metronome void setMetronome(bool enable); + // set metronome volume (1.0 = 100%) + void setMetronomeVol(float vol); + // halt now void halt(); @@ -642,14 +789,17 @@ class DivEngine { // public render samples void renderSamplesP(); + // public swap channels + void swapChannelsP(int src, int dest); + // change system - void changeSystem(int index, DivSystem which); + void changeSystem(int index, DivSystem which, bool preserveOrder=true); // add system bool addSystem(DivSystem which); // remove system - bool removeSystem(int index); + bool removeSystem(int index, bool preserveOrder=true); // write to register on system void poke(int sys, unsigned int addr, unsigned short val); @@ -697,25 +847,12 @@ class DivEngine { // quit dispatch void quitDispatch(); - // initialize the engine. optionally provide an output file name. + // initialize the engine. bool init(); // terminate the engine. bool quit(); - unsigned char* adpcmAMem; - size_t adpcmAMemLen; - unsigned char* adpcmBMem; - size_t adpcmBMemLen; - unsigned char* qsoundMem; - size_t qsoundMemLen; - unsigned char* qsoundAMem; - size_t qsoundAMemLen; - unsigned char* dpcmMem; - size_t dpcmMemLen; - unsigned char* x1_010Mem; - size_t x1_010MemLen; - DivEngine(): output(NULL), exportThread(NULL), @@ -731,6 +868,7 @@ class DivEngine { repeatPattern(false), metronome(false), exporting(false), + stopExport(false), halted(false), forceMono(false), cmdStreamEnabled(false), @@ -738,7 +876,11 @@ class DivEngine { firstTick(false), skipping(false), midiIsDirect(false), + lowLatency(false), + systemsRegistered(false), + hasLoadedSomething(false), softLockCount(0), + subticks(0), ticks(0), curRow(0), curOrder(0), @@ -756,35 +898,50 @@ class DivEngine { totalCmds(0), lastCmds(0), cmdsPerSecond(0), + globalPitch(0), extValue(0), speed1(3), speed2(3), view(DIV_STATUS_NOTHING), haltOn(DIV_HALT_NONE), audioEngine(DIV_AUDIO_NULL), + exportMode(DIV_EXPORT_MODE_ONE), midiBaseChan(0), + midiAgeCounter(0), + samp_bb(NULL), samp_bbInLen(0), samp_temp(0), samp_prevSample(0), + samp_bbIn(NULL), + samp_bbOut(NULL), metroTick(NULL), metroTickLen(0), metroFreq(0), metroPos(0), metroAmp(0.0f), + metroVol(1.0f), totalProcessed(0), + tempIns(NULL), oscBuf{NULL,NULL}, oscSize(1), oscReadPos(0), oscWritePos(0), - adpcmAMem(NULL), - adpcmAMemLen(0), - adpcmBMem(NULL), - adpcmBMemLen(0), - qsoundMem(NULL), - qsoundMemLen(0), - qsoundAMem(NULL), - qsoundAMemLen(0), - dpcmMem(NULL), - dpcmMemLen(0) {} + tickMult(1), + processTime(0) { + memset(isMuted,0,DIV_MAX_CHANS*sizeof(bool)); + memset(keyHit,0,DIV_MAX_CHANS*sizeof(bool)); + memset(dispatchChanOfChan,0,DIV_MAX_CHANS*sizeof(int)); + memset(dispatchOfChan,0,DIV_MAX_CHANS*sizeof(int)); + memset(sysOfChan,0,DIV_MAX_CHANS*sizeof(int)); + memset(vibTable,0,64*sizeof(short)); + memset(reversePitchTable,0,4096*sizeof(int)); + memset(pitchTable,0,4096*sizeof(int)); + memset(sysDefs,0,256*sizeof(void*)); + + for (int i=0; i<256; i++) { + sysFileMapFur[i]=DIV_SYSTEM_NULL; + sysFileMapDMF[i]=DIV_SYSTEM_NULL; + } + } }; #endif diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 356e40e7e..8c93ef5bd 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -19,6 +19,7 @@ #include "engine.h" #include "../ta-log.h" +#include "instrument.h" #include "song.h" #include #include @@ -26,6 +27,7 @@ #define DIV_READ_SIZE 131072 #define DIV_DMF_MAGIC ".DelekDefleMask." #define DIV_FUR_MAGIC "-Furnace module-" +#define DIV_FTM_MAGIC "FamiTracker Module" struct InflateBlock { unsigned char* buf; @@ -42,6 +44,12 @@ struct InflateBlock { } }; +struct NotZlibException { + int what; + NotZlibException(int w): + what(w) {} +}; + static double samplePitches[11]={ 0.1666666666, 0.2, 0.25, 0.333333333, 0.5, 1, @@ -154,6 +162,10 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.ignoreJumpAtEnd=true; ds.buggyPortaAfterSlide=true; ds.gbInsAffectsEnvelope=true; + ds.ignoreDACModeOutsideIntendedChannel=false; + ds.e1e2AlsoTakePriority=true; + ds.fbPortaPause=true; + ds.snDutyReset=true; // 1.1 compat flags if (ds.version>24) { @@ -491,9 +503,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } else { ins->std.dutyMacro.val[j]=reader.readI(); } - if ((ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) && ins->std.dutyMacro.val[j]>24) { + /*if ((ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) && ins->std.dutyMacro.val[j]>24) { ins->std.dutyMacro.val[j]=24; - } + }*/ } if (ins->std.dutyMacro.len>0) { ins->std.dutyMacro.open=true; @@ -546,6 +558,16 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ins->c64.bp=reader.readC(); ins->c64.lp=reader.readC(); ins->c64.ch3off=reader.readC(); + + // weird storage + if (ins->c64.volIsCutoff) { + for (int j=0; jstd.volMacro.len; j++) { + ins->std.volMacro.val[j]-=18; + } + } + for (int j=0; jstd.dutyMacro.len; j++) { + ins->std.dutyMacro.val[j]-=12; + } } if (ds.system[0]==DIV_SYSTEM_GB && ds.version>0x11) { @@ -617,14 +639,14 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { for (int i=0; i4 || chan.effectRows<1) { - logE("invalid effect row count %d. are you sure everything is ok?",chan.effectRows); - lastError="file is corrupt or unreadable at effect rows"; + logD("%d fx rows: %d",i,chan.effectCols); + if (chan.effectCols>4 || chan.effectCols<1) { + logE("invalid effect column count %d. are you sure everything is ok?",chan.effectCols); + lastError="file is corrupt or unreadable at effect columns"; delete[] file; return false; } @@ -674,7 +696,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { pat->data[k][3]=(pat->data[k][3]&3)*5; } } - for (int l=0; ldata[k][4+(l<<1)]=reader.readS(); pat->data[k][5+(l<<1)]=reader.readS(); @@ -879,12 +901,14 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { song.unload(); song=ds; recalcChans(); - renderSamples(); saveLock.unlock(); BUSY_END; if (active) { initDispatch(); - syncReset(); + BUSY_BEGIN; + renderSamples(); + reset(); + BUSY_END; } } catch (EndOfFileException& e) { logE("premature end of file!"); @@ -978,6 +1002,22 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<78) { ds.sharedExtStat=false; } + if (ds.version<83) { + ds.ignoreDACModeOutsideIntendedChannel=true; + ds.e1e2AlsoTakePriority=false; + } + if (ds.version<84) { + ds.newSegaPCM=false; + } + if (ds.version<85) { + ds.fbPortaPause=true; + } + if (ds.version<86) { + ds.snDutyReset=true; + } + if (ds.version<90) { + ds.pitchMacroIsLinear=false; + } ds.isDMF=false; reader.readS(); // reserved @@ -1068,9 +1108,11 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { return false; } + logD("systems:"); for (int i=0; i<32; i++) { unsigned char sysID=reader.readC(); ds.system[i]=systemFromFileFur(sysID); + logD("- %d: %.2x (%s)",i,sysID,getSystemName(ds.system[i])); if (sysID!=0 && systemToFileFur(ds.system[i])==0) { logE("unrecognized system ID %.2x",ds.system[i]); lastError=fmt::sprintf("unrecognized system ID %.2x!",ds.system[i]); @@ -1256,10 +1298,10 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } for (int i=0; i8) { - logE("channel %d has zero or too many effect columns! (%d)",i,ds.pat[i].effectRows); - lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,ds.pat[i].effectRows); + ds.pat[i].effectCols=reader.readC(); + if (ds.pat[i].effectCols<1 || ds.pat[i].effectCols>8) { + logE("channel %d has zero or too many effect columns! (%d)",i,ds.pat[i].effectCols); + lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,ds.pat[i].effectCols); delete[] file; return false; } @@ -1274,6 +1316,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { ds.chanCollapse[i]=reader.readC(); } + if (ds.version<92) { + for (int i=0; i0) ds.chanCollapse[i]=3; + } + } + for (int i=0; i=83) { + ds.ignoreDACModeOutsideIntendedChannel=reader.readC(); + ds.e1e2AlsoTakePriority=reader.readC(); + } else { + reader.readC(); + reader.readC(); + } + if (ds.version>=84) { + ds.newSegaPCM=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=85) { + ds.fbPortaPause=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=86) { + ds.snDutyReset=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=90) { + ds.pitchMacroIsLinear=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<19; i++) { reader.readC(); } } @@ -1508,7 +1583,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { pat->data[j][1]=reader.readS(); pat->data[j][2]=reader.readS(); pat->data[j][3]=reader.readS(); - for (int k=0; kdata[j][4+(k<<1)]=reader.readS(); pat->data[j][5+(k<<1)]=reader.readS(); } @@ -1531,12 +1606,14 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { song.unload(); song=ds; recalcChans(); - renderSamples(); saveLock.unlock(); BUSY_END; if (active) { initDispatch(); - syncReset(); + BUSY_BEGIN; + renderSamples(); + reset(); + BUSY_END; } } catch (EndOfFileException& e) { logE("premature end of file!"); @@ -1548,8 +1625,6 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { return true; } - - bool DivEngine::loadMod(unsigned char* file, size_t len) { struct InvalidHeaderException {}; bool success=false; @@ -1585,7 +1660,10 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { logD("couldn't seek to 1080"); throw EndOfFileException(&reader,reader.tell()); } - reader.read(magic,4); + if (reader.read(magic,4)!=4) { + logD("the magic isn't complete"); + throw EndOfFileException(&reader,reader.tell()); + } if (memcmp(magic,"M.K.",4)==0 || memcmp(magic,"M!K!",4)==0 || memcmp(magic,"M&K!",4)==0) { logD("detected a ProTracker module"); chCount=4; @@ -1606,20 +1684,26 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { logD("detected a Fast/TakeTracker module"); chCount=((magic[0]-'0')*10)+(magic[1]-'0'); } else { - // TODO: Soundtracker MOD? insCount=15; - throw InvalidHeaderException(); + logD("possibly a Soundtracker module"); + chCount=4; } // song name - reader.seek(0,SEEK_SET); + if (!reader.seek(0,SEEK_SET)) { + logD("couldn't seek to 0"); + throw EndOfFileException(&reader,reader.tell()); + } ds.name=reader.readString(20); + logI("%s",ds.name); // samples + logD("reading samples... (%d)",insCount); for (int i=0; idepth=8; sample->name=reader.readString(22); + logD("%d: %s",i+1,sample->name); int slen=((unsigned short)reader.readS_BE())*2; sampLens[i]=slen; if (slen==2) slen=0; @@ -1636,7 +1720,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { loopStart=0; loopLen=0; } - if(loopLen>=2) { + if (loopLen>=2) { if (loopEndloopStart=loopStart; } @@ -1647,7 +1731,21 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { // orders ds.ordersLen=ordCount=reader.readC(); - reader.readC(); // restart position, unused + if (ds.ordersLen<1 || ds.ordersLen>127) { + logD("invalid order count!"); + throw EndOfFileException(&reader,reader.tell()); + } + unsigned char restartPos=reader.readC(); // restart position, unused + logD("restart position byte: %.2x",restartPos); + if (insCount==15) { + if (restartPos>0x60 && restartPos<0x80) { + logD("detected a Soundtracker module"); + } else { + logD("no Soundtracker signature found"); + throw EndOfFileException(&reader,reader.tell()); + } + } + int patMax=0; for (int i=0; i<128; i++) { unsigned char pat=reader.readC(); @@ -1657,8 +1755,17 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { } } - // TODO: maybe change if this is a Soundtracker module? - reader.seek(1084,SEEK_SET); + if (insCount==15) { + if (!reader.seek(600,SEEK_SET)) { + logD("couldn't seek to 600"); + throw EndOfFileException(&reader,reader.tell()); + } + } else { + if (!reader.seek(1084,SEEK_SET)) { + logD("couldn't seek to 1084"); + throw EndOfFileException(&reader,reader.tell()); + } + } // patterns ds.patLen=64; @@ -1729,8 +1836,12 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { // samples size_t pos=reader.tell(); - for (int i=0; i<31; i++) { - reader.seek(pos,SEEK_SET); + logD("reading sample data..."); + for (int i=0; idata8,ds.sample[i]->samples); pos+=sampLens[i]; } @@ -1821,8 +1932,8 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { break; case 15: // set speed // TODO: somehow handle VBlank tunes - // TODO: klisje is still broken, perhaps because there wasn't tempo set back then? - if (fxVal>0x20) { + // TODO: i am so sorry + if (fxVal>0x20 && ds.name!="klisje paa klisje") { writeFxCol(0xf0,fxVal); } else { writeFxCol(0x09,fxVal); @@ -1867,7 +1978,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { } } } - ds.pat[ch].effectRows=fxCols; + ds.pat[ch].effectCols=fxCols; } ds.pal=false; @@ -1884,7 +1995,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { ds.chanShortName[i]=fmt::sprintf("C%d",i+1); } for(int i=chCount; ix) { \ + logE("incompatible block version %d for %s!",blockVersion,blockName); \ + lastError="incompatible block version"; \ + delete[] file; \ + return false; \ + } + +bool DivEngine::loadFTM(unsigned char* file, size_t len) { + SafeReader reader=SafeReader(file,len); + warnings=""; + try { + DivSong ds; + String blockName; + unsigned char expansions=0; + unsigned int tchans=0; + unsigned int n163Chans=0; + bool hasSequence[256][8]; + unsigned char sequenceIndex[256][8]; + + memset(hasSequence,0,256*8*sizeof(bool)); + memset(sequenceIndex,0,256*8); + + if (!reader.seek(18,SEEK_SET)) { + logE("premature end of file!"); + lastError="incomplete file"; + delete[] file; + return false; + } + ds.version=(unsigned short)reader.readI(); + logI("module version %d (0x%.4x)",ds.version,ds.version); + + if (ds.version>0x0450) { + logE("incompatible version %x!",ds.version); + lastError="incompatible version"; + delete[] file; + return false; + } + + while (true) { + blockName=reader.readString(3); + if (blockName=="END") { + // end of module + logD("end of data"); + break; + } + + // not the end + reader.seek(-3,SEEK_CUR); + blockName=reader.readString(16); + unsigned int blockVersion=(unsigned int)reader.readI(); + unsigned int blockSize=(unsigned int)reader.readI(); + size_t blockStart=reader.tell(); + + logD("reading block %s (version %d, %d bytes)",blockName,blockVersion,blockSize); + if (blockName=="PARAMS") { + CHECK_BLOCK_VERSION(6); + unsigned int oldSpeedTempo=0; + if (blockVersion<=1) { + oldSpeedTempo=reader.readI(); + } + if (blockVersion>=2) { + expansions=reader.readC(); + } + tchans=reader.readI(); + unsigned int pal=reader.readI(); + unsigned int customHz=reader.readI(); + unsigned int newVibrato=0; + unsigned int speedSplitPoint=0; + if (blockVersion>=3) { + newVibrato=reader.readI(); + } + if (blockVersion>=4) { + ds.hilightA=reader.readI(); + ds.hilightB=reader.readI(); + } + if (expansions&8) if (blockVersion>=5) { // N163 channels + n163Chans=reader.readI(); + } + if (blockVersion>=6) { + speedSplitPoint=reader.readI(); + } + + logV("old speed/tempo: %d",oldSpeedTempo); + logV("expansions: %x",expansions); + logV("channels: %d",tchans); + logV("PAL: %d",pal); + logV("custom Hz: %d",customHz); + logV("new vibrato: %d",newVibrato); + logV("N163 channels: %d",n163Chans); + logV("highlight 1: %d",ds.hilightA); + logV("highlight 2: %d",ds.hilightB); + logV("split point: %d",speedSplitPoint); + + if (customHz!=0) { + ds.hz=customHz; + } + + // initialize channels + int systemID=0; + ds.system[systemID++]=DIV_SYSTEM_NES; + if (expansions&1) { + ds.system[systemID++]=DIV_SYSTEM_VRC6; + } + if (expansions&2) { + ds.system[systemID++]=DIV_SYSTEM_VRC7; + } + if (expansions&4) { + ds.system[systemID++]=DIV_SYSTEM_FDS; + } + if (expansions&8) { + ds.system[systemID++]=DIV_SYSTEM_MMC5; + } + if (expansions&16) { + ds.system[systemID]=DIV_SYSTEM_N163; + ds.systemFlags[systemID++]=n163Chans; + } + if (expansions&32) { + ds.system[systemID]=DIV_SYSTEM_AY8910; + ds.systemFlags[systemID++]=38; // Sunsoft 5B + } + ds.systemLen=systemID; + + unsigned int calcChans=0; + for (int i=0; iDIV_MAX_CHANS) { + tchans=DIV_MAX_CHANS; + logW("too many channels!"); + } + } else if (blockName=="INFO") { + CHECK_BLOCK_VERSION(1); + ds.name=reader.readString(32); + ds.author=reader.readString(32); + ds.copyright=reader.readString(32); + } else if (blockName=="HEADER") { + CHECK_BLOCK_VERSION(3); + unsigned char totalSongs=reader.readC(); + logV("%d songs:",totalSongs+1); + for (int i=0; i<=totalSongs; i++) { + String subSongName=reader.readString(); + logV("- %s",subSongName); + } + for (unsigned int i=0; i256) { + logE("too many instruments/out of range!"); + lastError="too many instruments/out of range"; + delete[] file; + return false; + } + + for (int i=0; i=ds.ins.size()) { + logE("instrument index %d is out of range!",insIndex); + lastError="instrument index out of range"; + delete[] file; + return false; + } + + DivInstrument* ins=ds.ins[insIndex]; + unsigned char insType=reader.readC(); + switch (insType) { + case 1: + ins->type=DIV_INS_STD; + break; + case 2: // TODO: tell VRC6 and VRC6 saw instruments apart + ins->type=DIV_INS_VRC6; + break; + case 3: + ins->type=DIV_INS_OPLL; + break; + case 4: + ins->type=DIV_INS_FDS; + break; + case 5: + ins->type=DIV_INS_N163; + break; + case 6: // 5B? + ins->type=DIV_INS_AY; + break; + default: { + logE("%d: invalid instrument type %d",insIndex,insType); + lastError="invalid instrument type"; + delete[] file; + return false; + } + } + + // instrument data + switch (ins->type) { + case DIV_INS_STD: { + unsigned int totalSeqs=reader.readI(); + if (totalSeqs>5) { + logE("%d: too many sequences!",insIndex); + lastError="too many sequences"; + delete[] file; + return false; + } + + for (unsigned int j=0; j=2)?96:72; + for (int j=0; jamiga.noteMap[j]=(short)((unsigned char)reader.readC())-1; + ins->amiga.noteFreq[j]=(unsigned char)reader.readC(); + if (blockVersion>=6) { + reader.readC(); // DMC value + } + } + break; + } + case DIV_INS_VRC6: { + unsigned int totalSeqs=reader.readI(); + if (totalSeqs>4) { + logE("%d: too many sequences!",insIndex); + lastError="too many sequences"; + delete[] file; + return false; + } + + for (unsigned int j=0; jfm.opllPreset=(unsigned int)reader.readI(); + // TODO + break; + } + case DIV_INS_FDS: { + DivWavetable* wave=new DivWavetable; + wave->len=64; + wave->max=64; + for (int j=0; j<64; j++) { + wave->data[j]=reader.readC(); + } + ins->std.waveMacro.len=1; + ins->std.waveMacro.val[0]=ds.wave.size(); + for (int j=0; j<32; j++) { + ins->fds.modTable[j]=reader.readC()-3; + } + ins->fds.modSpeed=reader.readI(); + ins->fds.modDepth=reader.readI(); + reader.readI(); // this is delay. currently ignored. TODO. + ds.wave.push_back(wave); + + ins->std.volMacro.len=reader.readC(); + ins->std.volMacro.loop=reader.readI(); + ins->std.volMacro.rel=reader.readI(); + reader.readI(); // arp mode does not apply here + for (int j=0; jstd.volMacro.len; j++) { + ins->std.volMacro.val[j]=reader.readC(); + } + + ins->std.arpMacro.len=reader.readC(); + ins->std.arpMacro.loop=reader.readI(); + ins->std.arpMacro.rel=reader.readI(); + ins->std.arpMacro.mode=reader.readI(); + for (int j=0; jstd.arpMacro.len; j++) { + ins->std.arpMacro.val[j]=reader.readC(); + } + + ins->std.pitchMacro.len=reader.readC(); + ins->std.pitchMacro.loop=reader.readI(); + ins->std.pitchMacro.rel=reader.readI(); + reader.readI(); // arp mode does not apply here + for (int j=0; jstd.pitchMacro.len; j++) { + ins->std.pitchMacro.val[j]=reader.readC(); + } + + break; + } + case DIV_INS_N163: { + // TODO! + break; + } + // TODO: 5B! + default: { + logE("%d: what's going on here?",insIndex); + lastError="invalid instrument type"; + delete[] file; + return false; + } + } + + // name + ins->name=reader.readString((unsigned int)reader.readI()); + logV("- %d: %s",insIndex,ins->name); + } + } else if (blockName=="SEQUENCES") { + CHECK_BLOCK_VERSION(6); + } else if (blockName=="FRAMES") { + CHECK_BLOCK_VERSION(3); + } else if (blockName=="PATTERNS") { + CHECK_BLOCK_VERSION(5); + } else if (blockName=="DPCM SAMPLES") { + CHECK_BLOCK_VERSION(1); + } else if (blockName=="SEQUENCES_VRC6") { + // where are the 5B and FDS sequences? + CHECK_BLOCK_VERSION(6); + } else if (blockName=="SEQUENCES_N163") { + CHECK_BLOCK_VERSION(1); + } else if (blockName=="COMMENTS") { + CHECK_BLOCK_VERSION(1); + } else { + logE("block %s is unknown!",blockName); + lastError="unknown block "+blockName; + delete[] file; + return false; + } + + if ((reader.tell()-blockStart)!=blockSize) { + logE("block %s is incomplete!",blockName); + lastError="incomplete block "+blockName; + delete[] file; + return false; + } + } + } catch (EndOfFileException& e) { + logE("premature end of file!"); + lastError="incomplete file"; + delete[] file; + return false; + } + delete[] file; + return true; +} + bool DivEngine::load(unsigned char* f, size_t slen) { unsigned char* file; size_t len; - if (slen<16) { + if (slen<18) { logE("too small!"); lastError="file is too small"; delete[] f; return false; } - if (memcmp(f,DIV_DMF_MAGIC,16)!=0 && memcmp(f,DIV_FUR_MAGIC,16)!=0) { - // try loading as a .mod first before trying to decompress - // TODO: move to a different location? - logD("loading as .mod..."); - if (loadMod(f,slen)) { - delete[] f; - return true; - } - - lastError="not a .mod song"; - logD("loading as zlib..."); - // try zlib + + if (!systemsRegistered) registerSystems(); + + // step 1: try loading as a zlib-compressed file + logD("trying zlib..."); + try { z_stream zl; memset(&zl,0,sizeof(z_stream)); @@ -1956,14 +2423,13 @@ bool DivEngine::load(unsigned char* f, size_t slen) { nextErr=inflateInit(&zl); if (nextErr!=Z_OK) { if (zl.msg==NULL) { - logE("zlib error: unknown! %d",nextErr); + logD("zlib error: unknown! %d",nextErr); } else { - logE("zlib error: %s",zl.msg); + logD("zlib error: %s",zl.msg); } inflateEnd(&zl); - delete[] f; - lastError="not a .dmf song"; - return false; + lastError="not a .dmf/.fur song"; + throw NotZlibException(0); } std::vector blocks; @@ -1975,18 +2441,17 @@ bool DivEngine::load(unsigned char* f, size_t slen) { nextErr=inflate(&zl,Z_SYNC_FLUSH); if (nextErr!=Z_OK && nextErr!=Z_STREAM_END) { if (zl.msg==NULL) { - logE("zlib error: unknown error! %d",nextErr); + logD("zlib error: unknown error! %d",nextErr); lastError="unknown decompression error"; } else { - logE("zlib inflate: %s",zl.msg); + logD("zlib inflate: %s",zl.msg); lastError=fmt::sprintf("decompression error: %s",zl.msg); } for (InflateBlock* i: blocks) delete i; blocks.clear(); delete ib; inflateEnd(&zl); - delete[] f; - return false; + throw NotZlibException(0); } ib->blockSize=ib->len-zl.avail_out; blocks.push_back(ib); @@ -1997,16 +2462,15 @@ bool DivEngine::load(unsigned char* f, size_t slen) { nextErr=inflateEnd(&zl); if (nextErr!=Z_OK) { if (zl.msg==NULL) { - logE("zlib end error: unknown error! %d",nextErr); + logD("zlib end error: unknown error! %d",nextErr); lastError="unknown decompression finish error"; } else { - logE("zlib end: %s",zl.msg); + logD("zlib end: %s",zl.msg); lastError=fmt::sprintf("decompression finish error: %s",zl.msg); } for (InflateBlock* i: blocks) delete i; blocks.clear(); - delete[] f; - return false; + throw NotZlibException(0); } size_t finalSize=0; @@ -2015,12 +2479,11 @@ bool DivEngine::load(unsigned char* f, size_t slen) { finalSize+=i->blockSize; } if (finalSize<1) { - logE("compressed too small!"); + logD("compressed too small!"); lastError="file too small"; for (InflateBlock* i: blocks) delete i; blocks.clear(); - delete[] f; - return false; + throw NotZlibException(0); } file=new unsigned char[finalSize]; for (InflateBlock* i: blocks) { @@ -2031,16 +2494,28 @@ bool DivEngine::load(unsigned char* f, size_t slen) { blocks.clear(); len=finalSize; delete[] f; - } else { - logD("loading as uncompressed"); - file=(unsigned char*)f; + } catch (NotZlibException& e) { + logD("not zlib. loading as raw..."); + file=f; len=slen; } + + // step 2: try loading as .fur or .dmf if (memcmp(file,DIV_DMF_MAGIC,16)==0) { return loadDMF(file,len); + } else if (memcmp(file,DIV_FTM_MAGIC,18)==0) { + return loadFTM(file,len); } else if (memcmp(file,DIV_FUR_MAGIC,16)==0) { return loadFur(file,len); } + + // step 3: try loading as .mod + if (loadMod(f,slen)) { + delete[] f; + return true; + } + + // step 4: not a valid file logE("not a valid module!"); lastError="not a compatible song"; delete[] file; @@ -2188,7 +2663,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { } for (int i=0; iwriteC(song.pat[i].effectRows); + w->writeC(song.pat[i].effectCols); } for (int i=0; iwriteC(song.buggyPortaAfterSlide); w->writeC(song.gbInsAffectsEnvelope); w->writeC(song.sharedExtStat); - for (int i=0; i<25; i++) { + w->writeC(song.ignoreDACModeOutsideIntendedChannel); + w->writeC(song.e1e2AlsoTakePriority); + w->writeC(song.newSegaPCM); + w->writeC(song.fbPortaPause); + w->writeC(song.snDutyReset); + w->writeC(song.pitchMacroIsLinear); + for (int i=0; i<19; i++) { w->writeC(0); } @@ -2273,7 +2754,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeS(pat->data[j][1]); // octave w->writeS(pat->data[j][2]); // instrument w->writeS(pat->data[j][3]); // volume - w->write(&pat->data[j][4],2*song.pat[i>>16].effectRows*2); // effects + w->write(&pat->data[j][4],2*song.pat[i>>16].effectCols*2); // effects } w->writeString(pat->name,false); @@ -2525,7 +3006,13 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { } else { // STD if (sys!=DIV_SYSTEM_GB) { w->writeC(i->std.volMacro.len); - w->write(i->std.volMacro.val,4*i->std.volMacro.len); + if ((sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) && i->c64.volIsCutoff) { + for (int j=0; jstd.volMacro.len; j++) { + w->writeI(i->std.volMacro.val[j]+18); + } + } else { + w->write(i->std.volMacro.val,4*i->std.volMacro.len); + } if (i->std.volMacro.len>0) { w->writeC(i->std.volMacro.loop); } @@ -2545,7 +3032,13 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { w->writeC(i->std.arpMacro.mode); w->writeC(i->std.dutyMacro.len); - w->write(i->std.dutyMacro.val,4*i->std.dutyMacro.len); + if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) { + for (int j=0; jstd.dutyMacro.len; j++) { + w->writeI(i->std.dutyMacro.val[j]+12); + } + } else { + w->write(i->std.dutyMacro.val,4*i->std.dutyMacro.len); + } if (i->std.dutyMacro.len>0) { w->writeC(i->std.dutyMacro.loop); } @@ -2607,7 +3100,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { } for (int i=0; iwriteC(song.pat[i].effectRows); + w->writeC(song.pat[i].effectCols); for (int j=0; jwriteS(pat->data[k][0]); // note w->writeS(pat->data[k][1]); // octave w->writeS(pat->data[k][3]); // volume - w->write(&pat->data[k][4],2*song.pat[i].effectRows*2); // effects + w->write(&pat->data[k][4],2*song.pat[i].effectCols*2); // effects w->writeS(pat->data[k][2]); // instrument } } diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index b88da310e..521ab4249 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -30,7 +30,67 @@ enum DivInsFormats { DIV_INSFORMAT_BTI, DIV_INSFORMAT_S3I, DIV_INSFORMAT_SBI, - DIV_INSFORMAT_OPM + DIV_INSFORMAT_Y12, + DIV_INSFORMAT_OPLI, + DIV_INSFORMAT_OPNI, + DIV_INSFORMAT_BNK, + DIV_INSFORMAT_OPM, + DIV_INSFORMAT_FF, +}; + +// Patch data structures + +// SBI and some other OPL containers +struct sbi_t { + uint8_t Mcharacteristics, + Ccharacteristics, + Mscaling_output, + Cscaling_output, + Meg_AD, + Ceg_AD, + Meg_SR, + Ceg_SR, + Mwave, + Cwave, + FeedConnect; +}; + +// Adlib Visual Composer BNK +struct bnkop_t { + uint8_t ksl, + multiple, + feedback, // op1 only + attack, + sustain, + eg, + decay, + releaseRate, + totalLevel, + am, + vib, + ksr, + con; // op1 only +}; +struct bnktimbre_t { + uint8_t mode, + percVoice; + bnkop_t op[2]; + uint8_t wave0, + wave1; +}; + +auto readSbiOpData = [](sbi_t& sbi, SafeReader& reader) { + sbi.Mcharacteristics = reader.readC(); + sbi.Ccharacteristics = reader.readC(); + sbi.Mscaling_output = reader.readC(); + sbi.Cscaling_output = reader.readC(); + sbi.Meg_AD = reader.readC(); + sbi.Ceg_AD = reader.readC(); + sbi.Meg_SR = reader.readC(); + sbi.Ceg_SR = reader.readC(); + sbi.Mwave = reader.readC(); + sbi.Cwave = reader.readC(); + sbi.FeedConnect = reader.readC(); }; void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, String& stripPath) { @@ -44,7 +104,7 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St logD(".dmp version %d",version); } catch (EndOfFileException& e) { lastError="premature end of file"; - logE("premature end of file!"); + logE("premature end of file"); delete ins; return; } @@ -103,7 +163,7 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St } } catch (EndOfFileException& e) { lastError="premature end of file"; - logE("premature end of file!"); + logE("premature end of file"); delete ins; return; } @@ -285,6 +345,16 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St ins->c64.bp=reader.readC(); ins->c64.lp=reader.readC(); ins->c64.ch3off=reader.readC(); + + // weird storage + if (ins->c64.volIsCutoff) { + for (int j=0; jstd.volMacro.len; j++) { + ins->std.volMacro.val[j]-=18; + } + } + for (int j=0; jstd.dutyMacro.len; j++) { + ins->std.dutyMacro.val[j]-=12; + } } if (ins->type==DIV_INS_GB) { ins->gb.envVol=reader.readC(); @@ -295,7 +365,7 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St } } catch (EndOfFileException& e) { lastError="premature end of file"; - logE("premature end of file!"); + logE("premature end of file"); delete ins; return; } @@ -330,7 +400,7 @@ void DivEngine::loadTFI(SafeReader& reader, std::vector& ret, St } } catch (EndOfFileException& e) { lastError="premature end of file"; - logE("premature end of file!"); + logE("premature end of file"); delete ins; return; } @@ -372,7 +442,7 @@ void DivEngine::loadVGI(SafeReader& reader, std::vector& ret, St } } catch (EndOfFileException& e) { lastError="premature end of file"; - logE("premature end of file!"); + logE("premature end of file"); delete ins; return; } @@ -389,71 +459,70 @@ void DivEngine::loadS3I(SafeReader& reader, std::vector& ret, St if (s3i_type >= 2) { ins->type = DIV_INS_OPL; + if (s3i_type > 2 && s3i_type <= 7) { + ins->fm.opllPreset = (uint8_t)(1<<4); // Flag as Drum preset. + } // skip internal filename - we'll use the long name description reader.seek(12, SEEK_CUR); // skip reserved bytes reader.seek(3, SEEK_CUR); - // 12-byte opl value - uint8_t s3i_Mcharacteristics = reader.readC(); - uint8_t s3i_Ccharacteristics = reader.readC(); - uint8_t s3i_Mscaling_output = reader.readC(); - uint8_t s3i_Cscaling_output = reader.readC(); - uint8_t s3i_Meg_AD = reader.readC(); - uint8_t s3i_Ceg_AD = reader.readC(); - uint8_t s3i_Meg_SR = reader.readC(); - uint8_t s3i_Ceg_SR = reader.readC(); - uint8_t s3i_Mwave = reader.readC(); - uint8_t s3i_Cwave = reader.readC(); - uint8_t s3i_FeedConnect = reader.readC(); + // 12-byte opl value - identical to SBI format + sbi_t s3i; + + readSbiOpData(s3i, reader); DivInstrumentFM::Operator& opM = ins->fm.op[0]; DivInstrumentFM::Operator& opC = ins->fm.op[1]; ins->fm.ops = 2; - opM.mult = s3i_Mcharacteristics & 0xF; - opM.ksr = ((s3i_Mcharacteristics >> 4) & 0x1); - opM.sus = ((s3i_Mcharacteristics >> 5) & 0x1); - opM.vib = ((s3i_Mcharacteristics >> 6) & 0x1); - opM.am = ((s3i_Mcharacteristics >> 7) & 0x1); - opM.tl = s3i_Mscaling_output & 0x3F; - opM.ksl = ((s3i_Mscaling_output >> 6) & 0x3); - opM.ar = ((s3i_Meg_AD >> 4) & 0xF); - opM.dr = (s3i_Meg_AD & 0xF); - opM.rr = (s3i_Meg_SR & 0xF); - opM.sl = ((s3i_Meg_SR >> 4) & 0xF); - opM.ws = s3i_Mwave; + opM.mult = s3i.Mcharacteristics & 0xF; + opM.ksr = ((s3i.Mcharacteristics >> 4) & 0x1); + opM.sus = ((s3i.Mcharacteristics >> 5) & 0x1); + opM.vib = ((s3i.Mcharacteristics >> 6) & 0x1); + opM.am = ((s3i.Mcharacteristics >> 7) & 0x1); + opM.tl = s3i.Mscaling_output & 0x3F; + opM.ksl = ((s3i.Mscaling_output >> 6) & 0x3); + opM.ar = ((s3i.Meg_AD >> 4) & 0xF); + opM.dr = (s3i.Meg_AD & 0xF); + opM.rr = (s3i.Meg_SR & 0xF); + opM.sl = ((s3i.Meg_SR >> 4) & 0xF); + opM.ws = s3i.Mwave; - ins->fm.alg = (s3i_FeedConnect & 0x1); - ins->fm.fb = ((s3i_FeedConnect >> 1) & 0x7); + ins->fm.alg = (s3i.FeedConnect & 0x1); + ins->fm.fb = ((s3i.FeedConnect >> 1) & 0x7); - opC.mult = s3i_Ccharacteristics & 0xF; - opC.ksr = ((s3i_Ccharacteristics >> 4) & 0x1); - opC.sus = ((s3i_Ccharacteristics >> 5) & 0x1); - opC.vib = ((s3i_Ccharacteristics >> 6) & 0x1); - opC.am = ((s3i_Ccharacteristics >> 7) & 0x1); - opC.tl = s3i_Cscaling_output & 0x3F; - opC.ksl = ((s3i_Cscaling_output >> 6) & 0x3); - opC.ar = ((s3i_Ceg_AD >> 4) & 0xF); - opC.dr = (s3i_Ceg_AD & 0xF); - opC.rr = (s3i_Ceg_SR & 0xF); - opC.sl = ((s3i_Ceg_SR >> 4) & 0xF); - opC.ws = s3i_Cwave; + opC.mult = s3i.Ccharacteristics & 0xF; + opC.ksr = ((s3i.Ccharacteristics >> 4) & 0x1); + opC.sus = ((s3i.Ccharacteristics >> 5) & 0x1); + opC.vib = ((s3i.Ccharacteristics >> 6) & 0x1); + opC.am = ((s3i.Ccharacteristics >> 7) & 0x1); + opC.tl = s3i.Cscaling_output & 0x3F; + opC.ksl = ((s3i.Cscaling_output >> 6) & 0x3); + opC.ar = ((s3i.Ceg_AD >> 4) & 0xF); + opC.dr = (s3i.Ceg_AD & 0xF); + opC.rr = (s3i.Ceg_SR & 0xF); + opC.sl = ((s3i.Ceg_SR >> 4) & 0xF); + opC.ws = s3i.Cwave; // Skip more stuff we don't need reader.seek(21, SEEK_CUR); } else { + lastError="S3I PCM samples currently not supported."; logE("S3I PCM samples currently not supported."); } ins->name = reader.readString(28); + ins->name = (ins->name.size() == 0) ? stripPath : ins->name; + int s3i_signature = reader.readI(); if (s3i_signature != 0x49524353) { + addWarning("S3I signature invalid."); logW("S3I signature invalid."); }; } catch (EndOfFileException& e) { - lastError = "premature end of file"; - logE("premature end of file!"); + lastError="premature end of file"; + logE("premature end of file"); delete ins; return; } @@ -469,77 +538,69 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector& ret, St int sbi_header = reader.readI(); // SBI header determines format - bool is_2op = (sbi_header == 0x1A494253); // SBI\x1A + bool is_2op = (sbi_header == 0x1A494253 || sbi_header == 0x1A504F32); // SBI\x1A or 2OP\x1A bool is_4op = (sbi_header == 0x1A504F34); // 4OP\x1A bool is_6op = (sbi_header == 0x1A504F36); // 6OP\x1A - Freq Monster 801-specific // 32-byte null terminated instrument name - ins->name = reader.readString(32); + String patchName = reader.readString(32); + patchName = (patchName.size() == 0) ? stripPath : patchName; - // 2op SBI - uint8_t sbi_Mcharacteristics = reader.readC(); - uint8_t sbi_Ccharacteristics = reader.readC(); - uint8_t sbi_Mscaling_output = reader.readC(); - uint8_t sbi_Cscaling_output = reader.readC(); - uint8_t sbi_Meg_AD = reader.readC(); - uint8_t sbi_Ceg_AD = reader.readC(); - uint8_t sbi_Meg_SR = reader.readC(); - uint8_t sbi_Ceg_SR = reader.readC(); - uint8_t sbi_Mwave = reader.readC(); - uint8_t sbi_Cwave = reader.readC(); - uint8_t sbi_FeedConnect = reader.readC(); + auto writeOp = [](sbi_t& sbi, DivInstrumentFM::Operator& opM, DivInstrumentFM::Operator& opC) { + opM.mult = sbi.Mcharacteristics & 0xF; + opM.ksr = ((sbi.Mcharacteristics >> 4) & 0x1); + opM.sus = ((sbi.Mcharacteristics >> 5) & 0x1); + opM.vib = ((sbi.Mcharacteristics >> 6) & 0x1); + opM.am = ((sbi.Mcharacteristics >> 7) & 0x1); + opM.tl = sbi.Mscaling_output & 0x3F; + opM.ksl = ((sbi.Mscaling_output >> 6) & 0x3); + opM.ar = ((sbi.Meg_AD >> 4) & 0xF); + opM.dr = (sbi.Meg_AD & 0xF); + opM.rr = (sbi.Meg_SR & 0xF); + opM.sl = ((sbi.Meg_SR >> 4) & 0xF); + opM.ws = sbi.Mwave; - // 4op SBI - uint8_t sbi_M4characteristics; - uint8_t sbi_C4characteristics; - uint8_t sbi_M4scaling_output; - uint8_t sbi_C4scaling_output; - uint8_t sbi_M4eg_AD; - uint8_t sbi_C4eg_AD; - uint8_t sbi_M4eg_SR; - uint8_t sbi_C4eg_SR; - uint8_t sbi_M4wave; - uint8_t sbi_C4wave; - uint8_t sbi_4opConnect; + opC.mult = sbi.Ccharacteristics & 0xF; + opC.ksr = ((sbi.Ccharacteristics >> 4) & 0x1); + opC.sus = ((sbi.Ccharacteristics >> 5) & 0x1); + opC.vib = ((sbi.Ccharacteristics >> 6) & 0x1); + opC.am = ((sbi.Ccharacteristics >> 7) & 0x1); + opC.tl = sbi.Cscaling_output & 0x3F; + opC.ksl = ((sbi.Cscaling_output >> 6) & 0x3); + opC.ar = ((sbi.Ceg_AD >> 4) & 0xF); + opC.dr = (sbi.Ceg_AD & 0xF); + opC.rr = (sbi.Ceg_SR & 0xF); + opC.sl = ((sbi.Ceg_SR >> 4) & 0xF); + opC.ws = sbi.Cwave; + }; + + sbi_t sbi_op12; // 2op (+6op portion) + sbi_t sbi_op34; // 4op + readSbiOpData(sbi_op12, reader); + if (is_2op) { DivInstrumentFM::Operator& opM = ins->fm.op[0]; DivInstrumentFM::Operator& opC = ins->fm.op[1]; ins->fm.ops = 2; - opM.mult = sbi_Mcharacteristics & 0xF; - opM.ksr = ((sbi_Mcharacteristics >> 4) & 0x1); - opM.sus = ((sbi_Mcharacteristics >> 5) & 0x1); - opM.vib = ((sbi_Mcharacteristics >> 6) & 0x1); - opM.am = ((sbi_Mcharacteristics >> 7) & 0x1); - opM.tl = sbi_Mscaling_output & 0x3F; - opM.ksl = ((sbi_Mscaling_output >> 6) & 0x3); - opM.ar = ((sbi_Meg_AD >> 4) & 0xF); - opM.dr = (sbi_Meg_AD & 0xF); - opM.rr = (sbi_Meg_SR & 0xF); - opM.sl = ((sbi_Meg_SR >> 4) & 0xF); - opM.ws = sbi_Mwave; + ins->name = patchName; + writeOp(sbi_op12, opM, opC); + ins->fm.alg = (sbi_op12.FeedConnect & 0x1); + ins->fm.fb = ((sbi_op12.FeedConnect >> 1) & 0x7); - ins->fm.alg = (sbi_FeedConnect & 0x1); - ins->fm.fb = ((sbi_FeedConnect >> 1) & 0x7); - - opC.mult = sbi_Ccharacteristics & 0xF; - opC.ksr = ((sbi_Ccharacteristics >> 4) & 0x1); - opC.sus = ((sbi_Ccharacteristics >> 5) & 0x1); - opC.vib = ((sbi_Ccharacteristics >> 6) & 0x1); - opC.am = ((sbi_Ccharacteristics >> 7) & 0x1); - opC.tl = sbi_Cscaling_output & 0x3F; - opC.ksl = ((sbi_Cscaling_output >> 6) & 0x3); - opC.ar = ((sbi_Ceg_AD >> 4) & 0xF); - opC.dr = (sbi_Ceg_AD & 0xF); - opC.rr = (sbi_Ceg_SR & 0xF); - opC.sl = ((sbi_Ceg_SR >> 4) & 0xF); - opC.ws = sbi_Cwave; + // SBTimbre extensions + uint8_t perc_voc = reader.readC(); + if (perc_voc >= 6) { + ins->fm.opllPreset = (uint8_t)(1 << 4); + } // Ignore rest of file - rest is 'reserved padding'. - reader.seek(0, SEEK_END); - } + reader.seek(4, SEEK_CUR); + ret.push_back(ins); - if (is_4op || is_6op) { + } else if (is_4op || is_6op) { + readSbiOpData(sbi_op34, reader); + // Operator placement is different so need to place in correct registers. // Note: 6op is an unofficial extension of 4op SBIs by Darron Broad (Freq Monster 801). // We'll only use the 4op portion here for pure OPL3. @@ -548,100 +609,576 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector& ret, St DivInstrumentFM::Operator& opM4 = ins->fm.op[1]; DivInstrumentFM::Operator& opC4 = ins->fm.op[3]; ins->fm.ops = 4; + ins->name = patchName; + ins->fm.alg = (sbi_op12.FeedConnect & 0x1) | ((sbi_op34.FeedConnect & 0x1) << 1); + ins->fm.fb = ((sbi_op34.FeedConnect >> 1) & 0x7); + writeOp(sbi_op12, opM, opC); + writeOp(sbi_op34, opM4, opC4); - sbi_M4characteristics = reader.readC(); - sbi_C4characteristics = reader.readC(); - sbi_M4scaling_output = reader.readC(); - sbi_C4scaling_output = reader.readC(); - sbi_M4eg_AD = reader.readC(); - sbi_C4eg_AD = reader.readC(); - sbi_M4eg_SR = reader.readC(); - sbi_C4eg_SR = reader.readC(); - sbi_M4wave = reader.readC(); - sbi_C4wave = reader.readC(); - sbi_4opConnect = reader.readC(); - - ins->fm.alg = (sbi_FeedConnect & 0x1) | ((sbi_4opConnect & 0x1) << 1); - ins->fm.fb = ((sbi_FeedConnect >> 1) & 0x7); + if (is_6op) { + // Freq Monster 801 6op SBIs use a 4+2op layout + // Save the 4op portion before reading the 2op part + ins->name = fmt::sprintf("%s (4op)", ins->name); + ret.push_back(ins); - opM.mult = sbi_Mcharacteristics & 0xF; - opM.ksr = ((sbi_Mcharacteristics >> 4) & 0x1); - opM.sus = ((sbi_Mcharacteristics >> 5) & 0x1); - opM.vib = ((sbi_Mcharacteristics >> 6) & 0x1); - opM.am = ((sbi_Mcharacteristics >> 7) & 0x1); - opM.tl = sbi_Mscaling_output & 0x3F; - opM.ksl = ((sbi_Mscaling_output >> 6) & 0x3); - opM.ar = ((sbi_Meg_AD >> 4) & 0xF); - opM.dr = (sbi_Meg_AD & 0xF); - opM.rr = (sbi_Meg_SR & 0xF); - opM.sl = ((sbi_Meg_SR >> 4) & 0xF); - opM.ws = sbi_Mwave; + readSbiOpData(sbi_op12, reader); - opC.mult = sbi_Ccharacteristics & 0xF; - opC.ksr = ((sbi_Ccharacteristics >> 4) & 0x1); - opC.sus = ((sbi_Ccharacteristics >> 5) & 0x1); - opC.vib = ((sbi_Ccharacteristics >> 6) & 0x1); - opC.am = ((sbi_Ccharacteristics >> 7) & 0x1); - opC.tl = sbi_Cscaling_output & 0x3F; - opC.ksl = ((sbi_Cscaling_output >> 6) & 0x3); - opC.ar = ((sbi_Ceg_AD >> 4) & 0xF); - opC.dr = (sbi_Ceg_AD & 0xF); - opC.rr = (sbi_Ceg_SR & 0xF); - opC.sl = ((sbi_Ceg_SR >> 4) & 0xF); - opC.ws = sbi_Cwave; - - opM4.mult = sbi_M4characteristics & 0xF; - opM4.ksr = ((sbi_M4characteristics >> 4) & 0x1); - opM4.sus = ((sbi_M4characteristics >> 5) & 0x1); - opM4.vib = ((sbi_M4characteristics >> 6) & 0x1); - opM4.am = ((sbi_M4characteristics >> 7) & 0x1); - opM4.tl = sbi_M4scaling_output & 0x3F; - opM4.ksl = ((sbi_M4scaling_output >> 6) & 0x3); - opM4.ar = ((sbi_M4eg_AD >> 4) & 0xF); - opM4.dr = (sbi_M4eg_AD & 0xF); - opM4.rr = (sbi_M4eg_SR & 0xF); - opM4.sl = ((sbi_M4eg_SR >> 4) & 0xF); - opM4.ws = sbi_M4wave; - - opC4.mult = sbi_C4characteristics & 0xF; - opC4.ksr = ((sbi_C4characteristics >> 4) & 0x1); - opC4.sus = ((sbi_C4characteristics >> 5) & 0x1); - opC4.vib = ((sbi_C4characteristics >> 6) & 0x1); - opC4.am = ((sbi_C4characteristics >> 7) & 0x1); - opC4.tl = sbi_C4scaling_output & 0x3F; - opC4.ksl = ((sbi_C4scaling_output >> 6) & 0x3); - opC4.ar = ((sbi_C4eg_AD >> 4) & 0xF); - opC4.dr = (sbi_C4eg_AD & 0xF); - opC4.rr = (sbi_C4eg_SR & 0xF); - opC4.sl = ((sbi_C4eg_SR >> 4) & 0xF); - opC4.ws = sbi_C4wave; + ins = new DivInstrument; + DivInstrumentFM::Operator& opM6 = ins->fm.op[0]; + DivInstrumentFM::Operator& opC6 = ins->fm.op[1]; + ins->type = DIV_INS_OPL; + ins->fm.ops = 2; + ins->name = fmt::sprintf("%s (2op)", patchName); + writeOp(sbi_op12, opM6, opC6); + ins->fm.alg = (sbi_op12.FeedConnect & 0x1); + ins->fm.fb = ((sbi_op12.FeedConnect >> 1) & 0x7); + } // Ignore rest of file once we've read in all we need. // Note: Freq Monster 801 adds a ton of other additional fields irrelevant to chip registers. + // If instrument transpose is ever supported, we can read it in maybe? reader.seek(0, SEEK_END); + ret.push_back(ins); } } catch (EndOfFileException& e) { - lastError = "premature end of file"; - logE("premature end of file!"); + lastError="premature end of file"; + logE("premature end of file"); delete ins; + } +} + +void DivEngine::loadOPLI(SafeReader& reader, std::vector& ret, String& stripPath) { + DivInstrument* ins = new DivInstrument; + + try { + reader.seek(0, SEEK_SET); + String header = reader.readString(11); + if (header == "WOPL3-INST") { + uint16_t version = reader.readS(); + if (version > 3) { + logW("Unknown OPLI version."); + } + + reader.readC(); // skip isPerc field + + ins->type = DIV_INS_OPL; + String insName = reader.readString(32); + insName = (insName.size() > 0) ? insName : stripPath; + ins->name = insName; + reader.seek(7, SEEK_CUR); // skip MIDI params + uint8_t instTypeFlags = reader.readC(); // [0EEEDCBA] - see WOPL/OPLI spec + + bool is_4op = ((instTypeFlags & 0x1) == 1); + bool is_2x2op = (((instTypeFlags>>1) & 0x1) == 1); + bool is_rhythm = (((instTypeFlags>>4) & 0x7) > 0); + + auto readOpliOp = [](SafeReader& reader, DivInstrumentFM::Operator& op) { + uint8_t characteristics = reader.readC(); + uint8_t keyScaleLevel = reader.readC(); + uint8_t attackDecay = reader.readC(); + uint8_t sustainRelease = reader.readC(); + uint8_t waveSelect = reader.readC(); + + op.mult = characteristics & 0xF; + op.ksr = ((characteristics >> 4) & 0x1); + op.sus = ((characteristics >> 5) & 0x1); + op.vib = ((characteristics >> 6) & 0x1); + op.am = ((characteristics >> 7) & 0x1); + op.tl = keyScaleLevel & 0x3F; + op.ksl = ((keyScaleLevel >> 6) & 0x3); + op.ar = ((attackDecay >> 4) & 0xF); + op.dr = attackDecay & 0xF; + op.rr = sustainRelease & 0xF; + op.sl = ((sustainRelease >> 4) & 0xF); + op.ws = waveSelect; + }; + + uint8_t feedConnect = reader.readC(); + uint8_t feedConnect2nd = reader.readC(); + + ins->fm.alg = (feedConnect & 0x1); + ins->fm.fb = ((feedConnect >> 1) & 0xF); + + if (is_4op && !is_2x2op) { + ins->fm.ops = 4; + ins->fm.alg = (feedConnect & 0x1) | ((feedConnect2nd & 0x1) << 1); + for (int i : {2,0,3,1}) { // omfg >_< + readOpliOp(reader, ins->fm.op[i]); + } + } else { + ins->fm.ops = 2; + for (int i : {1,0}) { + readOpliOp(reader, ins->fm.op[i]); + } + if (is_rhythm) { + ins->fm.opllPreset = (uint8_t)(1<<4); + + } else if (is_2x2op) { + // Note: Pair detuning offset not mappable. Use E5xx effect :P + ins->name = fmt::sprintf("%s (1)", insName); + ret.push_back(ins); + + ins = new DivInstrument; + ins->type = DIV_INS_OPL; + ins->name = fmt::sprintf("%s (2)", insName); + for (int i : {1,0}) { + readOpliOp(reader, ins->fm.op[i]); + } + } + } + + // Skip rest of file + reader.seek(0, SEEK_END); + ret.push_back(ins); + } + } catch (EndOfFileException& e) { + lastError="premature end of file"; + logE("premature end of file"); + delete ins; + } +} + +void DivEngine::loadOPNI(SafeReader& reader, std::vector& ret, String& stripPath) { + DivInstrument* ins = new DivInstrument; + + try { + reader.seek(0, SEEK_SET); + + String header = reader.readString(11); + if (header == "WOPN2-INST" || header == "WOPN2-IN2T") { // omfg >_< + uint16_t version = reader.readS(); + if (!(version >= 2) || version > 0xF) { + // version 1 doesn't have a version field........ + reader.seek(-2, SEEK_CUR); + } + + reader.readC(); // skip isPerc + ins->type = DIV_INS_FM; + ins->fm.ops = 4; + + String insName = reader.readString(32); + ins->name = (insName.size() > 0) ? insName : stripPath; + reader.seek(3, SEEK_CUR); // skip MIDI params + uint8_t feedAlgo = reader.readC(); + ins->fm.alg = (feedAlgo & 0x7); + ins->fm.fb = ((feedAlgo>>3) & 0x7); + reader.readC(); // Skip global bank flags - see WOPN/OPNI spec + + auto readOpniOp = [](SafeReader& reader, DivInstrumentFM::Operator& op) { + uint8_t dtMul = reader.readC(); + uint8_t totalLevel = reader.readC(); + uint8_t arRateScale = reader.readC(); + uint8_t drAmpEnable = reader.readC(); + uint8_t d2r = reader.readC(); + uint8_t susRelease = reader.readC(); + uint8_t ssgEg = reader.readC(); + + op.mult = dtMul & 0xF; + op.dt = ((dtMul >> 4) & 0x7); + op.tl = totalLevel & 0x3F; + op.rs = ((arRateScale >> 6) & 0x3); + op.ar = arRateScale & 0x1F; + op.dr = drAmpEnable & 0x1F; + op.am = ((drAmpEnable >> 7) & 0x1); + op.d2r = d2r & 0x1F; + op.rr = susRelease & 0xF; + op.sl = ((susRelease >> 4) & 0xF); + op.ssgEnv = ssgEg; + }; + + for (int i = 0; i < 4; ++i) { + readOpniOp(reader, ins->fm.op[i]); + } + + // Skip rest of file + reader.seek(0, SEEK_END); + ret.push_back(ins); + } + } catch (EndOfFileException& e) { + lastError="premature end of file"; + logE("premature end of file"); + delete ins; + } +} + +void DivEngine::loadY12(SafeReader& reader, std::vector& ret, String& stripPath) { + DivInstrument *ins = new DivInstrument; + + try { + reader.seek(0, SEEK_SET); + ins->type = DIV_INS_FM; + ins->fm.ops = 4; + ins->name = stripPath; + + for (int i = 0; i < 4; ++i) { + DivInstrumentFM::Operator& insOp = ins->fm.op[i]; + uint8_t tmp = reader.readC(); + insOp.mult = tmp & 0xF; + insOp.dt = ((tmp >> 4) & 0x7); + insOp.tl = (reader.readC() & 0x3F); + tmp = reader.readC(); + insOp.rs = ((tmp >> 6) & 0x3); + insOp.ar = tmp & 0x1F; + tmp = reader.readC(); + insOp.dr = tmp & 0x1F; + insOp.am = ((tmp >> 7) & 0x1); + insOp.d2r = (reader.readC() & 0x1F); + tmp = reader.readC(); + insOp.rr = tmp & 0xF; + insOp.sl = ((tmp >> 4) & 0xF); + insOp.ssgEnv = reader.readC(); + if (!reader.seek(9, SEEK_CUR)) { + throw EndOfFileException(&reader, reader.tell() + 9); + } + } + ins->fm.alg = reader.readC(); + ins->fm.fb = reader.readC(); + if (!reader.seek(62, SEEK_CUR)) { + throw EndOfFileException(&reader, reader.tell() + 62); + } + ret.push_back(ins); + } catch (EndOfFileException& e) { + lastError="premature end of file"; + logE("premature end of file"); + delete ins; + } +} + +void DivEngine::loadBNK(SafeReader& reader, std::vector& ret, String& stripPath) { + std::vector insList; + std::vector instNames; + reader.seek(0, SEEK_SET); + + // First distinguish between GEMS BNK and Adlib BNK + uint64_t header = reader.readL(); + bool is_adlib = ((header>>8) == 0x2d42494c444100L); + bool is_failed = false; + int readCount = 0; + + if (is_adlib) { + try { + reader.seek(0x0c, SEEK_SET); + uint32_t name_offset = reader.readI(); + reader.seek(0x10, SEEK_SET); + uint32_t data_offset = reader.readI(); + + // Seek to BNK patch names + reader.seek(name_offset, SEEK_SET); + while (reader.tell() < data_offset) { + reader.seek(3, SEEK_CUR); + instNames.push_back(new String(reader.readString(9))); + ++readCount; + } + + // Seek to BNK data + if (!reader.seek(data_offset, SEEK_SET)) { + throw EndOfFileException(&reader, data_offset); + }; + + // Read until EOF + for (int i = 0; i < readCount; ++i) { + bnktimbre_t timbre; + insList.push_back(new DivInstrument); + auto& ins = insList[i]; + + ins->type = DIV_INS_OPL; + ins->fm.ops = 2; + + timbre.mode = reader.readC(); + timbre.percVoice = reader.readC(); + if (timbre.mode == 1) { + ins->fm.opllPreset = (uint8_t)(1<<4); + } + ins->fm.op[0].ksl = reader.readC(); + ins->fm.op[0].mult = reader.readC(); + ins->fm.fb = reader.readC(); + ins->fm.op[0].ar = reader.readC(); + ins->fm.op[0].sl = reader.readC(); + ins->fm.op[0].sus = (reader.readC() != 0) ? 1 : 0; + ins->fm.op[0].dr = reader.readC(); + ins->fm.op[0].rr = reader.readC(); + ins->fm.op[0].tl = reader.readC(); + ins->fm.op[0].am = reader.readC(); + ins->fm.op[0].vib = reader.readC(); + ins->fm.op[0].ksr = reader.readC(); + ins->fm.alg = (reader.readC() == 0) ? 1 : 0; + + ins->fm.op[1].ksl = reader.readC(); + ins->fm.op[1].mult = reader.readC(); + reader.readC(); // skip + ins->fm.op[1].ar = reader.readC(); + ins->fm.op[1].sl = reader.readC(); + ins->fm.op[1].sus = (reader.readC() != 0) ? 1 : 0; + ins->fm.op[1].dr = reader.readC(); + ins->fm.op[1].rr = reader.readC(); + ins->fm.op[1].tl = reader.readC(); + ins->fm.op[1].am = reader.readC(); + ins->fm.op[1].vib = reader.readC(); + ins->fm.op[1].ksr = reader.readC(); + reader.readC(); // skip + + ins->fm.op[0].ws = reader.readC(); + ins->fm.op[1].ws = reader.readC(); + ins->name = instNames[i]->length() > 0 ? (*instNames[i]) : fmt::sprintf("%s[%d]", stripPath, i); + } + reader.seek(0, SEEK_END); + + } catch (EndOfFileException& e) { + lastError="premature end of file"; + logE("premature end of file"); + for (int i = readCount - 1; i >= 0; --i) { + delete insList[i]; + } + is_failed = true; + } + + } else { + // assume GEMS BNK for now. + lastError="GEMS BNK currently not supported."; + logE("GEMS BNK currently not supported."); + } + + if (!is_failed) { + for (int i = 0; i < readCount; ++i) { + ret.push_back(insList[i]); + } + } + + for (auto& name : instNames) { + delete name; + } +} + +void DivEngine::loadFF(SafeReader& reader, std::vector& ret, String& stripPath) { + DivInstrument* insList[256]; + memset(insList,0,256*sizeof(void*)); + int readCount = 0; + size_t insCount = reader.size(); + insCount = (insCount >> 5) + (((insCount % 0x20) > 0) ? 1 : 0); + if (insCount > 256) insCount = 256; + uint8_t buf; + try { + reader.seek(0, SEEK_SET); + for (unsigned int i = 0; i < insCount; ++i) { + insList[i] = new DivInstrument; + DivInstrument* ins = insList[i]; + + ins->type = DIV_INS_FM; + DivInstrumentFM::Operator op; + + for (unsigned int j = 0; j < 4; j++) { + buf = reader.readC(); + ins->fm.op[j].mult = buf & 0xf; + // detune needs extra translation from register to furnace format + const int dtNative = (buf >> 4) & 0x7; + ins->fm.op[j].dt = (dtNative >= 4) ? (7 - dtNative) : (dtNative + 3); + ins->fm.op[j].ssgEnv = (buf >> 4) & 0x8; + } + for (unsigned int j = 0; j < 4; j++) { + buf = reader.readC(); + ins->fm.op[j].tl = buf & 0x7f; + ins->fm.op[j].ssgEnv |= (buf >> 5) & 0x4; + } + for (unsigned int j = 0; j < 4; j++) { + buf = reader.readC(); + ins->fm.op[j].ar = buf & 0x1f; + ins->fm.op[j].rs = buf >> 6; + } + for (unsigned int j = 0; j < 4; j++) { + buf = reader.readC(); + ins->fm.op[j].dr = buf & 0x1f; + ins->fm.op[j].ssgEnv |= (buf >> 5) & 0x3; + ins->fm.op[j].am = buf >> 7; + } + for (unsigned int j = 0; j < 4; j++) { + buf = reader.readC(); + ins->fm.op[j].d2r = buf & 0x1f; + } + for (unsigned int j = 0; j < 4; j++) { + buf = reader.readC(); + ins->fm.op[j].rr = buf & 0xf; + ins->fm.op[j].sl = buf >> 4; + } + + buf = reader.readC(); + ins->fm.alg = buf & 0x7; + ins->fm.fb = (buf >> 3) & 0x7; + + // FIXME This is encoded in Shift-JIS + ins->name = reader.readString(7); + ++readCount; + } + } catch (EndOfFileException& e) { + lastError="premature end of file"; + logE("premature end of file"); + for (int i = readCount - 1; i >= 0; --i) { + delete insList[i]; + } return; } - ret.push_back(ins); + for (unsigned int i = 0; i < insCount; ++i) { + ret.push_back(insList[i]); + } } void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, String& stripPath) { - DivInstrument* ins[128]; - memset(ins,0,128*sizeof(void*)); + std::vector insList; + + int readCount = 0; + bool is_failed = false; + + bool patchNameRead = false, + lfoRead = false, + characteristicRead = false, + m1Read = false, + c1Read = false, + m2Read = false, + c2Read = false; + + DivInstrument* newPatch = NULL; + + auto completePatchRead = [&]() { + return patchNameRead && lfoRead && characteristicRead && m1Read && c1Read && m2Read && c2Read; + }; + auto resetPatchRead = [&]() { + patchNameRead = lfoRead = characteristicRead = m1Read = c1Read = m2Read = c2Read = false; + newPatch = NULL; + }; + auto readIntStrWithinRange = [](String&& input, int limitLow, int limitHigh) { + int x = std::stoi(input.c_str()); + if (x > limitHigh || x < limitLow) { + throw std::invalid_argument(fmt::sprintf("%s is out of bounds of range [%d..%d]", input, limitLow, limitHigh)); + } + return (x>limitHigh) ? limitHigh : + (x= 4) ? (7 - op.dt) : (op.dt + 3); + op.dt2 = readIntStrWithinRange(reader.readStringToken(), 0, 3); + op.am = readIntStrWithinRange(reader.readStringToken(), 0, 1); + }; try { - String line; - + reader.seek(0, SEEK_SET); + while (!reader.isEOF()) { + String token = reader.readStringToken(); + if (token.size() == 0) { + continue; + } + + if (token.compare(0,2,"//") == 0) { + if (!reader.isEOF()) { + reader.readStringLine(); + } + continue; + } + + // At this point we know any other line would be associated with patch params + if (newPatch == NULL) { + newPatch = new DivInstrument; + newPatch->type = DIV_INS_FM; + newPatch->fm.ops = 4; + } + + // Read each line for their respective params. They may not be written in the same LINE order but they + // must absolutely be properly grouped per patch! Line prefixes must be separated by a space! (see inline comments) + + if (token.size() >= 2) { + if (token[0] == '@') { + // @:123 Name of patch + // Note: Fallback to bank filename and current patch number in _file_ order (not @n order) + newPatch->name = reader.readStringLine(); + newPatch->name = newPatch->name.size() > 0 ? newPatch->name : fmt::sprintf("%s[%d]", stripPath, readCount); + patchNameRead = true; + + } else if (token.compare(0,3,"CH:") == 0) { + // CH: PAN FL CON AMS PMS SLOT NE + reader.readStringToken(); // skip PAN + newPatch->fm.fb = readIntStrWithinRange(reader.readStringToken(), 0, 7); + newPatch->fm.alg = readIntStrWithinRange(reader.readStringToken(), 0, 7); + newPatch->fm.ams = readIntStrWithinRange(reader.readStringToken(), 0, 4); + newPatch->fm.fms = readIntStrWithinRange(reader.readStringToken(), 0, 7); + reader.readStringToken(); // skip SLOT + reader.readStringToken(); // skip NE + characteristicRead = true; + + } else if (token.compare(0,3,"C1:") == 0) { + // C1: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN + readOpmOperator(reader, newPatch->fm.op[2]); + c1Read = true; + + } else if (token.compare(0,3,"C2:") == 0) { + // C2: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN + readOpmOperator(reader, newPatch->fm.op[3]); + c2Read = true; + + } else if (token.compare(0,3,"M1:") == 0) { + // M1: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN + readOpmOperator(reader, newPatch->fm.op[0]); + m1Read = true; + + } else if (token.compare(0,3,"M2:") == 0) { + // M2: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN + readOpmOperator(reader, newPatch->fm.op[1]); + m2Read = true; + + } else if (token.compare(0,4,"LFO:") == 0) { + // LFO: LFRQ AMD PMD WF NFRQ + // Furnace patches do not store these as they are chip-global. + reader.readStringLine(); + lfoRead = true; + + } else { + // other unsupported lines ignored. + reader.readStringLine(); + } + } + + if (completePatchRead()) { + insList.push_back(newPatch); + resetPatchRead(); + ++readCount; + } + } + + if (newPatch != NULL) { + addWarning("Last OPM patch read was incomplete and therefore not imported."); + logW("Last OPM patch read was incomplete and therefore not imported."); + delete newPatch; + } + + for (int i = 0; i < readCount; ++i) { + ret.push_back(insList[i]); + } } catch (EndOfFileException& e) { lastError="premature end of file"; - logE("premature end of file!"); - return; + logE("premature end of file"); + is_failed = true; + } catch (std::invalid_argument& e) { + lastError=fmt::sprintf("Invalid value found in patch file. %s", e.what()); + logE("Invalid value found in patch file."); + logE(e.what()); + is_failed = true; + } + + if (is_failed) { + for (int i = readCount - 1; i >= 0; --i) { + delete insList[i]; + } + if (newPatch != NULL) { + delete newPatch; + } } } @@ -683,6 +1220,11 @@ std::vector DivEngine::instrumentFromFile(const char* path) { fclose(f); return ret; } + if (len==(SIZE_MAX>>1)) { + lastError=strerror(errno); + fclose(f); + return ret; + } if (len==0) { lastError=strerror(errno); fclose(f); @@ -738,7 +1280,7 @@ std::vector DivEngine::instrumentFromFile(const char* path) { } } catch (EndOfFileException& e) { lastError="premature end of file"; - logE("premature end of file!"); + logE("premature end of file"); delete ins; delete[] buf; return ret; @@ -769,8 +1311,18 @@ std::vector DivEngine::instrumentFromFile(const char* path) { format=DIV_INSFORMAT_S3I; } else if (extS==String(".sbi")) { format=DIV_INSFORMAT_SBI; + } else if (extS==String(".opli")) { + format=DIV_INSFORMAT_OPLI; + } else if (extS==String(".opni")) { + format=DIV_INSFORMAT_OPNI;; + } else if (extS==String(".y12")) { + format=DIV_INSFORMAT_Y12; + } else if (extS==String(".bnk")) { + format=DIV_INSFORMAT_BNK; } else if (extS==String(".opm")) { format=DIV_INSFORMAT_OPM; + } else if (extS==String(".ff")) { + format=DIV_INSFORMAT_FF; } } @@ -789,14 +1341,30 @@ std::vector DivEngine::instrumentFromFile(const char* path) { break; case DIV_INSFORMAT_BTI: // TODO break; - case DIV_INSFORMAT_OPM: // TODO - break; case DIV_INSFORMAT_S3I: loadS3I(reader,ret,stripPath); break; case DIV_INSFORMAT_SBI: loadSBI(reader,ret,stripPath); break; + case DIV_INSFORMAT_OPLI: + loadOPLI(reader,ret,stripPath); + break; + case DIV_INSFORMAT_OPNI: + loadOPNI(reader, ret, stripPath); + break; + case DIV_INSFORMAT_Y12: + loadY12(reader,ret,stripPath); + break; + case DIV_INSFORMAT_BNK: + loadBNK(reader, ret, stripPath); + break; + case DIV_INSFORMAT_FF: + loadFF(reader,ret,stripPath); + break; + case DIV_INSFORMAT_OPM: + loadOPM(reader, ret, stripPath); + break; } if (reader.tell()writeS(amiga.initSample); - for (int j=0; j<14; j++) { // reserved + w->writeC(amiga.useWave); + w->writeC(amiga.waveLen); + for (int j=0; j<12; j++) { // reserved w->writeC(0); } @@ -236,40 +238,40 @@ void DivInstrument::putInsData(SafeWriter* w) { for (int i=0; i<4; i++) { DivInstrumentSTD::OpMacro& op=std.opMacros[i]; for (int j=0; jwriteC(op.amMacro.val[j]); + w->writeC(op.amMacro.val[j]&0xff); } for (int j=0; jwriteC(op.arMacro.val[j]); + w->writeC(op.arMacro.val[j]&0xff); } for (int j=0; jwriteC(op.drMacro.val[j]); + w->writeC(op.drMacro.val[j]&0xff); } for (int j=0; jwriteC(op.multMacro.val[j]); + w->writeC(op.multMacro.val[j]&0xff); } for (int j=0; jwriteC(op.rrMacro.val[j]); + w->writeC(op.rrMacro.val[j]&0xff); } for (int j=0; jwriteC(op.slMacro.val[j]); + w->writeC(op.slMacro.val[j]&0xff); } for (int j=0; jwriteC(op.tlMacro.val[j]); + w->writeC(op.tlMacro.val[j]&0xff); } for (int j=0; jwriteC(op.dt2Macro.val[j]); + w->writeC(op.dt2Macro.val[j]&0xff); } for (int j=0; jwriteC(op.rsMacro.val[j]); + w->writeC(op.rsMacro.val[j]&0xff); } for (int j=0; jwriteC(op.dtMacro.val[j]); + w->writeC(op.dtMacro.val[j]&0xff); } for (int j=0; jwriteC(op.d2rMacro.val[j]); + w->writeC(op.d2rMacro.val[j]&0xff); } for (int j=0; jwriteC(op.ssgMacro.val[j]); + w->writeC(op.ssgMacro.val[j]&0xff); } } @@ -480,6 +482,44 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(ws.param2); w->writeC(ws.param3); w->writeC(ws.param4); + + // other macro modes + w->writeC(std.volMacro.mode); + w->writeC(std.dutyMacro.mode); + w->writeC(std.waveMacro.mode); + w->writeC(std.pitchMacro.mode); + w->writeC(std.ex1Macro.mode); + w->writeC(std.ex2Macro.mode); + w->writeC(std.ex3Macro.mode); + w->writeC(std.algMacro.mode); + w->writeC(std.fbMacro.mode); + w->writeC(std.fmsMacro.mode); + w->writeC(std.amsMacro.mode); + w->writeC(std.panLMacro.mode); + w->writeC(std.panRMacro.mode); + w->writeC(std.phaseResetMacro.mode); + w->writeC(std.ex4Macro.mode); + w->writeC(std.ex5Macro.mode); + w->writeC(std.ex6Macro.mode); + w->writeC(std.ex7Macro.mode); + w->writeC(std.ex8Macro.mode); + + // C64 no test + w->writeC(c64.noTest); + + // MultiPCM + w->writeC(multipcm.ar); + w->writeC(multipcm.d1r); + w->writeC(multipcm.dl); + w->writeC(multipcm.d2r); + w->writeC(multipcm.rr); + w->writeC(multipcm.rc); + w->writeC(multipcm.lfo); + w->writeC(multipcm.vib); + w->writeC(multipcm.am); + for (int j=0; j<23; j++) { // reserved + w->writeC(0); + } } DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { @@ -571,8 +611,15 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { // Amiga amiga.initSample=reader.readS(); + if (version>=82) { + amiga.useWave=reader.readC(); + amiga.waveLen=(unsigned char)reader.readC(); + } else { + reader.readC(); + reader.readC(); + } // reserved - for (int k=0; k<14; k++) reader.readC(); + for (int k=0; k<12; k++) reader.readC(); // standard std.volMacro.len=reader.readI(); @@ -609,6 +656,14 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { std.arpMacro.val[j]-=12; } } + if (type==DIV_INS_C64 && version<87) { + if (c64.volIsCutoff && !c64.filterIsAbs) for (int j=0; j=17) { reader.read(std.pitchMacro.val,4*std.pitchMacro.len); reader.read(std.ex1Macro.val,4*std.ex1Macro.len); @@ -696,20 +751,45 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { op.ssgMacro.open=reader.readC(); } + // FM macro low 8 bits for (int i=0; i<4; i++) { DivInstrumentSTD::OpMacro& op=std.opMacros[i]; - reader.read(op.amMacro.val,op.amMacro.len); - reader.read(op.arMacro.val,op.arMacro.len); - reader.read(op.drMacro.val,op.drMacro.len); - reader.read(op.multMacro.val,op.multMacro.len); - reader.read(op.rrMacro.val,op.rrMacro.len); - reader.read(op.slMacro.val,op.slMacro.len); - reader.read(op.tlMacro.val,op.tlMacro.len); - reader.read(op.dt2Macro.val,op.dt2Macro.len); - reader.read(op.rsMacro.val,op.rsMacro.len); - reader.read(op.dtMacro.val,op.dtMacro.len); - reader.read(op.d2rMacro.val,op.d2rMacro.len); - reader.read(op.ssgMacro.val,op.ssgMacro.len); + for (int j=0; j=76) { + // more macros std.panLMacro.len=reader.readI(); std.panRMacro.len=reader.readI(); std.phaseResetMacro.len=reader.readI(); @@ -888,10 +968,8 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { reader.read(std.ex6Macro.val,4*std.ex6Macro.len); reader.read(std.ex7Macro.val,4*std.ex7Macro.len); reader.read(std.ex8Macro.val,4*std.ex8Macro.len); - } - // FDS - if (version>=76) { + // FDS fds.modSpeed=reader.readI(); fds.modDepth=reader.readI(); fds.initModTableWithFirstWave=reader.readC(); @@ -921,6 +999,50 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { ws.param3=reader.readC(); ws.param4=reader.readC(); } + + // other macro modes + if (version>=84) { + std.volMacro.mode=reader.readC(); + std.dutyMacro.mode=reader.readC(); + std.waveMacro.mode=reader.readC(); + std.pitchMacro.mode=reader.readC(); + std.ex1Macro.mode=reader.readC(); + std.ex2Macro.mode=reader.readC(); + std.ex3Macro.mode=reader.readC(); + std.algMacro.mode=reader.readC(); + std.fbMacro.mode=reader.readC(); + std.fmsMacro.mode=reader.readC(); + std.amsMacro.mode=reader.readC(); + std.panLMacro.mode=reader.readC(); + std.panRMacro.mode=reader.readC(); + std.phaseResetMacro.mode=reader.readC(); + std.ex4Macro.mode=reader.readC(); + std.ex5Macro.mode=reader.readC(); + std.ex6Macro.mode=reader.readC(); + std.ex7Macro.mode=reader.readC(); + std.ex8Macro.mode=reader.readC(); + } + + // C64 no test + if (version>=89) { + c64.noTest=reader.readC(); + } + + // MultiPCM + if (version>=93) { + multipcm.ar=reader.readC(); + multipcm.d1r=reader.readC(); + multipcm.dl=reader.readC(); + multipcm.d2r=reader.readC(); + multipcm.rr=reader.readC(); + multipcm.rc=reader.readC(); + multipcm.lfo=reader.readC(); + multipcm.vib=reader.readC(); + multipcm.am=reader.readC(); + // reserved + for (int k=0; k<23; k++) reader.readC(); + } + return DIV_DATA_SUCCESS; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 605241c71..dc1b0b7f5 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -54,7 +54,12 @@ enum DivInstrumentType: unsigned short { DIV_INS_VERA=24, DIV_INS_X1_010=25, DIV_INS_VRC6_SAW=26, + DIV_INS_ES5506=27, + DIV_INS_MULTIPCM=28, + DIV_INS_SNES=29, + DIV_INS_SU=30, DIV_INS_MAX, + DIV_INS_NULL }; // FM operator structure: @@ -161,7 +166,7 @@ struct DivInstrumentMacro { unsigned char len; signed char loop; signed char rel; - DivInstrumentMacro(String n, bool initOpen=false): + explicit DivInstrumentMacro(const String& n, bool initOpen=false): name(n), mode(0), open(initOpen), @@ -260,7 +265,7 @@ struct DivInstrumentC64 { unsigned char a, d, s, r; unsigned short duty; unsigned char ringMod, oscSync; - bool toFilter, volIsCutoff, initFilter, dutyIsAbs, filterIsAbs; + bool toFilter, volIsCutoff, initFilter, dutyIsAbs, filterIsAbs, noTest; unsigned char res; unsigned short cut; bool hp, lp, bp, ch3off; @@ -282,6 +287,7 @@ struct DivInstrumentC64 { initFilter(false), dutyIsAbs(false), filterIsAbs(false), + noTest(false), res(0), cut(0), hp(false), @@ -293,12 +299,16 @@ struct DivInstrumentC64 { struct DivInstrumentAmiga { short initSample; bool useNoteMap; + bool useWave; + unsigned char waveLen; int noteFreq[120]; short noteMap[120]; DivInstrumentAmiga(): initSample(0), - useNoteMap(false) { + useNoteMap(false), + useWave(false), + waveLen(31) { memset(noteMap,-1,120*sizeof(short)); memset(noteFreq,0,120*sizeof(int)); } @@ -328,6 +338,16 @@ struct DivInstrumentFDS { } }; +struct DivInstrumentMultiPCM { + unsigned char ar, d1r, dl, d2r, rr, rc; + unsigned char lfo, vib, am; + + DivInstrumentMultiPCM(): + ar(15), d1r(15), dl(0), d2r(0), rr(15), rc(15), + lfo(0), vib(0), am(0) { + } +}; + enum DivWaveSynthEffects { DIV_WS_NONE=0, // one waveform effects @@ -383,6 +403,7 @@ struct DivInstrument { DivInstrumentAmiga amiga; DivInstrumentN163 n163; DivInstrumentFDS fds; + DivInstrumentMultiPCM multipcm; DivInstrumentWaveSynth ws; /** diff --git a/src/engine/macroInt.cpp b/src/engine/macroInt.cpp index 21ca93a1a..00fee080e 100644 --- a/src/engine/macroInt.cpp +++ b/src/engine/macroInt.cpp @@ -19,15 +19,21 @@ #include "macroInt.h" #include "instrument.h" +#include "engine.h" -void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released) { +void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tick) { + if (!tick) { + had=false; + return; + } if (finished) { finished=false; } - if (had!=has) { + if (actualHad!=has) { finished=true; } - had=has; + actualHad=has; + had=actualHad; if (has) { val=source.val[pos++]; if (source.rel>=0 && pos>source.rel && !released) { @@ -51,9 +57,17 @@ void DivMacroInt::next() { if (ins==NULL) return; // run macros // TODO: potentially get rid of list to avoid allocations + subTick--; for (size_t i=0; idoMacro(*macroSource[i],released); + macroList[i]->doMacro(*macroSource[i],released,subTick==0); + } + } + if (subTick<=0) { + if (e==NULL) { + subTick=1; + } else { + subTick=e->tickMult; } } } @@ -62,6 +76,10 @@ void DivMacroInt::release() { released=true; } +void DivMacroInt::setEngine(DivEngine* eng) { + e=eng; +} + #define ADD_MACRO(m,s) \ macroList[macroListLen]=&m; \ macroSource[macroListLen++]=&s; @@ -73,6 +91,7 @@ void DivMacroInt::init(DivInstrument* which) { if (macroList[i]!=NULL) macroList[i]->init(); } macroListLen=0; + subTick=1; released=false; diff --git a/src/engine/macroInt.h b/src/engine/macroInt.h index 3c26eb659..f1cd29a7e 100644 --- a/src/engine/macroInt.h +++ b/src/engine/macroInt.h @@ -22,18 +22,22 @@ #include "instrument.h" +class DivEngine; + struct DivMacroStruct { int pos; int val; - bool has, had, finished, will; + bool has, had, actualHad, finished, will; unsigned int mode; - void doMacro(DivInstrumentMacro& source, bool released); + void doMacro(DivInstrumentMacro& source, bool released, bool tick); void init() { pos=mode=0; - has=had=will=false; + has=had=actualHad=will=false; + // TODO: test whether this breaks anything? + val=0; } void prepare(DivInstrumentMacro& source) { - has=had=will=true; + has=had=actualHad=will=true; mode=source.mode; } DivMacroStruct(): @@ -41,16 +45,19 @@ struct DivMacroStruct { val(0), has(false), had(false), + actualHad(false), finished(false), will(false), mode(0) {} }; class DivMacroInt { + DivEngine* e; DivInstrument* ins; DivMacroStruct* macroList[128]; DivInstrumentMacro* macroSource[128]; size_t macroListLen; + int subTick; bool released; public: // common macro @@ -100,6 +107,12 @@ class DivMacroInt { */ void next(); + /** + * set the engine. + * @param the engine + */ + void setEngine(DivEngine* eng); + /** * initialize the macro interpreter. * @param which an instrument, or NULL. @@ -113,8 +126,10 @@ class DivMacroInt { void notifyInsDeletion(DivInstrument* which); DivMacroInt(): + e(NULL), ins(NULL), macroListLen(0), + subTick(1), released(false), vol(), arp(), diff --git a/src/engine/pattern.cpp b/src/engine/pattern.cpp index 8241255b3..4f4663cae 100644 --- a/src/engine/pattern.cpp +++ b/src/engine/pattern.cpp @@ -130,6 +130,6 @@ SafeReader* DivPattern::compile(int len, int fxRows) { } DivChannelData::DivChannelData(): - effectRows(1) { + effectCols(1) { memset(data,0,256*sizeof(void*)); } diff --git a/src/engine/pattern.h b/src/engine/pattern.h index fd0b0d076..4d1070f3b 100644 --- a/src/engine/pattern.h +++ b/src/engine/pattern.h @@ -40,7 +40,7 @@ struct DivPattern { }; struct DivChannelData { - unsigned char effectRows; + unsigned char effectCols; // data goes as follows: data[ROW][TYPE] // TYPE is: // 0: note diff --git a/src/engine/platform/abstract.cpp b/src/engine/platform/abstract.cpp index b860f6808..54366e1ba 100644 --- a/src/engine/platform/abstract.cpp +++ b/src/engine/platform/abstract.cpp @@ -22,13 +22,21 @@ void DivDispatch::acquire(short* bufL, short* bufR, size_t start, size_t len) { } -void DivDispatch::tick() { +void DivDispatch::tick(bool sysTick) { } void* DivDispatch::getChanState(int chan) { return NULL; } +DivMacroInt* DivDispatch::getChanMacroInt(int chan) { + return NULL; +} + +DivDispatchOscBuffer* DivDispatch::getOscBuffer(int chan) { + return NULL; +} + unsigned char* DivDispatch::getRegisterPool() { return NULL; } @@ -133,6 +141,22 @@ const char** DivDispatch::getRegisterSheet() { return NULL; } +const void* DivDispatch::getSampleMem(int index) { + return NULL; +} + +size_t DivDispatch::getSampleMemCapacity(int index) { + return 0; +} + +size_t DivDispatch::getSampleMemUsage(int index) { + return 0; +} + +void DivDispatch::renderSamples() { + +} + int DivDispatch::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { return 0; } diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 458c9329d..e78c3a77b 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -79,35 +79,51 @@ const char* DivPlatformAmiga::getEffectName(unsigned char effect) { return NULL; } +#define writeAudDat(x) \ + chan[i].audDat=x; \ + if (i<3 && chan[i].useV) { \ + chan[i+1].outVol=(unsigned char)chan[i].audDat^0x80; \ + if (chan[i+1].outVol>64) chan[i+1].outVol=64; \ + } \ + if (i<3 && chan[i].useP) { \ + chan[i+1].freq=(unsigned char)chan[i].audDat^0x80; \ + if (chan[i+1].freq=0 && chan[i].samplesong.sampleLen) { + if (!chan[i].active) { + oscBuf[i]->data[oscBuf[i]->needle++]=0; + continue; + } + if (chan[i].useWave || (chan[i].sample>=0 && chan[i].samplesong.sampleLen)) { chan[i].audSub-=AMIGA_DIVIDER; if (chan[i].audSub<0) { - DivSample* s=parent->getSample(chan[i].sample); - if (s->samples>0) { - chan[i].audDat=s->data8[chan[i].audPos++]; - if (i<3 && chan[i].useV) { - chan[i+1].outVol=(unsigned char)chan[i].audDat^0x80; - if (chan[i+1].outVol>64) chan[i+1].outVol=64; - } - if (i<3 && chan[i].useP) { - chan[i+1].freq=(unsigned char)chan[i].audDat^0x80; - if (chan[i+1].freq=s->samples || chan[i].audPos>=131071) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - chan[i].audPos=s->loopStart; - } else { - chan[i].sample=-1; - } + if (chan[i].useWave) { + writeAudDat(chan[i].ws.output[chan[i].audPos++]^0x80); + if (chan[i].audPos>=(unsigned int)(chan[i].audLen<<1)) { + chan[i].audPos=0; } } else { - chan[i].sample=-1; + DivSample* s=parent->getSample(chan[i].sample); + if (s->samples>0) { + if (chan[i].audPossamples) { + writeAudDat(s->data8[chan[i].audPos++]); + } + if (chan[i].audPos>=s->samples || chan[i].audPos>=131071) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { + chan[i].audPos=s->loopStart; + } else { + chan[i].sample=-1; + } + } + } else { + chan[i].sample=-1; + } } /*if (chan[i].freq<124) { if (++chan[i].busClock>=512) { @@ -126,13 +142,17 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le } } if (!isMuted[i]) { + output=chan[i].audDat*chan[i].outVol; if (i==0 || i==3) { - outL+=((chan[i].audDat*chan[i].outVol)*sep1)>>7; - outR+=((chan[i].audDat*chan[i].outVol)*sep2)>>7; + outL+=(output*sep1)>>7; + outR+=(output*sep2)>>7; } else { - outL+=((chan[i].audDat*chan[i].outVol)*sep2)>>7; - outR+=((chan[i].audDat*chan[i].outVol)*sep1)>>7; + outL+=(output*sep2)>>7; + outR+=(output*sep1)>>7; } + oscBuf[i]->data[oscBuf[i]->needle++]=output<<2; + } else { + oscBuf[i]->data[oscBuf[i]->needle++]=0; } } filter[0][0]+=(filtConst*(outL-filter[0][0]))>>12; @@ -144,14 +164,14 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le } } -void DivPlatformAmiga::tick() { +void DivPlatformAmiga::tick(bool sysTick) { for (int i=0; i<4; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { chan[i].outVol=((chan[i].vol%65)*MIN(64,chan[i].std.vol.val))>>6; } double off=1.0; - if (chan[i].sample>=0 && chan[i].samplesong.sampleLen) { + if (!chan[i].useWave && chan[i].sample>=0 && chan[i].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[i].sample); if (s->centerRate<1) { off=1.0; @@ -174,21 +194,36 @@ void DivPlatformAmiga::tick() { chan[i].freqChanged=true; } } - if (chan[i].std.wave.had) { - if (chan[i].wave!=chan[i].std.wave.val) { + if (chan[i].useWave && chan[i].std.wave.had) { + if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { chan[i].wave=chan[i].std.wave.val; + chan[i].ws.changeWave1(chan[i].wave); if (!chan[i].keyOff) chan[i].keyOn=true; } } + if (chan[i].useWave && chan[i].active) { + chan[i].ws.tick(); + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + chan[i].audPos=0; + } + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - //DivInstrument* ins=parent->getIns(chan[i].ins); - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); + //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); if (chan[i].freq>4095) chan[i].freq=4095; - if (chan[i].note>0x5d) chan[i].freq=0x01; + if (chan[i].freq<0) chan[i].freq=0; if (chan[i].keyOn) { - if (chan[i].wave<0) { - chan[i].wave=0; - } } if (chan[i].keyOff) { } @@ -202,21 +237,34 @@ void DivPlatformAmiga::tick() { int DivPlatformAmiga::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); - chan[c.chan].sample=ins->amiga.initSample; + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); double off=1.0; - if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { - DivSample* s=parent->getSample(chan[c.chan].sample); - if (s->centerRate<1) { - off=1.0; - } else { - off=8363.0/(double)s->centerRate; + if (ins->amiga.useWave) { + chan[c.chan].useWave=true; + chan[c.chan].audLen=(ins->amiga.waveLen+1)>>1; + if (chan[c.chan].insChanged) { + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + chan[c.chan].ws.setWidth(chan[c.chan].audLen<<1); + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + } + } + } else { + chan[c.chan].sample=ins->amiga.initSample; + chan[c.chan].useWave=false; + if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[c.chan].sample); + if (s->centerRate<1) { + off=1.0; + } else { + off=8363.0/(double)s->centerRate; + } } } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value)); } - if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { + if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { chan[c.chan].sample=-1; } if (chan[c.chan].setPos) { @@ -231,14 +279,18 @@ int DivPlatformAmiga::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); + if (chan[c.chan].useWave) { + chan[c.chan].ws.init(ins,chan[c.chan].audLen<<1,255,chan[c.chan].insChanged); + } + chan[c.chan].insChanged=false; break; } case DIV_CMD_NOTE_OFF: chan[c.chan].sample=-1; chan[c.chan].active=false; chan[c.chan].keyOff=true; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -247,6 +299,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) { case DIV_CMD_INSTRUMENT: if (chan[c.chan].ins!=c.value || c.value2==1) { chan[c.chan].ins=c.value; + chan[c.chan].insChanged=true; } break; case DIV_CMD_VOLUME: @@ -268,14 +321,16 @@ int DivPlatformAmiga::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; case DIV_CMD_WAVE: + if (!chan[c.chan].useWave) break; chan[c.chan].wave=c.value; chan[c.chan].keyOn=true; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); break; case DIV_CMD_NOTE_PORTA: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); chan[c.chan].sample=ins->amiga.initSample; double off=1.0; - if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + if (!chan[c.chan].useWave && chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); if (s->centerRate<1) { off=1.0; @@ -307,7 +362,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) { } case DIV_CMD_LEGATO: { double off=1.0; - if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + if (!chan[c.chan].useWave && chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); if (s->centerRate<1) { off=1.0; @@ -315,18 +370,19 @@ int DivPlatformAmiga::dispatch(DivCommand c) { off=8363.0/(double)s->centerRate; } } - chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0)))); + chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; } case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA)); } chan[c.chan].inPorta=c.value; break; case DIV_CMD_SAMPLE_POS: + if (chan[c.chan].useWave) break; chan[c.chan].audPos=c.value; chan[c.chan].setPos=true; break; @@ -370,9 +426,16 @@ void* DivPlatformAmiga::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformAmiga::getOscBuffer(int ch) { + return oscBuf[ch]; +} + void DivPlatformAmiga::reset() { for (int i=0; i<4; i++) { chan[i]=DivPlatformAmiga::Channel(); + chan[i].std.setEngine(parent); + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,32,255); filter[0][i]=0; filter[1][i]=0; } @@ -397,7 +460,11 @@ void DivPlatformAmiga::notifyInsChange(int ins) { } void DivPlatformAmiga::notifyWaveChange(int wave) { - // TODO when wavetables are added + for (int i=0; i<4; i++) { + if (chan[i].useWave && chan[i].wave==wave) { + chan[i].ws.changeWave1(wave); + } + } } void DivPlatformAmiga::notifyInsDeletion(void* ins) { @@ -413,6 +480,9 @@ void DivPlatformAmiga::setFlags(unsigned int flags) { chipClock=COLOR_NTSC; } rate=chipClock/AMIGA_DIVIDER; + for (int i=0; i<4; i++) { + oscBuf[i]->rate=rate; + } sep1=((flags>>8)&127)+127; sep2=127-((flags>>8)&127); amigaModel=flags&2; @@ -431,6 +501,7 @@ int DivPlatformAmiga::init(DivEngine* p, int channels, int sugRate, unsigned int dumpWrites=false; skipRegisterWrites=false; for (int i=0; i<4; i++) { + oscBuf[i]=new DivDispatchOscBuffer; isMuted[i]=false; } setFlags(flags); @@ -439,4 +510,7 @@ int DivPlatformAmiga::init(DivEngine* p, int channels, int sugRate, unsigned int } void DivPlatformAmiga::quit() { + for (int i=0; i<4; i++) { + delete oscBuf[i]; + } } diff --git a/src/engine/platform/amiga.h b/src/engine/platform/amiga.h index b5f13701d..539f7830c 100644 --- a/src/engine/platform/amiga.h +++ b/src/engine/platform/amiga.h @@ -23,33 +23,40 @@ #include "../dispatch.h" #include #include "../macroInt.h" +#include "../waveSynth.h" class DivPlatformAmiga: public DivDispatch { struct Channel { - int freq, baseFreq, pitch; + int freq, baseFreq, pitch, pitch2; unsigned int audLoc; unsigned short audLen; unsigned int audPos; int audSub; signed char audDat; int sample, wave; - unsigned char ins; + int ins; int busClock; int note; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos, useV, useP; signed char vol, outVol; DivMacroInt std; + DivWaveSynth ws; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freq(0), baseFreq(0), pitch(0), + pitch2(0), audLoc(0), audLen(0), audPos(0), audSub(0), audDat(0), sample(-1), - wave(0), + wave(-1), ins(-1), busClock(0), note(0), @@ -67,6 +74,7 @@ class DivPlatformAmiga: public DivDispatch { outVol(64) {} }; Channel chan[4]; + DivDispatchOscBuffer* oscBuf[4]; bool isMuted[4]; bool bypassLimits; bool amigaModel; @@ -84,9 +92,10 @@ class DivPlatformAmiga: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool isStereo(); bool keyOffAffectsArp(int ch); diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 55172394c..6bd4e2034 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -135,6 +135,54 @@ const char* DivPlatformArcade::getEffectName(unsigned char effect) { case 0x30: return "30xx: Toggle hard envelope reset on new notes"; break; + case 0x50: + return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; + break; + case 0x51: + return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; + break; + case 0x52: + return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; + break; + case 0x53: + return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)"; + break; + case 0x54: + return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)"; + break; + case 0x55: + return "55xy: Set detune 2 (x: operator from 1 to 4 (0 for all ops); y: detune from 0 to 3)"; + break; + case 0x56: + return "56xx: Set decay of all operators (0 to 1F)"; + break; + case 0x57: + return "57xx: Set decay of operator 1 (0 to 1F)"; + break; + case 0x58: + return "58xx: Set decay of operator 2 (0 to 1F)"; + break; + case 0x59: + return "59xx: Set decay of operator 3 (0 to 1F)"; + break; + case 0x5a: + return "5Axx: Set decay of operator 4 (0 to 1F)"; + break; + case 0x5b: + return "5Bxx: Set decay 2 of all operators (0 to 1F)"; + break; + case 0x5c: + return "5Cxx: Set decay 2 of operator 1 (0 to 1F)"; + break; + case 0x5d: + return "5Dxx: Set decay 2 of operator 2 (0 to 1F)"; + break; + case 0x5e: + return "5Exx: Set decay 2 of operator 3 (0 to 1F)"; + break; + case 0x5f: + return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; + break; } return NULL; } @@ -143,23 +191,29 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si static int o[2]; for (size_t h=start; hdata[oscBuf[i]->needle++]=fm.ch_out[i]; } - - OPM_Clock(&fm,NULL,NULL,NULL,NULL); - OPM_Clock(&fm,NULL,NULL,NULL,NULL); - OPM_Clock(&fm,NULL,NULL,NULL,NULL); - OPM_Clock(&fm,o,NULL,NULL,NULL); if (o[0]<-32768) o[0]=-32768; if (o[0]>32767) o[0]=32767; @@ -175,6 +229,8 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; + ymfm::ym2151::fm_engine* fme=fm_ymfm->debug_engine(); + for (size_t h=start; hgenerate(&out_ymfm); + for (int i=0; i<8; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1)); + } + os[0]=out_ymfm.data[0]; if (os[0]<-32768) os[0]=-32768; if (os[0]>32767) os[0]=32767; @@ -219,7 +279,7 @@ inline int hScale(int note) { return ((note/12)<<4)+(noteMap[note%12]); } -void DivPlatformArcade::tick() { +void DivPlatformArcade::tick(bool sysTick) { for (int i=0; i<8; i++) { chan[i].std.next(); @@ -264,6 +324,32 @@ void DivPlatformArcade::tick() { rWrite(0x1b,chan[i].std.wave.val&3); } + if (chan[i].std.panL.had) { + chan[i].chVolL=(chan[i].std.panL.val&2)>>1; + chan[i].chVolR=chan[i].std.panL.val&1; + if (isMuted[i]) { + rWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + } else { + 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)); + } + } + + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + chan[i].keyOn=true; + } + } + if (chan[i].std.ex1.had) { amDepth=chan[i].std.ex1.val; immWrite(0x19,amDepth); @@ -400,7 +486,7 @@ void DivPlatformArcade::tick() { for (int i=0; i<8; i++) { if (chan[i].freqChanged) { - chan[i].freq=chan[i].baseFreq+(chan[i].pitch>>1)-64; + chan[i].freq=chan[i].baseFreq+(chan[i].pitch>>1)-64+chan[i].pitch2; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>=(95<<6)) chan[i].freq=(95<<6)-1; immWrite(i+0x28,hScale(chan[i].freq>>6)); @@ -426,13 +512,13 @@ void DivPlatformArcade::muteChannel(int ch, bool mute) { int DivPlatformArcade::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; } - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } @@ -517,8 +603,8 @@ int DivPlatformArcade::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { - chan[c.chan].chVolL=((c.value>>4)>0); - chan[c.chan].chVolR=((c.value&15)>0); + chan[c.chan].chVolL=(c.value>0); + chan[c.chan].chVolR=(c.value2>0); if (isMuted[c.chan]) { rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); } else { @@ -612,6 +698,134 @@ int DivPlatformArcade::dispatch(DivCommand c) { } break; } + case DIV_CMD_FM_RS: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_AM: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_DR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_SL: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_RR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_DT2: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dt2=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dt2=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); + } + break; + } + case DIV_CMD_FM_D2R: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); + } + break; + } + case DIV_CMD_FM_DT: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dt=c.value&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dt=c.value2&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + break; + } case DIV_CMD_FM_AM_DEPTH: { amDepth=c.value; immWrite(0x19,amDepth); @@ -699,6 +913,10 @@ void* DivPlatformArcade::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformArcade::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformArcade::getRegisterPool() { return regPool; } @@ -729,6 +947,7 @@ void DivPlatformArcade::reset() { } for (int i=0; i<8; i++) { chan[i]=DivPlatformArcade::Channel(); + chan[i].std.setEngine(parent); chan[i].vol=0x7f; chan[i].outVol=0x7f; } @@ -765,10 +984,9 @@ void DivPlatformArcade::setFlags(unsigned int flags) { chipClock=COLOR_NTSC; baseFreqOff=0; } - if (useYMFM) { - rate=chipClock/64; - } else { - rate=chipClock/8; + rate=chipClock/64; + for (int i=0; i<8; i++) { + oscBuf[i]->rate=rate; } } @@ -786,6 +1004,7 @@ int DivPlatformArcade::init(DivEngine* p, int channels, int sugRate, unsigned in skipRegisterWrites=false; for (int i=0; i<8; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); if (useYMFM) fm_ymfm=new ymfm::ym2151(iface); @@ -795,6 +1014,9 @@ int DivPlatformArcade::init(DivEngine* p, int channels, int sugRate, unsigned in } void DivPlatformArcade::quit() { + for (int i=0; i<8; i++) { + delete oscBuf[i]; + } if (useYMFM) { delete fm_ymfm; } diff --git a/src/engine/platform/arcade.h b/src/engine/platform/arcade.h index a6ec82c94..6f68eedad 100644 --- a/src/engine/platform/arcade.h +++ b/src/engine/platform/arcade.h @@ -36,18 +36,23 @@ class DivPlatformArcade: public DivDispatch { DivInstrumentFM state; DivMacroInt std; unsigned char freqH, freqL; - int freq, baseFreq, pitch, note; - unsigned char ins; + int freq, baseFreq, pitch, pitch2, note; + int ins; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset; int vol, outVol; unsigned char chVolL, chVolR; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), + pitch2(0), note(0), ins(-1), active(false), @@ -65,6 +70,7 @@ class DivPlatformArcade: public DivDispatch { chVolR(127) {} }; Channel chan[8]; + DivDispatchOscBuffer* oscBuf[8]; struct QueuedWrite { unsigned short addr; unsigned char val; @@ -103,11 +109,12 @@ class DivPlatformArcade: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); void notifyInsChange(int ins); void setFlags(unsigned int flags); diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 727a14408..37108452e 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -146,6 +146,12 @@ void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t l bufR[i+start]=bufL[i+start]; } } + + for (int ch=0; ch<3; ch++) { + for (size_t i=0; idata[oscBuf[ch]->needle++]=ayBuf[ch][i]; + } + } } void DivPlatformAY8910::updateOutSel(bool immediate) { @@ -172,7 +178,7 @@ void DivPlatformAY8910::updateOutSel(bool immediate) { } } -void DivPlatformAY8910::tick() { +void DivPlatformAY8910::tick(bool sysTick) { // PSG for (int i=0; i<3; i++) { chan[i].std.next(); @@ -215,6 +221,21 @@ void DivPlatformAY8910::tick() { rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); } } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + oldWrites[0x08+i]=-1; + oldWrites[0x0d]=-1; + } + } if (chan[i].std.ex2.had) { ayEnvMode=chan[i].std.ex2.val; rWrite(0x0d,ayEnvMode); @@ -230,7 +251,7 @@ void DivPlatformAY8910::tick() { if (!chan[i].std.ex3.will) chan[i].autoEnvNum=1; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); if (chan[i].freq>4095) chan[i].freq=4095; if (chan[i].keyOn) { //rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); @@ -285,7 +306,7 @@ void DivPlatformAY8910::tick() { int DivPlatformAY8910::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AY); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); chan[c.chan].freqChanged=true; @@ -293,7 +314,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); if (isMuted[c.chan]) { rWrite(0x08+c.chan,0); } else if (intellivision && (chan[c.chan].psgMode&4)) { @@ -306,7 +327,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) { case DIV_CMD_NOTE_OFF: chan[c.chan].keyOff=true; chan[c.chan].active=false; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -448,7 +469,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AY)); } chan[c.chan].inPorta=c.value; break; @@ -485,6 +506,10 @@ void* DivPlatformAY8910::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformAY8910::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformAY8910::getRegisterPool() { return regPool; } @@ -507,6 +532,7 @@ void DivPlatformAY8910::reset() { memset(regPool,0,16); for (int i=0; i<3; i++) { chan[i]=DivPlatformAY8910::Channel(); + chan[i].std.setEngine(parent); chan[i].vol=0x0f; } if (dumpWrites) { @@ -599,6 +625,9 @@ void DivPlatformAY8910::setFlags(unsigned int flags) { break; } rate=chipClock/8; + for (int i=0; i<3; i++) { + oscBuf[i]->rate=rate; + } if (ay!=NULL) delete ay; switch ((flags>>4)&3) { @@ -634,6 +663,7 @@ int DivPlatformAY8910::init(DivEngine* p, int channels, int sugRate, unsigned in skipRegisterWrites=false; for (int i=0; i<3; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } ay=NULL; setFlags(flags); @@ -644,6 +674,9 @@ int DivPlatformAY8910::init(DivEngine* p, int channels, int sugRate, unsigned in } void DivPlatformAY8910::quit() { - for (int i=0; i<3; i++) delete[] ayBuf[i]; + for (int i=0; i<3; i++) { + delete oscBuf[i]; + delete[] ayBuf[i]; + } if (ay!=NULL) delete ay; } diff --git a/src/engine/platform/ay.h b/src/engine/platform/ay.h index b1a3ea127..b257e3bbc 100644 --- a/src/engine/platform/ay.h +++ b/src/engine/platform/ay.h @@ -32,14 +32,19 @@ class DivPlatformAY8910: public DivDispatch { inline unsigned char regRemap(unsigned char reg) { return intellivision?AY8914RegRemap[reg&0x0f]:reg&0x0f; } struct Channel { unsigned char freqH, freqL; - int freq, baseFreq, note, pitch; - unsigned char ins, psgMode, autoEnvNum, autoEnvDen; + int freq, baseFreq, note, pitch, pitch2; + int ins; + unsigned char psgMode, autoEnvNum, autoEnvDen; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; int vol, outVol; unsigned char pan; DivMacroInt std; - Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), note(0), pitch(0), ins(-1), psgMode(1), autoEnvNum(0), autoEnvDen(0), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(15), pan(3) {} + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), note(0), pitch(0), pitch2(0), ins(-1), psgMode(1), autoEnvNum(0), autoEnvDen(0), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(15), pan(3) {} }; Channel chan[3]; bool isMuted[3]; @@ -51,6 +56,7 @@ class DivPlatformAY8910: public DivDispatch { }; std::queue writes; ay8910_device* ay; + DivDispatchOscBuffer* oscBuf[3]; unsigned char regPool[16]; unsigned char lastBusy; @@ -85,12 +91,13 @@ class DivPlatformAY8910: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void flushWrites(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); void setFlags(unsigned int flags); bool isStereo(); diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index e9af64fc7..3fec732b5 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -25,9 +25,9 @@ #include #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} -#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define immWrite2(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } -#define CHIP_DIVIDER 8 +#define CHIP_DIVIDER 4 const char* regCheatSheetAY8930[]={ "FreqL_A", "00", @@ -61,6 +61,18 @@ const char* regCheatSheetAY8930[]={ NULL }; +void DivPlatformAY8930::immWrite(unsigned char a, unsigned char v) { + if ((int)bank!=(a>>4)) { + bank=a>>4; + immWrite2(0x0d, 0xa0|(bank<<4)|ayEnvMode[0]); + } + if (a==0x0d) { + immWrite2(0x0d,0xa0|(bank<<4)|(v&15)); + } else { + immWrite2(a&15,v); + } +} + const char** DivPlatformAY8930::getRegisterSheet() { return regCheatSheetAY8930; } @@ -123,18 +135,13 @@ void DivPlatformAY8930::acquire(short* bufL, short* bufR, size_t start, size_t l } while (!writes.empty()) { QueuedWrite w=writes.front(); - if ((int)bank!=(w.addr>>4)) { - bank=w.addr>>4; - ay->address_w(0x0d); - ay->data_w(0xa0|(bank<<4)|ayEnvMode[0]); - } - ay->address_w(w.addr&15); - if (w.addr==0x0d) { - ay->data_w(0xa0|(bank<<4)|(w.val&15)); + ay->address_w(w.addr); + ay->data_w(w.val); + if (w.addr!=0x0d && (regPool[0x0d]&0xf0)==0xb0) { + regPool[(w.addr&0x0f)|0x10]=w.val; } else { - ay->data_w(w.val); + regPool[w.addr&0x0f]=w.val; } - regPool[w.addr&0x1f]=w.val; writes.pop(); } ay->sound_stream_update(ayBuf,len); @@ -149,6 +156,12 @@ void DivPlatformAY8930::acquire(short* bufL, short* bufR, size_t start, size_t l bufR[i+start]=bufL[i+start]; } } + + for (int ch=0; ch<3; ch++) { + for (size_t i=0; idata[oscBuf[ch]->needle++]=ayBuf[ch][i]; + } + } } void DivPlatformAY8930::updateOutSel(bool immediate) { @@ -187,7 +200,7 @@ const unsigned char regMode[3]={ 0x0d, 0x14, 0x15 }; -void DivPlatformAY8930::tick() { +void DivPlatformAY8930::tick(bool sysTick) { // PSG for (int i=0; i<3; i++) { chan[i].std.next(); @@ -226,6 +239,21 @@ void DivPlatformAY8930::tick() { rWrite(0x08+i,(chan[i].outVol&31)|((chan[i].psgMode&4)<<3)); } } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + oldWrites[0x08+i]=-1; + oldWrites[regMode[i]]=-1; + } + } if (chan[i].std.ex1.had) { // duty rWrite(0x16+i,chan[i].std.ex1.val); } @@ -252,7 +280,7 @@ void DivPlatformAY8930::tick() { immWrite(0x1a,ayNoiseOr); } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); if (chan[i].freq>65535) chan[i].freq=65535; if (chan[i].keyOn) { if (chan[i].insChanged) { @@ -309,7 +337,7 @@ void DivPlatformAY8930::tick() { int DivPlatformAY8930::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AY8930); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); chan[c.chan].freqChanged=true; @@ -317,7 +345,7 @@ int DivPlatformAY8930::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); if (isMuted[c.chan]) { rWrite(0x08+c.chan,0); } else { @@ -328,7 +356,7 @@ int DivPlatformAY8930::dispatch(DivCommand c) { case DIV_CMD_NOTE_OFF: chan[c.chan].keyOff=true; chan[c.chan].active=false; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -473,7 +501,7 @@ int DivPlatformAY8930::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AY8930)); } chan[c.chan].inPorta=c.value; break; @@ -508,6 +536,10 @@ void* DivPlatformAY8930::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformAY8930::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformAY8930::getRegisterPool() { return regPool; } @@ -522,6 +554,7 @@ void DivPlatformAY8930::reset() { memset(regPool,0,32); for (int i=0; i<3; i++) { chan[i]=DivPlatformAY8930::Channel(); + chan[i].std.setEngine(parent); chan[i].vol=31; ayEnvPeriod[i]=0; ayEnvMode[i]=0; @@ -612,7 +645,11 @@ void DivPlatformAY8930::setFlags(unsigned int flags) { chipClock=COLOR_NTSC/2.0; break; } - rate=chipClock/8; + rate=chipClock/4; + for (int i=0; i<3; i++) { + oscBuf[i]->rate=rate; + } + stereo=flags>>6; } @@ -622,6 +659,7 @@ int DivPlatformAY8930::init(DivEngine* p, int channels, int sugRate, unsigned in skipRegisterWrites=false; for (int i=0; i<3; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); ay=new ay8930_device(rate); @@ -633,6 +671,9 @@ int DivPlatformAY8930::init(DivEngine* p, int channels, int sugRate, unsigned in } void DivPlatformAY8930::quit() { - for (int i=0; i<3; i++) delete[] ayBuf[i]; + for (int i=0; i<3; i++) { + delete oscBuf[i]; + delete[] ayBuf[i]; + } delete ay; } diff --git a/src/engine/platform/ay8930.h b/src/engine/platform/ay8930.h index 6bd51bdcd..5f477e123 100644 --- a/src/engine/platform/ay8930.h +++ b/src/engine/platform/ay8930.h @@ -28,14 +28,19 @@ class DivPlatformAY8930: public DivDispatch { protected: struct Channel { unsigned char freqH, freqL; - int freq, baseFreq, note, pitch; - unsigned char ins, psgMode, autoEnvNum, autoEnvDen, duty; + int freq, baseFreq, note, pitch, pitch2; + int ins; + unsigned char psgMode, autoEnvNum, autoEnvDen, duty; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; int vol, outVol; unsigned char pan; DivMacroInt std; - Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), note(0), pitch(0), ins(-1), psgMode(1), autoEnvNum(0), autoEnvDen(0), duty(4), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(31), pan(3) {} + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), note(0), pitch(0), pitch2(0), ins(-1), psgMode(1), autoEnvNum(0), autoEnvDen(0), duty(4), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(31), pan(3) {} }; Channel chan[3]; bool isMuted[3]; @@ -47,6 +52,7 @@ class DivPlatformAY8930: public DivDispatch { }; std::queue writes; ay8930_device* ay; + DivDispatchOscBuffer* oscBuf[3]; unsigned char regPool[32]; unsigned char ayNoiseAnd, ayNoiseOr; bool bank; @@ -67,6 +73,7 @@ class DivPlatformAY8930: public DivDispatch { size_t ayBufLen; void updateOutSel(bool immediate=false); + void immWrite(unsigned char a, unsigned char v); friend void putDispatchChan(void*,int,int); @@ -74,11 +81,12 @@ class DivPlatformAY8930: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); void setFlags(unsigned int flags); bool isStereo(); diff --git a/src/engine/platform/bubsyswsg.cpp b/src/engine/platform/bubsyswsg.cpp index 712496c1c..b12a92ed4 100644 --- a/src/engine/platform/bubsyswsg.cpp +++ b/src/engine/platform/bubsyswsg.cpp @@ -49,6 +49,7 @@ const char* DivPlatformBubSysWSG::getEffectName(unsigned char effect) { } void DivPlatformBubSysWSG::acquire(short* bufL, short* bufR, size_t start, size_t len) { + int chanOut=0; for (size_t h=start; haddr(i)]*(regPool[2+i]&0xf); + if (isMuted[i]) { + oscBuf[i]->data[oscBuf[i]->needle++]=0; + continue; + } else { + chanOut=chan[i].waveROM[k005289->addr(i)]*(regPool[2+i]&0xf); + out+=chanOut; + if (writeOscBuf==0) { + oscBuf[i]->data[oscBuf[i]->needle++]=chanOut<<7; + } + } } + if (++writeOscBuf>=64) writeOscBuf=0; + out<<=6; // scale output to 16 bit if (out<-32768) out=-32768; @@ -81,7 +92,7 @@ void DivPlatformBubSysWSG::updateWave(int ch) { } } -void DivPlatformBubSysWSG::tick() { +void DivPlatformBubSysWSG::tick(bool sysTick) { for (int i=0; i<2; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { @@ -110,14 +121,23 @@ void DivPlatformBubSysWSG::tick() { if (!chan[i].keyOff) chan[i].keyOn=true; } } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } if (chan[i].active) { if (chan[i].ws.tick()) { updateWave(i); } } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - //DivInstrument* ins=parent->getIns(chan[i].ins); - chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); + //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SCC); + chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)+chan[i].pitch2; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>4095) chan[i].freq=4095; k005289->load(i,chan[i].freq); @@ -139,7 +159,7 @@ void DivPlatformBubSysWSG::tick() { int DivPlatformBubSysWSG::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SCC); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); chan[c.chan].freqChanged=true; @@ -148,7 +168,7 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) { chan[c.chan].active=true; chan[c.chan].keyOn=true; rWrite(2+c.chan,(chan[c.chan].wave<<5)|chan[c.chan].vol); - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); if (chan[c.chan].wave<0) { chan[c.chan].wave=0; chan[c.chan].ws.changeWave1(chan[c.chan].wave); @@ -160,7 +180,7 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) { case DIV_CMD_NOTE_OFF: chan[c.chan].active=false; chan[c.chan].keyOff=true; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -225,7 +245,7 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SCC)); } chan[c.chan].inPorta=c.value; break; @@ -258,6 +278,10 @@ void* DivPlatformBubSysWSG::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformBubSysWSG::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformBubSysWSG::getRegisterPool() { return (unsigned char*)regPool; } @@ -274,6 +298,7 @@ void DivPlatformBubSysWSG::reset() { memset(regPool,0,4*2); for (int i=0; i<2; i++) { chan[i]=DivPlatformBubSysWSG::Channel(); + chan[i].std.setEngine(parent); chan[i].ws.setEngine(parent); chan[i].ws.init(NULL,32,15,false); } @@ -309,6 +334,9 @@ void DivPlatformBubSysWSG::notifyInsDeletion(void* ins) { void DivPlatformBubSysWSG::setFlags(unsigned int flags) { chipClock=COLOR_NTSC; rate=chipClock; + for (int i=0; i<2; i++) { + oscBuf[i]->rate=rate/64; + } } void DivPlatformBubSysWSG::poke(unsigned int addr, unsigned short val) { @@ -323,8 +351,10 @@ int DivPlatformBubSysWSG::init(DivEngine* p, int channels, int sugRate, unsigned parent=p; dumpWrites=false; skipRegisterWrites=false; + writeOscBuf=0; for (int i=0; i<2; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); k005289=new k005289_core(); @@ -333,6 +363,9 @@ int DivPlatformBubSysWSG::init(DivEngine* p, int channels, int sugRate, unsigned } void DivPlatformBubSysWSG::quit() { + for (int i=0; i<2; i++) { + delete oscBuf[i]; + } delete k005289; } diff --git a/src/engine/platform/bubsyswsg.h b/src/engine/platform/bubsyswsg.h index 6c0e2261e..cb30d85c0 100644 --- a/src/engine/platform/bubsyswsg.h +++ b/src/engine/platform/bubsyswsg.h @@ -28,17 +28,21 @@ class DivPlatformBubSysWSG: public DivDispatch { struct Channel { - int freq, baseFreq, pitch, note; - unsigned char ins; + int freq, baseFreq, pitch, pitch2, note, ins; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; signed char vol, outVol, wave; signed char waveROM[32] = {0}; // 4 bit PROM per channel on bubble system DivMacroInt std; DivWaveSynth ws; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freq(0), baseFreq(0), pitch(0), + pitch2(0), note(0), ins(-1), active(false), @@ -52,7 +56,9 @@ class DivPlatformBubSysWSG: public DivDispatch { wave(-1) {} }; Channel chan[2]; + DivDispatchOscBuffer* oscBuf[2]; bool isMuted[2]; + unsigned char writeOscBuf; k005289_core* k005289; unsigned short regPool[4]; @@ -62,12 +68,13 @@ class DivPlatformBubSysWSG: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); int getRegisterPoolDepth(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool isStereo(); bool keyOffAffectsArp(int ch); diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index b7521f035..c0efdd6f2 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -112,6 +112,12 @@ void DivPlatformC64::acquire(short* bufL, short* bufR, size_t start, size_t len) for (size_t i=start; i=8) { + writeOscBuf=0; + oscBuf[0]->data[oscBuf[0]->needle++]=sid.last_chan_out[0]>>5; + oscBuf[1]->data[oscBuf[1]->needle++]=sid.last_chan_out[1]>>5; + oscBuf[2]->data[oscBuf[2]->needle++]=sid.last_chan_out[2]>>5; + } } } @@ -122,16 +128,16 @@ void DivPlatformC64::updateFilter() { rWrite(0x18,(filtControl<<4)|vol); } -void DivPlatformC64::tick() { +void DivPlatformC64::tick(bool sysTick) { for (int i=0; i<3; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { - DivInstrument* ins=parent->getIns(chan[i].ins); + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_C64); if (ins->c64.volIsCutoff) { if (ins->c64.filterIsAbs) { filtCut=MIN(2047,chan[i].std.vol.val); } else { - filtCut-=((signed char)chan[i].std.vol.val-18)*7; + filtCut-=((signed char)chan[i].std.vol.val)*7; if (filtCut>2047) filtCut=2047; if (filtCut<0) filtCut=0; } @@ -157,27 +163,39 @@ void DivPlatformC64::tick() { } } if (chan[i].std.duty.had) { - DivInstrument* ins=parent->getIns(chan[i].ins); + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_C64); if (ins->c64.dutyIsAbs) { chan[i].duty=chan[i].std.duty.val; } else { - chan[i].duty-=((signed char)chan[i].std.duty.val-12)*4; + chan[i].duty-=((signed char)chan[i].std.duty.val)*4; } rWrite(i*7+2,chan[i].duty&0xff); rWrite(i*7+3,chan[i].duty>>8); } - if (chan[i].testWhen>0) { - if (--chan[i].testWhen<1) { - if (!chan[i].resetMask && !isMuted[i]) { - rWrite(i*7+5,0); - rWrite(i*7+6,0); - rWrite(i*7+4,(isMuted[i]?8:(chan[i].wave<<4))|8|(chan[i].ring<<2)|(chan[i].sync<<1)); + if (sysTick) { + if (chan[i].testWhen>0) { + if (--chan[i].testWhen<1) { + if (!chan[i].resetMask && !chan[i].inPorta) { + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_C64); + rWrite(i*7+5,0); + rWrite(i*7+6,0); + rWrite(i*7+4,(chan[i].wave<<4)|(ins->c64.noTest?0:8)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1)); + } } } } if (chan[i].std.wave.had) { chan[i].wave=chan[i].std.wave.val; - rWrite(i*7+4,(isMuted[i]?8:(chan[i].wave<<4))|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active)); + rWrite(i*7+4,(chan[i].wave<<4)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active)); + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; } if (chan[i].std.ex1.had) { filtControl=chan[i].std.ex1.val&15; @@ -191,20 +209,25 @@ void DivPlatformC64::tick() { chan[i].sync=chan[i].std.ex3.val&1; chan[i].ring=chan[i].std.ex3.val&2; chan[i].freqChanged=true; + rWrite(i*7+4,(chan[i].wave<<4)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active)); + } + if (chan[i].std.ex4.had) { + chan[i].test=chan[i].std.ex4.val&1; + rWrite(i*7+4,(chan[i].wave<<4)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active)); } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2); if (chan[i].freq>0xffff) chan[i].freq=0xffff; 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]?8:(chan[i].wave<<4))|(chan[i].ring<<2)|(chan[i].sync<<1)|1); + rWrite(i*7+4,(chan[i].wave<<4)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1)|1); } - if (chan[i].keyOff && !isMuted[i]) { + if (chan[i].keyOff) { 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]?8:(chan[i].wave<<4))|(chan[i].ring<<2)|(chan[i].sync<<1)|0); + rWrite(i*7+4,(chan[i].wave<<4)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1)|0); } rWrite(i*7,chan[i].freq&0xff); rWrite(i*7+1,chan[i].freq>>8); @@ -218,7 +241,7 @@ void DivPlatformC64::tick() { int DivPlatformC64::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_C64); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].freqChanged=true; @@ -226,6 +249,7 @@ int DivPlatformC64::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; + chan[c.chan].test=false; if (chan[c.chan].insChanged || chan[c.chan].resetDuty || ins->std.waveMacro.len>0) { chan[c.chan].duty=ins->c64.duty; rWrite(c.chan*7+2,chan[c.chan].duty&0xff); @@ -252,14 +276,14 @@ int DivPlatformC64::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].insChanged=false; } - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); break; } 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); + //chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: chan[c.chan].active=false; @@ -330,7 +354,7 @@ int DivPlatformC64::dispatch(DivCommand c) { break; case DIV_CMD_WAVE: chan[c.chan].wave=c.value; - rWrite(c.chan*7+4,(isMuted[c.chan]?8:(chan[c.chan].wave<<4))|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active)); + 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)); break; case DIV_CMD_LEGATO: chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); @@ -339,8 +363,8 @@ int DivPlatformC64::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) { - chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta || !chan[c.chan].inPorta) { + chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_C64)); chan[c.chan].keyOn=true; } } @@ -378,7 +402,7 @@ int DivPlatformC64::dispatch(DivCommand c) { break; case DIV_CMD_C64_FILTER_RESET: if (c.value&15) { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_C64); if (ins->c64.initFilter) { filtCut=ins->c64.cut; updateFilter(); @@ -388,7 +412,7 @@ int DivPlatformC64::dispatch(DivCommand c) { break; case DIV_CMD_C64_DUTY_RESET: if (c.value&15) { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_C64); chan[c.chan].duty=ins->c64.duty; rWrite(c.chan*7+2,chan[c.chan].duty&0xff); rWrite(c.chan*7+3,chan[c.chan].duty>>8); @@ -411,11 +435,11 @@ int DivPlatformC64::dispatch(DivCommand c) { break; case 4: chan[c.chan].ring=c.value; - rWrite(c.chan*7+4,(isMuted[c.chan]?8:(chan[c.chan].wave<<4))|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active)); + 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)); break; case 5: chan[c.chan].sync=c.value; - rWrite(c.chan*7+4,(isMuted[c.chan]?8:(chan[c.chan].wave<<4))|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active)); + 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)); break; case 6: filtControl&=7; @@ -434,7 +458,7 @@ int DivPlatformC64::dispatch(DivCommand c) { void DivPlatformC64::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - rWrite(ch*7+4,(isMuted[ch]?8:(chan[ch].wave<<4))|(chan[ch].ring<<2)|(chan[ch].sync<<1)|(int)(chan[ch].active)); + sid.set_is_muted(ch,mute); } void DivPlatformC64::forceIns() { @@ -467,6 +491,10 @@ void* DivPlatformC64::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformC64::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformC64::getRegisterPool() { return regPool; } @@ -475,9 +503,14 @@ int DivPlatformC64::getRegisterPoolSize() { return 32; } +bool DivPlatformC64::getDCOffRequired() { + return true; +} + void DivPlatformC64::reset() { for (int i=0; i<3; i++) { chan[i]=DivPlatformC64::Channel(); + chan[i].std.setEngine(parent); } sid.reset(); @@ -522,14 +555,19 @@ void DivPlatformC64::setFlags(unsigned int flags) { break; } chipClock=rate; + for (int i=0; i<3; i++) { + oscBuf[i]->rate=rate/16; + } } int DivPlatformC64::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; skipRegisterWrites=false; + writeOscBuf=0; for (int i=0; i<3; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); @@ -539,6 +577,9 @@ int DivPlatformC64::init(DivEngine* p, int channels, int sugRate, unsigned int f } void DivPlatformC64::quit() { + for (int i=0; i<3; i++) { + delete oscBuf[i]; + } } DivPlatformC64::~DivPlatformC64() { diff --git a/src/engine/platform/c64.h b/src/engine/platform/c64.h index 9045e3332..495da461d 100644 --- a/src/engine/platform/c64.h +++ b/src/engine/platform/c64.h @@ -26,17 +26,22 @@ class DivPlatformC64: public DivDispatch { struct Channel { - int freq, baseFreq, pitch, prevFreq, testWhen, note; - unsigned char ins, sweep, wave, attack, decay, sustain, release; + int freq, baseFreq, pitch, pitch2, prevFreq, testWhen, note, ins; + unsigned char sweep, wave, attack, decay, sustain, release; short duty; bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, filter; - bool resetMask, resetFilter, resetDuty, ring, sync; + bool resetMask, resetFilter, resetDuty, ring, sync, test; signed char vol, outVol; DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freq(0), baseFreq(0), pitch(0), + pitch2(0), prevFreq(65535), testWhen(0), note(0), @@ -61,12 +66,15 @@ class DivPlatformC64: public DivDispatch { resetDuty(false), ring(false), sync(false), + test(false), vol(15) {} }; Channel chan[3]; + DivDispatchOscBuffer* oscBuf[3]; bool isMuted[3]; unsigned char filtControl, filtRes, vol; + unsigned char writeOscBuf; int filtCut, resetTime; SID sid; @@ -79,14 +87,16 @@ class DivPlatformC64: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); void setFlags(unsigned int flags); void notifyInsChange(int ins); + bool getDCOffRequired(); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); diff --git a/src/engine/platform/dummy.cpp b/src/engine/platform/dummy.cpp index 308b83722..34d614eb3 100644 --- a/src/engine/platform/dummy.cpp +++ b/src/engine/platform/dummy.cpp @@ -22,15 +22,29 @@ #include #include +#define CHIP_FREQBASE 2048 + void DivPlatformDummy::acquire(short* bufL, short* bufR, size_t start, size_t len) { + int chanOut; for (size_t i=start; i>13; + if (!isMuted[j]) { + chanOut=(((signed short)chan[j].pos)*chan[j].amp*chan[j].vol)>>12; + oscBuf[j]->data[oscBuf[j]->needle++]=chanOut; + out+=chanOut; + } else { + oscBuf[j]->data[oscBuf[j]->needle++]=0; + } chan[j].pos+=chan[j].freq; + } else { + oscBuf[j]->data[oscBuf[j]->needle++]=0; } } + if (out<-32768) out=-32768; + if (out>32767) out=32767; + bufL[i]=out; } } @@ -38,10 +52,12 @@ void DivPlatformDummy::muteChannel(int ch, bool mute) { isMuted[ch]=mute; } -void DivPlatformDummy::tick() { +void DivPlatformDummy::tick(bool sysTick) { for (unsigned char i=0; ichan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) return 2; + break; + } case DIV_CMD_LEGATO: - chan[c.chan].baseFreq=65.6f*pow(2.0f,((float)c.value/12.0f)); + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].freqChanged=true; break; case DIV_CMD_GET_VOLMAX: @@ -104,15 +144,23 @@ int DivPlatformDummy::init(DivEngine* p, int channels, int sugRate, unsigned int skipRegisterWrites=false; for (int i=0; irate=65536; + } } rate=65536; + chipClock=65536; chans=channels; reset(); return channels; } void DivPlatformDummy::quit() { + for (int i=0; i #define CHIP_FREQBASE 262144 -#define rWrite(a,v) if (!skipRegisterWrites) {fds_wr_mem(fds,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {doWrite(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } const char* regCheatSheetFDS[]={ "IOCtrl", "4023", @@ -78,13 +79,49 @@ const char* DivPlatformFDS::getEffectName(unsigned char effect) { return NULL; } -void DivPlatformFDS::acquire(short* bufL, short* bufR, size_t start, size_t len) { +void DivPlatformFDS::acquire_puNES(short* bufL, short* bufR, size_t start, size_t len) { for (size_t i=start; isnd.main.output; if (sample>32767) sample=32767; if (sample<-32768) sample=-32768; bufL[i]=sample; + if (++writeOscBuf>=32) { + writeOscBuf=0; + oscBuf->data[oscBuf->needle++]=sample<<1; + } + } +} + +void DivPlatformFDS::acquire_NSFPlay(short* bufL, short* bufR, size_t start, size_t len) { + int out[2]; + for (size_t i=start; iTick(1); + fds_NP->Render(out); + int sample=isMuted[0]?0:(out[0]<<1); + if (sample>32767) sample=32767; + if (sample<-32768) sample=-32768; + bufL[i]=sample; + if (++writeOscBuf>=32) { + writeOscBuf=0; + oscBuf->data[oscBuf->needle++]=sample<<1; + } + } +} + +void DivPlatformFDS::doWrite(unsigned short addr, unsigned char data) { + if (useNP) { + fds_NP->Write(addr,data); + } else { + fds_wr_mem(fds,addr,data); + } +} + +void DivPlatformFDS::acquire(short* bufL, short* bufR, size_t start, size_t len) { + if (useNP) { + acquire_NSFPlay(bufL,bufR,start,len); + } else { + acquire_puNES(bufL,bufR,start,len); } } @@ -97,7 +134,7 @@ void DivPlatformFDS::updateWave() { rWrite(0x4089,0); } -void DivPlatformFDS::tick() { +void DivPlatformFDS::tick(bool sysTick) { for (int i=0; i<1; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { @@ -107,21 +144,11 @@ void DivPlatformFDS::tick() { rWrite(0x4080,0x80|chan[i].outVol); } if (chan[i].std.arp.had) { - if (i==3) { // noise + if (!chan[i].inPorta) { if (chan[i].std.arp.mode) { - chan[i].baseFreq=chan[i].std.arp.val; + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); } else { - chan[i].baseFreq=chan[i].note+chan[i].std.arp.val; - } - if (chan[i].baseFreq>255) chan[i].baseFreq=255; - if (chan[i].baseFreq<0) chan[i].baseFreq=0; - } else { - if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); } } chan[i].freqChanged=true; @@ -155,6 +182,15 @@ void DivPlatformFDS::tick() { //if (!chan[i].keyOff) chan[i].keyOn=true; } } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } if (chan[i].active) { if (ws.tick()) { updateWave(); @@ -184,7 +220,7 @@ void DivPlatformFDS::tick() { } } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2); if (chan[i].freq>4095) chan[i].freq=4095; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].keyOn) { @@ -205,7 +241,7 @@ void DivPlatformFDS::tick() { int DivPlatformFDS::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FDS); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].freqChanged=true; @@ -248,7 +284,7 @@ int DivPlatformFDS::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); if (chan[c.chan].wave<0) { chan[c.chan].wave=0; ws.changeWave1(chan[c.chan].wave); @@ -261,7 +297,7 @@ int DivPlatformFDS::dispatch(DivCommand c) { case DIV_CMD_NOTE_OFF: chan[c.chan].active=false; chan[c.chan].keyOff=true; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -365,7 +401,7 @@ int DivPlatformFDS::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FDS)); } chan[c.chan].inPorta=c.value; break; @@ -397,6 +433,10 @@ void* DivPlatformFDS::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformFDS::getOscBuffer(int ch) { + return oscBuf; +} + unsigned char* DivPlatformFDS::getRegisterPool() { return regPool; } @@ -408,6 +448,7 @@ int DivPlatformFDS::getRegisterPoolSize() { void DivPlatformFDS::reset() { for (int i=0; i<1; i++) { chan[i]=DivPlatformFDS::Channel(); + chan[i].std.setEngine(parent); } ws.setEngine(parent); ws.init(NULL,64,63,false); @@ -415,7 +456,11 @@ void DivPlatformFDS::reset() { addWrite(0xffffffff,0); } - fds_reset(fds); + if (useNP) { + fds_NP->Reset(); + } else { + fds_reset(fds); + } memset(regPool,0,128); rWrite(0x4023,0); @@ -427,6 +472,10 @@ bool DivPlatformFDS::keyOffAffectsArp(int ch) { return true; } +void DivPlatformFDS::setNSFPlay(bool use) { + useNP=use; +} + void DivPlatformFDS::setFlags(unsigned int flags) { if (flags==2) { // Dendy rate=COLOR_PAL*2.0/5.0; @@ -436,10 +485,15 @@ void DivPlatformFDS::setFlags(unsigned int flags) { rate=COLOR_NTSC/2.0; } chipClock=rate; + oscBuf->rate=rate/32; + if (useNP) { + fds_NP->SetClock(rate); + fds_NP->SetRate(rate); + } } void DivPlatformFDS::notifyInsDeletion(void* ins) { - for (int i=0; i<5; i++) { + for (int i=0; i<1; i++) { chan[i].std.notifyInsDeletion((DivInstrument*)ins); } } @@ -457,18 +511,29 @@ int DivPlatformFDS::init(DivEngine* p, int channels, int sugRate, unsigned int f apuType=flags; dumpWrites=false; skipRegisterWrites=false; - fds=new struct _fds; + writeOscBuf=0; + if (useNP) { + fds_NP=new xgm::NES_FDS; + } else { + fds=new struct _fds; + } + oscBuf=new DivDispatchOscBuffer; for (int i=0; i<1; i++) { isMuted[i]=false; } setFlags(flags); reset(); - return 5; + return 1; } void DivPlatformFDS::quit() { - delete fds; + delete oscBuf; + if (useNP) { + delete fds_NP; + } else { + delete fds; + } } DivPlatformFDS::~DivPlatformFDS() { diff --git a/src/engine/platform/fds.h b/src/engine/platform/fds.h index d8d588a23..1c08e1bbb 100644 --- a/src/engine/platform/fds.h +++ b/src/engine/platform/fds.h @@ -24,18 +24,25 @@ #include "../macroInt.h" #include "../waveSynth.h" +#include "sound/nes_nsfplay/nes_fds.h" + class DivPlatformFDS: public DivDispatch { struct Channel { - int freq, baseFreq, pitch, prevFreq, note, modFreq; - unsigned char ins, duty, sweep, modDepth, modPos; + int freq, baseFreq, pitch, pitch2, prevFreq, note, modFreq, ins; + unsigned char duty, sweep, modDepth, modPos; bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, modOn; signed char vol, outVol, wave; signed char modTable[32]; DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freq(0), baseFreq(0), pitch(0), + pitch2(0), prevFreq(65535), note(0), modFreq(0), @@ -59,27 +66,37 @@ class DivPlatformFDS: public DivDispatch { } }; Channel chan[1]; + DivDispatchOscBuffer* oscBuf; bool isMuted[1]; DivWaveSynth ws; unsigned char apuType; + unsigned char writeOscBuf; + bool useNP; struct _fds* fds; + xgm::NES_FDS* fds_NP; unsigned char regPool[128]; void updateWave(); friend void putDispatchChan(void*,int,int); + void doWrite(unsigned short addr, unsigned char data); + void acquire_puNES(short* bufL, short* bufR, size_t start, size_t len); + void acquire_NSFPlay(short* bufL, short* bufR, size_t start, size_t len); + public: void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool keyOffAffectsArp(int ch); + void setNSFPlay(bool use); void setFlags(unsigned int flags); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 13eac1f32..1fc2b278e 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -87,6 +87,10 @@ void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len) GB_advance_cycles(gb,16); bufL[i]=gb->apu_output.final_sample.left; bufR[i]=gb->apu_output.final_sample.right; + + for (int i=0; i<4; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(gb->apu_output.current_sample[i].left+gb->apu_output.current_sample[i].right)<<6; + } } } @@ -146,7 +150,7 @@ static unsigned char noiseTable[256]={ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; -void DivPlatformGB::tick() { +void DivPlatformGB::tick(bool sysTick) { for (int i=0; i<4; i++) { chan[i].std.next(); if (chan[i].std.arp.had) { @@ -176,7 +180,7 @@ void DivPlatformGB::tick() { } if (chan[i].std.duty.had) { chan[i].duty=chan[i].std.duty.val; - DivInstrument* ins=parent->getIns(chan[i].ins); + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB); if (i!=2) { rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); } else { @@ -192,6 +196,25 @@ void DivPlatformGB::tick() { if (!chan[i].keyOff) chan[i].keyOn=true; } } + if (chan[i].std.panL.had) { + lastPan&=~(0x11<getIns(chan[i].ins); + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB); if (i==3) { // noise int ntPos=chan[i].baseFreq; if (ntPos<0) ntPos=0; if (ntPos>255) ntPos=255; chan[i].freq=noiseTable[ntPos]; } else { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); if (chan[i].freq>2047) chan[i].freq=2047; + if (chan[i].freq<0) chan[i].freq=0; } if (chan[i].keyOn) { if (i==2) { // wave @@ -255,7 +279,7 @@ void DivPlatformGB::muteChannel(int ch, bool mute) { int DivPlatformGB::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_GB); if (c.value!=DIV_NOTE_NULL) { if (c.chan==3) { // noise chan[c.chan].baseFreq=c.value; @@ -267,7 +291,7 @@ int DivPlatformGB::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); if (c.chan==2) { if (chan[c.chan].wave<0) { chan[c.chan].wave=0; @@ -281,7 +305,7 @@ int DivPlatformGB::dispatch(DivCommand c) { case DIV_CMD_NOTE_OFF: chan[c.chan].active=false; chan[c.chan].keyOff=true; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -292,7 +316,7 @@ int DivPlatformGB::dispatch(DivCommand c) { chan[c.chan].ins=c.value; chan[c.chan].insChanged=true; if (c.chan!=2) { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_GB); chan[c.chan].vol=ins->gb.envVol; if (parent->song.gbInsAffectsEnvelope) { rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3)); @@ -346,14 +370,16 @@ int DivPlatformGB::dispatch(DivCommand c) { chan[c.chan].duty=c.value; if (c.chan!=2) { chan[c.chan].freqChanged=true; - rWrite(16+c.chan*5+1,((chan[c.chan].duty&3)<<6)|(63-(parent->getIns(chan[c.chan].ins)->gb.soundLen&63))); + rWrite(16+c.chan*5+1,((chan[c.chan].duty&3)<<6)|(63-(parent->getIns(chan[c.chan].ins,DIV_INS_GB)->gb.soundLen&63))); } break; case DIV_CMD_PANNING: { lastPan&=~(0x11<0)|(((c.value>>4)>0)<<4); - lastPan|=c.value<0) pan|=0x10; + if (c.value2>0) pan|=0x01; + if (pan==0) pan=0x11; + lastPan|=pan<song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_GB)); } chan[c.chan].inPorta=c.value; break; @@ -407,6 +433,10 @@ void* DivPlatformGB::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformGB::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformGB::getRegisterPool() { return regPool; } @@ -418,6 +448,7 @@ int DivPlatformGB::getRegisterPoolSize() { void DivPlatformGB::reset() { for (int i=0; i<4; i++) { chan[i]=DivPlatformGB::Channel(); + chan[i].std.setEngine(parent); } ws.setEngine(parent); ws.init(NULL,32,15,false); @@ -472,20 +503,25 @@ void DivPlatformGB::poke(std::vector& wlist) { } int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + chipClock=4194304; + rate=chipClock/16; for (int i=0; i<4; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + oscBuf[i]->rate=rate; } parent=p; dumpWrites=false; skipRegisterWrites=false; - chipClock=4194304; - rate=chipClock/16; gb=new GB_gameboy_t; reset(); return 4; } void DivPlatformGB::quit() { + for (int i=0; i<4; i++) { + delete oscBuf[i]; + } delete gb; } diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h index 5a345e45d..cce1ffb6c 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -27,15 +27,20 @@ class DivPlatformGB: public DivDispatch { struct Channel { - int freq, baseFreq, pitch, note; - unsigned char ins, duty, sweep; + int freq, baseFreq, pitch, pitch2, note, ins; + unsigned char duty, sweep; bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta; signed char vol, outVol, wave; DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freq(0), baseFreq(0), pitch(0), + pitch2(0), note(0), ins(-1), duty(0), @@ -52,6 +57,7 @@ class DivPlatformGB: public DivDispatch { wave(-1) {} }; Channel chan[4]; + DivDispatchOscBuffer* oscBuf[4]; bool isMuted[4]; unsigned char lastPan; DivWaveSynth ws; @@ -66,11 +72,12 @@ class DivPlatformGB: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool isStereo(); void notifyInsChange(int ins); diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index e85bc7688..0f665cba1 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -77,6 +77,54 @@ const char* DivPlatformGenesis::getEffectName(unsigned char effect) { case 0x30: return "30xx: Toggle hard envelope reset on new notes"; break; + case 0x50: + return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; + break; + case 0x51: + return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; + break; + case 0x52: + return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; + break; + case 0x53: + return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)"; + break; + case 0x54: + return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)"; + break; + case 0x55: + return "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)"; + break; + case 0x56: + return "56xx: Set decay of all operators (0 to 1F)"; + break; + case 0x57: + return "57xx: Set decay of operator 1 (0 to 1F)"; + break; + case 0x58: + return "58xx: Set decay of operator 2 (0 to 1F)"; + break; + case 0x59: + return "59xx: Set decay of operator 3 (0 to 1F)"; + break; + case 0x5a: + return "5Axx: Set decay of operator 4 (0 to 1F)"; + break; + case 0x5b: + return "5Bxx: Set decay 2 of all operators (0 to 1F)"; + break; + case 0x5c: + return "5Cxx: Set decay 2 of operator 1 (0 to 1F)"; + break; + case 0x5d: + return "5Dxx: Set decay 2 of operator 2 (0 to 1F)"; + break; + case 0x5e: + return "5Exx: Set decay 2 of operator 3 (0 to 1F)"; + break; + case 0x5f: + return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; + break; } return NULL; } @@ -86,14 +134,22 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s static int os[2]; for (size_t h=start; h=rate) { + dacDelay-=rate; + dacReady=true; + } + } if (dacMode && dacSample!=-1) { - dacPeriod-=6; - if (dacPeriod<1) { + dacPeriod+=dacRate; + if (dacPeriod>=rate) { DivSample* s=parent->getSample(dacSample); if (s->samples>0) { if (!isMuted[5]) { - if (writes.size()<16) { + if (dacReady && writes.size()<16) { urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); + dacReady=false; } } if (++dacPos>=s->samples) { @@ -106,7 +162,7 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s } } } - dacPeriod+=MAX(40,dacRate); + while (dacPeriod>=rate) dacPeriod-=rate; } else { dacSample=-1; } @@ -136,7 +192,8 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s OPN2_Clock(&fm,o); os[0]+=o[0]; os[1]+=o[1]; //OPN2_Write(&fm,0,0); - } + oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]<<7; + } os[0]=(os[0]<<5); if (os[0]<-32768) os[0]=-32768; @@ -154,15 +211,25 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; + ymfm::ym2612::fm_engine* fme=fm_ymfm->debug_engine(); + for (size_t h=start; h=rate) { + dacDelay-=rate; + dacReady=true; + } + } if (dacMode && dacSample!=-1) { - dacPeriod-=24; - if (dacPeriod<1) { + dacPeriod+=dacRate; + if (dacPeriod>=rate) { DivSample* s=parent->getSample(dacSample); if (s->samples>0) { if (!isMuted[5]) { - if (writes.size()<16) { + if (dacReady && writes.size()<16) { urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); + dacReady=false; } } if (++dacPos>=s->samples) { @@ -175,7 +242,7 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si } } } - dacPeriod+=MAX(40,dacRate); + while (dacPeriod>=rate) dacPeriod-=rate; } else { dacSample=-1; } @@ -200,6 +267,10 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si os[0]=out_ymfm.data[0]; os[1]=out_ymfm.data[1]; //OPN2_Write(&fm,0,0); + + for (int i=0; i<6; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1))<<6; + } if (os[0]<-32768) os[0]=-32768; if (os[0]>32767) os[0]=32767; @@ -220,7 +291,7 @@ void DivPlatformGenesis::acquire(short* bufL, short* bufR, size_t start, size_t } } -void DivPlatformGenesis::tick() { +void DivPlatformGenesis::tick(bool sysTick) { for (int i=0; i<6; i++) { if (i==2 && extMode) continue; chan[i].std.next(); @@ -245,19 +316,40 @@ void DivPlatformGenesis::tick() { if (chan[i].std.arp.had) { if (!chan[i].inPorta) { if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11); } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val); + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11); } } chan[i].freqChanged=true; } else { if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11); chan[i].freqChanged=true; } } + if (chan[i].std.panL.had) { + chan[i].pan=chan[i].std.panL.val&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].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + chan[i].keyOn=true; + } + } + if (chan[i].std.alg.had) { chan[i].state.alg=chan[i].std.alg.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); @@ -379,11 +471,20 @@ void DivPlatformGenesis::tick() { for (int i=0; i<6; i++) { if (i==2 && extMode) continue; if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); - if (chan[i].freq>262143) chan[i].freq=262143; - int freqt=toFreq(chan[i].freq); - immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8); - immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff); + int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2); + int block=(chan[i].baseFreq&0xf800)>>11; + if (fNum<0) fNum=0; + if (fNum>2047) { + while (block<7) { + fNum>>=1; + block++; + } + if (fNum>2047) fNum=2047; + } + chan[i].freq=(block<<11)|fNum; + if (chan[i].freq>0x3fff) chan[i].freq=0x3fff; + immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8); + immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); if (chan[i].furnaceDac && dacMode) { double off=1.0; if (dacSample>=0 && dacSamplesong.sampleLen) { @@ -391,12 +492,13 @@ void DivPlatformGenesis::tick() { if (s->centerRate<1) { off=1.0; } else { - off=8363.0/(double)s->centerRate; + off=(double)s->centerRate/8363.0; } } - dacRate=(1280000*1.25*off)/MAX(1,chan[i].baseFreq); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4)+chan[i].pitch2; + dacRate=chan[i].freq*off; if (dacRate<1) dacRate=1; - if (dumpWrites) addWrite(0xffff0001,1280000/dacRate); + if (dumpWrites) addWrite(0xffff0001,dacRate); } chan[i].freqChanged=false; } @@ -407,47 +509,6 @@ void DivPlatformGenesis::tick() { } } -int DivPlatformGenesis::octave(int freq) { - if (freq>=82432) { - return 128; - } else if (freq>=41216) { - return 64; - } else if (freq>=20608) { - return 32; - } else if (freq>=10304) { - return 16; - } else if (freq>=5152) { - return 8; - } else if (freq>=2576) { - return 4; - } else if (freq>=1288) { - return 2; - } else { - return 1; - } - return 1; -} - -int DivPlatformGenesis::toFreq(int freq) { - if (freq>=82432) { - return 0x3800|((freq>>7)&0x7ff); - } else if (freq>=41216) { - return 0x3000|((freq>>6)&0x7ff); - } else if (freq>=20608) { - return 0x2800|((freq>>5)&0x7ff); - } else if (freq>=10304) { - return 0x2000|((freq>>4)&0x7ff); - } else if (freq>=5152) { - return 0x1800|((freq>>3)&0x7ff); - } else if (freq>=2576) { - return 0x1000|((freq>>2)&0x7ff); - } else if (freq>=1288) { - return 0x800|((freq>>1)&0x7ff); - } else { - return freq&0x7ff; - } -} - void DivPlatformGenesis::muteChannel(int ch, bool mute) { isMuted[ch]=mute; for (int j=0; j<4; j++) { @@ -469,7 +530,7 @@ void DivPlatformGenesis::muteChannel(int ch, bool mute) { int DivPlatformGenesis::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); if (c.chan==5) { if (ins->type==DIV_INS_AMIGA) { dacMode=1; @@ -493,8 +554,10 @@ int DivPlatformGenesis::dispatch(DivCommand c) { } dacPos=0; dacPeriod=0; - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); - chan[c.chan].freqChanged=true; + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false); + chan[c.chan].freqChanged=true; + } chan[c.chan].furnaceDac=true; } else { // compatible mode if (c.value!=DIV_NOTE_NULL) { @@ -511,7 +574,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { } dacPos=0; dacPeriod=0; - dacRate=1280000/MAX(1,parent->getSample(dacSample)->rate); + dacRate=MAX(1,parent->getSample(dacSample)->rate); if (dumpWrites) addWrite(0xffff0001,parent->getSample(dacSample)->rate); chan[c.chan].furnaceDac=false; } @@ -522,7 +585,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { chan[c.chan].state=ins->fm; } - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } @@ -559,7 +622,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { chan[c.chan].insChanged=false; if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11); chan[c.chan].portaPause=false; chan[c.chan].note=c.value; chan[c.chan].freqChanged=true; @@ -625,10 +688,10 @@ int DivPlatformGenesis::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { - if (c.value==0) { + if (c.value==0 && c.value2==0) { chan[c.chan].pan=3; } else { - chan[c.chan].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); + chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1); } rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); break; @@ -639,31 +702,65 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - int destFreq=NOTE_FREQUENCY(c.value2); + if (c.chan==5 && chan[c.chan].furnaceDac && dacMode) { + int destFreq=parent->calcBaseFreq(1,1,c.value2,false); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value*16; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value*16; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + int destFreq=NOTE_FNUM_BLOCK(c.value2,11); int newFreq; bool return2=false; + if (chan[c.chan].portaPause) { + chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq; + } if (destFreq>chan[c.chan].baseFreq) { - newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq); + newFreq=chan[c.chan].baseFreq+c.value; if (newFreq>=destFreq) { newFreq=destFreq; return2=true; } } else { - newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq); + newFreq=chan[c.chan].baseFreq-c.value; if (newFreq<=destFreq) { newFreq=destFreq; return2=true; } } + // check for octave boundary + // what the heck! if (!chan[c.chan].portaPause) { - if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) { + if ((newFreq&0x7ff)>1288 && (newFreq&0xf800)<0x3800) { + chan[c.chan].portaPauseFreq=(644)|((newFreq+0x800)&0xf800); + chan[c.chan].portaPause=true; + break; + } + if ((newFreq&0x7ff)<644 && (newFreq&0xf800)>0) { + chan[c.chan].portaPauseFreq=newFreq=(1287)|((newFreq-0x800)&0xf800); chan[c.chan].portaPause=true; break; } } - chan[c.chan].baseFreq=newFreq; chan[c.chan].portaPause=false; chan[c.chan].freqChanged=true; + chan[c.chan].baseFreq=newFreq; if (return2) { chan[c.chan].inPorta=false; return 2; @@ -682,7 +779,11 @@ int DivPlatformGenesis::dispatch(DivCommand c) { } break; case DIV_CMD_LEGATO: { - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + if (c.chan==5 && chan[c.chan].furnaceDac && dacMode) { + chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false); + } else { + chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11); + } chan[c.chan].note=c.value; chan[c.chan].freqChanged=true; break; @@ -727,7 +828,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); } - } else { + } else if (c.value<4) { DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; op.ar=c.value2&31; unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; @@ -735,6 +836,134 @@ int DivPlatformGenesis::dispatch(DivCommand c) { } break; } + case DIV_CMD_FM_RS: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_AM: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_DR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_SL: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_RR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_D2R: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + break; + } + case DIV_CMD_FM_DT: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dt=c.value&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dt=c.value2&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + break; + } + case DIV_CMD_FM_SSG: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + break; + } case DIV_CMD_FM_HARD_RESET: chan[c.chan].hardReset=c.value; break; @@ -798,6 +1027,10 @@ void* DivPlatformGenesis::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformGenesis::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformGenesis::getRegisterPool() { return regPool; } @@ -819,6 +1052,7 @@ void DivPlatformGenesis::reset() { } for (int i=0; i<10; i++) { chan[i]=DivPlatformGenesis::Channel(); + chan[i].std.setEngine(parent); chan[i].vol=0x7f; chan[i].outVol=0x7f; } @@ -833,6 +1067,8 @@ void DivPlatformGenesis::reset() { dacPeriod=0; dacPos=0; dacRate=0; + dacDelay=0; + dacReady=true; dacSample=-1; sampleBank=0; lfoValue=8; @@ -907,6 +1143,9 @@ void DivPlatformGenesis::setFlags(unsigned int flags) { } else { rate=chipClock/36; } + for (int i=0; i<10; i++) { + oscBuf[i]->rate=rate; + } } int DivPlatformGenesis::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { @@ -916,6 +1155,7 @@ int DivPlatformGenesis::init(DivEngine* p, int channels, int sugRate, unsigned i skipRegisterWrites=false; for (int i=0; i<10; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } fm_ymfm=NULL; setFlags(flags); @@ -925,6 +1165,9 @@ int DivPlatformGenesis::init(DivEngine* p, int channels, int sugRate, unsigned i } void DivPlatformGenesis::quit() { + for (int i=0; i<10; i++) { + delete oscBuf[i]; + } if (fm_ymfm!=NULL) delete fm_ymfm; } diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index 51d98d807..236e5c43f 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -36,17 +36,23 @@ class DivPlatformGenesis: public DivDispatch { DivInstrumentFM state; DivMacroInt std; unsigned char freqH, freqL; - int freq, baseFreq, pitch, note; - unsigned char ins; + int freq, baseFreq, pitch, pitch2, portaPauseFreq, note; + int ins; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset; int vol, outVol; unsigned char pan; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), + pitch2(0), + portaPauseFreq(0), note(0), ins(-1), active(false), @@ -62,6 +68,7 @@ class DivPlatformGenesis: public DivDispatch { pan(3) {} }; Channel chan[10]; + DivDispatchOscBuffer* oscBuf[10]; bool isMuted[10]; struct QueuedWrite { unsigned short addr; @@ -84,6 +91,8 @@ class DivPlatformGenesis: public DivDispatch { int dacRate; unsigned int dacPos; int dacSample; + int dacDelay; + bool dacReady; unsigned char sampleBank; unsigned char lfoValue; @@ -93,9 +102,6 @@ class DivPlatformGenesis: public DivDispatch { short oldWrites[512]; short pendingWrites[512]; - int octave(int freq); - int toFreq(int freq); - friend void putDispatchChan(void*,int,int); void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len); @@ -105,11 +111,12 @@ class DivPlatformGenesis: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool isStereo(); void setYMFM(bool use); diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 60f877b7e..ae4d74cd9 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -37,7 +37,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { int ordch=orderedOps[ch]; switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(opChan[ch].ins); + DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); if (opChan[ch].insChanged) { chan[2].state.alg=ins->fm.alg; @@ -72,7 +72,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { opChan[ch].insChanged=false; if (c.value!=DIV_NOTE_NULL) { - opChan[ch].baseFreq=NOTE_FREQUENCY(c.value); + opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11); opChan[ch].portaPause=false; opChan[ch].freqChanged=true; } @@ -107,10 +107,10 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { opChan[ch].ins=c.value; break; case DIV_CMD_PANNING: { - if (c.value==0) { + if (c.value==0 && c.value2==0) { opChan[ch].pan=3; } else { - opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); + opChan[ch].pan=(c.value2>0)|((c.value>0)<<1); } if (parent->song.sharedExtStat) { for (int i=0; i<4; i++) { @@ -127,40 +127,70 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - int destFreq=NOTE_FREQUENCY(c.value2); + int destFreq=NOTE_FNUM_BLOCK(c.value2,11); int newFreq; bool return2=false; + if (opChan[ch].portaPause) { + opChan[ch].baseFreq=opChan[ch].portaPauseFreq; + } if (destFreq>opChan[ch].baseFreq) { - newFreq=opChan[ch].baseFreq+c.value*octave(opChan[ch].baseFreq); + newFreq=opChan[ch].baseFreq+c.value; if (newFreq>=destFreq) { newFreq=destFreq; return2=true; } } else { - newFreq=opChan[ch].baseFreq-c.value*octave(opChan[ch].baseFreq); + newFreq=opChan[ch].baseFreq-c.value; if (newFreq<=destFreq) { newFreq=destFreq; return2=true; } } + // what the heck! if (!opChan[ch].portaPause) { - if (octave(opChan[ch].baseFreq)!=octave(newFreq)) { - opChan[ch].portaPause=true; - break; + if ((newFreq&0x7ff)>1288 && (newFreq&0xf800)<0x3800) { + if (parent->song.fbPortaPause) { + opChan[ch].portaPauseFreq=(644)|((newFreq+0x800)&0xf800); + opChan[ch].portaPause=true; + break; + } else { + newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800); + } + } + if ((newFreq&0x7ff)<644 && (newFreq&0xf800)>0) { + if (parent->song.fbPortaPause) { + opChan[ch].portaPauseFreq=newFreq=(1287)|((newFreq-0x800)&0xf800); + opChan[ch].portaPause=true; + break; + } else { + newFreq=(newFreq<<1)|((newFreq-0x800)&0xf800); + } } } - opChan[ch].baseFreq=newFreq; opChan[ch].portaPause=false; opChan[ch].freqChanged=true; + opChan[ch].baseFreq=newFreq; if (return2) return 2; break; } case DIV_CMD_SAMPLE_MODE: { - // ignored on extended channel 3 mode. + // not ignored actually! + if (!parent->song.ignoreDACModeOutsideIntendedChannel) { + dacMode=c.value; + rWrite(0x2b,c.value<<7); + } break; } + case DIV_CMD_SAMPLE_BANK: + if (!parent->song.ignoreDACModeOutsideIntendedChannel) { + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + } + break; case DIV_CMD_LEGATO: { - opChan[ch].baseFreq=NOTE_FREQUENCY(c.value); + opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11); opChan[ch].freqChanged=true; break; } @@ -205,6 +235,134 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { } break; } + case DIV_CMD_FM_RS: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[2].state.op[i]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_AM: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[2].state.op[i]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_DR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[2].state.op[i]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_SL: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[2].state.op[i]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_RR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[2].state.op[i]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_D2R: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[2].state.op[i]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + break; + } + case DIV_CMD_FM_DT: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[2].state.op[i]; + op.dt=c.value&7; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.dt=c.value2&7; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + break; + } + case DIV_CMD_FM_SSG: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[2].state.op[i]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + break; + } case DIV_CMD_GET_VOLMAX: return 127; break; @@ -254,7 +412,7 @@ static int opChanOffsH[4]={ 0xad, 0xae, 0xac, 0xa6 }; -void DivPlatformGenesisExt::tick() { +void DivPlatformGenesisExt::tick(bool sysTick) { if (extMode) { bool writeSomething=false; unsigned char writeMask=2; @@ -271,41 +429,26 @@ void DivPlatformGenesisExt::tick() { } } - DivPlatformGenesis::tick(); + DivPlatformGenesis::tick(sysTick); bool writeNoteOn=false; unsigned char writeMask=2; if (extMode) for (int i=0; i<4; i++) { if (opChan[i].freqChanged) { - opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch); - if (opChan[i].freq>262143) opChan[i].freq=262143; - if (opChan[i].freq>=82432) { - opChan[i].freqH=((opChan[i].freq>>15)&7)|0x38; - opChan[i].freqL=(opChan[i].freq>>7)&0xff; - } else if (opChan[i].freq>=41216) { - opChan[i].freqH=((opChan[i].freq>>14)&7)|0x30; - opChan[i].freqL=(opChan[i].freq>>6)&0xff; - } else if (opChan[i].freq>=20608) { - opChan[i].freqH=((opChan[i].freq>>13)&7)|0x28; - opChan[i].freqL=(opChan[i].freq>>5)&0xff; - } else if (opChan[i].freq>=10304) { - opChan[i].freqH=((opChan[i].freq>>12)&7)|0x20; - opChan[i].freqL=(opChan[i].freq>>4)&0xff; - } else if (opChan[i].freq>=5152) { - opChan[i].freqH=((opChan[i].freq>>11)&7)|0x18; - opChan[i].freqL=(opChan[i].freq>>3)&0xff; - } else if (opChan[i].freq>=2576) { - opChan[i].freqH=((opChan[i].freq>>10)&7)|0x10; - opChan[i].freqL=(opChan[i].freq>>2)&0xff; - } else if (opChan[i].freq>=1288) { - opChan[i].freqH=((opChan[i].freq>>9)&7)|0x08; - opChan[i].freqL=(opChan[i].freq>>1)&0xff; - } else { - opChan[i].freqH=(opChan[i].freq>>8)&7; - opChan[i].freqL=opChan[i].freq&0xff; + int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2); + int block=(opChan[i].baseFreq&0xf800)>>11; + if (fNum<0) fNum=0; + if (fNum>2047) { + while (block<7) { + fNum>>=1; + block++; + } + if (fNum>2047) fNum=2047; } - immWrite(opChanOffsH[i],opChan[i].freqH); - immWrite(opChanOffsL[i],opChan[i].freqL); + opChan[i].freq=(block<<11)|fNum; + if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff; + immWrite(opChanOffsH[i],opChan[i].freq>>8); + immWrite(opChanOffsL[i],opChan[i].freq&0xff); } writeMask|=opChan[i].active<<(4+i); if (opChan[i].keyOn) { @@ -376,6 +519,12 @@ void* DivPlatformGenesisExt::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformGenesisExt::getOscBuffer(int ch) { + if (ch>=6) return oscBuf[ch-3]; + if (ch<3) return oscBuf[ch]; + return NULL; +} + void DivPlatformGenesisExt::reset() { DivPlatformGenesis::reset(); diff --git a/src/engine/platform/genesisext.h b/src/engine/platform/genesisext.h index e3320e191..b482663c2 100644 --- a/src/engine/platform/genesisext.h +++ b/src/engine/platform/genesisext.h @@ -25,13 +25,28 @@ class DivPlatformGenesisExt: public DivPlatformGenesis { struct OpChannel { DivMacroInt std; unsigned char freqH, freqL; - int freq, baseFreq, pitch; - unsigned char ins; + int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause; int vol; unsigned char pan; - OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} + OpChannel(): + freqH(0), + freqL(0), + freq(0), + baseFreq(0), + pitch(0), + pitch2(0), + portaPauseFreq(0), + ins(-1), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + portaPause(false), + vol(0), + pan(3) {} }; OpChannel opChan[4]; bool isOpMuted[4]; @@ -39,9 +54,10 @@ class DivPlatformGenesisExt: public DivPlatformGenesis { public: int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool keyOffAffectsArp(int ch); bool keyOffAffectsPorta(int ch); diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index 70e7bc413..095710612 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -142,10 +142,10 @@ const char* DivPlatformLynx::getEffectName(unsigned char effect) { } void DivPlatformLynx::acquire(short* bufL, short* bufR, size_t start, size_t len) { - mikey->sampleAudio( bufL + start, bufR + start, len ); + mikey->sampleAudio( bufL + start, bufR + start, len, oscBuf ); } -void DivPlatformLynx::tick() { +void DivPlatformLynx::tick(bool sysTick) { for (int i=0; i<4; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { @@ -171,21 +171,45 @@ void DivPlatformLynx::tick() { } } + if (chan[i].std.panL.had) { + chan[i].pan&=0x0f; + chan[i].pan|=(chan[i].std.panL.val&15)<<4; + } + + if (chan[i].std.panR.had) { + chan[i].pan&=0xf0; + chan[i].pan|=chan[i].std.panR.val&15; + } + + if (chan[i].std.panL.had || chan[i].std.panR.had) { + WRITE_ATTEN(i,chan[i].pan); + } + + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + if (chan[i].freqChanged) { if (chan[i].lfsr >= 0) { WRITE_LFSR(i, (chan[i].lfsr&0xff)); WRITE_OTHER(i, ((chan[i].lfsr&0xf00)>>4)); chan[i].lfsr=-1; } - chan[i].fd=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); + chan[i].fd=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); if (chan[i].std.duty.had) { chan[i].duty=chan[i].std.duty.val; WRITE_FEEDBACK(i, chan[i].duty.feedback); } WRITE_CONTROL(i, (chan[i].fd.clockDivider|0x18|chan[i].duty.int_feedback7)); WRITE_BACKUP( i, chan[i].fd.backup ); - } - else if (chan[i].std.duty.had) { + chan[i].freqChanged=false; + } else if (chan[i].std.duty.had) { chan[i].duty = chan[i].std.duty.val; WRITE_FEEDBACK(i, chan[i].duty.feedback); WRITE_CONTROL(i, (chan[i].fd.clockDivider|0x18|chan[i].duty.int_feedback7)); @@ -206,12 +230,12 @@ int DivPlatformLynx::dispatch(DivCommand c) { } chan[c.chan].active=true; WRITE_VOLUME(c.chan,(isMuted[c.chan]?0:(chan[c.chan].vol&127))); - chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_MIKEY)); break; case DIV_CMD_NOTE_OFF: chan[c.chan].active=false; WRITE_VOLUME(c.chan, 0); - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_LYNX_LFSR_LOAD: chan[c.chan].freqChanged=true; @@ -223,7 +247,7 @@ int DivPlatformLynx::dispatch(DivCommand c) { break; case DIV_CMD_INSTRUMENT: chan[c.chan].ins=c.value; - //chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + //chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_MIKEY)); break; case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { @@ -235,7 +259,7 @@ int DivPlatformLynx::dispatch(DivCommand c) { } break; case DIV_CMD_PANNING: - chan[c.chan].pan=c.value; + chan[c.chan].pan=(c.value&0xf0)|(c.value2>>4); WRITE_ATTEN(c.chan,chan[c.chan].pan); break; case DIV_CMD_GET_VOLUME: @@ -279,7 +303,7 @@ int DivPlatformLynx::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_MIKEY)); } chan[c.chan].inPorta=c.value; break; @@ -318,6 +342,10 @@ void* DivPlatformLynx::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformLynx::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformLynx::getRegisterPool() { return const_cast( mikey->getRegisterPool() ); @@ -329,11 +357,11 @@ int DivPlatformLynx::getRegisterPoolSize() } void DivPlatformLynx::reset() { - - mikey = std::make_unique( rate ); + mikey=std::make_unique(rate); for (int i=0; i<4; i++) { - chan[i]= DivPlatformLynx::Channel(); + chan[i]=DivPlatformLynx::Channel(); + chan[i].std.setEngine(parent); } if (dumpWrites) { addWrite(0xffffffff,0); @@ -374,16 +402,24 @@ int DivPlatformLynx::init(DivEngine* p, int channels, int sugRate, unsigned int for (int i=0; i<4; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } chipClock = 16000000; rate = chipClock/128; + for (int i=0; i<4; i++) { + oscBuf[i]->rate=rate; + } + reset(); return 4; } void DivPlatformLynx::quit() { + for (int i=0; i<4; i++) { + delete oscBuf[i]; + } mikey.reset(); } diff --git a/src/engine/platform/lynx.h b/src/engine/platform/lynx.h index 536a874a8..fab8b0f82 100644 --- a/src/engine/platform/lynx.h +++ b/src/engine/platform/lynx.h @@ -44,16 +44,21 @@ class DivPlatformLynx: public DivDispatch { DivMacroInt std; MikeyFreqDiv fd; MikeyDuty duty; - int baseFreq, pitch, note, actualNote, lfsr; - unsigned char ins, pan; + int baseFreq, pitch, pitch2, note, actualNote, lfsr, ins; + unsigned char pan; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; signed char vol, outVol; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): std(), fd(0), duty(0), baseFreq(0), pitch(0), + pitch2(0), note(0), actualNote(0), lfsr(-1), @@ -69,6 +74,7 @@ class DivPlatformLynx: public DivDispatch { outVol(127) {} }; Channel chan[4]; + DivDispatchOscBuffer* oscBuf[4]; bool isMuted[4]; std::unique_ptr mikey; friend void putDispatchChan(void*,int,int); @@ -76,11 +82,12 @@ class DivPlatformLynx: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool isStereo(); bool keyOffAffectsArp(int ch); diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp index 79d9fc61e..8446643b6 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -59,7 +59,7 @@ void DivPlatformMMC5::acquire(short* bufL, short* bufR, size_t start, size_t len if (dacPeriod>=rate) { DivSample* s=parent->getSample(dacSample); if (s->samples>0) { - if (!isMuted[4]) { + if (!isMuted[2]) { rWrite(0x5011,((unsigned char)s->data8[dacPos]+0x80)); } if (++dacPos>=s->samples) { @@ -92,10 +92,17 @@ void DivPlatformMMC5::acquire(short* bufL, short* bufR, size_t start, size_t len if (sample>32767) sample=32767; if (sample<-32768) sample=-32768; bufL[i]=sample; + + if (++writeOscBuf>=32) { + writeOscBuf=0; + oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:((mmc5->S3.output*10)<<7); + oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:((mmc5->S4.output*10)<<7); + oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:((mmc5->pcm.output*2)<<6); + } } } -void DivPlatformMMC5::tick() { +void DivPlatformMMC5::tick(bool sysTick) { for (int i=0; i<2; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { @@ -123,8 +130,23 @@ void DivPlatformMMC5::tick() { chan[i].duty=chan[i].std.duty.val; rWrite(0x5000+i*4,0x30|chan[i].outVol|((chan[i].duty&3)<<6)); } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + chan[i].freqChanged=true; + chan[i].prevFreq=-1; + } + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1; if (chan[i].freq>2047) chan[i].freq=2047; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].keyOn) { @@ -149,18 +171,18 @@ void DivPlatformMMC5::tick() { } // PCM - if (chan[4].freqChanged) { - chan[4].freq=parent->calcFreq(chan[4].baseFreq,chan[4].pitch,false); - if (chan[4].furnaceDac) { + if (chan[2].freqChanged) { + chan[2].freq=parent->calcFreq(chan[2].baseFreq,chan[2].pitch,false); + if (chan[2].furnaceDac) { double off=1.0; if (dacSample>=0 && dacSamplesong.sampleLen) { DivSample* s=parent->getSample(dacSample); off=(double)s->centerRate/8363.0; } - dacRate=MIN(chan[4].freq*off,32000); + dacRate=MIN(chan[2].freq*off,32000); if (dumpWrites) addWrite(0xffff0001,dacRate); } - chan[4].freqChanged=false; + chan[2].freqChanged=false; } } @@ -168,7 +190,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: if (c.chan==2) { // PCM - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD); if (ins->type==DIV_INS_AMIGA) { dacSample=ins->amiga.initSample; if (dacSample<0 || dacSample>=parent->song.sampleLen) { @@ -218,7 +240,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD)); rWrite(0x5000+c.chan*4,0x30|chan[c.chan].vol|((chan[c.chan].duty&3)<<6)); break; case DIV_CMD_NOTE_OFF: @@ -228,7 +250,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) { } chan[c.chan].active=false; chan[c.chan].keyOff=true; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -282,6 +304,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) { } case DIV_CMD_STD_NOISE_MODE: chan[c.chan].duty=c.value; + rWrite(0x5000+c.chan*4,0x30|chan[c.chan].outVol|((chan[c.chan].duty&3)<<6)); break; case DIV_CMD_SAMPLE_BANK: sampleBank=c.value; @@ -296,7 +319,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD)); } chan[c.chan].inPorta=c.value; break; @@ -327,6 +350,10 @@ void* DivPlatformMMC5::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformMMC5::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformMMC5::getRegisterPool() { return regPool; } @@ -342,6 +369,7 @@ float DivPlatformMMC5::getPostAmp() { void DivPlatformMMC5::reset() { for (int i=0; i<3; i++) { chan[i]=DivPlatformMMC5::Channel(); + chan[i].std.setEngine(parent); } if (dumpWrites) { addWrite(0xffffffff,0); @@ -373,6 +401,9 @@ void DivPlatformMMC5::setFlags(unsigned int flags) { rate=COLOR_NTSC/2.0; } chipClock=rate; + for (int i=0; i<3; i++) { + oscBuf[i]->rate=rate/32; + } } void DivPlatformMMC5::notifyInsDeletion(void* ins) { @@ -394,9 +425,11 @@ int DivPlatformMMC5::init(DivEngine* p, int channels, int sugRate, unsigned int apuType=flags; dumpWrites=false; skipRegisterWrites=false; + writeOscBuf=0; mmc5=new struct _mmc5; for (int i=0; i<3; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; //mmc5->muted[i]=false; // TODO } setFlags(flags); @@ -407,6 +440,9 @@ int DivPlatformMMC5::init(DivEngine* p, int channels, int sugRate, unsigned int } void DivPlatformMMC5::quit() { + for (int i=0; i<3; i++) { + delete oscBuf[i]; + } delete mmc5; } diff --git a/src/engine/platform/mmc5.h b/src/engine/platform/mmc5.h index 6b364d5ad..2213b995f 100644 --- a/src/engine/platform/mmc5.h +++ b/src/engine/platform/mmc5.h @@ -25,15 +25,20 @@ class DivPlatformMMC5: public DivDispatch { struct Channel { - int freq, baseFreq, pitch, prevFreq, note; - unsigned char ins, duty, sweep; + int freq, baseFreq, pitch, pitch2, prevFreq, note, ins; + unsigned char duty, sweep; bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, furnaceDac; signed char vol, outVol, wave; DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freq(0), baseFreq(0), pitch(0), + pitch2(0), prevFreq(65535), note(0), ins(-1), @@ -52,12 +57,14 @@ class DivPlatformMMC5: public DivDispatch { wave(-1) {} }; Channel chan[5]; + DivDispatchOscBuffer* oscBuf[3]; bool isMuted[5]; int dacPeriod, dacRate; unsigned int dacPos; int dacSample; unsigned char sampleBank; unsigned char apuType; + unsigned char writeOscBuf; struct _mmc5* mmc5; unsigned char regPool[128]; @@ -67,11 +74,12 @@ class DivPlatformMMC5: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool keyOffAffectsArp(int ch); float getPostAmp(); diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 7c9188d49..ef368203b 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -161,6 +161,10 @@ void DivPlatformN163::acquire(short* bufL, short* bufR, size_t start, size_t len if (out<-32768) out=-32768; bufL[i]=bufR[i]=out; + if (n163.voice_cycle()==0x78) for (int i=0; i<8; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=n163.chan_out(i)<<7; + } + // command queue while (!writes.empty()) { QueuedWrite w=writes.front(); @@ -171,36 +175,50 @@ void DivPlatformN163::acquire(short* bufL, short* bufR, size_t start, size_t len } } -void DivPlatformN163::updateWave(int wave, int pos, int len) { +void DivPlatformN163::updateWave(int ch, int wave, int pos, int len) { len&=0xfc; // 4 nibble boundary - DivWavetable* wt=parent->getWave(wave); - for (int i=0; i=((0x78-(chanMax<<3))<<1)) { // avoid conflict with channel register area - break; - } - unsigned char mask=(addr&1)?0xf0:0x0f; - if (wt->max<1 || wt->len<1) { - rWriteMask(addr>>1,0,mask); - } else { - int data=wt->data[i*wt->len/len]*15/wt->max; - if (data<0) data=0; - if (data>15) data=15; + if (wave<0) { + // load from wave synth + for (int i=0; i=((0x78-(chanMax<<3))<<1)) { // avoid conflict with channel register area + break; + } + unsigned char mask=(addr&1)?0xf0:0x0f; + int data=chan[ch].ws.output[i]; rWriteMask(addr>>1,(addr&1)?(data<<4):(data&0xf),mask); } + } else { + // load from custom + DivWavetable* wt=parent->getWave(wave); + for (int i=0; i=((0x78-(chanMax<<3))<<1)) { // avoid conflict with channel register area + break; + } + unsigned char mask=(addr&1)?0xf0:0x0f; + if (wt->max<1 || wt->len<1) { + rWriteMask(addr>>1,0,mask); + } else { + int data=wt->data[i*wt->len/len]*15/wt->max; + if (data<0) data=0; + if (data>15) data=15; + rWriteMask(addr>>1,(addr&1)?(data<<4):(data&0xf),mask); + } + } } } void DivPlatformN163::updateWaveCh(int ch) { if (ch<=chanMax) { - updateWave(chan[ch].wave,chan[ch].wavePos,chan[ch].waveLen); + updateWave(ch,-1,chan[ch].wavePos,chan[ch].waveLen); if (chan[ch].active && !isMuted[ch]) { chan[ch].volumeChanged=true; } } } -void DivPlatformN163::tick() { +void DivPlatformN163::tick(bool sysTick) { for (int i=0; i<=chanMax; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { @@ -241,14 +259,25 @@ void DivPlatformN163::tick() { if (chan[i].std.wave.had) { if (chan[i].wave!=chan[i].std.wave.val) { chan[i].wave=chan[i].std.wave.val; + chan[i].ws.changeWave1(chan[i].wave); if (chan[i].waveMode&0x2) { chan[i].waveUpdated=true; } } } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } if (chan[i].std.ex1.had) { if (chan[i].waveLen!=(chan[i].std.ex1.val&0xfc)) { chan[i].waveLen=chan[i].std.ex1.val&0xfc; + chan[i].ws.setWidth(chan[i].waveLen); if (chan[i].waveMode&0x2) { chan[i].waveUpdated=true; } @@ -275,7 +304,7 @@ void DivPlatformN163::tick() { if (chan[i].loadWave!=chan[i].std.ex3.val) { chan[i].loadWave=chan[i].std.ex3.val; if (chan[i].loadMode&0x2) { - updateWave(chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc); + updateWave(i,chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc); } } } @@ -296,7 +325,7 @@ void DivPlatformN163::tick() { if ((chan[i].loadMode&0x1)!=(chan[i].std.fms.val&0x1)) { // load now chan[i].loadMode=(chan[i].loadMode&~0x1)|(chan[i].std.fms.val&0x1); if (chan[i].loadMode&0x1) { // rising edge - updateWave(chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc); + updateWave(i,chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc); } } } @@ -315,6 +344,11 @@ void DivPlatformN163::tick() { } chan[i].waveChanged=false; } + if (chan[i].active) { + if (chan[i].ws.tick()) { + chan[i].waveUpdated=true; + } + } if (chan[i].waveUpdated) { updateWaveCh(i); if (chan[i].active) { @@ -323,7 +357,7 @@ void DivPlatformN163::tick() { chan[i].waveUpdated=false; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].waveLen)*(chanMax+1))/16),chan[i].pitch,false,0); + chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].waveLen)*(chanMax+1))/16),chan[i].pitch,false,0,chan[i].pitch2); if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff; if (chan[i].keyOn) { @@ -350,14 +384,15 @@ void DivPlatformN163::tick() { int DivPlatformN163::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_N163); if (chan[c.chan].insChanged) { chan[c.chan].wave=ins->n163.wave; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); chan[c.chan].wavePos=ins->n163.wavePos; chan[c.chan].waveLen=ins->n163.waveLen; chan[c.chan].waveMode=ins->n163.waveMode; chan[c.chan].waveChanged=true; - if (chan[c.chan].waveMode&0x3) { + if (chan[c.chan].waveMode&0x3 || ins->ws.enabled) { chan[c.chan].waveUpdated=true; } chan[c.chan].insChanged=false; @@ -373,14 +408,15 @@ int DivPlatformN163::dispatch(DivCommand c) { if (!isMuted[c.chan]) { chan[c.chan].volumeChanged=true; } - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); + chan[c.chan].ws.init(ins,chan[c.chan].waveLen,15,chan[c.chan].insChanged); break; } 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); + //chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: chan[c.chan].active=false; @@ -472,7 +508,7 @@ int DivPlatformN163::dispatch(DivCommand c) { case DIV_CMD_N163_WAVE_LOAD: chan[c.chan].loadWave=c.value; if (chan[c.chan].loadMode&0x2) { // load when every waveform changes - updateWave(chan[c.chan].loadWave,chan[c.chan].loadPos,chan[c.chan].loadLen); + updateWave(c.chan,chan[c.chan].loadWave,chan[c.chan].loadPos,chan[c.chan].loadLen); } break; case DIV_CMD_N163_WAVE_LOADPOS: @@ -484,13 +520,13 @@ int DivPlatformN163::dispatch(DivCommand c) { case DIV_CMD_N163_WAVE_LOADMODE: chan[c.chan].loadMode=c.value&0x3; if (chan[c.chan].loadMode&0x1) { // load now - updateWave(chan[c.chan].loadWave,chan[c.chan].loadPos,chan[c.chan].loadLen); + updateWave(c.chan,chan[c.chan].loadWave,chan[c.chan].loadPos,chan[c.chan].loadLen); } break; case DIV_CMD_N163_GLOBAL_WAVE_LOAD: loadWave=c.value; if (loadMode&0x2) { // load when every waveform changes - updateWave(loadWave,loadPos,loadLen); + updateWave(c.chan,loadWave,loadPos,loadLen); } break; case DIV_CMD_N163_GLOBAL_WAVE_LOADPOS: @@ -502,7 +538,7 @@ int DivPlatformN163::dispatch(DivCommand c) { case DIV_CMD_N163_GLOBAL_WAVE_LOADMODE: loadMode=c.value&0x3; if (loadMode&0x3) { // load now - updateWave(loadWave,loadPos,loadLen); + updateWave(c.chan,loadWave,loadPos,loadLen); } break; case DIV_CMD_N163_CHANNEL_LIMIT: @@ -520,7 +556,7 @@ int DivPlatformN163::dispatch(DivCommand c) { case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) { - chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_N163)); chan[c.chan].keyOn=true; } } @@ -562,6 +598,7 @@ void DivPlatformN163::notifyWaveChange(int wave) { for (int i=0; i<8; i++) { if (chan[i].wave==wave) { if (chan[i].waveMode&0x2) { + chan[i].ws.changeWave1(wave); chan[i].waveUpdated=true; } } @@ -586,6 +623,10 @@ void* DivPlatformN163::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformN163::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformN163::getRegisterPool() { for (int i=0; i<128; i++) { regPool[i]=n163.reg(i); @@ -601,6 +642,9 @@ void DivPlatformN163::reset() { while (!writes.empty()) writes.pop(); for (int i=0; i<8; i++) { chan[i]=DivPlatformN163::Channel(); + chan[i].std.setEngine(parent); + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,32,15,false); } n163.reset(); @@ -642,6 +686,9 @@ void DivPlatformN163::setFlags(unsigned int flags) { rate/=15; n163.set_multiplex(multiplex); rWrite(0x7f,initChanMax<<4); + for (int i=0; i<8; i++) { + oscBuf[i]->rate=rate/(initChanMax+1); + } } int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { @@ -650,6 +697,7 @@ int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int skipRegisterWrites=false; for (int i=0; i<8; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); @@ -659,6 +707,9 @@ int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int } void DivPlatformN163::quit() { + for (int i=0; i<8; i++) { + delete oscBuf[i]; + } } DivPlatformN163::~DivPlatformN163() { diff --git a/src/engine/platform/n163.h b/src/engine/platform/n163.h index 70f842adf..6ecb1e0c5 100644 --- a/src/engine/platform/n163.h +++ b/src/engine/platform/n163.h @@ -23,11 +23,12 @@ #include "../dispatch.h" #include #include "../macroInt.h" +#include "../waveSynth.h" #include "sound/n163/n163.hpp" class DivPlatformN163: public DivDispatch { struct Channel { - int freq, baseFreq, pitch, note; + int freq, baseFreq, pitch, pitch2, note; short ins, wave, wavePos, waveLen; unsigned char waveMode; short loadWave, loadPos, loadLen; @@ -35,10 +36,16 @@ class DivPlatformN163: public DivDispatch { bool active, insChanged, freqChanged, volumeChanged, waveChanged, waveUpdated, keyOn, keyOff, inPorta; signed char vol, outVol, resVol; DivMacroInt std; + DivWaveSynth ws; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freq(0), baseFreq(0), pitch(0), + pitch2(0), note(0), ins(-1), wave(-1), @@ -63,6 +70,7 @@ class DivPlatformN163: public DivDispatch { resVol(15) {} }; Channel chan[8]; + DivDispatchOscBuffer* oscBuf[8]; bool isMuted[8]; struct QueuedWrite { unsigned char addr; @@ -79,7 +87,7 @@ class DivPlatformN163: public DivDispatch { n163_core n163; unsigned char regPool[128]; - void updateWave(int wave, int pos, int len); + void updateWave(int ch, int wave, int pos, int len); void updateWaveCh(int ch); friend void putDispatchChan(void*,int,int); @@ -87,11 +95,12 @@ class DivPlatformN163: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); void setFlags(unsigned int flags); void notifyWaveChange(int wave); diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 9f56e4e56..4b301839d 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -20,14 +20,15 @@ #include "nes.h" #include "sound/nes/cpu_inline.h" #include "../engine.h" -#include +#include "../../ta-log.h" +#include #include struct _nla_table nla_table; #define CHIP_DIVIDER 16 -#define rWrite(a,v) if (!skipRegisterWrites) {apu_wr_reg(nes,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {doWrite(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } const char* regCheatSheetNES[]={ "S0Volume", "4000", @@ -53,12 +54,19 @@ const char* regCheatSheetNES[]={ NULL }; +unsigned char _readDMC(void* user, unsigned short addr) { + return ((DivPlatformNES*)user)->readDMC(addr); +} + const char** DivPlatformNES::getRegisterSheet() { return regCheatSheetNES; } const char* DivPlatformNES::getEffectName(unsigned char effect) { switch (effect) { + case 0x11: + return "Write to delta modulation counter (0 to 7F)"; + break; case 0x12: return "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)"; break; @@ -68,50 +76,108 @@ const char* DivPlatformNES::getEffectName(unsigned char effect) { case 0x14: return "14xy: Sweep down (x: time; y: shift)"; break; + case 0x18: + return "18xx: Select PCM/DPCM mode (0: PCM; 1: DPCM)"; + break; } return NULL; } -void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len) { +void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) { + if (useNP) { + nes1_NP->Write(addr,data); + nes2_NP->Write(addr,data); + } else { + apu_wr_reg(nes,addr,data); + } +} + +#define doPCM \ + if (!dpcmMode && dacSample!=-1) { \ + dacPeriod+=dacRate; \ + if (dacPeriod>=rate) { \ + DivSample* s=parent->getSample(dacSample); \ + if (s->samples>0) { \ + if (!isMuted[4]) { \ + unsigned char next=((unsigned char)s->data8[dacPos]+0x80)>>1; \ + if (dacAntiClickOn && dacAntiClick=s->samples) { \ + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { \ + dacPos=s->loopStart; \ + } else { \ + dacSample=-1; \ + } \ + } \ + dacPeriod-=rate; \ + } else { \ + dacSample=-1; \ + } \ + } \ + } + +void DivPlatformNES::acquire_puNES(short* bufL, short* bufR, size_t start, size_t len) { for (size_t i=start; i=rate) { - DivSample* s=parent->getSample(dacSample); - if (s->samples>0) { - if (!isMuted[4]) { - unsigned char next=((unsigned char)s->data8[dacPos]+0x80)>>1; - if (dacAntiClickOn && dacAntiClick=s->samples) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - dacPos=s->loopStart; - } else { - dacSample=-1; - } - } - dacPeriod-=rate; - } else { - dacSample=-1; - } - } - } + doPCM; apu_tick(nes,NULL); nes->apu.odd_cycle=!nes->apu.odd_cycle; if (nes->apu.clocked) { nes->apu.clocked=false; } - int sample=(pulse_output(nes)+tnd_output(nes)); + int sample=(pulse_output(nes)+tnd_output(nes))<<6; if (sample>32767) sample=32767; if (sample<-32768) sample=-32768; bufL[i]=sample; + if (++writeOscBuf>=32) { + writeOscBuf=0; + oscBuf[0]->data[oscBuf[0]->needle++]=nes->S1.output<<11; + oscBuf[1]->data[oscBuf[1]->needle++]=nes->S2.output<<11; + oscBuf[2]->data[oscBuf[2]->needle++]=nes->TR.output<<11; + oscBuf[3]->data[oscBuf[3]->needle++]=nes->NS.output<<11; + oscBuf[4]->data[oscBuf[4]->needle++]=nes->DMC.output<<8; + } + } +} + +void DivPlatformNES::acquire_NSFPlay(short* bufL, short* bufR, size_t start, size_t len) { + int out1[2]; + int out2[2]; + for (size_t i=start; iTick(1); + nes2_NP->TickFrameSequence(1); + nes2_NP->Tick(1); + nes1_NP->Render(out1); + nes2_NP->Render(out2); + + int sample=(out1[0]+out1[1]+out2[0]+out2[1])<<1; + if (sample>32767) sample=32767; + if (sample<-32768) sample=-32768; + bufL[i]=sample; + if (++writeOscBuf>=32) { + writeOscBuf=0; + oscBuf[0]->data[oscBuf[0]->needle++]=nes1_NP->out[0]<<11; + oscBuf[1]->data[oscBuf[1]->needle++]=nes1_NP->out[1]<<11; + oscBuf[2]->data[oscBuf[2]->needle++]=nes2_NP->out[0]<<11; + oscBuf[3]->data[oscBuf[3]->needle++]=nes2_NP->out[1]<<11; + oscBuf[4]->data[oscBuf[4]->needle++]=nes2_NP->out[2]<<8; + } + } +} + +void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len) { + if (useNP) { + acquire_NSFPlay(bufL,bufR,start,len); + } else { + acquire_puNES(bufL,bufR,start,len); } } @@ -140,7 +206,26 @@ static unsigned char noiseTable[253]={ 15 }; -void DivPlatformNES::tick() { +unsigned char DivPlatformNES::calcDPCMRate(int inRate) { + if (inRate<4450) return 0; + if (inRate<5000) return 1; + if (inRate<5400) return 2; + if (inRate<5900) return 3; + if (inRate<6650) return 4; + if (inRate<7450) return 5; + if (inRate<8100) return 6; + if (inRate<8800) return 7; + if (inRate<10200) return 8; + if (inRate<11700) return 9; + if (inRate<13300) return 10; + if (inRate<15900) return 11; + if (inRate<18900) return 12; + if (inRate<23500) return 13; + if (inRate<29000) return 14; + return 15; +} + +void DivPlatformNES::tick(bool sysTick) { for (int i=0; i<4; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { @@ -195,12 +280,27 @@ void DivPlatformNES::tick() { chan[i].freqChanged=true; } } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } if (chan[i].sweepChanged) { chan[i].sweepChanged=false; if (i==0) { //rWrite(16+i*5,chan[i].sweep); } } + if (i<2) if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + chan[i].freqChanged=true; + chan[i].prevFreq=-1; + } + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (i==3) { // noise int ntPos=chan[i].baseFreq; @@ -208,13 +308,11 @@ void DivPlatformNES::tick() { if (ntPos>252) ntPos=252; 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; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1; if (chan[i].freq>2047) chan[i].freq=2047; if (chan[i].freq<0) chan[i].freq=0; } if (chan[i].keyOn) { - //rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); - //rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3)); } if (chan[i].keyOff) { //rWrite(16+i*5+2,8); @@ -243,7 +341,7 @@ void DivPlatformNES::tick() { } // PCM - if (chan[4].freqChanged) { + if (chan[4].freqChanged || chan[4].keyOn) { chan[4].freq=parent->calcFreq(chan[4].baseFreq,chan[4].pitch,false); if (chan[4].furnaceDac) { double off=1.0; @@ -252,8 +350,27 @@ void DivPlatformNES::tick() { off=(double)s->centerRate/8363.0; } dacRate=MIN(chan[4].freq*off,32000); - if (dumpWrites) addWrite(0xffff0001,dacRate); + if (chan[4].keyOn) { + if (dpcmMode && !skipRegisterWrites && dacSample>=0 && dacSamplesong.sampleLen) { + unsigned int dpcmAddr=parent->getSample(dacSample)->offDPCM; + unsigned int dpcmLen=(parent->getSample(dacSample)->lengthDPCM+15)>>4; + if (dpcmLen>255) dpcmLen=255; + // write DPCM + rWrite(0x4015,15); + rWrite(0x4010,calcDPCMRate(dacRate)); + rWrite(0x4012,(dpcmAddr>>6)&0xff); + rWrite(0x4013,dpcmLen&0xff); + rWrite(0x4015,31); + dpcmBank=dpcmAddr>>14; + } + } else { + if (dpcmMode) { + rWrite(0x4010,calcDPCMRate(dacRate)); + } + } + if (dumpWrites && !dpcmMode) addWrite(0xffff0001,dacRate); } + if (chan[4].keyOn) chan[4].keyOn=false; chan[4].freqChanged=false; } } @@ -262,20 +379,20 @@ int DivPlatformNES::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: if (c.chan==4) { // PCM - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD); if (ins->type==DIV_INS_AMIGA) { dacSample=ins->amiga.initSample; if (dacSample<0 || dacSample>=parent->song.sampleLen) { dacSample=-1; - if (dumpWrites) addWrite(0xffff0002,0); + if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0); break; } else { - if (dumpWrites) addWrite(0xffff0000,dacSample); + if (dumpWrites && !dpcmMode) addWrite(0xffff0000,dacSample); } dacPos=0; dacPeriod=0; - chan[c.chan].baseFreq=parent->song.tuning*pow(2.0f,((float)(c.value+3)/12.0f)); if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; } @@ -289,16 +406,28 @@ int DivPlatformNES::dispatch(DivCommand c) { dacSample=12*sampleBank+chan[c.chan].note%12; if (dacSample>=parent->song.sampleLen) { dacSample=-1; - if (dumpWrites) addWrite(0xffff0002,0); + if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0); break; } else { - if (dumpWrites) addWrite(0xffff0000,dacSample); + if (dumpWrites && !dpcmMode) addWrite(0xffff0000,dacSample); } dacPos=0; dacPeriod=0; dacRate=parent->getSample(dacSample)->rate; - if (dumpWrites) addWrite(0xffff0001,dacRate); + if (dumpWrites && !dpcmMode) addWrite(0xffff0001,dacRate); chan[c.chan].furnaceDac=false; + if (dpcmMode && !skipRegisterWrites) { + unsigned int dpcmAddr=parent->getSample(dacSample)->offDPCM; + unsigned int dpcmLen=(parent->getSample(dacSample)->lengthDPCM+15)>>4; + if (dpcmLen>255) dpcmLen=255; + // write DPCM + rWrite(0x4015,15); + rWrite(0x4010,calcDPCMRate(dacRate)); + rWrite(0x4012,(dpcmAddr>>6)&0xff); + rWrite(0x4013,dpcmLen&0xff); + rWrite(0x4015,31); + dpcmBank=dpcmAddr>>14; + } } break; } else if (c.chan==3) { // noise @@ -316,7 +445,7 @@ int DivPlatformNES::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD)); if (c.chan==2) { rWrite(0x4000+c.chan*4,0xff); } else { @@ -327,10 +456,11 @@ int DivPlatformNES::dispatch(DivCommand c) { if (c.chan==4) { dacSample=-1; if (dumpWrites) addWrite(0xffff0002,0); + if (dpcmMode && !skipRegisterWrites) rWrite(0x4015,15); } chan[c.chan].active=false; chan[c.chan].keyOff=true; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -364,7 +494,7 @@ int DivPlatformNES::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; case DIV_CMD_NOTE_PORTA: { - int destFreq=NOTE_PERIODIC(c.value2); + int destFreq=(c.chan==4)?(parent->calcBaseFreq(1,1,c.value2,false)):(NOTE_PERIODIC(c.value2)); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { chan[c.chan].baseFreq+=c.value; @@ -390,6 +520,8 @@ int DivPlatformNES::dispatch(DivCommand c) { chan[c.chan].duty=c.value; if (c.chan==3) { // noise chan[c.chan].freqChanged=true; + } else if (c.chan<2) { + rWrite(0x4000+c.chan*4,0x30|chan[c.chan].outVol|((chan[c.chan].duty&3)<<6)); } break; case DIV_CMD_NES_SWEEP: @@ -405,6 +537,19 @@ int DivPlatformNES::dispatch(DivCommand c) { } rWrite(0x4001+(c.chan*4),chan[c.chan].sweep); break; + case DIV_CMD_NES_DMC: + rWrite(0x4011,c.value&0x7f); + break; + case DIV_CMD_SAMPLE_MODE: + dpcmMode=c.value; + if (dumpWrites && dpcmMode) addWrite(0xffff0002,0); + dacSample=-1; + rWrite(0x4015,15); + rWrite(0x4010,0); + rWrite(0x4012,0); + rWrite(0x4013,0); + rWrite(0x4015,31); + break; case DIV_CMD_SAMPLE_BANK: sampleBank=c.value; if (sampleBank>(parent->song.sample.size()/12)) { @@ -413,13 +558,17 @@ int DivPlatformNES::dispatch(DivCommand c) { break; case DIV_CMD_LEGATO: if (c.chan==3) break; - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); + if (c.chan==4) { + chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)),false); + } else { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); + } chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD)); } chan[c.chan].inPorta=c.value; break; @@ -437,7 +586,12 @@ int DivPlatformNES::dispatch(DivCommand c) { void DivPlatformNES::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - nes->muted[ch]=mute; + if (useNP) { + nes1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1)); + nes2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2)); + } else { + nes->muted[ch]=mute; + } } void DivPlatformNES::forceIns() { @@ -453,6 +607,10 @@ void* DivPlatformNES::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformNES::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformNES::getRegisterPool() { return regPool; } @@ -462,12 +620,13 @@ int DivPlatformNES::getRegisterPoolSize() { } float DivPlatformNES::getPostAmp() { - return 128.0f; + return 2.0f; } void DivPlatformNES::reset() { for (int i=0; i<5; i++) { chan[i]=DivPlatformNES::Channel(); + chan[i].std.setEngine(parent); } if (dumpWrites) { addWrite(0xffffffff,0); @@ -478,11 +637,20 @@ void DivPlatformNES::reset() { dacRate=0; dacSample=-1; sampleBank=0; + dpcmBank=0; + dpcmMode=false; - apu_turn_on(nes,apuType); + if (useNP) { + nes1_NP->Reset(); + nes2_NP->Reset(); + nes1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1)); + nes2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2)); + } else { + apu_turn_on(nes,apuType); + nes->apu.cpu_cycles=0; + nes->apu.cpu_opcode_cycle=0; + } memset(regPool,0,128); - nes->apu.cpu_cycles=0; - nes->apu.cpu_opcode_cycle=0; rWrite(0x4015,0x1f); rWrite(0x4001,chan[0].sweep); @@ -500,17 +668,26 @@ void DivPlatformNES::setFlags(unsigned int flags) { if (flags==2) { // Dendy rate=COLOR_PAL*2.0/5.0; apuType=2; - nes->apu.type=apuType; } else if (flags==1) { // PAL rate=COLOR_PAL*3.0/8.0; apuType=1; - nes->apu.type=apuType; } else { // NTSC rate=COLOR_NTSC/2.0; apuType=0; + } + if (useNP) { + nes1_NP->SetClock(rate); + nes1_NP->SetRate(rate); + nes2_NP->SetClock(rate); + nes2_NP->SetRate(rate); + nes2_NP->SetPal(apuType==1); + } else { nes->apu.type=apuType; } chipClock=rate; + for (int i=0; i<5; i++) { + oscBuf[i]->rate=rate/32; + } } void DivPlatformNES::notifyInsDeletion(void* ins) { @@ -527,25 +704,102 @@ void DivPlatformNES::poke(std::vector& wlist) { for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); } +void DivPlatformNES::setNSFPlay(bool use) { + useNP=use; +} + +unsigned char DivPlatformNES::readDMC(unsigned short addr) { + return dpcmMem[(addr&0x3fff)|((dpcmBank&15)<<14)]; +} + +const void* DivPlatformNES::getSampleMem(int index) { + return index==0?dpcmMem:NULL; +} + +size_t DivPlatformNES::getSampleMemCapacity(int index) { + return index==0?262144:0; +} + +size_t DivPlatformNES::getSampleMemUsage(int index) { + return index==0?dpcmMemLen:0; +} + +void DivPlatformNES::renderSamples() { + memset(dpcmMem,0,getSampleMemCapacity(0)); + + size_t memPos=0; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + unsigned int paddedLen=(s->lengthDPCM+63)&(~0x3f); + logV("%d padded length: %d",i,paddedLen); + if ((memPos&(~0x3fff))!=((memPos+paddedLen)&(~0x3fff))) { + memPos=(memPos+0x3fff)&(~0x3fff); + } + if (paddedLen>4081) { + paddedLen=4096; + } + if (memPos>=getSampleMemCapacity(0)) { + logW("out of DPCM memory for sample %d!",i); + break; + } + if (memPos+paddedLen>=getSampleMemCapacity(0)) { + memcpy(dpcmMem+memPos,s->dataDPCM,getSampleMemCapacity(0)-memPos); + logW("out of DPCM memory for sample %d!",i); + } else { + memcpy(dpcmMem+memPos,s->dataDPCM,MIN(s->lengthDPCM,paddedLen)); + } + s->offDPCM=memPos; + memPos+=paddedLen; + } + dpcmMemLen=memPos; +} + int DivPlatformNES::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; apuType=flags; dumpWrites=false; skipRegisterWrites=false; - nes=new struct NESAPU; + if (useNP) { + nes1_NP=new xgm::NES_APU; + nes1_NP->SetOption(xgm::NES_APU::OPT_NONLINEAR_MIXER,1); + nes2_NP=new xgm::NES_DMC; + nes2_NP->SetOption(xgm::NES_DMC::OPT_NONLINEAR_MIXER,1); + nes2_NP->SetMemory([this](unsigned short addr, unsigned int& data) { + data=readDMC(addr); + }); + nes2_NP->SetAPU(nes1_NP); + } else { + nes=new struct NESAPU; + nes->readDMC=_readDMC; + nes->readDMCUser=this; + } + writeOscBuf=0; for (int i=0; i<5; i++) { isMuted[i]=false; - nes->muted[i]=false; + if (!useNP) nes->muted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); + dpcmMem=new unsigned char[262144]; + dpcmMemLen=0; + dpcmBank=0; + init_nla_table(500,500); reset(); return 5; } void DivPlatformNES::quit() { - delete nes; + for (int i=0; i<5; i++) { + delete oscBuf[i]; + } + if (useNP) { + delete nes1_NP; + delete nes2_NP; + } else { + delete nes; + } } DivPlatformNES::~DivPlatformNES() { diff --git a/src/engine/platform/nes.h b/src/engine/platform/nes.h index 1dbb4411b..a03efc7a3 100644 --- a/src/engine/platform/nes.h +++ b/src/engine/platform/nes.h @@ -23,17 +23,24 @@ #include "../dispatch.h" #include "../macroInt.h" +#include "sound/nes_nsfplay/nes_apu.h" + class DivPlatformNES: public DivDispatch { struct Channel { - int freq, baseFreq, pitch, prevFreq, note; - unsigned char ins, duty, sweep; + int freq, baseFreq, pitch, pitch2, prevFreq, note, ins; + unsigned char duty, sweep; bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, furnaceDac; signed char vol, outVol, wave; DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freq(0), baseFreq(0), pitch(0), + pitch2(0), prevFreq(65535), note(0), ins(-1), @@ -52,36 +59,57 @@ class DivPlatformNES: public DivDispatch { wave(-1) {} }; Channel chan[5]; + DivDispatchOscBuffer* oscBuf[5]; bool isMuted[5]; int dacPeriod, dacRate; unsigned int dacPos, dacAntiClick; int dacSample; + unsigned char* dpcmMem; + size_t dpcmMemLen; + unsigned char dpcmBank; unsigned char sampleBank; + unsigned char writeOscBuf; unsigned char apuType; + bool dpcmMode; bool dacAntiClickOn; + bool useNP; struct NESAPU* nes; + xgm::NES_APU* nes1_NP; + xgm::NES_DMC* nes2_NP; unsigned char regPool[128]; friend void putDispatchChan(void*,int,int); + void doWrite(unsigned short addr, unsigned char data); + unsigned char calcDPCMRate(int inRate); + void acquire_puNES(short* bufL, short* bufR, size_t start, size_t len); + void acquire_NSFPlay(short* bufL, short* bufR, size_t start, size_t len); + public: void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool keyOffAffectsArp(int ch); float getPostAmp(); + unsigned char readDMC(unsigned short addr); + void setNSFPlay(bool use); void setFlags(unsigned int flags); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); const char* getEffectName(unsigned char effect); + const void* getSampleMem(int index); + size_t getSampleMemCapacity(int index); + size_t getSampleMemUsage(int index); + void renderSamples(); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformNES(); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 3e222eec1..e43895289 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -52,6 +52,10 @@ const unsigned short chanMapOPL2Drums[20]={ 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, N, N, N, N, N, N, N, N, N }; +const unsigned char outChanMapOPL2[18]={ + 0, 1, 2, 3, 4, 5, 6, 7, 8, N, N, N, N, N, N, N, N, N +}; + const unsigned char* slotsOPL2[4]={ slotsOPL2i[0], slotsOPL2i[1], @@ -88,6 +92,10 @@ const unsigned short chanMapOPL3Drums[20]={ 0, 3, 1, 4, 2, 5, 0x100, 0x103, 0x101, 0x104, 0x102, 0x105, 0x106, 0x107, 0x108, 6, 7, 8, 8, 7 }; +const unsigned char outChanMapOPL3[18]={ + 0, 3, 1, 4, 2, 5, 9, 12, 10, 13, 11, 14, 15, 16, 17, 6, 7, 8 +}; + const unsigned char* slotsOPL3[4]={ slotsOPL3i[0], slotsOPL3i[1], @@ -189,6 +197,48 @@ const char* DivPlatformOPL::getEffectName(unsigned char effect) { case 0x1d: return "1Dxx: Set attack of operator 4 (0 to F; 4-op only)"; break; + case 0x2a: + return "2Axy: Set waveform (x: operator from 1 to 4 (0 for all ops); y: waveform from 0 to 3 in OPL2 and 0 to 7 in OPL3)"; + break; + case 0x30: + return "30xx: Toggle hard envelope reset on new notes"; + break; + case 0x50: + return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; + break; + case 0x51: + return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; + break; + case 0x52: + return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; + break; + case 0x53: + return "53xy: Set vibrato (x: operator from 1 to 4 (0 for all ops); y: enabled)"; + break; + case 0x54: + return "54xy: Set key scale level (x: operator from 1 to 4 (0 for all ops); y: level from 0 to 3)"; + break; + case 0x55: + return "55xy: Set envelope sustain (x: operator from 1 to 4 (0 for all ops); y: enabled)"; + break; + case 0x56: + return "56xx: Set decay of all operators (0 to F)"; + break; + case 0x57: + return "57xx: Set decay of operator 1 (0 to F)"; + break; + case 0x58: + return "58xx: Set decay of operator 2 (0 to F)"; + break; + case 0x59: + return "59xx: Set decay of operator 3 (0 to F; 4-op only)"; + break; + case 0x5a: + return "5Axx: Set decay of operator 4 (0 to F; 4-op only)"; + break; + case 0x5b: + return "5Bxy: Set whether key will scale envelope (x: operator from 1 to 4 (0 for all ops); y: enabled)"; + break; } return NULL; } @@ -208,6 +258,20 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_ } OPL3_Generate(&fm,o); os[0]+=o[0]; os[1]+=o[1]; + + for (int i=0; idata[oscBuf[i]->needle]=0; + if (fm.channel[i].out[0]!=NULL) { + oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[0]; + } + if (fm.channel[i].out[1]!=NULL) { + oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[1]; + } + oscBuf[i]->data[oscBuf[i]->needle]<<=1; + oscBuf[i]->needle++; + } if (os[0]<-32768) os[0]=-32768; if (os[0]>32767) os[0]=32767; @@ -228,7 +292,7 @@ void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len) //} } -void DivPlatformOPL::tick() { +void DivPlatformOPL::tick(bool sysTick) { for (int i=0; i>1); + } + + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + chan[i].keyOn=true; + } + } + if (chan[i].std.alg.had) { chan[i].state.alg=chan[i].std.alg.val; } @@ -276,7 +360,7 @@ void DivPlatformOPL::tick() { chan[i].state.fb=chan[i].std.fb.val; } - if (chan[i].std.alg.had || chan[i].std.fb.had) { + if (chan[i].std.alg.had || chan[i].std.fb.had || (oplType==3 && chan[i].std.panL.had)) { if (isMuted[i]) { rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1)); if (ops==4) { @@ -403,9 +487,9 @@ void DivPlatformOPL::tick() { bool updateDrums=false; for (int i=0; icalcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2); if (chan[i].freq>131071) chan[i].freq=131071; - int freqt=toFreq(chan[i].freq); + int freqt=toFreq(chan[i].freq)+chan[i].pitch2; chan[i].freqH=freqt>>8; chan[i].freqL=freqt&0xff; immWrite(chanMap[i]+ADDR_FREQ,chan[i].freqL); @@ -480,6 +564,9 @@ int DivPlatformOPL::toFreq(int freq) { void DivPlatformOPL::muteChannel(int ch, bool mute) { isMuted[ch]=mute; + if (oplType<3 && chgetIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_OPL); if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; } - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } if (chan[c.chan].insChanged) { int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; chan[c.chan].fourOp=(ops==4); + if (chan[c.chan].fourOp) { + chan[c.chan+1].macroInit(NULL); + } update4OpMask=true; for (int i=0; i0)<<1)|((c.value>>4)>0); + chan[c.chan].pan=(c.value>0)|((c.value2>0)<<1); } int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (isMuted[c.chan]) { @@ -796,6 +886,222 @@ int DivPlatformOPL::dispatch(DivCommand c) { } break; } + case DIV_CMD_FM_DR: { + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + if (c.value<0) { + for (int i=0; i=ops) break; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value]; + op.dr=c.value2&15; + unsigned char slot=slots[c.value][c.chan]; + if (slot==255) break; + unsigned short baseAddr=slotMap[slot]; + rWrite(baseAddr+ADDR_AR_DR,(op.ar<<4)|op.dr); + } + break; + } + case DIV_CMD_FM_SL: { + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + if (c.value<0) { + for (int i=0; i=ops) break; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value]; + op.sl=c.value2&15; + unsigned char slot=slots[c.value][c.chan]; + if (slot==255) break; + unsigned short baseAddr=slotMap[slot]; + rWrite(baseAddr+ADDR_SL_RR,(op.sl<<4)|op.rr); + } + break; + } + case DIV_CMD_FM_RR: { + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + if (c.value<0) { + for (int i=0; i=ops) break; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value]; + op.rr=c.value2&15; + unsigned char slot=slots[c.value][c.chan]; + if (slot==255) break; + unsigned short baseAddr=slotMap[slot]; + rWrite(baseAddr+ADDR_SL_RR,(op.sl<<4)|op.rr); + } + break; + } + case DIV_CMD_FM_AM: { + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + if (c.value<0) { + for (int i=0; i=ops) break; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value]; + op.am=c.value2&1; + unsigned char slot=slots[c.value][c.chan]; + if (slot==255) break; + unsigned short baseAddr=slotMap[slot]; + rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult); + } + break; + } + case DIV_CMD_FM_VIB: { + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + if (c.value<0) { + for (int i=0; i=ops) break; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value]; + op.vib=c.value2&1; + unsigned char slot=slots[c.value][c.chan]; + if (slot==255) break; + unsigned short baseAddr=slotMap[slot]; + rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult); + } + break; + } + case DIV_CMD_FM_SUS: { + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + if (c.value<0) { + for (int i=0; i=ops) break; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value]; + op.sus=c.value2&1; + unsigned char slot=slots[c.value][c.chan]; + if (slot==255) break; + unsigned short baseAddr=slotMap[slot]; + rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult); + } + break; + } + case DIV_CMD_FM_KSR: { + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + if (c.value<0) { + for (int i=0; i=ops) break; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value]; + op.ksr=c.value2&1; + unsigned char slot=slots[c.value][c.chan]; + if (slot==255) break; + unsigned short baseAddr=slotMap[slot]; + rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult); + } + break; + } + case DIV_CMD_FM_WS: { + if (oplType<2) break; + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + if (c.value<0) { + for (int i=0; i=ops) break; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value]; + op.ws=c.value2&7; + unsigned char slot=slots[c.value][c.chan]; + if (slot==255) break; + unsigned short baseAddr=slotMap[slot]; + rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3)); + } + break; + } + case DIV_CMD_FM_RS: { + if (oplType<2) break; + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + if (c.value<0) { + for (int i=0; i=ops) break; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value]; + op.ksl=c.value2&3; + unsigned char slot=slots[c.value][c.chan]; + if (slot==255) break; + unsigned short baseAddr=slotMap[slot]; + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); + } else { + if (isOutputL[ops==4][chan[c.chan].state.alg][c.value]) { + rWrite(baseAddr+ADDR_KSL_TL,(63-(((63-op.tl)*(chan[c.chan].outVol&0x3f))/63))|(op.ksl<<6)); + } else { + rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6)); + } + } + } + break; + } case DIV_CMD_FM_EXTCH: { if (!properDrumsSys) break; properDrums=c.value; @@ -894,6 +1200,11 @@ void* DivPlatformOPL::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformOPL::getOscBuffer(int ch) { + if (ch>=18) return NULL; + return oscBuf[ch]; +} + unsigned char* DivPlatformOPL::getRegisterPool() { return regPool; } @@ -918,19 +1229,26 @@ void DivPlatformOPL::reset() { properDrums=properDrumsSys; if (oplType==3) { chanMap=properDrums?chanMapOPL3Drums:chanMapOPL3; + outChanMap=outChanMapOPL3; melodicChans=properDrums?15:18; totalChans=properDrums?20:18; } else { chanMap=properDrums?chanMapOPL2Drums:chanMapOPL2; + outChanMap=outChanMapOPL2; melodicChans=properDrums?6:9; totalChans=properDrums?11:9; } for (int i=0; irate=rate; + } } int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { @@ -1081,6 +1405,9 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int f for (int i=0; i<20; i++) { isMuted[i]=false; } + for (int i=0; i<18; i++) { + oscBuf[i]=new DivDispatchOscBuffer; + } setFlags(flags); reset(); @@ -1088,6 +1415,9 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int f } void DivPlatformOPL::quit() { + for (int i=0; i<18; i++) { + delete oscBuf[i]; + } } DivPlatformOPL::~DivPlatformOPL() { diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index 44d80fda2..0a5cfa52b 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -22,7 +22,7 @@ #include "../dispatch.h" #include "../macroInt.h" #include -#include "../../../extern/Nuked-OPL3/opl3.h" +#include "../../../extern/opl/opl3.h" class DivPlatformOPL: public DivDispatch { protected: @@ -30,17 +30,21 @@ class DivPlatformOPL: public DivDispatch { DivInstrumentFM state; DivMacroInt std; unsigned char freqH, freqL; - int freq, baseFreq, pitch, note; - unsigned char ins; + int freq, baseFreq, pitch, pitch2, note, ins; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, fourOp; int vol, outVol; unsigned char pan; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), + pitch2(0), note(0), ins(-1), active(false), @@ -58,6 +62,7 @@ class DivPlatformOPL: public DivDispatch { } }; Channel chan[20]; + DivDispatchOscBuffer* oscBuf[18]; bool isMuted[20]; struct QueuedWrite { unsigned short addr; @@ -71,6 +76,7 @@ class DivPlatformOPL: public DivDispatch { const unsigned char** slotsDrums; const unsigned char** slots; const unsigned short* chanMap; + const unsigned char* outChanMap; double chipFreqBase; int delay, oplType, chans, melodicChans, totalChans; unsigned char lastBusy; @@ -100,11 +106,12 @@ class DivPlatformOPL: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool isStereo(); void setYMFM(bool use); diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index ee07463c1..b4f0916a8 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -55,6 +55,36 @@ const char* DivPlatformOPLL::getEffectName(unsigned char effect) { case 0x1b: return "1Bxx: Set attack of operator 2 (0 to F)"; break; + case 0x50: + return "50xy: Set AM (x: operator from 1 to 2 (0 for all ops); y: AM)"; + break; + case 0x51: + return "51xy: Set sustain level (x: operator from 1 to 2 (0 for all ops); y: sustain)"; + break; + case 0x52: + return "52xy: Set release (x: operator from 1 to 2 (0 for all ops); y: release)"; + break; + case 0x53: + return "53xy: Set vibrato (x: operator from 1 to 2 (0 for all ops); y: enabled)"; + break; + case 0x54: + return "54xy: Set key scale level (x: operator from 1 to 2 (0 for all ops); y: level from 0 to 3)"; + break; + case 0x55: + return "55xy: Set envelope sustain (x: operator from 1 to 2 (0 for all ops); y: enabled)"; + break; + case 0x56: + return "56xx: Set decay of all operators (0 to F)"; + break; + case 0x57: + return "57xx: Set decay of operator 1 (0 to F)"; + break; + case 0x58: + return "58xx: Set decay of operator 2 (0 to F)"; + break; + case 0x5b: + return "5Bxy: Set whether key will scale envelope (x: operator from 1 to 2 (0 for all ops); y: enabled)"; + break; } return NULL; } @@ -94,7 +124,10 @@ void DivPlatformOPLL::acquire_nuked(short* bufL, short* bufR, size_t start, size OPLL_Clock(&fm,o); unsigned char nextOut=cycleMapOPLL[fm.cycles]; if ((nextOut>=6 && properDrums) || !isMuted[nextOut]) { + oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=(o[0]+o[1])<<6; os+=(o[0]+o[1]); + } else { + oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=0; } } os*=50; @@ -111,7 +144,7 @@ void DivPlatformOPLL::acquire(short* bufL, short* bufR, size_t start, size_t len acquire_nuked(bufL,bufR,start,len); } -void DivPlatformOPLL::tick() { +void DivPlatformOPLL::tick(bool sysTick) { for (int i=0; i<11; i++) { chan[i].std.next(); @@ -145,6 +178,22 @@ void DivPlatformOPLL::tick() { } } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + chan[i].keyOn=true; + } + } + if (chan[i].state.opllPreset==0) { if (chan[i].std.alg.had) { // SUS chan[i].state.alg=chan[i].std.alg.val; @@ -251,9 +300,9 @@ void DivPlatformOPLL::tick() { for (int i=0; i<11; i++) { if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2); if (chan[i].freq>262143) chan[i].freq=262143; - int freqt=toFreq(chan[i].freq); + int freqt=toFreq(chan[i].freq)+chan[i].pitch2; chan[i].freqL=freqt&0xff; if (i>=6 && properDrums) { immWrite(0x10+drumSlot[i],freqt&0xff); @@ -355,12 +404,12 @@ int DivPlatformOPLL::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { if (c.chan>=9 && !properDrums) return 0; - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_OPLL); if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; } - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } @@ -428,6 +477,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { if (drums) { drums=false; immWrite(0x0e,0); + drumState=0; } } if (c.chan<9) { @@ -618,6 +668,150 @@ int DivPlatformOPLL::dispatch(DivCommand c) { rWrite(0x05,(car.ar<<4)|(car.dr)); break; } + case DIV_CMD_FM_DR: { + if (c.chan>=9 && !properDrums) return 0; + DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0]; + DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; + if (c.value<0) { + mod.dr=c.value2&15; + car.dr=c.value2&15; + } else { + if (c.value==0) { + mod.dr=c.value2&15; + } else { + car.dr=c.value2&15; + } + } + rWrite(0x04,(mod.ar<<4)|(mod.dr)); + rWrite(0x05,(car.ar<<4)|(car.dr)); + break; + } + case DIV_CMD_FM_SL: { + if (c.chan>=9 && !properDrums) return 0; + DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0]; + DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; + if (c.value<0) { + mod.sl=c.value2&15; + car.sl=c.value2&15; + } else { + if (c.value==0) { + mod.sl=c.value2&15; + } else { + car.sl=c.value2&15; + } + } + rWrite(0x06,(mod.sl<<4)|(mod.rr)); + rWrite(0x07,(car.sl<<4)|(car.rr)); + break; + } + case DIV_CMD_FM_RR: { + if (c.chan>=9 && !properDrums) return 0; + DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0]; + DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; + if (c.value<0) { + mod.rr=c.value2&15; + car.rr=c.value2&15; + } else { + if (c.value==0) { + mod.rr=c.value2&15; + } else { + car.rr=c.value2&15; + } + } + rWrite(0x06,(mod.sl<<4)|(mod.rr)); + rWrite(0x07,(car.sl<<4)|(car.rr)); + break; + } + case DIV_CMD_FM_AM: { + if (c.chan>=9 && !properDrums) return 0; + DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0]; + DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; + if (c.value<0) { + mod.am=c.value2&1; + car.am=c.value2&1; + } else { + if (c.value==0) { + mod.am=c.value2&1; + } else { + car.am=c.value2&1; + } + } + rWrite(0x00,(mod.am<<7)|(mod.vib<<6)|((mod.ssgEnv&8)<<2)|(mod.ksr<<4)|(mod.mult)); + rWrite(0x01,(car.am<<7)|(car.vib<<6)|((car.ssgEnv&8)<<2)|(car.ksr<<4)|(car.mult)); + break; + } + case DIV_CMD_FM_VIB: { + if (c.chan>=9 && !properDrums) return 0; + DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0]; + DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; + if (c.value<0) { + mod.vib=c.value2&1; + car.vib=c.value2&1; + } else { + if (c.value==0) { + mod.vib=c.value2&1; + } else { + car.vib=c.value2&1; + } + } + rWrite(0x00,(mod.am<<7)|(mod.vib<<6)|((mod.ssgEnv&8)<<2)|(mod.ksr<<4)|(mod.mult)); + rWrite(0x01,(car.am<<7)|(car.vib<<6)|((car.ssgEnv&8)<<2)|(car.ksr<<4)|(car.mult)); + break; + } + case DIV_CMD_FM_KSR: { + if (c.chan>=9 && !properDrums) return 0; + DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0]; + DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; + if (c.value<0) { + mod.ksr=c.value2&1; + car.ksr=c.value2&1; + } else { + if (c.value==0) { + mod.ksr=c.value2&1; + } else { + car.ksr=c.value2&1; + } + } + rWrite(0x00,(mod.am<<7)|(mod.vib<<6)|((mod.ssgEnv&8)<<2)|(mod.ksr<<4)|(mod.mult)); + rWrite(0x01,(car.am<<7)|(car.vib<<6)|((car.ssgEnv&8)<<2)|(car.ksr<<4)|(car.mult)); + break; + } + case DIV_CMD_FM_SUS: { + if (c.chan>=9 && !properDrums) return 0; + DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0]; + DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; + if (c.value<0) { + mod.ssgEnv=c.value2?8:0; + car.ssgEnv=c.value2?8:0; + } else { + if (c.value==0) { + mod.ssgEnv=c.value2?8:0; + } else { + car.ssgEnv=c.value2?8:0; + } + } + rWrite(0x00,(mod.am<<7)|(mod.vib<<6)|((mod.ssgEnv&8)<<2)|(mod.ksr<<4)|(mod.mult)); + rWrite(0x01,(car.am<<7)|(car.vib<<6)|((car.ssgEnv&8)<<2)|(car.ksr<<4)|(car.mult)); + break; + } + case DIV_CMD_FM_RS: { + if (c.chan>=9 && !properDrums) return 0; + DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0]; + DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; + if (c.value<0) { + mod.ksl=c.value2&3; + car.ksl=c.value2&3; + } else { + if (c.value==0) { + mod.ksl=c.value2&3; + } else { + car.ksl=c.value2&3; + } + } + rWrite(0x02,(mod.ksl<<6)|(mod.tl&63)); + rWrite(0x03,(car.ksl<<6)|((chan[c.chan].state.fms&1)<<4)|((chan[c.chan].state.ams&1)<<3)|chan[c.chan].state.fb); + break; + } case DIV_CMD_FM_EXTCH: if (!properDrumsSys) break; if ((int)properDrums==c.value) break; @@ -715,6 +909,11 @@ void* DivPlatformOPLL::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformOPLL::getOscBuffer(int ch) { + if (ch>=9) return NULL; + return oscBuf[ch]; +} + unsigned char* DivPlatformOPLL::getRegisterPool() { return regPool; } @@ -750,6 +949,7 @@ void DivPlatformOPLL::reset() { } for (int i=0; i<11; i++) { chan[i]=DivPlatformOPLL::Channel(); + chan[i].std.setEngine(parent); chan[i].vol=15; chan[i].outVol=15; } @@ -825,6 +1025,9 @@ void DivPlatformOPLL::setFlags(unsigned int flags) { } rate=chipClock/36; patchSet=flags>>4; + for (int i=0; i<11; i++) { + oscBuf[i]->rate=rate/2; + } } int DivPlatformOPLL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { @@ -834,14 +1037,18 @@ int DivPlatformOPLL::init(DivEngine* p, int channels, int sugRate, unsigned int patchSet=0; for (int i=0; i<11; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); reset(); - return 10; + return 11; } void DivPlatformOPLL::quit() { + for (int i=0; i<11; i++) { + delete oscBuf[i]; + } } DivPlatformOPLL::~DivPlatformOPLL() { diff --git a/src/engine/platform/opll.h b/src/engine/platform/opll.h index 4a7a4c97f..7a06bbb77 100644 --- a/src/engine/platform/opll.h +++ b/src/engine/platform/opll.h @@ -33,17 +33,21 @@ class DivPlatformOPLL: public DivDispatch { DivInstrumentFM state; DivMacroInt std; unsigned char freqH, freqL; - int freq, baseFreq, pitch, note; - unsigned char ins; + int freq, baseFreq, pitch, pitch2, note, ins; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta; int vol, outVol; unsigned char pan; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), + pitch2(0), note(0), ins(-1), active(false), @@ -59,6 +63,7 @@ class DivPlatformOPLL: public DivDispatch { }; Channel chan[11]; bool isMuted[11]; + DivDispatchOscBuffer* oscBuf[11]; struct QueuedWrite { unsigned short addr; unsigned char val; @@ -96,11 +101,12 @@ class DivPlatformOPLL: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); void setYMFM(bool use); bool keyOffAffectsArp(int ch); diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index 0789c804d..c28b81db7 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -116,6 +116,10 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len) pce->Update(24); pce->ResetTS(0); + for (int i=0; i<6; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1])<<1; + } + tempL[0]=(tempL[0]>>1)+(tempL[0]>>2); tempR[0]=(tempR[0]>>1)+(tempR[0]>>2); @@ -146,7 +150,7 @@ static unsigned char noiseFreq[12]={ 4,13,15,18,21,23,25,27,29,31,0,2 }; -void DivPlatformPCE::tick() { +void DivPlatformPCE::tick(bool sysTick) { for (int i=0; i<6; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { @@ -196,14 +200,34 @@ void DivPlatformPCE::tick() { if (!chan[i].keyOff) chan[i].keyOn=true; } } + if (chan[i].std.panL.had) { + chan[i].pan&=0x0f; + chan[i].pan|=(chan[i].std.panL.val&15)<<4; + } + if (chan[i].std.panR.had) { + chan[i].pan&=0xf0; + chan[i].pan|=chan[i].std.panR.val&15; + } + if (chan[i].std.panL.had || chan[i].std.panR.had) { + chWrite(i,0x05,isMuted[i]?0:chan[i].pan); + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } if (chan[i].active) { - if (chan[i].ws.tick()) { + if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1)) { updateWave(i); } } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - //DivInstrument* ins=parent->getIns(chan[i].ins); - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); + //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); if (chan[i].furnaceDac) { double off=1.0; if (chan[i].dacSample>=0 && chan[i].dacSamplesong.sampleLen) { @@ -237,7 +261,7 @@ void DivPlatformPCE::tick() { int DivPlatformPCE::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PCE); if (ins->type==DIV_INS_AMIGA) { chan[c.chan].pcm=true; } else if (chan[c.chan].furnaceDac) { @@ -265,7 +289,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { chan[c.chan].note=c.value; } chan[c.chan].active=true; - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); //chan[c.chan].keyOn=true; chan[c.chan].furnaceDac=true; } else { @@ -302,7 +326,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { chan[c.chan].active=true; chan[c.chan].keyOn=true; chWrite(c.chan,0x04,0x80|chan[c.chan].vol); - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); if (chan[c.chan].wave<0) { chan[c.chan].wave=0; chan[c.chan].ws.changeWave1(chan[c.chan].wave); @@ -317,7 +341,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { chan[c.chan].pcm=false; chan[c.chan].active=false; chan[c.chan].keyOff=true; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -404,7 +428,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { } break; case DIV_CMD_PANNING: { - chan[c.chan].pan=c.value; + chan[c.chan].pan=(c.value&0xf0)|(c.value2>>4); chWrite(c.chan,0x05,isMuted[c.chan]?0:chan[c.chan].pan); break; } @@ -415,7 +439,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE)); } chan[c.chan].inPorta=c.value; break; @@ -449,6 +473,10 @@ void* DivPlatformPCE::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformPCE::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformPCE::getRegisterPool() { return regPool; } @@ -462,6 +490,7 @@ void DivPlatformPCE::reset() { memset(regPool,0,128); for (int i=0; i<6; i++) { chan[i]=DivPlatformPCE::Channel(); + chan[i].std.setEngine(parent); chan[i].ws.setEngine(parent); chan[i].ws.init(NULL,32,31,false); } @@ -520,6 +549,9 @@ void DivPlatformPCE::setFlags(unsigned int flags) { chipClock=COLOR_NTSC; } rate=chipClock/12; + for (int i=0; i<6; i++) { + oscBuf[i]->rate=rate; + } } void DivPlatformPCE::poke(unsigned int addr, unsigned short val) { @@ -536,6 +568,7 @@ int DivPlatformPCE::init(DivEngine* p, int channels, int sugRate, unsigned int f skipRegisterWrites=false; for (int i=0; i<6; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); pce=new PCE_PSG(tempL,tempR,PCE_PSG::REVISION_HUC6280A); @@ -544,6 +577,9 @@ int DivPlatformPCE::init(DivEngine* p, int channels, int sugRate, unsigned int f } void DivPlatformPCE::quit() { + for (int i=0; i<6; i++) { + delete oscBuf[i]; + } delete pce; } diff --git a/src/engine/platform/pce.h b/src/engine/platform/pce.h index 2e8614ba8..58fa6600a 100644 --- a/src/engine/platform/pce.h +++ b/src/engine/platform/pce.h @@ -28,19 +28,24 @@ class DivPlatformPCE: public DivDispatch { struct Channel { - int freq, baseFreq, pitch, note; + int freq, baseFreq, pitch, pitch2, note; int dacPeriod, dacRate; unsigned int dacPos; - int dacSample; - unsigned char ins, pan; + int dacSample, ins; + unsigned char pan; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac; signed char vol, outVol, wave; DivMacroInt std; DivWaveSynth ws; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freq(0), baseFreq(0), pitch(0), + pitch2(0), note(0), dacPeriod(0), dacRate(0), @@ -62,6 +67,7 @@ class DivPlatformPCE: public DivDispatch { wave(-1) {} }; Channel chan[6]; + DivDispatchOscBuffer* oscBuf[6]; bool isMuted[6]; struct QueuedWrite { unsigned char addr; @@ -83,11 +89,12 @@ class DivPlatformPCE: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool isStereo(); bool keyOffAffectsArp(int ch); diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index ee6ba7ef8..434ab10b4 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -50,9 +50,11 @@ const float cut=0.05; const float reso=0.06; void DivPlatformPCSpeaker::acquire_unfilt(short* bufL, short* bufR, size_t start, size_t len) { + int out=0; for (size_t i=start; ifreq) pos=freq; while (pos<0) { if (freq<1) { pos=1; @@ -60,9 +62,12 @@ void DivPlatformPCSpeaker::acquire_unfilt(short* bufL, short* bufR, size_t start pos+=freq; } } - bufL[i]=(pos>(freq>>1) && !isMuted[0])?32767:0; + out=(pos>(freq>>1) && !isMuted[0])?32767:0; + bufL[i]=out; + oscBuf->data[oscBuf->needle++]=out; } else { bufL[i]=0; + oscBuf->data[oscBuf->needle++]=0; } } } @@ -71,6 +76,7 @@ void DivPlatformPCSpeaker::acquire_cone(short* bufL, short* bufR, size_t start, for (size_t i=start; ifreq) pos=freq; while (pos<0) { if (freq<1) { pos=1; @@ -85,8 +91,10 @@ void DivPlatformPCSpeaker::acquire_cone(short* bufL, short* bufR, size_t start, if (out>1.0) out=1.0; if (out<-1.0) out=-1.0; bufL[i]=out*32767; + oscBuf->data[oscBuf->needle++]=out*32767; } else { bufL[i]=0; + oscBuf->data[oscBuf->needle++]=0; } } } @@ -95,6 +103,7 @@ void DivPlatformPCSpeaker::acquire_piezo(short* bufL, short* bufR, size_t start, for (size_t i=start; ifreq) pos=freq; while (pos<0) { if (freq<1) { pos=1; @@ -109,8 +118,10 @@ void DivPlatformPCSpeaker::acquire_piezo(short* bufL, short* bufR, size_t start, if (out>1.0) out=1.0; if (out<-1.0) out=-1.0; bufL[i]=out*32767; + oscBuf->data[oscBuf->needle++]=out*32767; } else { bufL[i]=0; + oscBuf->data[oscBuf->needle++]=0; } } } @@ -137,12 +148,28 @@ void DivPlatformPCSpeaker::beepFreq(int freq) { } void DivPlatformPCSpeaker::acquire_real(short* bufL, short* bufR, size_t start, size_t len) { + int out=0; if (lastOn!=on || lastFreq!=freq) { lastOn=on; lastFreq=freq; beepFreq((on && !isMuted[0])?freq:0); } for (size_t i=start; ifreq) pos=freq; + while (pos<0) { + if (freq<1) { + pos=1; + } else { + pos+=freq; + } + } + out=(pos>(freq>>1) && !isMuted[0])?32767:0; + oscBuf->data[oscBuf->needle++]=out; + } else { + oscBuf->data[oscBuf->needle++]=0; + } bufL[i]=0; } } @@ -164,7 +191,7 @@ void DivPlatformPCSpeaker::acquire(short* bufL, short* bufR, size_t start, size_ } } -void DivPlatformPCSpeaker::tick() { +void DivPlatformPCSpeaker::tick(bool sysTick) { for (int i=0; i<1; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { @@ -186,8 +213,17 @@ void DivPlatformPCSpeaker::tick() { chan[i].freqChanged=true; } } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>65535) chan[i].freq=65535; if (chan[i].keyOn) { @@ -214,12 +250,12 @@ int DivPlatformPCSpeaker::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER)); break; case DIV_CMD_NOTE_OFF: chan[c.chan].active=false; chan[c.chan].keyOff=true; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -279,7 +315,7 @@ int DivPlatformPCSpeaker::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER)); } chan[c.chan].inPorta=c.value; break; @@ -309,6 +345,10 @@ void* DivPlatformPCSpeaker::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformPCSpeaker::getOscBuffer(int ch) { + return oscBuf; +} + unsigned char* DivPlatformPCSpeaker::getRegisterPool() { if (on) { regPool[0]=freq; @@ -327,6 +367,7 @@ int DivPlatformPCSpeaker::getRegisterPoolSize() { void DivPlatformPCSpeaker::reset() { for (int i=0; i<1; i++) { chan[i]=DivPlatformPCSpeaker::Channel(); + chan[i].std.setEngine(parent); } if (dumpWrites) { addWrite(0xffffffff,0); @@ -366,6 +407,7 @@ void DivPlatformPCSpeaker::setFlags(unsigned int flags) { chipClock=COLOR_NTSC/3.0; rate=chipClock/PCSPKR_DIVIDER; speakerType=flags&3; + oscBuf->rate=rate; } void DivPlatformPCSpeaker::notifyInsDeletion(void* ins) { @@ -394,6 +436,7 @@ int DivPlatformPCSpeaker::init(DivEngine* p, int channels, int sugRate, unsigned for (int i=0; i<1; i++) { isMuted[i]=false; } + oscBuf=new DivDispatchOscBuffer; setFlags(flags); reset(); @@ -407,6 +450,7 @@ void DivPlatformPCSpeaker::quit() { #ifdef __linux__ if (beepFD>=0) close(beepFD); #endif + delete oscBuf; } DivPlatformPCSpeaker::~DivPlatformPCSpeaker() { diff --git a/src/engine/platform/pcspkr.h b/src/engine/platform/pcspkr.h index 17dedf247..155416bb8 100644 --- a/src/engine/platform/pcspkr.h +++ b/src/engine/platform/pcspkr.h @@ -25,15 +25,20 @@ class DivPlatformPCSpeaker: public DivDispatch { struct Channel { - int freq, baseFreq, pitch, note; - unsigned char ins, duty, sweep; + int freq, baseFreq, pitch, pitch2, note, ins; + unsigned char duty, sweep; bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, furnaceDac; signed char vol, outVol, wave; DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freq(0), baseFreq(0), pitch(0), + pitch2(0), note(0), ins(-1), duty(0), @@ -51,6 +56,7 @@ class DivPlatformPCSpeaker: public DivDispatch { wave(-1) {} }; Channel chan[1]; + DivDispatchOscBuffer* oscBuf; bool isMuted[1]; bool on, flip, lastOn; int pos, speakerType, beepFD; @@ -73,11 +79,12 @@ class DivPlatformPCSpeaker: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool keyOffAffectsArp(int ch); void setFlags(unsigned int flags); diff --git a/src/engine/platform/pet.cpp b/src/engine/platform/pet.cpp index e46277e0a..8ddee9e86 100644 --- a/src/engine/platform/pet.cpp +++ b/src/engine/platform/pet.cpp @@ -64,12 +64,14 @@ void DivPlatformPET::acquire(short* bufL, short* bufR, size_t start, size_t len) } bufL[h]=chan.out; bufR[h]=chan.out; + oscBuf->data[oscBuf->needle++]=chan.out; } } else { chan.out=0; for (size_t h=start; hdata[oscBuf->needle++]=0; } } } @@ -85,7 +87,7 @@ void DivPlatformPET::writeOutVol() { } } -void DivPlatformPET::tick() { +void DivPlatformPET::tick(bool sysTick) { chan.std.next(); if (chan.std.vol.had) { chan.outVol=chan.std.vol.val&chan.vol; @@ -112,8 +114,11 @@ void DivPlatformPET::tick() { rWrite(10,chan.wave); } } + if (chan.std.pitch.had) { + chan.freqChanged=true; + } if (chan.freqChanged || chan.keyOn || chan.keyOff) { - chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true); + chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2); if (chan.freq>257) chan.freq=257; if (chan.freq<2) chan.freq=2; rWrite(8,chan.freq-2); @@ -135,7 +140,7 @@ void DivPlatformPET::tick() { int DivPlatformPET::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan.ins); + DivInstrument* ins=parent->getIns(chan.ins,DIV_INS_PET); if (c.value!=DIV_NOTE_NULL) { chan.baseFreq=NOTE_PERIODIC(c.value); chan.freqChanged=true; @@ -143,13 +148,13 @@ int DivPlatformPET::dispatch(DivCommand c) { } chan.active=true; chan.keyOn=true; - chan.std.init(ins); + chan.macroInit(ins); break; } case DIV_CMD_NOTE_OFF: chan.active=false; chan.keyOff=true; - chan.std.init(NULL); + chan.macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -210,7 +215,7 @@ int DivPlatformPET::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan.active && c.value2) { - if (parent->song.resetMacroOnPorta) chan.std.init(parent->getIns(chan.ins)); + if (parent->song.resetMacroOnPorta) chan.macroInit(parent->getIns(chan.ins,DIV_INS_PET)); } chan.inPorta=c.value; break; @@ -241,6 +246,10 @@ void* DivPlatformPET::getChanState(int ch) { return &chan; } +DivDispatchOscBuffer* DivPlatformPET::getOscBuffer(int ch) { + return oscBuf; +} + unsigned char* DivPlatformPET::getRegisterPool() { return regPool; } @@ -252,6 +261,7 @@ int DivPlatformPET::getRegisterPoolSize() { void DivPlatformPET::reset() { memset(regPool,0,16); chan=Channel(); + chan.std.setEngine(parent); } bool DivPlatformPET::isStereo() { @@ -277,9 +287,15 @@ int DivPlatformPET::init(DivEngine* p, int channels, int sugRate, unsigned int f chipClock=1000000; rate=chipClock/SAMP_DIVIDER; // = 250000kHz isMuted=false; + oscBuf=new DivDispatchOscBuffer; + oscBuf->rate=rate; reset(); return 1; } +void DivPlatformPET::quit() { + delete oscBuf; +} + DivPlatformPET::~DivPlatformPET() { } diff --git a/src/engine/platform/pet.h b/src/engine/platform/pet.h index 3b6af48d9..1e5e49ce5 100644 --- a/src/engine/platform/pet.h +++ b/src/engine/platform/pet.h @@ -25,18 +25,22 @@ class DivPlatformPET: public DivDispatch { struct Channel { - int freq, baseFreq, pitch, note; - unsigned char ins; + int freq, baseFreq, pitch, pitch2, note, ins; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; int vol, outVol, wave; unsigned char sreg; int cnt; short out; DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freq(0), baseFreq(0), pitch(0), + pitch2(0), note(0), ins(-1), active(false), @@ -53,6 +57,7 @@ class DivPlatformPET: public DivDispatch { out(0) {} }; Channel chan; + DivDispatchOscBuffer* oscBuf; bool isMuted; unsigned char regPool[16]; @@ -61,11 +66,12 @@ class DivPlatformPET: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); void notifyInsDeletion(void* ins); bool isStereo(); @@ -74,6 +80,7 @@ class DivPlatformPET: public DivDispatch { const char** getRegisterSheet(); const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); ~DivPlatformPET(); private: void writeOutVol(); diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index 0fd25ec72..6e89e5b61 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -257,6 +257,9 @@ const char* DivPlatformQSound::getEffectName(unsigned char effect) { case 0x11: return "11xx: Set channel echo level (00 to FF)"; break; + case 0x12: + return "12xx: Toggle QSound algorithm (0: disabled; 1: enabled)"; + break; default: if ((effect & 0xf0) == 0x30) { return "3xxx: Set echo delay buffer length (000 to AA5)"; @@ -265,16 +268,21 @@ const char* DivPlatformQSound::getEffectName(unsigned char effect) { return NULL; } void DivPlatformQSound::acquire(short* bufL, short* bufR, size_t start, size_t len) { - chip.rom_data = parent->qsoundMem; - chip.rom_mask = 0xffffff; for (size_t h=start; h32767) data=32767; + oscBuf[i]->data[oscBuf[i]->needle++]=data; + } } } -void DivPlatformQSound::tick() { +void DivPlatformQSound::tick(bool sysTick) { for (int i=0; i<16; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { @@ -326,9 +334,27 @@ void DivPlatformQSound::tick() { chan[i].freqChanged=true; } } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + if (chan[i].std.panL.had) { // panning + chan[i].panning=chan[i].std.panL.val+16; + } + if (chan[i].std.panR.had) { // surround + chan[i].surround=chan[i].std.panR.val; + } + if (chan[i].std.panL.had || chan[i].std.panR.had) { + immWrite(Q1_PAN+i,chan[i].panning+0x110+(chan[i].surround?0:0x30)); + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - //DivInstrument* ins=parent->getIns(chan[i].ins); - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false); + //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2); if (chan[i].freq>0xffff) chan[i].freq=0xffff; if (chan[i].keyOn) { rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank); @@ -360,7 +386,7 @@ void DivPlatformQSound::tick() { int DivPlatformQSound::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); chan[c.chan].sample=ins->amiga.initSample; double off=1.0; if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { @@ -383,14 +409,14 @@ int DivPlatformQSound::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); break; } case DIV_CMD_NOTE_OFF: chan[c.chan].sample=-1; chan[c.chan].active=false; chan[c.chan].keyOff=true; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -420,7 +446,8 @@ int DivPlatformQSound::dispatch(DivCommand c) { return chan[c.chan].outVol; break; case DIV_CMD_PANNING: - immWrite(Q1_PAN+c.chan, c.value + 0x110); + chan[c.chan].panning=parent->convertPanSplitToLinearLR(c.value,c.value2,32); + immWrite(Q1_PAN+c.chan,chan[c.chan].panning+0x110+(chan[c.chan].surround?0:0x30)); break; case DIV_CMD_QSOUND_ECHO_LEVEL: immWrite(Q1_ECHO+c.chan, c.value << 7); @@ -431,6 +458,10 @@ int DivPlatformQSound::dispatch(DivCommand c) { case DIV_CMD_QSOUND_ECHO_DELAY: immWrite(Q1_ECHO_LENGTH, (c.value > 2725 ? 0xfff : 0xfff - (2725 - c.value))); break; + case DIV_CMD_QSOUND_SURROUND: + chan[c.chan].surround=c.value; + immWrite(Q1_PAN+c.chan,chan[c.chan].panning+0x110+(chan[c.chan].surround?0:0x30)); + break; case DIV_CMD_PITCH: chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; @@ -484,7 +515,7 @@ int DivPlatformQSound::dispatch(DivCommand c) { } case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA)); } chan[c.chan].inPorta=c.value; break; @@ -520,9 +551,14 @@ void* DivPlatformQSound::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformQSound::getOscBuffer(int ch) { + return oscBuf[ch]; +} + void DivPlatformQSound::reset() { for (int i=0; i<16; i++) { chan[i]=DivPlatformQSound::Channel(); + chan[i].std.setEngine(parent); } qsound_reset(&chip); while(!chip.ready_flag) { @@ -600,23 +636,79 @@ int DivPlatformQSound::getRegisterPoolDepth() { return 16; } +const void* DivPlatformQSound::getSampleMem(int index) { + return index == 0 ? sampleMem : NULL; +} + +size_t DivPlatformQSound::getSampleMemCapacity(int index) { + return index == 0 ? 16777216 : 0; +} + +size_t DivPlatformQSound::getSampleMemUsage(int index) { + return index == 0 ? sampleMemLen : 0; +} + +void DivPlatformQSound::renderSamples() { + memset(sampleMem,0,getSampleMemCapacity()); + + size_t memPos=0; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + int length=s->length8; + if (length>65536-16) { + length=65536-16; + } + if ((memPos&0xff0000)!=((memPos+length)&0xff0000)) { + memPos=(memPos+0xffff)&0xff0000; + } + if (memPos>=getSampleMemCapacity()) { + logW("out of QSound PCM memory for sample %d!",i); + break; + } + if (memPos+length>=getSampleMemCapacity()) { + for (unsigned int i=0; idata8[i]; + } + logW("out of QSound PCM memory for sample %d!",i); + } else { + for (int i=0; idata8[i]; + } + } + s->offQSound=memPos^0x8000; + memPos+=length+16; + } + sampleMemLen=memPos+256; +} + int DivPlatformQSound::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; skipRegisterWrites=false; - //for (int i=0; i<16; i++) { - // isMuted[i]=false; - //} + for (int i=0; i<19; i++) { + oscBuf[i]=new DivDispatchOscBuffer; + //isMuted[i]=false; + } setFlags(flags); chipClock=60000000; rate = qsound_start(&chip, chipClock); - chip.rom_data = (unsigned char*)&chip.rom_mask; - chip.rom_mask = 0; + sampleMem=new unsigned char[getSampleMemCapacity()]; + sampleMemLen=0; + chip.rom_data=sampleMem; + chip.rom_mask=0xffffff; reset(); + + for (int i=0; i<19; i++) { + oscBuf[i]->rate=rate; + } return 19; } void DivPlatformQSound::quit() { + delete[] sampleMem; + for (int i=0; i<19; i++) { + delete oscBuf[i]; + } } diff --git a/src/engine/platform/qsound.h b/src/engine/platform/qsound.h index 16420055d..d12e952ef 100644 --- a/src/engine/platform/qsound.h +++ b/src/engine/platform/qsound.h @@ -27,20 +27,24 @@ class DivPlatformQSound: public DivDispatch { struct Channel { - int freq, baseFreq, pitch; + int freq, baseFreq, pitch, pitch2; unsigned short audLen; unsigned int audPos; - int sample, wave; - unsigned char ins; + int sample, wave, ins; int note; int panning; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, surround; int vol, outVol; DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freq(0), baseFreq(0), pitch(0), + pitch2(0), audLen(0), audPos(0), sample(-1), @@ -53,13 +57,18 @@ class DivPlatformQSound: public DivDispatch { keyOn(false), keyOff(false), inPorta(false), + useWave(false), + surround(true), vol(255), outVol(255) {} }; Channel chan[19]; + DivDispatchOscBuffer* oscBuf[19]; int echoDelay; int echoFeedback; + unsigned char* sampleMem; + size_t sampleMemLen; struct qsound_chip chip; unsigned short regPool[512]; @@ -69,12 +78,13 @@ class DivPlatformQSound: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); int getRegisterPoolDepth(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool isStereo(); bool keyOffAffectsArp(int ch); @@ -86,6 +96,10 @@ class DivPlatformQSound: public DivDispatch { void poke(std::vector& wlist); const char** getRegisterSheet(); const char* getEffectName(unsigned char effect); + const void* getSampleMem(int index = 0); + size_t getSampleMemCapacity(int index = 0); + size_t getSampleMemUsage(int index = 0); + void renderSamples(); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); }; diff --git a/src/engine/platform/saa.cpp b/src/engine/platform/saa.cpp index 708fbc7ab..3ecf0a3ed 100644 --- a/src/engine/platform/saa.cpp +++ b/src/engine/platform/saa.cpp @@ -86,7 +86,7 @@ void DivPlatformSAA1099::acquire_mame(short* bufL, short* bufR, size_t start, si regPool[w.addr&0x1f]=w.val; writes.pop(); } - saa.sound_stream_update(saaBuf,len); + saa.sound_stream_update(saaBuf,len,oscBuf); for (size_t i=0; iGenerateMany((unsigned char*)saaBuf[0],len); + saa_saaSound->GenerateMany((unsigned char*)saaBuf[0],len,oscBuf); for (size_t i=0; i>4))/15)|(((vol*(pan&15))/15)<<4); } -void DivPlatformSAA1099::tick() { +void DivPlatformSAA1099::tick(bool sysTick) { for (int i=0; i<6; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { @@ -166,12 +166,41 @@ void DivPlatformSAA1099::tick() { if (chan[i].std.wave.had) { chan[i].psgMode=chan[i].std.wave.val&3; } + if (chan[i].std.panL.had) { + chan[i].pan&=0x0f; + chan[i].pan|=(chan[i].std.panL.val&15)<<4; + } + + if (chan[i].std.panR.had) { + chan[i].pan&=0xf0; + chan[i].pan|=chan[i].std.panR.val&15; + } + if (chan[i].std.panL.had || chan[i].std.panR.had) { + if (isMuted[i]) { + rWrite(i,0); + } else { + if (chan[i].std.vol.had) { + if (chan[i].active) rWrite(i,applyPan(chan[i].outVol&15,chan[i].pan)); + } else { + if (chan[i].active) rWrite(i,applyPan(chan[i].vol&15,chan[i].pan)); + } + } + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } if (chan[i].std.ex1.had) { saaEnv[i/3]=chan[i].std.ex1.val; rWrite(0x18+(i/3),saaEnv[i/3]); } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); if (chan[i].freq>65535) chan[i].freq=65535; if (chan[i].freq>=32768) { chan[i].freqH=7; @@ -226,7 +255,7 @@ void DivPlatformSAA1099::tick() { int DivPlatformSAA1099::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SAA1099); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); chan[c.chan].freqChanged=true; @@ -234,7 +263,7 @@ int DivPlatformSAA1099::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); if (isMuted[c.chan]) { rWrite(c.chan,0); } else { @@ -245,7 +274,7 @@ int DivPlatformSAA1099::dispatch(DivCommand c) { case DIV_CMD_NOTE_OFF: chan[c.chan].keyOff=true; chan[c.chan].active=false; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -302,7 +331,7 @@ int DivPlatformSAA1099::dispatch(DivCommand c) { break; } case DIV_CMD_PANNING: - chan[c.chan].pan=c.value; + chan[c.chan].pan=(c.value&0xf0)|(c.value2>>4); if (isMuted[c.chan]) { rWrite(c.chan,0); } else { @@ -333,7 +362,7 @@ int DivPlatformSAA1099::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SAA1099)); } chan[c.chan].inPorta=c.value; break; @@ -369,6 +398,10 @@ void* DivPlatformSAA1099::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformSAA1099::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformSAA1099::getRegisterPool() { return regPool; } @@ -392,6 +425,7 @@ void DivPlatformSAA1099::reset() { } for (int i=0; i<6; i++) { chan[i]=DivPlatformSAA1099::Channel(); + chan[i].std.setEngine(parent); chan[i].vol=0x0f; } if (dumpWrites) { @@ -455,6 +489,10 @@ void DivPlatformSAA1099::setFlags(unsigned int flags) { } rate=chipClock/32; + for (int i=0; i<6; i++) { + oscBuf[i]->rate=rate; + } + switch (core) { case DIV_SAA_CORE_MAME: break; @@ -486,6 +524,7 @@ int DivPlatformSAA1099::init(DivEngine* p, int channels, int sugRate, unsigned i saa_saaSound=NULL; for (int i=0; i<6; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } if (core==DIV_SAA_CORE_SAASOUND) { saa_saaSound=CreateCSAASound(); @@ -500,6 +539,9 @@ int DivPlatformSAA1099::init(DivEngine* p, int channels, int sugRate, unsigned i } void DivPlatformSAA1099::quit() { + for (int i=0; i<6; i++) { + delete oscBuf[i]; + } if (saa_saaSound!=NULL) { DestroyCSAASound(saa_saaSound); saa_saaSound=NULL; diff --git a/src/engine/platform/saa.h b/src/engine/platform/saa.h index 8df44f616..70edaa249 100644 --- a/src/engine/platform/saa.h +++ b/src/engine/platform/saa.h @@ -35,16 +35,21 @@ class DivPlatformSAA1099: public DivDispatch { protected: struct Channel { unsigned char freqH, freqL; - int freq, baseFreq, pitch, note; - unsigned char ins, psgMode; + int freq, baseFreq, pitch, pitch2, note, ins; + unsigned char psgMode; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; int vol, outVol; unsigned char pan; DivMacroInt std; - Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), note(0), ins(-1), psgMode(1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(15), pan(255) {} + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), note(0), ins(-1), psgMode(1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(15), pan(255) {} }; Channel chan[6]; + DivDispatchOscBuffer* oscBuf[6]; bool isMuted[6]; struct QueuedWrite { unsigned short addr; @@ -86,11 +91,12 @@ class DivPlatformSAA1099: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); void setCore(DivSAACores core); void setFlags(unsigned int flags); diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index b87ee9a86..346daeae1 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -46,9 +46,11 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t DivSample* s=parent->getSample(chan[i].pcm.sample); if (s->samples<=0) { chan[i].pcm.sample=-1; + oscBuf[i]->data[oscBuf[i]->needle++]=0; continue; } if (!isMuted[i]) { + oscBuf[i]->data[oscBuf[i]->needle++]=s->data8[chan[i].pcm.pos>>8]*(chan[i].chVolL+chan[i].chVolR)>>1; pcmL+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolL); pcmR+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolR); } @@ -60,6 +62,8 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t chan[i].pcm.sample=-1; } } + } else { + oscBuf[i]->data[oscBuf[i]->needle++]=0; } } @@ -76,13 +80,14 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t } } -void DivPlatformSegaPCM::tick() { +void DivPlatformSegaPCM::tick(bool sysTick) { for (int i=0; i<16; i++) { chan[i].std.next(); - if (chan[i].std.vol.had) { + // TODO: fix + /*if (chan[i].std.vol.had) { chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol.val))/127; - } + }*/ if (chan[i].std.arp.had) { if (!chan[i].inPorta) { @@ -99,6 +104,30 @@ void DivPlatformSegaPCM::tick() { chan[i].freqChanged=true; } } + + if (chan[i].std.panL.had) { + chan[i].chVolL=chan[i].std.panL.val&127; + if (dumpWrites) { + addWrite(0x10002+(i<<3),chan[i].chVolL); + } + } + + if (chan[i].std.panR.had) { + chan[i].chVolR=chan[i].std.panR.val&127; + if (dumpWrites) { + addWrite(0x10003+(i<<3),chan[i].chVolR); + } + } + + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } /*if (chan[i].keyOn || chan[i].keyOff) { chan[i].keyOff=false; }*/ @@ -113,7 +142,7 @@ void DivPlatformSegaPCM::tick() { DivSample* s=parent->getSample(chan[i].pcm.sample); off=(double)s->centerRate/8363.0; } - chan[i].pcm.freq=MIN(255,(15625+(off*parent->song.tuning*pow(2.0,double(chan[i].freq+256)/(64.0*12.0)))*255)/31250); + chan[i].pcm.freq=MIN(255,(15625+(off*parent->song.tuning*pow(2.0,double(chan[i].freq+256)/(64.0*12.0)))*255)/31250)+chan[i].pitch2; if (dumpWrites) { addWrite(0x10007+(i<<3),chan[i].pcm.freq); } @@ -130,7 +159,7 @@ void DivPlatformSegaPCM::muteChannel(int ch, bool mute) { int DivPlatformSegaPCM::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); if (skipRegisterWrites) break; if (ins->type==DIV_INS_AMIGA) { chan[c.chan].pcm.sample=ins->amiga.initSample; @@ -139,12 +168,17 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { if (dumpWrites) { addWrite(0x10086+(c.chan<<3),3); } + chan[c.chan].macroInit(NULL); break; } chan[c.chan].pcm.pos=0; - chan[c.chan].baseFreq=(c.value<<6); - chan[c.chan].freqChanged=true; + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + chan[c.chan].baseFreq=(c.value<<6); + chan[c.chan].freqChanged=true; + } chan[c.chan].furnacePCM=true; + chan[c.chan].macroInit(ins); if (dumpWrites) { // Sega PCM writes DivSample* s=parent->getSample(chan[c.chan].pcm.sample); addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3)); @@ -161,6 +195,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { } } } else { + chan[c.chan].macroInit(NULL); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; } @@ -202,6 +237,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: chan[c.chan].keyOff=true; @@ -236,8 +272,8 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { - chan[c.chan].chVolL=(c.value>>4)|(((c.value>>4)>>1)<<4); - chan[c.chan].chVolR=(c.value&15)|(((c.value&15)>>1)<<4); + chan[c.chan].chVolL=c.value>>1; + chan[c.chan].chVolR=c.value2>>1; if (dumpWrites) { addWrite(0x10002+(c.chan<<3),chan[c.chan].chVolL); addWrite(0x10003+(c.chan<<3),chan[c.chan].chVolR); @@ -327,6 +363,10 @@ void* DivPlatformSegaPCM::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformSegaPCM::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformSegaPCM::getRegisterPool() { return regPool; } @@ -348,6 +388,7 @@ void DivPlatformSegaPCM::reset() { memset(regPool,0,256); for (int i=0; i<16; i++) { chan[i]=DivPlatformSegaPCM::Channel(); + chan[i].std.setEngine(parent); chan[i].vol=0x7f; chan[i].outVol=0x7f; } @@ -375,6 +416,9 @@ void DivPlatformSegaPCM::reset() { void DivPlatformSegaPCM::setFlags(unsigned int flags) { chipClock=8000000.0; rate=31250; + for (int i=0; i<16; i++) { + oscBuf[i]->rate=rate; + } } bool DivPlatformSegaPCM::isStereo() { @@ -387,6 +431,7 @@ int DivPlatformSegaPCM::init(DivEngine* p, int channels, int sugRate, unsigned i skipRegisterWrites=false; for (int i=0; i<16; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); reset(); @@ -395,6 +440,9 @@ int DivPlatformSegaPCM::init(DivEngine* p, int channels, int sugRate, unsigned i } void DivPlatformSegaPCM::quit() { + for (int i=0; i<16; i++) { + delete oscBuf[i]; + } } DivPlatformSegaPCM::~DivPlatformSegaPCM() { diff --git a/src/engine/platform/segapcm.h b/src/engine/platform/segapcm.h index b3c84cd3b..32cd22c29 100644 --- a/src/engine/platform/segapcm.h +++ b/src/engine/platform/segapcm.h @@ -29,8 +29,7 @@ class DivPlatformSegaPCM: public DivDispatch { struct Channel { DivMacroInt std; unsigned char freqH, freqL; - int freq, baseFreq, pitch, note; - unsigned char ins; + int freq, baseFreq, pitch, pitch2, note, ins; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM; int vol, outVol; @@ -43,9 +42,14 @@ class DivPlatformSegaPCM: public DivDispatch { unsigned char freq; PCMChannel(): sample(-1), pos(0), len(0), freq(0) {} } pcm; - Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), note(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), inPorta(false), portaPause(false), furnacePCM(false), vol(0), outVol(0), chVolL(127), chVolR(127) {} + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), note(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), inPorta(false), portaPause(false), furnacePCM(false), vol(0), outVol(0), chVolL(127), chVolR(127) {} }; Channel chan[16]; + DivDispatchOscBuffer* oscBuf[16]; struct QueuedWrite { unsigned short addr; unsigned char val; @@ -74,11 +78,12 @@ class DivPlatformSegaPCM: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); void notifyInsChange(int ins); void setFlags(unsigned int flags); diff --git a/src/engine/platform/sms.cpp b/src/engine/platform/sms.cpp index f4bbc46e1..f1c661634 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -23,8 +23,6 @@ #define rWrite(v) {if (!skipRegisterWrites) {sn->write(v); if (dumpWrites) {addWrite(0x200,v);}}} -#define CHIP_DIVIDER 64 - const char* regCheatSheetSN[]={ "DATA", "0", NULL @@ -44,7 +42,16 @@ const char* DivPlatformSMS::getEffectName(unsigned char effect) { } void DivPlatformSMS::acquire(short* bufL, short* bufR, size_t start, size_t len) { - sn->sound_stream_update(bufL+start,len); + for (size_t h=start; hsound_stream_update(bufL+h,1); + for (int i=0; i<4; i++) { + if (isMuted[i]) { + oscBuf[i]->data[oscBuf[i]->needle++]=0; + } else { + oscBuf[i]->data[oscBuf[i]->needle++]=sn->get_channel_output(i); + } + } + } } int DivPlatformSMS::acquireOne() { @@ -53,8 +60,10 @@ int DivPlatformSMS::acquireOne() { return v; } -void DivPlatformSMS::tick() { +void DivPlatformSMS::tick(bool sysTick) { for (int i=0; i<4; i++) { + int CHIP_DIVIDER=64; + if (i==3 && isRealSN) CHIP_DIVIDER=60; chan[i].std.next(); if (chan[i].std.vol.had) { chan[i].outVol=MIN(15,chan[i].std.vol.val)-(15-(chan[i].vol&15)); @@ -84,19 +93,38 @@ void DivPlatformSMS::tick() { chan[i].freqChanged=true; } } - if (i==3) if (chan[i].std.duty.had) { - snNoiseMode=chan[i].std.duty.val; - if (chan[i].std.duty.val<2) { - chan[3].freqChanged=false; + if (i==3) { + if (chan[i].std.duty.had) { + if (chan[i].std.duty.val!=snNoiseMode || parent->song.snDutyReset) { + snNoiseMode=chan[i].std.duty.val; + if (chan[i].std.duty.val<2) { + chan[3].freqChanged=false; + } + updateSNMode=true; + } } - updateSNMode=true; + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + updateSNMode=true; + } + } + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; } } for (int i=0; i<3; i++) { if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); if (chan[i].freq>1023) chan[i].freq=1023; - if (chan[i].actualNote>0x5d) chan[i].freq=0x01; + if (chan[i].freq<8) chan[i].freq=1; + //if (chan[i].actualNote>0x5d) chan[i].freq=0x01; rWrite(0x80|i<<5|(chan[i].freq&15)); rWrite(chan[i].freq>>4); // what? @@ -108,8 +136,7 @@ void DivPlatformSMS::tick() { } } if (chan[3].freqChanged || updateSNMode) { - // seems arbitrary huh? - chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch-1-(isRealSN?127:0),true); + chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2); if (chan[3].freq>1023) chan[3].freq=1023; if (chan[3].actualNote>0x5d) chan[3].freq=0x01; if (snNoiseMode&2) { // take period from channel 3 @@ -153,6 +180,8 @@ void DivPlatformSMS::tick() { } int DivPlatformSMS::dispatch(DivCommand c) { + int CHIP_DIVIDER=64; + if (c.chan==3 && isRealSN) CHIP_DIVIDER=60; switch (c.cmd) { case DIV_CMD_NOTE_ON: if (c.value!=DIV_NOTE_NULL) { @@ -163,12 +192,12 @@ int DivPlatformSMS::dispatch(DivCommand c) { } chan[c.chan].active=true; rWrite(0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15)))); - chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD)); break; case DIV_CMD_NOTE_OFF: chan[c.chan].active=false; rWrite(0x9f|c.chan<<5); - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -176,7 +205,7 @@ int DivPlatformSMS::dispatch(DivCommand c) { break; case DIV_CMD_INSTRUMENT: chan[c.chan].ins=c.value; - //chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + //chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD)); break; case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { @@ -232,7 +261,7 @@ int DivPlatformSMS::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD)); } chan[c.chan].inPorta=c.value; break; @@ -267,9 +296,14 @@ void* DivPlatformSMS::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformSMS::getOscBuffer(int ch) { + return oscBuf[ch]; +} + void DivPlatformSMS::reset() { for (int i=0; i<4; i++) { chan[i]=DivPlatformSMS::Channel(); + chan[i].std.setEngine(parent); } if (dumpWrites) { addWrite(0xffffffff,0); @@ -338,6 +372,9 @@ void DivPlatformSMS::setFlags(unsigned int flags) { break; } rate=chipClock/16; + for (int i=0; i<4; i++) { + oscBuf[i]->rate=rate; + } } int DivPlatformSMS::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { @@ -348,6 +385,7 @@ int DivPlatformSMS::init(DivEngine* p, int channels, int sugRate, unsigned int f oldValue=0xff; for (int i=0; i<4; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } sn=NULL; setFlags(flags); @@ -356,6 +394,9 @@ int DivPlatformSMS::init(DivEngine* p, int channels, int sugRate, unsigned int f } void DivPlatformSMS::quit() { + for (int i=0; i<4; i++) { + delete oscBuf[i]; + } if (sn!=NULL) delete sn; } diff --git a/src/engine/platform/sms.h b/src/engine/platform/sms.h index b49a5e276..d1b528818 100644 --- a/src/engine/platform/sms.h +++ b/src/engine/platform/sms.h @@ -26,15 +26,19 @@ class DivPlatformSMS: public DivDispatch { struct Channel { - int freq, baseFreq, pitch, note, actualNote; - unsigned char ins; + int freq, baseFreq, pitch, pitch2, note, actualNote, ins; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; signed char vol, outVol; DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freq(0), baseFreq(0), pitch(0), + pitch2(0), note(0), actualNote(0), ins(-1), @@ -48,6 +52,7 @@ class DivPlatformSMS: public DivDispatch { outVol(15) {} }; Channel chan[4]; + DivDispatchOscBuffer* oscBuf[4]; bool isMuted[4]; unsigned char oldValue; unsigned char snNoiseMode; @@ -61,9 +66,10 @@ class DivPlatformSMS: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool keyOffAffectsArp(int ch); bool keyOffAffectsPorta(int ch); diff --git a/src/engine/platform/sound/ay8910.cpp b/src/engine/platform/sound/ay8910.cpp index 8bb387885..4d19e7de5 100644 --- a/src/engine/platform/sound/ay8910.cpp +++ b/src/engine/platform/sound/ay8910.cpp @@ -1064,7 +1064,7 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen) { tone = &m_tone[chan]; const int period = std::max(1,tone->period); - tone->count += is_expanded_mode() ? 16 : 1; + tone->count += is_expanded_mode() ? 16 : (m_feature & PSG_HAS_EXPANDED_MODE) ? 2 : 1; while (tone->count >= period) { tone->duty_cycle = (tone->duty_cycle - 1) & 0x1f; @@ -1080,7 +1080,7 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen) * channels. */ m_count_noise = 0; - m_prescale_noise ^= 1; + m_prescale_noise = (m_prescale_noise + 1) & ((m_feature & PSG_HAS_EXPANDED_MODE) ? 3 : 1); if (!m_prescale_noise || is_expanded_mode()) // AY8930 noise generator rate is twice compares as compatibility mode { @@ -1469,7 +1469,7 @@ ay8910_device::ay8910_device(device_type type, unsigned int clock, m_noise_latch(0), m_mode(0), m_env_step_mask((!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? 0x0f : 0x1f), - m_step( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? 2 : 1), + m_step( (feature & PSG_HAS_EXPANDED_MODE) || (psg_type == PSG_TYPE_AY) ? 2 : 1), m_zero_is_off( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? 1 : 0), m_par( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? &ay8910_param : &ym2149_param), m_par_env( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? &ay8910_param : &ym2149_param_env), @@ -1502,7 +1502,7 @@ void ay8910_device::set_type(psg_type_t psg_type) else { m_env_step_mask = 0x1f; - m_step = 1; + m_step = (m_feature & PSG_HAS_EXPANDED_MODE) ? 2 : 1; m_zero_is_off = 0; m_par = &ym2149_param; m_par_env = &ym2149_param_env; diff --git a/src/engine/platform/sound/c64/sid.cc b/src/engine/platform/sound/c64/sid.cc index 73d5c2bb3..d6ebbb449 100644 --- a/src/engine/platform/sound/c64/sid.cc +++ b/src/engine/platform/sound/c64/sid.cc @@ -18,6 +18,7 @@ // --------------------------------------------------------------------------- #include "sid.h" +#include #include // ---------------------------------------------------------------------------- @@ -39,6 +40,14 @@ SID::SID() bus_value_ttl = 0; ext_in = 0; + + isMuted[0]=false; + isMuted[1]=false; + isMuted[2]=false; + + last_chan_out[0]=0; + last_chan_out[1]=0; + last_chan_out[2]=0; } @@ -51,6 +60,14 @@ SID::~SID() delete[] fir; } +// ---------------------------------------------------------------------------- +// Mute/unmute channel. +// ---------------------------------------------------------------------------- +void SID::set_is_muted(int ch, bool val) { + if (ch<0 || ch>2) return; + isMuted[ch]=val; +} + // ---------------------------------------------------------------------------- // Set chip model. @@ -625,8 +642,13 @@ void SID::clock() voice[i].wave.synchronize(); } + // write voice output + last_chan_out[0]=isMuted[0]?0:voice[0].output(); + last_chan_out[1]=isMuted[1]?0:voice[1].output(); + last_chan_out[2]=isMuted[2]?0:voice[2].output(); + // Clock filter. - filter.clock(voice[0].output(), voice[1].output(), voice[2].output(), ext_in); + filter.clock(last_chan_out[0], last_chan_out[1], last_chan_out[2], ext_in); // Clock external filter. extfilt.clock(filter.output()); @@ -704,9 +726,14 @@ void SID::clock(cycle_count delta_t) delta_t_osc -= delta_t_min; } + // write voice output + last_chan_out[0]=isMuted[0]?0:voice[0].output(); + last_chan_out[1]=isMuted[1]?0:voice[1].output(); + last_chan_out[2]=isMuted[2]?0:voice[2].output(); + // Clock filter. filter.clock(delta_t, - voice[0].output(), voice[1].output(), voice[2].output(), ext_in); + last_chan_out[0], last_chan_out[1], last_chan_out[2], ext_in); // Clock external filter. extfilt.clock(delta_t, filter.output()); diff --git a/src/engine/platform/sound/c64/sid.h b/src/engine/platform/sound/c64/sid.h index 3292cd8fc..f6b392713 100644 --- a/src/engine/platform/sound/c64/sid.h +++ b/src/engine/platform/sound/c64/sid.h @@ -32,6 +32,9 @@ public: SID(); ~SID(); + sound_sample last_chan_out[3]; + + void set_is_muted(int ch, bool val); void set_chip_model(chip_model model); void enable_filter(bool enable); void enable_external_filter(bool enable); @@ -102,6 +105,8 @@ protected: Potentiometer potx; Potentiometer poty; + bool isMuted[3]; + reg8 bus_value; cycle_count bus_value_ttl; diff --git a/src/engine/platform/sound/lynx/Mikey.cpp b/src/engine/platform/sound/lynx/Mikey.cpp index 5d12bbb86..270e21bdf 100644 --- a/src/engine/platform/sound/lynx/Mikey.cpp +++ b/src/engine/platform/sound/lynx/Mikey.cpp @@ -458,23 +458,31 @@ public: return mAudioChannels[timer].fireAction( tick ); } - AudioSample sampleAudio() const + AudioSample sampleAudio( DivDispatchOscBuffer** oscb ) const { int left{}; int right{}; for ( size_t i = 0; i < 4; ++i ) { + int oscbWrite = 0; + if ( ( mStereo & ( (uint8_t)0x01 << i ) ) == 0 ) { const int attenuation = ( mPan & ( (uint8_t)0x01 << i ) ) != 0 ? mAttenuationLeft[i] : 0x3c; left += mAudioChannels[i].getOutput() * attenuation; + oscbWrite += mAudioChannels[i].getOutput() * attenuation; } if ( ( mStereo & ( (uint8_t)0x10 << i ) ) == 0 ) { const int attenuation = ( mPan & ( (uint8_t)0x01 << i ) ) != 0 ? mAttenuationRight[i] : 0x3c; right += mAudioChannels[i].getOutput() * attenuation; + oscbWrite += mAudioChannels[i].getOutput() * attenuation; + } + + if (oscb!=NULL) { + oscb[i]->data[oscb[i]->needle++]=oscbWrite; } } @@ -534,7 +542,7 @@ void Mikey::enqueueSampling() mQueue->push( ( mNextTick & ~15 ) | 4 ); } -void Mikey::sampleAudio( int16_t* bufL, int16_t* bufR, size_t size ) +void Mikey::sampleAudio( int16_t* bufL, int16_t* bufR, size_t size, DivDispatchOscBuffer** oscb ) { size_t i = 0; while ( i < size ) @@ -549,7 +557,7 @@ void Mikey::sampleAudio( int16_t* bufL, int16_t* bufR, size_t size ) } else { - auto sample = mMikey->sampleAudio(); + auto sample = mMikey->sampleAudio( oscb ); bufL[i] = sample.left; bufR[i] = sample.right; i += 1; diff --git a/src/engine/platform/sound/lynx/Mikey.hpp b/src/engine/platform/sound/lynx/Mikey.hpp index 20b8b0868..9d0f6884e 100644 --- a/src/engine/platform/sound/lynx/Mikey.hpp +++ b/src/engine/platform/sound/lynx/Mikey.hpp @@ -3,6 +3,9 @@ #include #include +// can you forgive me +#include "../../../dispatch.h" + namespace Lynx { @@ -18,7 +21,7 @@ public: ~Mikey(); void write( uint8_t address, uint8_t value ); - void sampleAudio( int16_t* bufL, int16_t* bufR, size_t size ); + void sampleAudio( int16_t* bufL, int16_t* bufR, size_t size, DivDispatchOscBuffer** oscb = NULL ); uint8_t const* getRegisterPool(); diff --git a/src/engine/platform/sound/n163/n163.cpp b/src/engine/platform/sound/n163/n163.cpp index b18f146ba..20853963e 100644 --- a/src/engine/platform/sound/n163/n163.cpp +++ b/src/engine/platform/sound/n163/n163.cpp @@ -98,6 +98,8 @@ void n163_core::tick() m_ram[m_voice_cycle + 3] = bitfield(accum, 8, 8); m_ram[m_voice_cycle + 5] = bitfield(accum, 16, 8); + const u8 prev_voice_cycle = m_voice_cycle; + // update voice cycle bool flush = m_multiplex ? true : false; m_voice_cycle -= 0x8; @@ -109,7 +111,9 @@ void n163_core::tick() } // output 4 bit waveform and volume, multiplexed - m_acc += wave * volume; + const u8 chan_index = ((0x78-prev_voice_cycle)>>3)&7; + m_ch_out[chan_index]=wave * volume; + m_acc += m_ch_out[chan_index]; if (flush) { m_out = m_acc / (m_multiplex ? 1 : (bitfield(m_ram[0x7f], 4, 3) + 1)); @@ -127,6 +131,7 @@ void n163_core::reset() m_addr_latch.reset(); m_out = 0; m_acc = 0; + std::fill(std::begin(m_ch_out), std::end(m_ch_out), 0); } // accessor diff --git a/src/engine/platform/sound/n163/n163.hpp b/src/engine/platform/sound/n163/n163.hpp index a31827572..aa059206b 100644 --- a/src/engine/platform/sound/n163/n163.hpp +++ b/src/engine/platform/sound/n163/n163.hpp @@ -46,6 +46,12 @@ public: // sound output pin s16 out() { return m_out; } + // get channel output + s16 chan_out(u8 ch) { return m_ch_out[ch]; } + + // get voice cycle + u8 voice_cycle() { return m_voice_cycle; } + // register pool u8 reg(u8 addr) { return m_ram[addr & 0x7f]; } void set_multiplex(bool multiplex = true) { m_multiplex = multiplex; } @@ -74,6 +80,7 @@ private: u8 m_voice_cycle = 0x78; // Voice cycle for processing addr_latch_t m_addr_latch; // address latch s16 m_out = 0; // output + s16 m_ch_out[8] = {0}; // per channel output // demultiplex related bool m_multiplex = true; // multiplex flag, but less noisy = inaccurate! s16 m_acc = 0; // accumulated output diff --git a/src/engine/platform/sound/nes/apu.h b/src/engine/platform/sound/nes/apu.h index 224ea2a04..d0f9c31f9 100644 --- a/src/engine/platform/sound/nes/apu.h +++ b/src/engine/platform/sound/nes/apu.h @@ -207,7 +207,7 @@ enum apu_mode { APU_60HZ, APU_48HZ }; break;\ }\ {\ - a->DMC.buffer = 0;\ + a->DMC.buffer = a->readDMC(a->readDMCUser,a->DMC.address);\ }\ /* incremento gli hwtick da compiere */\ if (hwtick) { hwtick[0] += tick; }\ @@ -525,6 +525,8 @@ EXTERNC struct NESAPU { _apuTriangle TR; _apuNoise NS; _apuDMC DMC; + void* readDMCUser; + unsigned char (*readDMC)(void*,unsigned short); unsigned char muted[5]; }; diff --git a/src/engine/platform/sound/nes_nsfplay/common.h b/src/engine/platform/sound/nes_nsfplay/common.h new file mode 100644 index 000000000..894a69737 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/common.h @@ -0,0 +1,4 @@ +namespace xgm { + const unsigned int DEFAULT_CLOCK=1789773; + const unsigned int DEFAULT_RATE=1789773; +}; diff --git a/src/engine/platform/sound/nes_nsfplay/nes_apu.cpp b/src/engine/platform/sound/nes_nsfplay/nes_apu.cpp new file mode 100644 index 000000000..f215a9aa6 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_apu.cpp @@ -0,0 +1,380 @@ +// +// NES 2A03 +// +#include +#include "nes_apu.h" +#include "common.h" + +namespace xgm +{ + void NES_APU::sweep_sqr (int i) + { + int shifted = freq[i] >> sweep_amount[i]; + if (i == 0 && sweep_mode[i]) shifted += 1; + sfreq[i] = freq[i] + (sweep_mode[i] ? -shifted : shifted); + //DEBUG_OUT("shifted[%d] = %d (%d >> %d)¥n",i,shifted,freq[i],sweep_amount[i]); + } + + void NES_APU::FrameSequence(int s) + { + //DEBUG_OUT("FrameSequence(%d)¥n",s); + + if (s > 3) return; // no operation in step 4 + + // 240hz clock + for (int i=0; i < 2; ++i) + { + bool divider = false; + if (envelope_write[i]) + { + envelope_write[i] = false; + envelope_counter[i] = 15; + envelope_div[i] = 0; + } + else + { + ++envelope_div[i]; + if (envelope_div[i] > envelope_div_period[i]) + { + divider = true; + envelope_div[i] = 0; + } + } + if (divider) + { + if (envelope_loop[i] && envelope_counter[i] == 0) + envelope_counter[i] = 15; + else if (envelope_counter[i] > 0) + --envelope_counter[i]; + } + } + + // 120hz clock + if ((s&1) == 0) + for (int i=0; i < 2; ++i) + { + if (!envelope_loop[i] && (length_counter[i] > 0)) + --length_counter[i]; + + if (sweep_enable[i]) + { + //DEBUG_OUT("Clock sweep: %d¥n", i); + + --sweep_div[i]; + if (sweep_div[i] <= 0) + { + sweep_sqr(i); // calculate new sweep target + + //DEBUG_OUT("sweep_div[%d] (0/%d)¥n",i,sweep_div_period[i]); + //DEBUG_OUT("freq[%d]=%d > sfreq[%d]=%d¥n",i,freq[i],i,sfreq[i]); + + if (freq[i] >= 8 && sfreq[i] < 0x800 && sweep_amount[i] > 0) // update frequency if appropriate + { + freq[i] = sfreq[i] < 0 ? 0 : sfreq[i]; + } + sweep_div[i] = sweep_div_period[i] + 1; + + //DEBUG_OUT("freq[%d]=%d¥n",i,freq[i]); + } + + if (sweep_write[i]) + { + sweep_div[i] = sweep_div_period[i] + 1; + sweep_write[i] = false; + } + } + } + + } + + int NES_APU::calc_sqr (int i, unsigned int clocks) + { + static const short sqrtbl[4][16] = { + {0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, + {1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + }; + + scounter[i] -= clocks; + while (scounter[i] < 0) + { + sphase[i] = (sphase[i] + 1) & 15; + scounter[i] += freq[i] + 1; + } + + int ret = 0; + if (length_counter[i] > 0 && + freq[i] >= 8 && + sfreq[i] < 0x800 + ) + { + int v = envelope_disable[i] ? volume[i] : envelope_counter[i]; + ret = sqrtbl[duty[i]][sphase[i]] ? v : 0; + } + + return ret; + } + + bool NES_APU::Read (unsigned int adr, unsigned int & val, unsigned int id) + { + if (0x4000 <= adr && adr < 0x4008) + { + val |= reg[adr&0x7]; + return true; + } + else if(adr==0x4015) + { + val |= (length_counter[1]?2:0)|(length_counter[0]?1:0); + return true; + } + else + return false; + } + + void NES_APU::Tick (unsigned int clocks) + { + out[0] = calc_sqr(0, clocks); + out[1] = calc_sqr(1, clocks); + } + + // ツ青カツ青ャツつウツづェツづゥツ波ツ形ツづ個振ツ閉敖づ0-8191 + unsigned int NES_APU::Render (int b[2]) + { + out[0] = (mask & 1) ? 0 : out[0]; + out[1] = (mask & 2) ? 0 : out[1]; + + int m[2]; + + if(option[OPT_NONLINEAR_MIXER]) + { + int voltage = square_table[out[0] + out[1]]; + m[0] = out[0] << 6; + m[1] = out[1] << 6; + int ref = m[0] + m[1]; + if (ref > 0) + { + m[0] = (m[0] * voltage) / ref; + m[1] = (m[1] * voltage) / ref; + } + else + { + m[0] = voltage; + m[1] = voltage; + } + } + else + { + m[0] = (out[0] * square_linear) / 15; + m[1] = (out[1] * square_linear) / 15; + } + + b[0] = m[0] * sm[0][0]; + b[0] += m[1] * sm[0][1]; + b[0] >>= 7; + + b[1] = m[0] * sm[1][0]; + b[1] += m[1] * sm[1][1]; + b[1] >>= 7; + + return 2; + } + + NES_APU::NES_APU () + { + SetClock (DEFAULT_CLOCK); + SetRate (DEFAULT_RATE); + option[OPT_UNMUTE_ON_RESET] = true; + option[OPT_PHASE_REFRESH] = true; + option[OPT_NONLINEAR_MIXER] = true; + option[OPT_DUTY_SWAP] = false; + option[OPT_NEGATE_SWEEP_INIT] = false; + + square_table[0] = 0; + for(int i=1;i<32;i++) + square_table[i]=(int)((8192.0*95.88)/(8128.0/i+100)); + + square_linear = square_table[15]; // match linear scale to one full volume square of nonlinear + + for(int c=0;c<2;++c) + for(int t=0;t<2;++t) + sm[c][t] = 128; + } + + NES_APU::~NES_APU () + { + } + + void NES_APU::Reset () + { + int i; + gclock = 0; + mask = 0; + + for (int i=0; i<2; ++i) + { + scounter[i] = 0; + sphase[i] = 0; + duty[i] = 0; + volume[i] = 0; + freq[i] = 0; + sfreq[i] = 0; + sweep_enable[i] = 0; + sweep_mode[i] = 0; + sweep_write[i] = 0; + sweep_div_period[i] = 0; + sweep_div[i] = 1; + sweep_amount[i] = 0; + envelope_disable[i] = 0; + envelope_loop[i] = 0; + envelope_write[i] = 0; + envelope_div_period[i] = 0; + envelope_div[0] = 0; + envelope_counter[i] = 0; + length_counter[i] = 0; + enable[i] = 0; + } + + for (i = 0x4000; i < 0x4008; i++) + Write (i, 0); + + Write (0x4015, 0); + if (option[OPT_UNMUTE_ON_RESET]) + Write (0x4015, 0x0f); + if (option[OPT_NEGATE_SWEEP_INIT]) + { + Write (0x4001, 0x08); + Write (0x4005, 0x08); + } + + for (i = 0; i < 2; i++) + out[i] = 0; + + SetRate(rate); + } + + void NES_APU::SetOption (int id, int val) + { + if(id 1) return; + sm[0][trk] = mixl; + sm[1][trk] = mixr; + } + + bool NES_APU::Write (unsigned int adr, unsigned int val, unsigned int id) + { + int ch; + + static const unsigned char length_table[32] = { + 0x0A, 0xFE, + 0x14, 0x02, + 0x28, 0x04, + 0x50, 0x06, + 0xA0, 0x08, + 0x3C, 0x0A, + 0x0E, 0x0C, + 0x1A, 0x0E, + 0x0C, 0x10, + 0x18, 0x12, + 0x30, 0x14, + 0x60, 0x16, + 0xC0, 0x18, + 0x48, 0x1A, + 0x10, 0x1C, + 0x20, 0x1E + }; + + if (0x4000 <= adr && adr < 0x4008) + { + //DEBUG_OUT("$%04X = %02X¥n",adr,val); + + adr &= 0xf; + ch = adr >> 2; + switch (adr) + { + case 0x0: + case 0x4: + volume[ch] = val & 15; + envelope_disable[ch] = (val >> 4) & 1; + envelope_loop[ch] = (val >> 5) & 1; + envelope_div_period[ch] = (val & 15); + duty[ch] = (val >> 6) & 3; + if (option[OPT_DUTY_SWAP]) + { + if (duty[ch] == 1) duty[ch] = 2; + else if (duty[ch] == 2) duty[ch] = 1; + } + break; + + case 0x1: + case 0x5: + sweep_enable[ch] = (val >> 7) & 1; + sweep_div_period[ch] = (((val >> 4) & 7)); + sweep_mode[ch] = (val >> 3) & 1; + sweep_amount[ch] = val & 7; + sweep_write[ch] = true; + sweep_sqr(ch); + break; + + case 0x2: + case 0x6: + freq[ch] = val | (freq[ch] & 0x700) ; + sweep_sqr(ch); + break; + + case 0x3: + case 0x7: + freq[ch] = (freq[ch] & 0xFF) | ((val & 0x7) << 8) ; + if (option[OPT_PHASE_REFRESH]) + sphase[ch] = 0; + envelope_write[ch] = true; + if (enable[ch]) + { + length_counter[ch] = length_table[(val >> 3) & 0x1f]; + } + sweep_sqr(ch); + break; + + default: + return false; + } + reg[adr] = val; + return true; + } + else if (adr == 0x4015) + { + enable[0] = (val & 1) ? true : false; + enable[1] = (val & 2) ? true : false; + + if (!enable[0]) + length_counter[0] = 0; + if (!enable[1]) + length_counter[1] = 0; + + reg[adr-0x4000] = val; + return true; + } + + // 4017 is handled in nes_dmc.cpp + //else if (adr == 0x4017) + //{ + //} + + return false; + } +} // namespace xgm; diff --git a/src/engine/platform/sound/nes_nsfplay/nes_apu.h b/src/engine/platform/sound/nes_nsfplay/nes_apu.h new file mode 100644 index 000000000..20c4d3663 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_apu.h @@ -0,0 +1,85 @@ +#ifndef _NES_APU_H_ +#define _NES_APU_H_ +#include "nes_dmc.h" + +namespace xgm +{ + /** Upper half of APU **/ + class NES_APU + { + public: + enum + { + OPT_UNMUTE_ON_RESET=0, + OPT_PHASE_REFRESH, + OPT_NONLINEAR_MIXER, + OPT_DUTY_SWAP, + OPT_NEGATE_SWEEP_INIT, + OPT_END }; + + enum + { SQR0_MASK = 1, SQR1_MASK = 2, }; + + protected: + int option[OPT_END]; // 各種オプション + int mask; + int sm[2][2]; + + unsigned int gclock; + unsigned char reg[0x20]; + double rate, clock; + + int square_table[32]; // nonlinear mixer + int square_linear; // linear mix approximation + + int scounter[2]; // frequency divider + int sphase[2]; // phase counter + + int duty[2]; + int volume[2]; + int freq[2]; + int sfreq[2]; + + bool sweep_enable[2]; + bool sweep_mode[2]; + bool sweep_write[2]; + int sweep_div_period[2]; + int sweep_div[2]; + int sweep_amount[2]; + + bool envelope_disable[2]; + bool envelope_loop[2]; + bool envelope_write[2]; + int envelope_div_period[2]; + int envelope_div[2]; + int envelope_counter[2]; + + int length_counter[2]; + + bool enable[2]; + + void sweep_sqr (int ch); // calculates target sweep frequency + int calc_sqr (int ch, unsigned int clocks); + + public: + int out[2]; + NES_APU (); + ~NES_APU (); + + void FrameSequence(int s); + + void Reset (); + void Tick (unsigned int clocks); + unsigned int Render (int b[2]); + bool Read (unsigned int adr, unsigned int & val, unsigned int id=0); + bool Write (unsigned int adr, unsigned int val, unsigned int id=0); + void SetRate (double rate); + void SetClock (double clock); + void SetOption (int id, int b); + void SetMask(int m){ mask = m; } + void SetStereoMix (int trk, short mixl, short mixr); + }; + +} // namespace + +#endif diff --git a/src/engine/platform/sound/nes_nsfplay/nes_dmc.cpp b/src/engine/platform/sound/nes_nsfplay/nes_dmc.cpp new file mode 100644 index 000000000..a37b544d2 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_dmc.cpp @@ -0,0 +1,717 @@ +#include "nes_dmc.h" +#include "nes_apu.h" +#include "common.h" +#include +#include + +namespace xgm +{ + const unsigned int NES_DMC::wavlen_table[2][16] = { + { // NTSC + 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068 + }, + { // PAL + 4, 8, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778 + }}; + + const unsigned int NES_DMC::freq_table[2][16] = { + { // NTSC + 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54 + }, + { // PAL + 398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50 + }}; + + const unsigned int BITREVERSE[256] = { + 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, + 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, + 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, + 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, + 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, + 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, + 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, + 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, + 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, + 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, + 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, + 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, + 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, + 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, + 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, + 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF, + }; + + NES_DMC::NES_DMC () : GETA_BITS (20) + { + SetClock (DEFAULT_CLOCK); + SetRate (DEFAULT_RATE); + SetPal (false); + option[OPT_ENABLE_4011] = 1; + option[OPT_ENABLE_PNOISE] = 1; + option[OPT_UNMUTE_ON_RESET] = 1; + option[OPT_DPCM_ANTI_CLICK] = 0; + option[OPT_NONLINEAR_MIXER] = 1; + option[OPT_RANDOMIZE_NOISE] = 1; + option[OPT_RANDOMIZE_TRI] = 1; + option[OPT_TRI_MUTE] = 1; + option[OPT_DPCM_REVERSE] = 0; + tnd_table[0][0][0][0] = 0; + tnd_table[1][0][0][0] = 0; + + apu = NULL; + frame_sequence_count = 0; + frame_sequence_length = 7458; + frame_sequence_steps = 4; + + for(int c=0;c<2;++c) + for(int t=0;t<3;++t) + sm[c][t] = 128; + } + + + NES_DMC::~NES_DMC () + { + } + + void NES_DMC::SetStereoMix(int trk, short mixl, short mixr) + { + if (trk < 0) return; + if (trk > 2) return; + sm[0][trk] = mixl; + sm[1][trk] = mixr; + } + + void NES_DMC::FrameSequence(int s) + { + //DEBUG_OUT("FrameSequence: %d¥n",s); + + if (s > 3) return; // no operation in step 4 + + if (apu) + { + apu->FrameSequence(s); + } + + if (s == 0 && (frame_sequence_steps == 4)) + { + if (frame_irq_enable) frame_irq = true; + } + + // 240hz clock + { + // triangle linear counter + if (linear_counter_halt) + { + linear_counter = linear_counter_reload; + } + else + { + if (linear_counter > 0) --linear_counter; + } + if (!linear_counter_control) + { + linear_counter_halt = false; + } + + // noise envelope + bool divider = false; + if (envelope_write) + { + envelope_write = false; + envelope_counter = 15; + envelope_div = 0; + } + else + { + ++envelope_div; + if (envelope_div > envelope_div_period) + { + divider = true; + envelope_div = 0; + } + } + if (divider) + { + if (envelope_loop && envelope_counter == 0) + envelope_counter = 15; + else if (envelope_counter > 0) + --envelope_counter; + } + } + + // 120hz clock + if ((s&1) == 0) + { + // triangle length counter + if (!linear_counter_control && (length_counter[0] > 0)) + --length_counter[0]; + + // noise length counter + if (!envelope_loop && (length_counter[1] > 0)) + --length_counter[1]; + } + + } + + // 三角波チャンネルの計算 戻り値は0-15 + unsigned int NES_DMC::calc_tri (unsigned int clocks) + { + static unsigned int tritbl[32] = + { + 15,14,13,12,11,10, 9, 8, + 7, 6, 5, 4, 3, 2, 1, 0, + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9,10,11,12,13,14,15, + }; + + if (linear_counter > 0 && length_counter[0] > 0 + && (!option[OPT_TRI_MUTE] || tri_freq > 0)) + { + counter[0] -= clocks; + while (counter[0] < 0) + { + tphase = (tphase + 1) & 31; + counter[0] += (tri_freq + 1); + } + } + + unsigned int ret = tritbl[tphase]; + return ret; + } + + // ノイズチャンネルの計算 戻り値は0-127 + // 低サンプリングレートで合成するとエイリアスノイズが激しいので + // ノイズだけはこの関数内で高クロック合成し、簡易なサンプリングレート + // 変換を行っている。 + unsigned int NES_DMC::calc_noise(unsigned int clocks) + { + unsigned int env = envelope_disable ? noise_volume : envelope_counter; + if (length_counter[1] < 1) env = 0; + + unsigned int last = (noise & 0x4000) ? 0 : env; + if (clocks < 1) return last; + + // simple anti-aliasing (noise requires it, even when oversampling is off) + unsigned int count = 0; + unsigned int accum = counter[1] * last; // samples pending from previous calc + unsigned int accum_clocks = counter[1]; + #ifdef _DEBUG + int start_clocks = counter[1]; + #endif + if (counter[1] < 0) // only happens on startup when using the randomize noise option + { + accum = 0; + accum_clocks = 0; + } + + counter[1] -= clocks; + assert (nfreq > 0); // prevent infinite loop + while (counter[1] < 0) + { + // tick the noise generator + unsigned int feedback = (noise&1) ^ ((noise&noise_tap)?1:0); + noise = (noise>>1) | (feedback<<14); + + last = (noise & 0x4000) ? 0 : env; + accum += (last * nfreq); + counter[1] += nfreq; + ++count; + accum_clocks += nfreq; + } + + if (count < 1) // no change over interval, don't anti-alias + { + return last; + } + + accum -= (last * counter[1]); // remove these samples which belong in the next calc + accum_clocks -= counter[1]; + #ifdef _DEBUG + if (start_clocks >= 0) assert(accum_clocks == clocks); // these should be equal + #endif + + unsigned int average = accum / accum_clocks; + assert(average <= 15); // above this would indicate overflow + return average; + } + + // Tick the DMC for the number of clocks, and return output counter; + unsigned int NES_DMC::calc_dmc (unsigned int clocks) + { + counter[2] -= clocks; + assert (dfreq > 0); // prevent infinite loop + while (counter[2] < 0) + { + counter[2] += dfreq; + + if ( data > 0x100 ) // data = 0x100 when shift register is empty + { + if (!empty) + { + if ((data & 1) && (damp < 63)) + damp++; + else if (!(data & 1) && (0 < damp)) + damp--; + } + data >>=1; + } + + if ( data <= 0x100 ) // shift register is empty + { + if (dlength > 0) + { + memory (daddress, data); + // (checking for the 3-cycle case would require sub-instruction emulation) + data &= 0xFF; // read 8 bits + if (option[OPT_DPCM_REVERSE]) data = BITREVERSE[data]; + data |= 0x10000; // use an extra bit to signal end of data + empty = false; + daddress = ((daddress+1)&0xFFFF)|0x8000 ; + --dlength; + if (dlength == 0) + { + if (mode & 1) // looped DPCM = auto-reload + { + daddress = ((adr_reg<<6)|0xC000); + dlength = (len_reg<<4)+1; + } + else if (mode & 2) // IRQ and not looped + { + irq = true; + } + } + } + else + { + data = 0x10000; // DMC will do nothing + empty = true; + } + } + } + + return (damp<<1) + dac_lsb; + } + + void NES_DMC::TickFrameSequence (unsigned int clocks) + { + frame_sequence_count += clocks; + while (frame_sequence_count > frame_sequence_length) + { + FrameSequence(frame_sequence_step); + frame_sequence_count -= frame_sequence_length; + ++frame_sequence_step; + if(frame_sequence_step >= frame_sequence_steps) + frame_sequence_step = 0; + } + } + + void NES_DMC::Tick (unsigned int clocks) + { + out[0] = calc_tri(clocks); + out[1] = calc_noise(clocks); + out[2] = calc_dmc(clocks); + } + + unsigned int NES_DMC::Render (int b[2]) + { + out[0] = (mask & 1) ? 0 : out[0]; + out[1] = (mask & 2) ? 0 : out[1]; + out[2] = (mask & 4) ? 0 : out[2]; + + int m[3]; + m[0] = tnd_table[0][out[0]][0][0]; + m[1] = tnd_table[0][0][out[1]][0]; + m[2] = tnd_table[0][0][0][out[2]]; + + if (option[OPT_NONLINEAR_MIXER]) + { + int ref = m[0] + m[1] + m[2]; + int voltage = tnd_table[1][out[0]][out[1]][out[2]]; + if (ref) + { + for (int i=0; i < 3; ++i) + m[i] = (m[i] * voltage) / ref; + } + else + { + for (int i=0; i < 3; ++i) + m[i] = voltage; + } + } + + // anti-click nullifies any 4011 write but preserves nonlinearity + if (option[OPT_DPCM_ANTI_CLICK]) + { + if (dmc_pop) // $4011 will cause pop this frame + { + // adjust offset to counteract pop + dmc_pop_offset += dmc_pop_follow - m[2]; + dmc_pop = false; + + // prevent overflow, keep headspace at edges + const int OFFSET_MAX = (1 << 30) - (4 << 16); + if (dmc_pop_offset > OFFSET_MAX) dmc_pop_offset = OFFSET_MAX; + if (dmc_pop_offset < -OFFSET_MAX) dmc_pop_offset = -OFFSET_MAX; + } + dmc_pop_follow = m[2]; // remember previous position + + m[2] += dmc_pop_offset; // apply offset + + // TODO implement this in a better way + // roll off offset (not ideal, but prevents overflow) + if (dmc_pop_offset > 0) --dmc_pop_offset; + else if (dmc_pop_offset < 0) ++dmc_pop_offset; + } + + b[0] = m[0] * sm[0][0]; + b[0] += m[1] * sm[0][1]; + b[0] += m[2] * sm[0][2]; + b[0] >>= 7; + + b[1] = m[0] * sm[1][0]; + b[1] += m[1] * sm[1][1]; + b[1] += m[2] * sm[1][2]; + b[1] >>= 7; + + return 2; + } + + void NES_DMC::SetClock (double c) + { + clock = c; + } + + void NES_DMC::SetRate (double r) + { + rate = (unsigned int)(r?r:DEFAULT_RATE); + } + + void NES_DMC::SetPal (bool is_pal) + { + pal = (is_pal ? 1 : 0); + // set CPU cycles in frame_sequence + frame_sequence_length = is_pal ? 8314 : 7458; + } + + void NES_DMC::SetAPU (NES_APU* apu_) + { + apu = apu_; + } + + // Initializing TRI, NOISE, DPCM mixing table + void NES_DMC::InitializeTNDTable(double wt, double wn, double wd) { + + // volume adjusted by 0.95 based on empirical measurements + const double MASTER = 8192.0 * 0.95; + // truthfully, the nonlinear curve does not appear to match well + // with my tests. Do more testing of the APU/DMC DAC later. + // this value keeps the triangle consistent with measured levels, + // but not necessarily the rest of this APU channel, + // because of the lack of a good DAC model, currently. + + { // Linear Mixer + for(int t=0; t<16 ; t++) { + for(int n=0; n<16; n++) { + for(int d=0; d<128; d++) { + tnd_table[0][t][n][d] = (unsigned int)(MASTER*(3.0*t+2.0*n+d)/208.0); + } + } + } + } + { // Non-Linear Mixer + tnd_table[1][0][0][0] = 0; + for(int t=0; t<16 ; t++) { + for(int n=0; n<16; n++) { + for(int d=0; d<128; d++) { + if(t!=0||n!=0||d!=0) + tnd_table[1][t][n][d] = (unsigned int)((MASTER*159.79)/(100.0+1.0/((double)t/wt+(double)n/wn+(double)d/wd))); + } + } + } + } + + } + + void NES_DMC::Reset () + { + int i; + mask = 0; + + InitializeTNDTable(8227,12241,22638); + + counter[0] = 0; + counter[1] = 0; + counter[2] = 0; + tphase = 0; + nfreq = wavlen_table[0][0]; + dfreq = freq_table[0][0]; + tri_freq = 0; + linear_counter = 0; + linear_counter_reload = 0; + linear_counter_halt = 0; + linear_counter_control = 0; + noise_volume = 0; + noise = 0; + noise_tap = 0; + envelope_loop = 0; + envelope_disable = 0; + envelope_write = 0; + envelope_div_period = 0; + envelope_div = 0; + envelope_counter = 0; + enable[0] = 0; + enable[1] = 0; + length_counter[0] = 0; + length_counter[1] = 0; + frame_irq = false; + frame_irq_enable = false; + frame_sequence_count = 0; + frame_sequence_steps = 4; + frame_sequence_step = 0; + + for (i = 0; i < 0x0F; i++) + Write (0x4008 + i, 0); + Write (0x4017, 0x40); + + irq = false; + Write (0x4015, 0x00); + if (option[OPT_UNMUTE_ON_RESET]) + Write (0x4015, 0x0f); + + out[0] = out[1] = out[2] = 0; + damp = 0; + dmc_pop = false; + dmc_pop_offset = 0; + dmc_pop_follow = 0; + dac_lsb = 0; + data = 0x100; + empty = true; + adr_reg = 0; + dlength = 0; + len_reg = 0; + daddress = 0; + noise = 1; + noise_tap = (1<<1); + + if (option[OPT_RANDOMIZE_NOISE]) + { + noise |= ::rand(); + counter[1] = -(rand() & 511); + } + if (option[OPT_RANDOMIZE_TRI]) + { + tphase = ::rand() & 31; + counter[0] = -(rand() & 2047); + } + + SetRate(rate); + } + + void NES_DMC::SetMemory (std::function r) + { + memory = r; + } + + void NES_DMC::SetOption (int id, int val) + { + if(id> 7) & 1; + linear_counter_reload = val & 0x7F; + break; + + case 0x4009: + break; + + case 0x400a: + tri_freq = val | (tri_freq & 0x700) ; + break; + + case 0x400b: + tri_freq = (tri_freq & 0xff) | ((val & 0x7) << 8) ; + linear_counter_halt = true; + if (enable[0]) + { + length_counter[0] = length_table[(val >> 3) & 0x1f]; + } + break; + + // noise + + case 0x400c: + noise_volume = val & 15; + envelope_div_period = val & 15; + envelope_disable = (val >> 4) & 1; + envelope_loop = (val >> 5) & 1; + break; + + case 0x400d: + break; + + case 0x400e: + if (option[OPT_ENABLE_PNOISE]) + noise_tap = (val & 0x80) ? (1<<6) : (1<<1); + else + noise_tap = (1<<1); + nfreq = wavlen_table[pal][val&15]; + break; + + case 0x400f: + if (enable[1]) + { + length_counter[1] = length_table[(val >> 3) & 0x1f]; + } + envelope_write = true; + break; + + // dmc + + case 0x4010: + mode = (val >> 6) & 3; + if (!(mode & 2)) + { + irq = false; + } + dfreq = freq_table[pal][val&15]; + break; + + case 0x4011: + if (option[OPT_ENABLE_4011]) + { + damp = (val >> 1) & 0x3f; + dac_lsb = val & 1; + dmc_pop = true; + } + break; + + case 0x4012: + adr_reg = val&0xff; + // ここでdaddressは更新されない + break; + + case 0x4013: + len_reg = val&0xff; + // ここでlengthは更新されない + break; + + default: + return false; + } + + return true; + } + + bool NES_DMC::Read (unsigned int adr, unsigned int & val, unsigned int id) + { + if (adr == 0x4015) + { + val |=(irq ? 0x80 : 0) + | (frame_irq ? 0x40 : 0) + | ((dlength>0) ? 0x10 : 0) + | (length_counter[1] ? 0x08 : 0) + | (length_counter[0] ? 0x04 : 0) + ; + + frame_irq = false; + return true; + } + else if (0x4008<=adr&&adr<=0x4014) + { + val |= reg[adr-0x4008]; + return true; + } + else + return false; + } +} // namespace diff --git a/src/engine/platform/sound/nes_nsfplay/nes_dmc.h b/src/engine/platform/sound/nes_nsfplay/nes_dmc.h new file mode 100644 index 000000000..f78dd9f4a --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_dmc.h @@ -0,0 +1,119 @@ +#ifndef _NES_DMC_H_ +#define _NES_DMC_H_ +#include + +namespace xgm +{ + class NES_APU; // forward declaration + + /** Bottom Half of APU **/ + class NES_DMC + { + public: + enum + { + OPT_ENABLE_4011=0, + OPT_ENABLE_PNOISE, + OPT_UNMUTE_ON_RESET, + OPT_DPCM_ANTI_CLICK, + OPT_NONLINEAR_MIXER, + OPT_RANDOMIZE_NOISE, + OPT_TRI_MUTE, + OPT_RANDOMIZE_TRI, + OPT_DPCM_REVERSE, + OPT_END + }; + protected: + const int GETA_BITS; + static const unsigned int freq_table[2][16]; + static const unsigned int wavlen_table[2][16]; + unsigned int tnd_table[2][16][16][128]; + + int option[OPT_END]; + int mask; + int sm[2][3]; + unsigned int reg[0x10]; + unsigned int len_reg; + unsigned int adr_reg; + std::function memory; + unsigned int daddress; + unsigned int dlength; + unsigned int data; + bool empty; + short damp; + int dac_lsb; + bool dmc_pop; + int dmc_pop_offset; + int dmc_pop_follow; + double clock; + unsigned int rate; + int pal; + int mode; + bool irq; + + int counter[3]; // frequency dividers + int tphase; // triangle phase + unsigned int nfreq; // noise frequency + unsigned int dfreq; // DPCM frequency + + unsigned int tri_freq; + int linear_counter; + int linear_counter_reload; + bool linear_counter_halt; + bool linear_counter_control; + + int noise_volume; + unsigned int noise, noise_tap; + + // noise envelope + bool envelope_loop; + bool envelope_disable; + bool envelope_write; + int envelope_div_period; + int envelope_div; + int envelope_counter; + + bool enable[2]; // tri/noise enable + int length_counter[2]; // 0=tri, 1=noise + + // frame sequencer + NES_APU* apu; // apu is clocked by DMC's frame sequencer + int frame_sequence_count; // current cycle count + int frame_sequence_length; // CPU cycles per FrameSequence + int frame_sequence_step; // current step of frame sequence + int frame_sequence_steps; // 4/5 steps per frame + bool frame_irq; + bool frame_irq_enable; + + inline unsigned int calc_tri (unsigned int clocks); + inline unsigned int calc_dmc (unsigned int clocks); + inline unsigned int calc_noise (unsigned int clocks); + + public: + unsigned int out[3]; + NES_DMC (); + ~NES_DMC (); + + void InitializeTNDTable(double wt, double wn, double wd); + void SetPal (bool is_pal); + void SetAPU (NES_APU* apu_); + void SetMemory (std::function r); + void FrameSequence(int s); + int GetDamp(){ return (damp<<1)|dac_lsb ; } + void TickFrameSequence (unsigned int clocks); + + void Reset (); + void Tick (unsigned int clocks); + unsigned int Render (int b[2]); + bool Write (unsigned int adr, unsigned int val, unsigned int id=0); + bool Read (unsigned int adr, unsigned int & val, unsigned int id=0); + void SetRate (double rate); + void SetClock (double rate); + void SetOption (int, int); + void SetMask(int m){ mask = m; } + void SetStereoMix (int trk, short mixl, short mixr); + }; + +} + +#endif diff --git a/src/engine/platform/sound/nes_nsfplay/nes_fds.cpp b/src/engine/platform/sound/nes_nsfplay/nes_fds.cpp new file mode 100644 index 000000000..7599fa857 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_fds.cpp @@ -0,0 +1,387 @@ +#include +#include +#include "nes_fds.h" +#include "common.h" + +namespace xgm { + +const int RC_BITS = 12; + +NES_FDS::NES_FDS () +{ + option[OPT_CUTOFF] = 2000; + option[OPT_4085_RESET] = 0; + option[OPT_WRITE_PROTECT] = 0; // not used here, see nsfplay.cpp + + rc_k = 0; + rc_l = (1< 1) return; + sm[0] = mixl; + sm[1] = mixr; +} + +void NES_FDS::SetClock (double c) +{ + clock = c; +} + +void NES_FDS::SetRate (double r) +{ + rate = r; + + // configure lowpass filter + double cutoff = double(option[OPT_CUTOFF]); + double leak = 0.0; + if (cutoff > 0) + leak = exp(-2.0 * 3.14159 * cutoff / rate); + rc_k = int(leak * double(1<= period) + { + // clock the envelope + if (env_mode[i]) + { + if (env_out[i] < 32) ++env_out[i]; + } + else + { + if (env_out[i] > 0 ) --env_out[i]; + } + env_timer[i] -= period; + } + } + } + } + + // clock the mod table + if (!mod_halt) + { + // advance phase, adjust for modulator + unsigned int start_pos = phase[TMOD] >> 16; + phase[TMOD] += (clocks * freq[TMOD]); + unsigned int end_pos = phase[TMOD] >> 16; + + // wrap the phase to the 64-step table (+ 16 bit accumulator) + phase[TMOD] = phase[TMOD] & 0x3FFFFF; + + // execute all clocked steps + for (unsigned int p = start_pos; p < end_pos; ++p) + { + int wv = wave[TMOD][p & 0x3F]; + if (wv == 4) // 4 resets mod position + mod_pos = 0; + else + { + const int BIAS[8] = { 0, 1, 2, 4, 0, -4, -2, -1 }; + mod_pos += BIAS[wv]; + mod_pos &= 0x7F; // 7-bit clamp + } + } + } + + // clock the wav table + if (!wav_halt) + { + // complex mod calculation + int mod = 0; + if (env_out[EMOD] != 0) // skip if modulator off + { + // convert mod_pos to 7-bit signed + int pos = (mod_pos < 64) ? mod_pos : (mod_pos-128); + + // multiply pos by gain, + // shift off 4 bits but with odd "rounding" behaviour + int temp = pos * env_out[EMOD]; + int rem = temp & 0x0F; + temp >>= 4; + if ((rem > 0) && ((temp & 0x80) == 0)) + { + if (pos < 0) temp -= 1; + else temp += 2; + } + + // wrap if range is exceeded + while (temp >= 192) temp -= 256; + while (temp < -64) temp += 256; + + // multiply result by pitch, + // shift off 6 bits, round to nearest + temp = freq[TWAV] * temp; + rem = temp & 0x3F; + temp >>= 6; + if (rem >= 32) temp += 1; + + mod = temp; + } + + // advance wavetable position + int f = freq[TWAV] + mod; + phase[TWAV] = phase[TWAV] + (clocks * f); + phase[TWAV] = phase[TWAV] & 0x3FFFFF; // wrap + + // store for trackinfo + last_freq = f; + } + + // output volume caps at 32 + int vol_out = env_out[EVOL]; + if (vol_out > 32) vol_out = 32; + + // final output + if (!wav_write) + fout = wave[TWAV][(phase[TWAV]>>16)&0x3F] * vol_out; + + // NOTE: during wav_halt, the unit still outputs (at phase 0) + // and volume can affect it if the first sample is nonzero. + // haven't worked out 100% of the conditions for volume to + // effect (vol envelope does not seem to run, but am unsure) + // but this implementation is very close to correct + + // store for trackinfo + last_vol = vol_out; +} + +unsigned int NES_FDS::Render (int b[2]) +{ + // 8 bit approximation of master volume + const double MASTER_VOL = 2.4 * 1223.0; // max FDS vol vs max APU square (arbitrarily 1223) + const double MAX_OUT = 32.0f * 63.0f; // value that should map to master vol + const int MASTER[4] = { + int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 2.0f), + int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 3.0f), + int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 4.0f), + int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 5.0f) }; + + int v = fout * MASTER[master_vol] >> 8; + + // lowpass RC filter + int rc_out = ((rc_accum * rc_k) + (v * rc_l)) >> RC_BITS; + rc_accum = rc_out; + v = rc_out; + + // output mix + int m = mask ? 0 : v; + b[0] = (m * sm[0]) >> 7; + b[1] = (m * sm[1]) >> 7; + return 2; +} + +bool NES_FDS::Write (unsigned int adr, unsigned int val, unsigned int id) +{ + // $4023 master I/O enable/disable + if (adr == 0x4023) + { + master_io = ((val & 2) != 0); + return true; + } + + if (!master_io) + return false; + if (adr < 0x4040 || adr > 0x408A) + return false; + + if (adr < 0x4080) // $4040-407F wave table write + { + if (wav_write) + wave[TWAV][adr - 0x4040] = val & 0x3F; + return true; + } + + switch (adr & 0x00FF) + { + case 0x80: // $4080 volume envelope + env_disable[EVOL] = ((val & 0x80) != 0); + env_mode[EVOL] = ((val & 0x40) != 0); + env_timer[EVOL] = 0; + env_speed[EVOL] = val & 0x3F; + if (env_disable[EVOL]) + env_out[EVOL] = env_speed[EVOL]; + return true; + case 0x81: // $4081 --- + return false; + case 0x82: // $4082 wave frequency low + freq[TWAV] = (freq[TWAV] & 0xF00) | val; + return true; + case 0x83: // $4083 wave frequency high / enables + freq[TWAV] = (freq[TWAV] & 0x0FF) | ((val & 0x0F) << 8); + wav_halt = ((val & 0x80) != 0); + env_halt = ((val & 0x40) != 0); + if (wav_halt) + phase[TWAV] = 0; + if (env_halt) + { + env_timer[EMOD] = 0; + env_timer[EVOL] = 0; + } + return true; + case 0x84: // $4084 mod envelope + env_disable[EMOD] = ((val & 0x80) != 0); + env_mode[EMOD] = ((val & 0x40) != 0); + env_timer[EMOD] = 0; + env_speed[EMOD] = val & 0x3F; + if (env_disable[EMOD]) + env_out[EMOD] = env_speed[EMOD]; + return true; + case 0x85: // $4085 mod position + mod_pos = val & 0x7F; + // not hardware accurate., but prevents detune due to cycle inaccuracies + // (notably in Bio Miracle Bokutte Upa) + if (option[OPT_4085_RESET]) + phase[TMOD] = mod_write_pos << 16; + return true; + case 0x86: // $4086 mod frequency low + freq[TMOD] = (freq[TMOD] & 0xF00) | val; + return true; + case 0x87: // $4087 mod frequency high / enable + freq[TMOD] = (freq[TMOD] & 0x0FF) | ((val & 0x0F) << 8); + mod_halt = ((val & 0x80) != 0); + if (mod_halt) + phase[TMOD] = phase[TMOD] & 0x3F0000; // reset accumulator phase + return true; + case 0x88: // $4088 mod table write + if (mod_halt) + { + // writes to current playback position (there is no direct way to set phase) + wave[TMOD][(phase[TMOD] >> 16) & 0x3F] = val & 0x07; + phase[TMOD] = (phase[TMOD] + 0x010000) & 0x3FFFFF; + wave[TMOD][(phase[TMOD] >> 16) & 0x3F] = val & 0x07; + phase[TMOD] = (phase[TMOD] + 0x010000) & 0x3FFFFF; + mod_write_pos = phase[TMOD] >> 16; // used by OPT_4085_RESET + } + return true; + case 0x89: // $4089 wave write enable, master volume + wav_write = ((val & 0x80) != 0); + master_vol = val & 0x03; + return true; + case 0x8A: // $408A envelope speed + master_env_speed = val; + // haven't tested whether this register resets phase on hardware, + // but this ensures my inplementation won't spam envelope clocks + // if this value suddenly goes low. + env_timer[EMOD] = 0; + env_timer[EVOL] = 0; + return true; + default: + return false; + } + return false; +} + +bool NES_FDS::Read (unsigned int adr, unsigned int & val, unsigned int id) +{ + if (adr >= 0x4040 && adr <= 0x407F) + { + // TODO: if wav_write is not enabled, the + // read address may not be reliable? need + // to test this on hardware. + val = wave[TWAV][adr - 0x4040]; + return true; + } + + if (adr == 0x4090) // $4090 read volume envelope + { + val = env_out[EVOL] | 0x40; + return true; + } + + if (adr == 0x4092) // $4092 read mod envelope + { + val = env_out[EMOD] | 0x40; + return true; + } + + return false; +} + +} // namespace diff --git a/src/engine/platform/sound/nes_nsfplay/nes_fds.h b/src/engine/platform/sound/nes_nsfplay/nes_fds.h new file mode 100644 index 000000000..9185e4da6 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_fds.h @@ -0,0 +1,73 @@ +#ifndef _NES_FDS_H_ +#define _NES_FDS_H_ + +namespace xgm { + +class NES_FDS +{ +public: + enum + { + OPT_CUTOFF=0, + OPT_4085_RESET, + OPT_WRITE_PROTECT, + OPT_END + }; + +protected: + double rate, clock; + int mask; + int sm[2]; // stereo mix + int fout; // current output + int option[OPT_END]; + + bool master_io; + unsigned int master_vol; + unsigned int last_freq; // for trackinfo + unsigned int last_vol; // for trackinfo + + // two wavetables + enum { TMOD=0, TWAV=1 }; + int wave[2][64]; + unsigned int freq[2]; + unsigned int phase[2]; + bool wav_write; + bool wav_halt; + bool env_halt; + bool mod_halt; + unsigned int mod_pos; + unsigned int mod_write_pos; + + // two ramp envelopes + enum { EMOD=0, EVOL=1 }; + bool env_mode[2]; + bool env_disable[2]; + unsigned int env_timer[2]; + unsigned int env_speed[2]; + unsigned int env_out[2]; + unsigned int master_env_speed; + + // 1-pole RC lowpass filter + int rc_accum; + int rc_k; + int rc_l; + +public: + NES_FDS (); + ~NES_FDS (); + + void Reset (); + void Tick (unsigned int clocks); + unsigned int Render (int b[2]); + bool Write (unsigned int adr, unsigned int val, unsigned int id=0); + bool Read (unsigned int adr, unsigned int & val, unsigned int id=0); + void SetRate (double); + void SetClock (double); + void SetOption (int, int); + void SetMask(int m){ mask = m&1; } + void SetStereoMix (int trk, short mixl, short mixr); +}; + +} // namespace xgm + +#endif diff --git a/src/engine/platform/sound/nes_nsfplay/nes_mmc5.cpp b/src/engine/platform/sound/nes_nsfplay/nes_mmc5.cpp new file mode 100644 index 000000000..500be87ca --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_mmc5.cpp @@ -0,0 +1,372 @@ +#include "nes_mmc5.h" +#include "common.h" + +namespace xgm +{ + + NES_MMC5::NES_MMC5 () + { + SetClock (DEFAULT_CLOCK); + SetRate (DEFAULT_RATE); + option[OPT_NONLINEAR_MIXER] = true; + option[OPT_PHASE_REFRESH] = true; + frame_sequence_count = 0; + + // square nonlinear mix, same as 2A03 + square_table[0] = 0; + for(int i=1;i<32;i++) + square_table[i]=(int)((8192.0*95.88)/(8128.0/i+100)); + + // 2A03 style nonlinear pcm mix with double the bits + //pcm_table[0] = 0; + //int wd = 22638; + //for(int d=1;d<256; ++d) + // pcm_table[d] = (int)((8192.0*159.79)/(100.0+1.0/((double)d/wd))); + + // linear pcm mix (actual hardware seems closer to this) + pcm_table[0] = 0; + double pcm_scale = 32.0; + for (int d=1; d<256; ++d) + pcm_table[d] = (int)(double(d) * pcm_scale); + + // stereo mix + for(int c=0;c<2;++c) + for(int t=0;t<3;++t) + sm[c][t] = 128; + } + + NES_MMC5::~NES_MMC5 () + { + } + + void NES_MMC5::Reset () + { + int i; + + scounter[0] = 0; + scounter[1] = 0; + sphase[0] = 0; + sphase[1] = 0; + + envelope_div[0] = 0; + envelope_div[1] = 0; + length_counter[0] = 0; + length_counter[1] = 0; + envelope_counter[0] = 0; + envelope_counter[1] = 0; + frame_sequence_count = 0; + + for (i = 0; i < 8; i++) + Write (0x5000 + i, 0); + + Write(0x5015, 0); + + for (i = 0; i < 3; ++i) + out[i] = 0; + + mask = 0; + pcm = 0; // PCM channel + pcm_mode = false; // write mode + + SetRate(rate); + } + + void NES_MMC5::SetOption (int id, int val) + { + if(idclock = c; + } + + void NES_MMC5::SetRate (double r) + { + rate = r ? r : DEFAULT_RATE; + } + + void NES_MMC5::FrameSequence () + { + // 240hz clock + for (int i=0; i < 2; ++i) + { + bool divider = false; + if (envelope_write[i]) + { + envelope_write[i] = false; + envelope_counter[i] = 15; + envelope_div[i] = 0; + } + else + { + ++envelope_div[i]; + if (envelope_div[i] > envelope_div_period[i]) + { + divider = true; + envelope_div[i] = 0; + } + } + if (divider) + { + if (envelope_loop[i] && envelope_counter[i] == 0) + envelope_counter[i] = 15; + else if (envelope_counter[i] > 0) + --envelope_counter[i]; + } + } + + // MMC5 length counter is clocked at 240hz, unlike 2A03 + for (int i=0; i < 2; ++i) + { + if (!envelope_loop[i] && (length_counter[i] > 0)) + --length_counter[i]; + } + } + + int NES_MMC5::calc_sqr (int i, unsigned int clocks) + { + static const short sqrtbl[4][16] = { + {0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, + {1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + }; + + scounter[i] += clocks; + while (scounter[i] > freq[i]) + { + sphase[i] = (sphase[i] + 1) & 15; + scounter[i] -= (freq[i] + 1); + } + + int ret = 0; + if (length_counter[i] > 0) + { + // note MMC5 does not silence the highest 8 frequencies like APU, + // because this is done by the sweep unit. + + int v = envelope_disable[i] ? volume[i] : envelope_counter[i]; + ret = sqrtbl[duty[i]][sphase[i]] ? v : 0; + } + + return ret; + } + + void NES_MMC5::TickFrameSequence (unsigned int clocks) + { + frame_sequence_count += clocks; + while (frame_sequence_count > 7458) + { + FrameSequence(); + frame_sequence_count -= 7458; + } + } + + void NES_MMC5::Tick (unsigned int clocks) + { + out[0] = calc_sqr(0, clocks); + out[1] = calc_sqr(1, clocks); + out[2] = pcm; + } + + unsigned int NES_MMC5::Render (int b[2]) + { + out[0] = (mask & 1) ? 0 : out[0]; + out[1] = (mask & 2) ? 0 : out[1]; + out[2] = (mask & 4) ? 0 : out[2]; + + int m[3]; + + if(option[OPT_NONLINEAR_MIXER]) + { + // squares nonlinear + int voltage = square_table[out[0] + out[1]]; + m[0] = out[0] << 6; + m[1] = out[1] << 6; + int ref = m[0] + m[1]; + if (ref > 0) + { + m[0] = (m[0] * voltage) / ref; + m[1] = (m[1] * voltage) / ref; + } + else + { + m[0] = voltage; + m[1] = voltage; + } + + // pcm nonlinear + m[2] = pcm_table[out[2]]; + } + else + { + // squares + m[0] = out[0] << 6; + m[1] = out[1] << 6; + + // pcm channel + m[2] = out[2] << 5; + } + + // note polarity is flipped on output + + b[0] = m[0] * -sm[0][0]; + b[0] += m[1] * -sm[0][1]; + b[0] += m[2] * -sm[0][2]; + b[0] >>= 7; + + b[1] = m[0] * -sm[1][0]; + b[1] += m[1] * -sm[1][1]; + b[1] += m[2] * -sm[1][2]; + b[1] >>= 7; + + return 2; + } + + bool NES_MMC5::Write (unsigned int adr, unsigned int val, unsigned int id) + { + int ch; + + static const unsigned char length_table[32] = { + 0x0A, 0xFE, + 0x14, 0x02, + 0x28, 0x04, + 0x50, 0x06, + 0xA0, 0x08, + 0x3C, 0x0A, + 0x0E, 0x0C, + 0x1A, 0x0E, + 0x0C, 0x10, + 0x18, 0x12, + 0x30, 0x14, + 0x60, 0x16, + 0xC0, 0x18, + 0x48, 0x1A, + 0x10, 0x1C, + 0x20, 0x1E + }; + + if ((0x5c00 <= adr) && (adr < 0x5ff0)) + { + ram[adr & 0x3ff] = val; + return true; + } + else if ((0x5000 <= adr) && (adr < 0x5008)) + { + reg[adr & 0x7] = val; + } + + switch (adr) + { + case 0x5000: + case 0x5004: + ch = (adr >> 2) & 1; + volume[ch] = val & 15; + envelope_disable[ch] = (val >> 4) & 1; + envelope_loop[ch] = (val >> 5) & 1; + envelope_div_period[ch] = (val & 15); + duty[ch] = (val >> 6) & 3; + break; + + case 0x5002: + case 0x5006: + ch = (adr >> 2) & 1; + freq[ch] = val + (freq[ch] & 0x700); + if (scounter[ch] > freq[ch]) scounter[ch] = freq[ch]; + break; + + case 0x5003: + case 0x5007: + ch = (adr >> 2) & 1; + freq[ch] = (freq[ch] & 0xff) + ((val & 7) << 8); + if (scounter[ch] > freq[ch]) scounter[ch] = freq[ch]; + // phase reset + if (option[OPT_PHASE_REFRESH]) + sphase[ch] = 0; + envelope_write[ch] = true; + if (enable[ch]) + { + length_counter[ch] = length_table[(val >> 3) & 0x1f]; + } + break; + + // PCM channel control + case 0x5010: + pcm_mode = ((val & 1) != 0); // 0 = write, 1 = read + break; + + // PCM channel control + case 0x5011: + if (!pcm_mode) + { + val &= 0xFF; + if (val != 0) pcm = val; + } + break; + + case 0x5015: + enable[0] = (val & 1) ? true : false; + enable[1] = (val & 2) ? true : false; + if (!enable[0]) + length_counter[0] = 0; + if (!enable[1]) + length_counter[1] = 0; + break; + + case 0x5205: + mreg[0] = val; + break; + + case 0x5206: + mreg[1] = val; + break; + + default: + return false; + + } + return true; + } + + bool NES_MMC5::Read (unsigned int adr, unsigned int & val, unsigned int id) + { + + if ((0x5000 <= adr) && (adr < 0x5008)) + { + val = reg[adr&0x7]; + return true; + } + else if(adr == 0x5015) + { + val = (enable[1]?2:0)|(enable[0]?1:0); + return true; + } + + if ((0x5c00 <= adr) && (adr < 0x5ff0)) + { + val = ram[adr & 0x3ff]; + return true; + } + else if (adr == 0x5205) + { + val = (mreg[0] * mreg[1]) & 0xff; + return true; + } + else if (adr == 0x5206) + { + val = (mreg[0] * mreg[1]) >> 8; + return true; + } + + return false; + } + + void NES_MMC5::SetStereoMix(int trk, short mixl, short mixr) + { + if (trk < 0) return; + if (trk > 2) return; + sm[0][trk] = mixl; + sm[1][trk] = mixr; + } +}// namespace diff --git a/src/engine/platform/sound/nes_nsfplay/nes_mmc5.h b/src/engine/platform/sound/nes_nsfplay/nes_mmc5.h new file mode 100644 index 000000000..19d5ab269 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_mmc5.h @@ -0,0 +1,67 @@ +#ifndef _NES_MMC5_H_ +#define _NES_MMC5_H_ + +namespace xgm +{ + class NES_MMC5 + { + public: + enum + { OPT_NONLINEAR_MIXER=0, OPT_PHASE_REFRESH, OPT_END }; + + protected: + int option[OPT_END]; + int mask; + int sm[2][3]; // stereo panning + unsigned char ram[0x6000 - 0x5c00]; + unsigned char reg[8]; + unsigned char mreg[2]; + unsigned char pcm; // PCM channel + bool pcm_mode; // PCM channel + + unsigned int scounter[2]; // frequency divider + unsigned int sphase[2]; // phase counter + + unsigned int duty[2]; + unsigned int volume[2]; + unsigned int freq[2]; + int out[3]; + bool enable[2]; + + bool envelope_disable[2]; // エンベロープ有効フラグ + bool envelope_loop[2]; // エンベロープループ + bool envelope_write[2]; + int envelope_div_period[2]; + int envelope_div[2]; + int envelope_counter[2]; + + int length_counter[2]; + + int frame_sequence_count; + + double clock, rate; + int calc_sqr (int i, unsigned int clocks); + int square_table[32]; + int pcm_table[256]; + public: + NES_MMC5 (); + ~NES_MMC5 (); + + void FrameSequence (); + void TickFrameSequence (unsigned int clocks); + + void Reset (); + void Tick (unsigned int clocks); + unsigned int Render (int b[2]); + bool Write (unsigned int adr, unsigned int val, unsigned int id=0); + bool Read (unsigned int adr, unsigned int & val, unsigned int id=0); + void SetOption (int id, int b); + void SetClock (double); + void SetRate (double); + void SetMask (int m){ mask = m; } + void SetStereoMix (int trk, short mixl, short mixr); + }; + +} + +#endif diff --git a/src/engine/platform/sound/nes_nsfplay/nes_n106.cpp b/src/engine/platform/sound/nes_nsfplay/nes_n106.cpp new file mode 100644 index 000000000..25e80fb47 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_n106.cpp @@ -0,0 +1,332 @@ +#include +#include "nes_n106.h" +#include "common.h" + +namespace xgm { + +NES_N106::NES_N106 () +{ + option[OPT_SERIAL] = 0; + option[OPT_PHASE_READ_ONLY] = 0; + option[OPT_LIMIT_WAVELENGTH] = 0; + SetClock (DEFAULT_CLOCK); + SetRate (DEFAULT_RATE); + for (int i=0; i < 8; ++i) + { + sm[0][i] = 128; + sm[1][i] = 128; + } + Reset(); +} + +NES_N106::~NES_N106 () +{ +} + +void NES_N106::SetStereoMix (int trk, short mixl, short mixr) +{ + if (trk < 0 || trk >= 8) return; + trk = 7-trk; // displayed channels are inverted + sm[0][trk] = mixl; + sm[1][trk] = mixr; +} + + +void NES_N106::SetClock (double c) +{ + clock = c; +} + +void NES_N106::SetRate (double r) +{ + rate = r; +} + +void NES_N106::SetMask (int m) +{ + // bit reverse the mask, + // N163 waves are displayed in reverse order + mask = 0 + | ((m & (1<<0)) ? (1<<7) : 0) + | ((m & (1<<1)) ? (1<<6) : 0) + | ((m & (1<<2)) ? (1<<5) : 0) + | ((m & (1<<3)) ? (1<<4) : 0) + | ((m & (1<<4)) ? (1<<3) : 0) + | ((m & (1<<5)) ? (1<<2) : 0) + | ((m & (1<<6)) ? (1<<1) : 0) + | ((m & (1<<7)) ? (1<<0) : 0); +} + +void NES_N106::SetOption (int id, int val) +{ + if (id 0) + { + int channel = 7-tick_channel; + + unsigned int phase = get_phase(channel); + unsigned int freq = get_freq(channel); + unsigned int len = get_len(channel); + unsigned int off = get_off(channel); + int vol = get_vol(channel); + + // accumulate 24-bit phase + phase = (phase + freq) & 0x00FFFFFF; + + // wrap phase if wavelength exceeded + unsigned int hilen = len << 16; + while (phase >= hilen) phase -= hilen; + + // write back phase + set_phase(phase, channel); + + // fetch sample (note: N163 output is centred at 8, and inverted w.r.t 2A03) + int sample = 8 - get_sample(((phase >> 16) + off) & 0xFF); + fout[channel] = sample * vol; + + // cycle to next channel every 15 clocks + tick_clock -= 15; + ++tick_channel; + if (tick_channel >= channels) + tick_channel = 0; + } +} + +unsigned int NES_N106::Render (int b[2]) +{ + b[0] = 0; + b[1] = 0; + if (master_disable) return 2; + + int channels = get_channels(); + + if (option[OPT_SERIAL]) // hardware accurate serial multiplexing + { + // this could be made more efficient than going clock-by-clock + // but this way is simpler + int clocks = render_clock; + while (clocks > 0) + { + int c = 7-render_channel; + if (0 == ((mask >> c) & 1)) + { + b[0] += fout[c] * sm[0][c]; + b[1] += fout[c] * sm[1][c]; + } + + ++render_subclock; + if (render_subclock >= 15) // each channel gets a 15-cycle slice + { + render_subclock = 0; + ++render_channel; + if (render_channel >= channels) + render_channel = 0; + } + --clocks; + } + + // increase output level by 1 bits (7 bits already added from sm) + b[0] <<= 1; + b[1] <<= 1; + + // average the output + if (render_clock > 0) + { + b[0] /= render_clock; + b[1] /= render_clock; + } + render_clock = 0; + } + else // just mix all channels + { + for (int i = (8-channels); i<8; ++i) + { + if (0 == ((mask >> i) & 1)) + { + b[0] += fout[i] * sm[0][i]; + b[1] += fout[i] * sm[1][i]; + } + } + + // mix together, increase output level by 8 bits, roll off 7 bits from sm + int MIX[9] = { 256/1, 256/1, 256/2, 256/3, 256/4, 256/5, 256/6, 256/6, 256/6 }; + b[0] = (b[0] * MIX[channels]) >> 7; + b[1] = (b[1] * MIX[channels]) >> 7; + // when approximating the serial multiplex as a straight mix, once the + // multiplex frequency gets below the nyquist frequency an average mix + // begins to sound too quiet. To approximate this effect, I don't attenuate + // any further after 6 channels are active. + } + + // 8 bit approximation of master volume + // max N163 vol vs max APU square + // unfortunately, games have been measured as low as 3.4x and as high as 8.5x + // with higher volumes on Erika, King of Kings, and Rolling Thunder + // and lower volumes on others. Using 6.0x as a rough "one size fits all". + const double MASTER_VOL = 6.0 * 1223.0; + const double MAX_OUT = 15.0 * 15.0 * 256.0; // max digital value + const int GAIN = int((MASTER_VOL / MAX_OUT) * 256.0f); + b[0] = (b[0] * GAIN) >> 8; + b[1] = (b[1] * GAIN) >> 8; + + return 2; +} + +bool NES_N106::Write (unsigned int adr, unsigned int val, unsigned int id) +{ + if (adr == 0xE000) // master disable + { + master_disable = ((val & 0x40) != 0); + return true; + } + else if (adr == 0xF800) // register select + { + reg_select = (val & 0x7F); + reg_advance = (val & 0x80) != 0; + return true; + } + else if (adr == 0x4800) // register write + { + if (option[OPT_PHASE_READ_ONLY]) // old emulators didn't know phase was stored here + { + int c = 15 - (reg_select/8); + int r = reg_select & 7; + if (c < get_channels() && + (r == 1 || + r == 3 || + r == 5)) + { + if (reg_advance) + reg_select = (reg_select + 1) & 0x7F; + return true; + } + } + if (option[OPT_LIMIT_WAVELENGTH]) // old emulators ignored top 3 bits of length + { + int c = 15 - (reg_select/8); + int r = reg_select & 7; + if (c < get_channels() && r == 4) + { + val |= 0xE0; + } + } + reg[reg_select] = val; + if (reg_advance) + reg_select = (reg_select + 1) & 0x7F; + return true; + } + return false; +} + +bool NES_N106::Read (unsigned int adr, unsigned int & val, unsigned int id) +{ + if (adr == 0x4800) // register read + { + val = reg[reg_select]; + if (reg_advance) + reg_select = (reg_select + 1) & 0x7F; + return true; + } + return false; +} + +// +// register decoding/encoding functions +// + +inline unsigned int NES_N106::get_phase (int channel) +{ + // 24-bit phase stored in channel regs 1/3/5 + channel = channel << 3; + return (reg[0x41 + channel] ) + + (reg[0x43 + channel] << 8 ) + + (reg[0x45 + channel] << 16); +} + +inline unsigned int NES_N106::get_freq (int channel) +{ + // 19-bit frequency stored in channel regs 0/2/4 + channel = channel << 3; + return ( reg[0x40 + channel] ) + + ( reg[0x42 + channel] << 8 ) + + ((reg[0x44 + channel] & 0x03) << 16); +} + +inline unsigned int NES_N106::get_off (int channel) +{ + // 8-bit offset stored in channel reg 6 + channel = channel << 3; + return reg[0x46 + channel]; +} + +inline unsigned int NES_N106::get_len (int channel) +{ + // 6-bit<<3 length stored obscurely in channel reg 4 + channel = channel << 3; + return 256 - (reg[0x44 + channel] & 0xFC); +} + +inline int NES_N106::get_vol (int channel) +{ + // 4-bit volume stored in channel reg 7 + channel = channel << 3; + return reg[0x47 + channel] & 0x0F; +} + +inline int NES_N106::get_sample (unsigned int index) +{ + // every sample becomes 2 samples in regs + return (index&1) ? + ((reg[index>>1] >> 4) & 0x0F) : + ( reg[index>>1] & 0x0F) ; +} + +inline int NES_N106::get_channels () +{ + // 3-bit channel count stored in reg 0x7F + return ((reg[0x7F] >> 4) & 0x07) + 1; +} + +inline void NES_N106::set_phase (unsigned int phase, int channel) +{ + // 24-bit phase stored in channel regs 1/3/5 + channel = channel << 3; + reg[0x41 + channel] = phase & 0xFF; + reg[0x43 + channel] = (phase >> 8 ) & 0xFF; + reg[0x45 + channel] = (phase >> 16) & 0xFF; +} + +} //namespace diff --git a/src/engine/platform/sound/nes_nsfplay/nes_n106.h b/src/engine/platform/sound/nes_nsfplay/nes_n106.h new file mode 100644 index 000000000..a59dd6687 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_n106.h @@ -0,0 +1,64 @@ +#ifndef _NES_N106_H_ +#define _NES_N106_H_ + +namespace xgm { + + +class NES_N106 +{ +public: + enum + { + OPT_SERIAL = 0, + OPT_PHASE_READ_ONLY = 1, + OPT_LIMIT_WAVELENGTH = 2, + OPT_END + }; + +protected: + double rate, clock; + int mask; + int sm[2][8]; // stereo mix + int fout[8]; // current output + int option[OPT_END]; + + bool master_disable; + unsigned int reg[0x80]; // all state is contained here + unsigned int reg_select; + bool reg_advance; + int tick_channel; + int tick_clock; + int render_channel; + int render_clock; + int render_subclock; + + // convenience functions to interact with regs + inline unsigned int get_phase (int channel); + inline unsigned int get_freq (int channel); + inline unsigned int get_off (int channel); + inline unsigned int get_len (int channel); + inline int get_vol (int channel); + inline int get_sample (unsigned int index); + inline int get_channels (); + // for storing back the phase after modifying + inline void set_phase (unsigned int phase, int channel); + +public: + NES_N106 (); + ~NES_N106 (); + + void Reset (); + void Tick (unsigned int clocks); + unsigned int Render (int b[2]); + bool Write (unsigned int adr, unsigned int val, unsigned int id=0); + bool Read (unsigned int adr, unsigned int & val, unsigned int id=0); + void SetRate (double); + void SetClock (double); + void SetOption (int, int); + void SetMask (int m); + void SetStereoMix (int trk, short mixl, short mixr); +}; + +} // namespace xgm + +#endif diff --git a/src/engine/platform/sound/nes_nsfplay/nes_vrc6.cpp b/src/engine/platform/sound/nes_nsfplay/nes_vrc6.cpp new file mode 100644 index 000000000..754d44cf7 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_vrc6.cpp @@ -0,0 +1,239 @@ +#include "nes_vrc6.h" +#include "common.h" + +namespace xgm +{ + + NES_VRC6::NES_VRC6 () + { + SetClock (DEFAULT_CLOCK); + SetRate (DEFAULT_RATE); + + halt = false; + freq_shift = 0; + + for(int c=0;c<2;++c) + for(int t=0;t<3;++t) + sm[c][t] = 128; + } + + NES_VRC6::~NES_VRC6 () + { + } + + void NES_VRC6::SetStereoMix(int trk, short mixl, short mixr) + { + if (trk < 0) return; + if (trk > 2) return; + sm[0][trk] = mixl; + sm[1][trk] = mixr; + } + + void NES_VRC6::SetClock (double c) + { + clock = c; + } + + void NES_VRC6::SetRate (double r) + { + rate = r ? r : DEFAULT_RATE; + } + + void NES_VRC6::SetOption (int id, int val) + { + if(id freq2[i]) + { + phase[i] = (phase[i] + 1) & 15; + counter[i] -= (freq2[i] + 1); + } + } + + return (gate[i] + || sqrtbl[duty[i]][phase[i]])? volume[i] : 0; + } + + short NES_VRC6::calc_saw (unsigned int clocks) + { + if (!enable[2]) + return 0; + + if (!halt) + { + counter[2] += clocks; + while(counter[2] > freq2[2]) + { + counter[2] -= (freq2[2] + 1); + + // accumulate saw + ++count14; + if (count14 >= 14) + { + count14 = 0; + phase[2] = 0; + } + else if (0 == (count14 & 1)) // only accumulate on even ticks + { + phase[2] = (phase[2] + volume[2]) & 0xFF; // note 8-bit wrapping behaviour + } + } + } + + // only top 5 bits of saw are output + return phase[2] >> 3; + } + + void NES_VRC6::Tick (unsigned int clocks) + { + out[0] = calc_sqr(0,clocks); + out[1] = calc_sqr(1,clocks); + out[2] = calc_saw(clocks); + } + + unsigned int NES_VRC6::Render (int b[2]) + { + int m[3]; + m[0] = out[0]; + m[1] = out[1]; + m[2] = out[2]; + + // note: signal is inverted compared to 2A03 + + m[0] = (mask & 1) ? 0 : -m[0]; + m[1] = (mask & 2) ? 0 : -m[1]; + m[2] = (mask & 4) ? 0 : -m[2]; + + b[0] = m[0] * sm[0][0]; + b[0] += m[1] * sm[0][1]; + b[0] += m[2] * sm[0][2]; + //b[0] >>= (7 - 7); + + b[1] = m[0] * sm[1][0]; + b[1] += m[1] * sm[1][1]; + b[1] += m[2] * sm[1][2]; + //b[1] >>= (7 - 7); + + // master volume adjustment + const int MASTER = int(256.0 * 1223.0 / 1920.0); + b[0] = (b[0] * MASTER) >> 8; + b[1] = (b[1] * MASTER) >> 8; + + return 2; + } + + bool NES_VRC6::Write (unsigned int adr, unsigned int val, unsigned int id) + { + int ch, cmap[4] = { 0, 0, 1, 2 }; + + switch (adr) + { + case 0x9000: + case 0xa000: + ch = cmap[(adr >> 12) & 3]; + volume[ch] = val & 15; + duty[ch] = (val >> 4) & 7; + gate[ch] = (val >> 7) & 1; + break; + case 0xb000: + volume[2] = val & 63; + break; + + case 0x9001: + case 0xa001: + case 0xb001: + ch = cmap[(adr >> 12) & 3]; + freq[ch] = (freq[ch] & 0xf00) | val; + freq2[ch] = (freq[ch] >> freq_shift); + if (counter[ch] > freq2[ch]) counter[ch] = freq2[ch]; + break; + + case 0x9002: + case 0xa002: + case 0xb002: + ch = cmap[(adr >> 12) & 3]; + freq[ch] = ((val & 0xf) << 8) + (freq[ch] & 0xff); + freq2[ch] = (freq[ch] >> freq_shift); + if (counter[ch] > freq2[ch]) counter[ch] = freq2[ch]; + if (!enable[ch]) // if enable is being turned on, phase should be reset + { + if (ch == 2) + { + count14 = 0; // reset saw + } + phase[ch] = 0; + } + enable[ch] = (val >> 7) & 1; + break; + + case 0x9003: + halt = val & 1; + freq_shift = + (val & 4) ? 8 : + (val & 2) ? 4 : + 0; + freq2[0] = (freq[0] >> freq_shift); + freq2[1] = (freq[1] >> freq_shift); + freq2[2] = (freq[2] >> freq_shift); + if (counter[0] > freq2[0]) counter[0] = freq2[0]; + if (counter[1] > freq2[1]) counter[1] = freq2[1]; + if (counter[2] > freq2[2]) counter[2] = freq2[2]; + break; + + default: + return false; + + } + + return true; + } + + bool NES_VRC6::Read (unsigned int adr, unsigned int & val, unsigned int id) + { + return false; + } + + +} // namespace diff --git a/src/engine/platform/sound/nes_nsfplay/nes_vrc6.h b/src/engine/platform/sound/nes_nsfplay/nes_vrc6.h new file mode 100644 index 000000000..198a8bd66 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/nes_vrc6.h @@ -0,0 +1,53 @@ +#ifndef _NES_VRC6_H_ +#define _NES_VRC6_H_ + +namespace xgm +{ + + class NES_VRC6 + { + public: + enum + { + OPT_END + }; + protected: + unsigned int counter[3]; // frequency divider + unsigned int phase[3]; // phase counter + unsigned int freq2[3]; // adjusted frequency + int count14; // saw 14-stage counter + + //int option[OPT_END]; + int mask; + int sm[2][3]; // stereo mix + int duty[2]; + int volume[3]; + int enable[3]; + int gate[3]; + unsigned int freq[3]; + short calc_sqr (int i, unsigned int clocks); + short calc_saw (unsigned int clocks); + bool halt; + int freq_shift; + double clock, rate; + int out[3]; + + public: + NES_VRC6 (); + ~NES_VRC6 (); + + void Reset (); + void Tick (unsigned int clocks); + unsigned int Render (int b[2]); + bool Read (unsigned int adr, unsigned int & val, unsigned int id=0); + bool Write (unsigned int adr, unsigned int val, unsigned int id=0); + void SetClock (double); + void SetRate (double); + void SetOption (int, int); + void SetMask (int m){ mask = m; } + void SetStereoMix (int trk, short mixl, short mixr); + }; + +} // namespace + +#endif diff --git a/src/engine/platform/sound/nes_nsfplay/readme.txt b/src/engine/platform/sound/nes_nsfplay/readme.txt new file mode 100644 index 000000000..f3a625d90 --- /dev/null +++ b/src/engine/platform/sound/nes_nsfplay/readme.txt @@ -0,0 +1,60 @@ +MODIFIED + +this is a modified version of the NES audio emulation core. +it converts the files to UTF-8 and Unix line endings. + +XGM SOURCE ARCHIVE + +This source archive is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY. You can reuse these source code freely. However, +we possibly change the structure and interface of the program code without +any advance notice. + +HOW TO COMPILE + +Open the workspace file top.sln with Visual C++ 7.0 or later +version. We can compile both KbMediaPlayer and Winamp version of +NSFplug on this workspace. + +To make a KbMediaPlayer version of NSFplug, that is, in_nsf.kpi, +Please choose 'kbnsf' project as an active project. Then, you can +build in_nsf.kpi by build menu. + +On the other hand, to make a Winamp version of NSFplug, activate +'wa2nsf' project and build it. + +Note that after the build process, VC++ copies the plugin files to: + +C:\Program Files\KbMediaPlayer\Plugins\OK\in_nsf\in_nsf.kpi +C:\Program Files\Windamp\Plugins\in_nsf.dll + +If you don't need to have these copies, please remove or modify the + custom build settings. + +ACKNOWLEDGEMENT + +I thank Mamiya and Kobarin and Nullsoft for their great source code. +I thank Norix and Izumi for the fruitful discussions and the NSFplug +users for their comments and bug reports. + +COPYRIGHTS + +NSFplug is built on KM6502, KbMediaPlayer plugin SDK and Winamp2 +plugin SDK. + +NSFplug uses KM6502 in emulating a 6502 cpu. KM6502 code is stored in +devices\CPU\km6502 folder of this source archive. KM6502 is a public +domain software. See the document of KM6502 stored in the folder. + +KbMediaPlayer Plugin SDK is provided by Kobarin. The SDK code is also +packed in the kbmedia\sdk folder of this archive. The copyright of +the source code remains with Kobarin. + +The files in winamp/sdk folder of this archive are the header files +from Winamp2 Plugin SDK provided by Nullsoft. The copyright of these +header files remains with Nullsoft. + +CONTACT + +Digital Sound Antiques +http://dsa.sakura.ne.jp/ diff --git a/src/engine/platform/sound/pce_psg.cpp b/src/engine/platform/sound/pce_psg.cpp index f9c1ee099..0fa9993bf 100644 --- a/src/engine/platform/sound/pce_psg.cpp +++ b/src/engine/platform/sound/pce_psg.cpp @@ -96,10 +96,11 @@ inline void PCE_PSG::UpdateOutputSub(const int32_t timestamp, psg_channel *ch, c HRBufs[1][l + 4] += delta[1] * c[4]; HRBufs[1][l + 5] += delta[1] * c[5]; HRBufs[1][l + 6] += delta[1] * c[6]; + */ ch->blip_prev_samp[0] = samp0; ch->blip_prev_samp[1] = samp1; - */ + } void PCE_PSG::UpdateOutput_Norm(const int32_t timestamp, psg_channel *ch) diff --git a/src/engine/platform/sound/pce_psg.h b/src/engine/platform/sound/pce_psg.h index 595494036..08f7f88ce 100644 --- a/src/engine/platform/sound/pce_psg.h +++ b/src/engine/platform/sound/pce_psg.h @@ -147,6 +147,8 @@ class PCE_PSG void PeekWave(const unsigned int ch, uint32_t Address, uint32_t Length, uint8_t *Buffer); void PokeWave(const unsigned int ch, uint32_t Address, uint32_t Length, const uint8_t *Buffer); + + psg_channel channel[6]; private: @@ -178,8 +180,6 @@ class PCE_PSG int32_t vol_update_vllatch; bool vol_pending; - psg_channel channel[6]; - int32_t lastts; int revision; diff --git a/src/engine/platform/sound/saa1099.cpp b/src/engine/platform/sound/saa1099.cpp index 670afb97f..918ade54c 100644 --- a/src/engine/platform/sound/saa1099.cpp +++ b/src/engine/platform/sound/saa1099.cpp @@ -164,7 +164,7 @@ void saa1099_device::device_start() // sound_stream_update - handle a stream update //------------------------------------------------- -void saa1099_device::sound_stream_update(short** outputs, int len) +void saa1099_device::sound_stream_update(short** outputs, int len, DivDispatchOscBuffer** oscBuf) { int j, ch; /* if the channels are disabled we're done */ @@ -225,9 +225,14 @@ void saa1099_device::sound_stream_update(short** outputs, int len) } if (level) { - output_l += m_channels[ch].amplitude[ LEFT] * m_channels[ch].envelope[ LEFT] / 16; - output_r += m_channels[ch].amplitude[RIGHT] * m_channels[ch].envelope[RIGHT] / 16; - } + int this_output_l = m_channels[ch].amplitude[ LEFT] * m_channels[ch].envelope[ LEFT] / 16; + int this_output_r = m_channels[ch].amplitude[RIGHT] * m_channels[ch].envelope[RIGHT] / 16; + output_l+=this_output_l; + output_r+=this_output_r; + oscBuf[ch]->data[oscBuf[ch]->needle++]=(this_output_l+this_output_r)<<1; + } else if (oscBuf!=NULL) { + oscBuf[ch]->data[oscBuf[ch]->needle++]=0; + } } for (ch = 0; ch < 2; ch++) diff --git a/src/engine/platform/sound/saa1099.h b/src/engine/platform/sound/saa1099.h index 8ebad8a30..8b7cead25 100644 --- a/src/engine/platform/sound/saa1099.h +++ b/src/engine/platform/sound/saa1099.h @@ -7,6 +7,8 @@ #ifndef MAME_SOUND_SAA1099_H #define MAME_SOUND_SAA1099_H +#include "../../dispatch.h" + //************************************************************************** // TYPE DEFINITIONS //************************************************************************** @@ -28,7 +30,7 @@ public: void device_clock_changed(); // sound stream update overrides - void sound_stream_update(short** outputs, int len); + void sound_stream_update(short** outputs, int len, DivDispatchOscBuffer** oscBuf=NULL); private: struct saa1099_channel diff --git a/src/engine/platform/sound/sn76496.h b/src/engine/platform/sound/sn76496.h index 2387574d1..4c24e938c 100644 --- a/src/engine/platform/sound/sn76496.h +++ b/src/engine/platform/sound/sn76496.h @@ -16,6 +16,9 @@ public: void write(u8 data); void device_start(); void sound_stream_update(short* outputs, int outLen); + inline int32_t get_channel_output(int ch) { + return ((m_output[ch]!=0)?m_volume[ch]:0); + } //DECLARE_READ_LINE_MEMBER( ready_r ) { return m_ready_state ? 1 : 0; } sn76496_base_device( diff --git a/src/engine/platform/sound/su.cpp b/src/engine/platform/sound/su.cpp new file mode 100644 index 000000000..a9e6d2430 --- /dev/null +++ b/src/engine/platform/sound/su.cpp @@ -0,0 +1,287 @@ +#define _USE_MATH_DEFINES +#include "su.h" +#include + +#define minval(a,b) (((a)<(b))?(a):(b)) +#define maxval(a,b) (((a)>(b))?(a):(b)) + +void SoundUnit::NextSample(short* l, short* r) { + for (int i=0; i<8; i++) { + if (chan[i].vol==0 && !chan[i].flags.swvol) {fns[i]=0; continue;} + if (chan[i].flags.pcm) { + ns[i]=pcm[chan[i].pcmpos]; + } else switch (chan[i].flags.shape) { + case 0: + ns[i]=(((cycle[i]>>15)&127)>chan[i].duty)*127; + break; + case 1: + ns[i]=cycle[i]>>14; + break; + case 2: + ns[i]=SCsine[(cycle[i]>>14)&255]; + break; + case 3: + ns[i]=SCtriangle[(cycle[i]>>14)&255]; + break; + case 4: case 5: + ns[i]=(lfsr[i]&1)*127; + break; + case 6: + ns[i]=((((cycle[i]>>15)&127)>chan[i].duty)*127)^(short)SCsine[(cycle[i]>>14)&255]; + break; + case 7: + ns[i]=((((cycle[i]>>15)&127)>chan[i].duty)*127)^(short)SCtriangle[(cycle[i]>>14)&255]; + break; + } + + if (chan[i].flags.ring) { + ns[i]=(ns[i]*ns[(i+1)&7])>>7; + } + + if (chan[i].flags.pcm) { + if (chan[i].freq>0x8000) { + pcmdec[i]+=0x8000; + } else { + pcmdec[i]+=chan[i].freq; + } + if (pcmdec[i]>=32768) { + pcmdec[i]-=32768; + if (chan[i].pcmpos>4)&3) { + case 0: + cycle[i]+=chan[i].freq*1-(chan[i].freq>>3); + break; + case 1: + cycle[i]+=chan[i].freq*2-(chan[i].freq>>3); + break; + case 2: + cycle[i]+=chan[i].freq*4-(chan[i].freq>>3); + break; + case 3: + cycle[i]+=chan[i].freq*8-(chan[i].freq>>3); + break; + } + } else { + cycle[i]+=chan[i].freq; + } + if ((cycle[i]&0xf80000)!=(ocycle[i]&0xf80000)) { + if (chan[i].flags.shape==4) { + lfsr[i]=(lfsr[i]>>1|(((lfsr[i]) ^ (lfsr[i] >> 2) ^ (lfsr[i] >> 3) ^ (lfsr[i] >> 5) ) & 1)<<31); + } else { + switch ((chan[i].duty>>4)&3) { + case 0: + lfsr[i]=(lfsr[i]>>1|(((lfsr[i] >> 3) ^ (lfsr[i] >> 4) ) & 1)<<5); + break; + case 1: + lfsr[i]=(lfsr[i]>>1|(((lfsr[i] >> 2) ^ (lfsr[i] >> 3) ) & 1)<<5); + break; + case 2: + lfsr[i]=(lfsr[i]>>1|(((lfsr[i]) ^ (lfsr[i] >> 2) ^ (lfsr[i] >> 3) ) & 1)<<5); + break; + case 3: + lfsr[i]=(lfsr[i]>>1|(((lfsr[i]) ^ (lfsr[i] >> 2) ^ (lfsr[i] >> 3) ^ (lfsr[i] >> 5) ) & 1)<<5); + break; + } + if ((lfsr[i]&63)==0) { + lfsr[i]=0xaaaa; + } + } + } + if (chan[i].flags.restim) { + if (--rcycle[i]<=0) { + cycle[i]=0; + rcycle[i]=chan[i].restimer; + lfsr[i]=0xaaaa; + } + } + } + fns[i]=ns[i]*chan[i].vol*2; + if (chan[i].flags.fmode!=0) { + int ff=chan[i].cutoff; + nslow[i]=nslow[i]+(((ff)*nsband[i])>>16); + nshigh[i]=fns[i]-nslow[i]-(((256-chan[i].reson)*nsband[i])>>8); + nsband[i]=(((ff)*nshigh[i])>>16)+nsband[i]; + fns[i]=(((chan[i].flags.fmode&1)?(nslow[i]):(0))+((chan[i].flags.fmode&2)?(nshigh[i]):(0))+((chan[i].flags.fmode&4)?(nsband[i]):(0))); + } + nsL[i]=(fns[i]*SCpantabL[(unsigned char)chan[i].pan])>>8; + nsR[i]=(fns[i]*SCpantabR[(unsigned char)chan[i].pan])>>8; + oldfreq[i]=chan[i].freq; + oldflags[i]=chan[i].flags.flags; + if (chan[i].flags.swvol) { + if (--swvolt[i]<=0) { + swvolt[i]=chan[i].swvol.speed; + if (chan[i].swvol.dir) { + chan[i].vol+=chan[i].swvol.amt; + if (chan[i].vol>chan[i].swvol.bound && !chan[i].swvol.loop) { + chan[i].vol=chan[i].swvol.bound; + } + if (chan[i].vol&0x80) { + if (chan[i].swvol.loop) { + if (chan[i].swvol.loopi) { + chan[i].swvol.dir=!chan[i].swvol.dir; + chan[i].vol=0xff-chan[i].vol; + } else { + chan[i].vol&=~0x80; + } + } else { + chan[i].vol=0x7f; + } + } + } else { + chan[i].vol-=chan[i].swvol.amt; + if (chan[i].vol&0x80) { + if (chan[i].swvol.loop) { + if (chan[i].swvol.loopi) { + chan[i].swvol.dir=!chan[i].swvol.dir; + chan[i].vol=-chan[i].vol; + } else { + chan[i].vol&=~0x80; + } + } else { + chan[i].vol=0x0; + } + } + if (chan[i].vol(0xffff-chan[i].swfreq.amt)) { + chan[i].freq=0xffff; + } else { + chan[i].freq=(chan[i].freq*(0x80+chan[i].swfreq.amt))>>7; + if ((chan[i].freq>>8)>chan[i].swfreq.bound) { + chan[i].freq=chan[i].swfreq.bound<<8; + } + } + } else { + if (chan[i].freq>8; + if ((chan[i].freq>>8)(0xffff-chan[i].swcut.amt)) { + chan[i].cutoff=0xffff; + } else { + chan[i].cutoff+=chan[i].swcut.amt; + if ((chan[i].cutoff>>8)>chan[i].swcut.bound) { + chan[i].cutoff=chan[i].swcut.bound<<8; + } + } + } else { + if (chan[i].cutoff>11; + if ((chan[i].cutoff>>8)>2; + tnsR=(nsR[0]+nsR[1]+nsR[2]+nsR[3]+nsR[4]+nsR[5]+nsR[6]+nsR[7])>>2; + + *l=minval(32767,maxval(-32767,tnsL)); + *r=minval(32767,maxval(-32767,tnsR)); +} + +void SoundUnit::Init() { + Reset(); + memset(pcm,0,SOUNDCHIP_PCM_SIZE); + for (int i=0; i<256; i++) { + SCsine[i]=sin((i/128.0f)*M_PI)*127; + SCtriangle[i]=(i>127)?(255-i):(i); + SCpantabL[i]=127; + SCpantabR[i]=127; + } + for (int i=0; i<128; i++) { + SCpantabL[i]=127-i; + SCpantabR[128+i]=i-1; + } + SCpantabR[128]=0; + for (int i=0; i<8; i++) { + muted[i]=false; + } +} + +void SoundUnit::Reset() { + for (int i=0; i<8; i++) { + ocycle[i]=0; + cycle[i]=0; + rcycle[i]=0; + resetfreq[i]=0; + voldcycles[i]=0; + volicycles[i]=0; + fscycles[i]=0; + sweep[i]=0; + ns[i]=0; + fns[i]=0; + nsL[i]=0; + nsR[i]=0; + nslow[i]=0; + nshigh[i]=0; + nsband[i]=0; + swvolt[i]=1; + swfreqt[i]=1; + swcutt[i]=1; + lfsr[i]=0xaaaa; + oldfreq[i]=0; + oldflags[i]=0; + pcmdec[i]=0; + } + tnsL=0; + tnsR=0; + memset(chan,0,sizeof(SUChannel)*8); +} + +void SoundUnit::Write(unsigned char addr, unsigned char data) { + ((unsigned char*)chan)[addr]=data; +} + +SoundUnit::SoundUnit() { + Init(); + memset(pcm,0,SOUNDCHIP_PCM_SIZE); +} diff --git a/src/engine/platform/sound/su.h b/src/engine/platform/sound/su.h new file mode 100644 index 000000000..3152e8568 --- /dev/null +++ b/src/engine/platform/sound/su.h @@ -0,0 +1,100 @@ +#include +#include +#include +#include + +#define SOUNDCHIP_PCM_SIZE 8192 + +class SoundUnit { + signed char SCsine[256]; + signed char SCtriangle[256]; + signed char SCpantabL[256]; + signed char SCpantabR[256]; + unsigned int ocycle[8]; + unsigned int cycle[8]; + int rcycle[8]; + unsigned int lfsr[8]; + signed char ns[8]; + int fns[8]; + int nsL[8]; + int nsR[8]; + int nslow[8]; + int nshigh[8]; + int nsband[8]; + int tnsL, tnsR; + unsigned short oldfreq[8]; + unsigned short oldflags[8]; + public: + unsigned short resetfreq[8]; + unsigned short voldcycles[8]; + unsigned short volicycles[8]; + unsigned short fscycles[8]; + unsigned char sweep[8]; + unsigned short swvolt[8]; + unsigned short swfreqt[8]; + unsigned short swcutt[8]; + unsigned short pcmdec[8]; + struct SUChannel { + unsigned short freq; + signed char vol; + signed char pan; + union { + unsigned short flags; + struct { + unsigned char shape: 3; + unsigned char pcm: 1; + unsigned char ring: 1; + unsigned char fmode: 3; + unsigned char resosc: 1; + unsigned char resfilt: 1; + unsigned char pcmloop: 1; + unsigned char restim: 1; + unsigned char swfreq: 1; + unsigned char swvol: 1; + unsigned char swcut: 1; + unsigned char padding: 1; + }; + } flags; + unsigned short cutoff; + unsigned char duty; + unsigned char reson; + unsigned short pcmpos; + unsigned short pcmbnd; + unsigned short pcmrst; + struct { + unsigned short speed; + unsigned char amt: 7; + unsigned char dir: 1; + unsigned char bound; + } swfreq; + struct { + unsigned short speed; + unsigned char amt: 5; + unsigned char dir: 1; + unsigned char loop: 1; + unsigned char loopi: 1; + unsigned char bound; + } swvol; + struct { + unsigned short speed; + unsigned char amt: 7; + unsigned char dir: 1; + unsigned char bound; + } swcut; + unsigned short wc; + unsigned short restimer; + } chan[8]; + signed char pcm[SOUNDCHIP_PCM_SIZE]; + bool muted[8]; + void Write(unsigned char addr, unsigned char data); + void NextSample(short* l, short* r); + inline int GetSample(int ch) { + int ret=(nsL[ch]+nsR[ch])>>1; + if (ret<-32768) ret=-32768; + if (ret>32767) ret=32767; + return ret; + } + void Init(); + void Reset(); + SoundUnit(); +}; diff --git a/src/engine/platform/sound/swan.h b/src/engine/platform/sound/swan.h index a1d01fa54..bd421a74c 100644 --- a/src/engine/platform/sound/swan.h +++ b/src/engine/platform/sound/swan.h @@ -41,6 +41,8 @@ public: void SoundUpdate(uint32_t); void RAMWrite(uint32_t, uint8_t); + + int32_t sample_cache[4][2]; private: // Blip_Synth WaveSynth; @@ -61,8 +63,6 @@ private: uint8_t sweep_counter; uint8_t SampleRAMPos; - int32_t sample_cache[4][2]; - int32_t last_v_val; uint8_t HyperVoice; diff --git a/src/engine/platform/sound/tia/TIASnd.cpp b/src/engine/platform/sound/tia/TIASnd.cpp index f6ef80460..e2d0568c4 100644 --- a/src/engine/platform/sound/tia/TIASnd.cpp +++ b/src/engine/platform/sound/tia/TIASnd.cpp @@ -157,7 +157,7 @@ void TIASound::volume(unsigned int percent) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::process(short* buffer, unsigned int samples) +void TIASound::process(short* buffer, unsigned int samples, DivDispatchOscBuffer** oscBuf) { // Make temporary local copy unsigned char audc0 = myAUDC[0], audc1 = myAUDC[1]; @@ -339,6 +339,11 @@ void TIASound::process(short* buffer, unsigned int samples) samples--; break; } + + if (oscBuf!=NULL) { + oscBuf[0]->data[oscBuf[0]->needle++]=v0; + oscBuf[1]->data[oscBuf[1]->needle++]=v1; + } } // Save for next round diff --git a/src/engine/platform/sound/tia/TIASnd.h b/src/engine/platform/sound/tia/TIASnd.h index 14e48577a..78459426f 100644 --- a/src/engine/platform/sound/tia/TIASnd.h +++ b/src/engine/platform/sound/tia/TIASnd.h @@ -21,6 +21,7 @@ #define TIASOUND_HXX #include +#include "../../../dispatch.h" /** This class implements a fairly accurate emulation of the TIA sound @@ -87,7 +88,7 @@ class TIASound @param buffer The location to store generated samples @param samples The number of samples to generate */ - void process(short* buffer, unsigned int samples); + void process(short* buffer, unsigned int samples, DivDispatchOscBuffer** oscBuf=NULL); /** Set the volume of the samples created (0-100) diff --git a/src/engine/platform/sound/vera_psg.c b/src/engine/platform/sound/vera_psg.c index afdf69cec..19f15d4eb 100644 --- a/src/engine/platform/sound/vera_psg.c +++ b/src/engine/platform/sound/vera_psg.c @@ -86,6 +86,12 @@ render(struct VERA_PSG* psg, int16_t *left, int16_t *right) if (ch->right) { r += val; } + + if (ch->left || ch->right) { + ch->lastOut=val; + } else { + ch->lastOut=0; + } } *left = l; diff --git a/src/engine/platform/sound/vera_psg.h b/src/engine/platform/sound/vera_psg.h index 7a6a7f01d..6a3f6828b 100644 --- a/src/engine/platform/sound/vera_psg.h +++ b/src/engine/platform/sound/vera_psg.h @@ -15,6 +15,7 @@ struct VERAChannel { uint8_t waveform; unsigned phase; + int lastOut; uint8_t noiseval; }; diff --git a/src/engine/platform/sound/vrcvi/vrcvi.cpp b/src/engine/platform/sound/vrcvi/vrcvi.cpp index 19152ed74..bca3ecda3 100644 --- a/src/engine/platform/sound/vrcvi/vrcvi.cpp +++ b/src/engine/platform/sound/vrcvi/vrcvi.cpp @@ -88,13 +88,23 @@ void vrcvi_core::tick() if (!m_control.m_halt) // Halt flag { // tick per each clock + int elemIndex=0; for (auto & elem : m_pulse) { - if (elem.tick()) + if (elem.tick()) { m_out += elem.m_control.m_volume; // add 4 bit pulse output + m_ch_out[elemIndex]=elem.m_control.m_volume; + } else { + m_ch_out[elemIndex]=0; + } + elemIndex++; } - if (m_sawtooth.tick()) + if (m_sawtooth.tick()) { m_out += bitfield(m_sawtooth.m_accum, 3, 5); // add 5 bit sawtooth output + m_ch_out[2]=bitfield(m_sawtooth.m_accum, 3, 5); + } else { + m_ch_out[2]=0; + } } if (m_timer.tick()) m_timer.counter_tick(); @@ -109,6 +119,7 @@ void vrcvi_core::reset() m_timer.reset(); m_control.reset(); m_out = 0; + std::fill(std::begin(m_ch_out),std::end(m_ch_out),0); } bool vrcvi_core::alu_t::tick() diff --git a/src/engine/platform/sound/vrcvi/vrcvi.hpp b/src/engine/platform/sound/vrcvi/vrcvi.hpp index 40b4245e3..d88ba7cf9 100644 --- a/src/engine/platform/sound/vrcvi/vrcvi.hpp +++ b/src/engine/platform/sound/vrcvi/vrcvi.hpp @@ -62,6 +62,8 @@ public: // 6 bit output s8 out() { return m_out; } + // channel output + s16 chan_out(u8 ch) { return m_ch_out[ch]; } private: // Common ALU for sound channels struct alu_t @@ -233,6 +235,7 @@ private: vrcvi_intf &m_intf; s8 m_out = 0; // 6 bit output + s8 m_ch_out[3] = {0}; // per-channel output }; #endif diff --git a/src/engine/platform/sound/x1_010/x1_010.hpp b/src/engine/platform/sound/x1_010/x1_010.hpp index e8f5e3e40..f208611da 100644 --- a/src/engine/platform/sound/x1_010/x1_010.hpp +++ b/src/engine/platform/sound/x1_010/x1_010.hpp @@ -63,6 +63,7 @@ public: // getters s32 output(u8 channel) { return m_out[channel & 1]; } + s32 chan_out(u8 channel) { return (m_voice[channel].data * (m_voice[channel].vol_out[0]+m_voice[channel].vol_out[1]))<<2; } // internal state void reset(); diff --git a/src/engine/platform/sound/ymfm/ymfm_adpcm.cpp b/src/engine/platform/sound/ymfm/ymfm_adpcm.cpp index 0d285cd16..76839e787 100644 --- a/src/engine/platform/sound/ymfm/ymfm_adpcm.cpp +++ b/src/engine/platform/sound/ymfm/ymfm_adpcm.cpp @@ -220,7 +220,7 @@ bool adpcm_a_channel::clock() //------------------------------------------------- template -void adpcm_a_channel::output(ymfm_output &output) const +void adpcm_a_channel::output(ymfm_output &output) { // volume combines instrument and total levels int vol = (m_regs.ch_instrument_level(m_choffs) ^ 0x1f) + (m_regs.total_level() ^ 0x3f); @@ -239,12 +239,18 @@ void adpcm_a_channel::output(ymfm_output &output) const int16_t value = ((int16_t(m_accumulator << 4) * mul) >> shift) & ~3; // apply to left/right as appropriate - if (NumOutputs == 1 || m_regs.ch_pan_left(m_choffs)) + if (NumOutputs == 1 || m_regs.ch_pan_left(m_choffs)) { output.data[0] += value; - if (NumOutputs > 1 && m_regs.ch_pan_right(m_choffs)) + m_lastOut[0] = value; + } + if (NumOutputs > 1 && m_regs.ch_pan_right(m_choffs)) { output.data[1] += value; + m_lastOut[1] = value; + } } +template void adpcm_a_channel::output<1>(ymfm_output<1> &output); +template void adpcm_a_channel::output<2>(ymfm_output<2> &output); //********************************************************* @@ -526,7 +532,7 @@ void adpcm_b_channel::clock() //------------------------------------------------- template -void adpcm_b_channel::output(ymfm_output &output, uint32_t rshift) const +void adpcm_b_channel::output(ymfm_output &output, uint32_t rshift) { // mask out some channels for debug purposes if ((debug::GLOBAL_ADPCM_B_CHANNEL_MASK & 1) == 0) @@ -539,10 +545,14 @@ void adpcm_b_channel::output(ymfm_output &output, uint32_t rshift) c result = (result * int32_t(m_regs.level())) >> (8 + rshift); // apply to left/right - if (NumOutputs == 1 || m_regs.pan_left()) + if (NumOutputs == 1 || m_regs.pan_left()) { + m_lastOut[0] = result; output.data[0] += result; - if (NumOutputs > 1 && m_regs.pan_right()) + } + if (NumOutputs > 1 && m_regs.pan_right()) { + m_lastOut[1] = result; output.data[1] += result; + } } diff --git a/src/engine/platform/sound/ymfm/ymfm_adpcm.h b/src/engine/platform/sound/ymfm/ymfm_adpcm.h index 4b4af0fdd..58bee8a22 100644 --- a/src/engine/platform/sound/ymfm/ymfm_adpcm.h +++ b/src/engine/platform/sound/ymfm/ymfm_adpcm.h @@ -146,7 +146,10 @@ public: // return the computed output value, with panning applied template - void output(ymfm_output &output) const; + void output(ymfm_output &output); + + // return the last output + int32_t get_last_out(int ch) { return m_lastOut[ch]; } private: // internal state @@ -158,6 +161,7 @@ private: uint32_t m_curaddress; // current address int32_t m_accumulator; // accumulator int32_t m_step_index; // index in the stepping table + int32_t m_lastOut[2]; // last output adpcm_a_registers &m_regs; // reference to registers adpcm_a_engine &m_owner; // reference to our owner }; @@ -203,6 +207,9 @@ public: // return a reference to our registers adpcm_a_registers ®s() { return m_regs; } + // debug functions + adpcm_a_channel* debug_channel(uint32_t index) const { return m_channel[index].get(); } + private: // internal state ymfm_interface &m_intf; // reference to the interface @@ -323,7 +330,7 @@ public: // return the computed output value, with panning applied template - void output(ymfm_output &output, uint32_t rshift) const; + void output(ymfm_output &output, uint32_t rshift); // return the status register uint8_t status() const { return m_status; } @@ -334,6 +341,9 @@ public: // handle special register writes void write(uint32_t regnum, uint8_t value); + // return the last output + int32_t get_last_out(int ch) { return m_lastOut[ch]; } + private: // helper - return the current address shift uint32_t address_shift() const; @@ -358,6 +368,7 @@ private: int32_t m_accumulator; // accumulator int32_t m_prev_accum; // previous accumulator (for linear interp) int32_t m_adpcm_step; // next forecast + int32_t m_lastOut[2]; // last output adpcm_b_registers &m_regs; // reference to registers adpcm_b_engine &m_owner; // reference to our owner }; @@ -384,6 +395,9 @@ public: template void output(ymfm_output &output, uint32_t rshift); + // get last output + int32_t get_last_out(int ch) { return m_channel->get_last_out(ch); } + // read from the ADPCM-B registers uint32_t read(uint32_t regnum) { return m_channel->read(regnum); } diff --git a/src/engine/platform/sound/ymfm/ymfm_fm.h b/src/engine/platform/sound/ymfm/ymfm_fm.h index 3239880e5..352ac9464 100644 --- a/src/engine/platform/sound/ymfm/ymfm_fm.h +++ b/src/engine/platform/sound/ymfm/ymfm_fm.h @@ -301,6 +301,7 @@ public: // simple getters for debugging fm_operator *debug_operator(uint32_t index) const { return m_op[index]; } + int32_t debug_output(uint32_t index) const { return m_output[index]; } private: // helper to add values to the outputs based on channel enables @@ -313,14 +314,22 @@ private: constexpr int out2_index = 2 % RegisterType::OUTPUTS; constexpr int out3_index = 3 % RegisterType::OUTPUTS; - if (RegisterType::OUTPUTS == 1 || m_regs.ch_output_0(choffs)) + if (RegisterType::OUTPUTS == 1 || m_regs.ch_output_0(choffs)) { + m_output[out0_index]=value; output.data[out0_index] += value; - if (RegisterType::OUTPUTS >= 2 && m_regs.ch_output_1(choffs)) + } + if (RegisterType::OUTPUTS >= 2 && m_regs.ch_output_1(choffs)) { + m_output[out1_index]=value; output.data[out1_index] += value; - if (RegisterType::OUTPUTS >= 3 && m_regs.ch_output_2(choffs)) + } + if (RegisterType::OUTPUTS >= 3 && m_regs.ch_output_2(choffs)) { + m_output[out2_index]=value; output.data[out2_index] += value; - if (RegisterType::OUTPUTS >= 4 && m_regs.ch_output_3(choffs)) + } + if (RegisterType::OUTPUTS >= 4 && m_regs.ch_output_3(choffs)) { + m_output[out3_index]=value; output.data[out3_index] += value; + } } // internal state @@ -330,6 +339,7 @@ private: fm_operator *m_op[4]; // up to 4 operators RegisterType &m_regs; // direct reference to registers fm_engine_base &m_owner; // reference to the owning engine + mutable int32_t m_output[4]; }; diff --git a/src/engine/platform/sound/ymfm/ymfm_fm.ipp b/src/engine/platform/sound/ymfm/ymfm_fm.ipp index 84948aedb..d532d7b2e 100644 --- a/src/engine/platform/sound/ymfm/ymfm_fm.ipp +++ b/src/engine/platform/sound/ymfm/ymfm_fm.ipp @@ -480,7 +480,7 @@ if (m_choffs == 0) #endif // early out if the envelope is effectively off - if (m_env_attenuation > EG_QUIET) + if (m_env_attenuation > EG_QUIET && m_cache.eg_shift == 0) return 0; // get the absolute value of the sin, as attenuation, as a 4.8 fixed point value @@ -808,7 +808,8 @@ fm_channel::fm_channel(fm_engine_base &owner, uint32 m_feedback_in(0), m_op{ nullptr, nullptr, nullptr, nullptr }, m_regs(owner.regs()), - m_owner(owner) + m_owner(owner), + m_output{ 0, 0, 0, 0 } { } @@ -823,6 +824,11 @@ void fm_channel::reset() // reset our data m_feedback[0] = m_feedback[1] = 0; m_feedback_in = 0; + + m_output[0] = 0; + m_output[1] = 0; + m_output[2] = 0; + m_output[3] = 0; } diff --git a/src/engine/platform/sound/ymfm/ymfm_opm.h b/src/engine/platform/sound/ymfm/ymfm_opm.h index ad657ac01..830b195e4 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opm.h +++ b/src/engine/platform/sound/ymfm/ymfm_opm.h @@ -281,6 +281,9 @@ public: // generate one sample of sound void generate(output_data *output, uint32_t numsamples = 1); + // get the engine + fm_engine* debug_engine() { return &m_fm; } + protected: // variants enum opm_variant diff --git a/src/engine/platform/sound/ymfm/ymfm_opn.h b/src/engine/platform/sound/ymfm/ymfm_opn.h index f4136c731..1b47885b9 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opn.h +++ b/src/engine/platform/sound/ymfm/ymfm_opn.h @@ -697,6 +697,12 @@ public: // generate one sample of sound void generate(output_data *output, uint32_t numsamples = 1); + // get the engine + fm_engine* debug_fm_engine() { return &m_fm; } + ssg_engine* debug_ssg_engine() { return &m_ssg; } + adpcm_a_engine* debug_adpcm_a_engine() { return &m_adpcm_a; } + adpcm_b_engine* debug_adpcm_b_engine() { return &m_adpcm_b; } + protected: // internal helpers void update_prescale(); @@ -761,6 +767,9 @@ public: // generate one sample of sound void generate(output_data *output, uint32_t numsamples = 1); + // get the engine + fm_engine* debug_engine() { return &m_fm; } + protected: // simulate the DAC discontinuity constexpr int32_t dac_discontinuity(int32_t value) const { return (value < 0) ? (value - 2) : (value + 3); } diff --git a/src/engine/platform/sound/ymfm/ymfm_opz.cpp b/src/engine/platform/sound/ymfm/ymfm_opz.cpp index 62a3d4d9c..94123bf62 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opz.cpp +++ b/src/engine/platform/sound/ymfm/ymfm_opz.cpp @@ -70,16 +70,24 @@ // OPZ supports a "fixed frequency" mode for each operator, with a 3-bit // range and 4-bit frequency value, plus a 1-bit enable. Not sure how that // works at all, so it's not implemented. +// note by tildearrow: +// - I have verified behavior of this mode against real hardware. +// after applying a small fix on the existing early implementation, it matches hardware. +// this means fixed frequency is fully implemented and working. // // There are also several mystery fields in the operators which I have no // clue about: "fine" (4 bits), "eg_shift" (2 bits), and "rev" (3 bits). // eg_shift is some kind of envelope generator effect, but how it works is // unknown. +// note by tildearrow: +// - behavior of "fine" is now confirmed and matches hardware. // // Also, according to the site above, the panning controls are changed from // OPM, with a "mono" bit and only one control bit for the right channel. // Current implementation is just a guess. // +// additional modifications by tildearrow for Furnace +// namespace ymfm { @@ -409,9 +417,6 @@ uint32_t opz_registers::lfo_am_offset(uint32_t choffs) const void opz_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache) { - // TODO: how does fixed frequency mode work? appears to be enabled by - // op_fix_mode(), and controlled by op_fix_range(), op_fix_frequency() - // TODO: what is op_rev()? // set up the easy stuff @@ -467,8 +472,8 @@ void opz_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata if (reverb != 0) cache.eg_rate[EG_REVERB] = std::min(effective_rate(reverb * 4 + 2, ksrval), cache.eg_rate[EG_REVERB]); - // set the envelope shift; TX81Z manual says operator 1 shift is fixed at "off" - cache.eg_shift = ((opoffs & 0x18) == 0) ? 0 : op_eg_shift(opoffs); + // set the envelope shift; TX81Z manual says operator 1 (actually operator 4) shift is fixed at "off" + cache.eg_shift = ((opoffs & 0x18) == 0x18) ? 0 : op_eg_shift(opoffs); } @@ -498,7 +503,7 @@ uint32_t opz_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opd // additional 12 bits of resolution; this calculation gives us, for // example, a frequency of 8.0009Hz when 8Hz is requested uint32_t substep = m_phase_substep[opoffs]; - substep += 75 * freq; + substep += 75 * 1024 * freq; phase_step = substep >> 12; m_phase_substep[opoffs] = substep & 0xfff; diff --git a/src/engine/platform/sound/ymfm/ymfm_opz.h b/src/engine/platform/sound/ymfm/ymfm_opz.h index 4bc4663a0..5be148a38 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opz.h +++ b/src/engine/platform/sound/ymfm/ymfm_opz.h @@ -320,6 +320,9 @@ public: // generate one sample of sound void generate(output_data *output, uint32_t numsamples = 1); + // get the engine + fm_engine* debug_engine() { return &m_fm; } + protected: // internal state uint8_t m_address; // address register diff --git a/src/engine/platform/sound/ymfm/ymfm_ssg.cpp b/src/engine/platform/sound/ymfm/ymfm_ssg.cpp index 3335d47c7..5a9a9d43e 100644 --- a/src/engine/platform/sound/ymfm/ymfm_ssg.cpp +++ b/src/engine/platform/sound/ymfm/ymfm_ssg.cpp @@ -232,6 +232,8 @@ void ssg_engine::output(output_data &output) // convert to amplitude output.data[chan] = s_amplitudes[volume]; } + + m_last_out=output; } diff --git a/src/engine/platform/sound/ymfm/ymfm_ssg.h b/src/engine/platform/sound/ymfm/ymfm_ssg.h index 9f31c92f5..55e748d6b 100644 --- a/src/engine/platform/sound/ymfm/ymfm_ssg.h +++ b/src/engine/platform/sound/ymfm/ymfm_ssg.h @@ -187,6 +187,9 @@ public: // indicate the prescale has changed void prescale_changed() { if (m_override != nullptr) m_override->ssg_prescale_changed(); } + // get the last output + void get_last_out(output_data& out) { out=m_last_out; } + private: // internal state ymfm_interface &m_intf; // reference to the interface @@ -198,6 +201,7 @@ private: uint32_t m_noise_state; // current noise state ssg_registers m_regs; // registers ssg_override *m_override; // override interface + output_data m_last_out; }; } diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp new file mode 100644 index 000000000..e80153f07 --- /dev/null +++ b/src/engine/platform/su.cpp @@ -0,0 +1,492 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "su.h" +#include "../engine.h" +#include "../../ta-log.h" +#include + +//#define rWrite(a,v) pendingWrites[a]=v; +#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define chWrite(c,a,v) rWrite(((c)<<5)|(a),v); + +#define CHIP_FREQBASE 524288 + +const char** DivPlatformSoundUnit::getRegisterSheet() { + return NULL; +} + +const char* DivPlatformSoundUnit::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xx: Set waveform (0 to 7)"; + break; + case 0x12: + return "12xx: Set pulse width (0 to 7F)"; + break; + case 0x13: + return "13xx: Set resonance (0 to F)"; + break; + case 0x14: + return "14xx: Set filter mode (bit 0: ring mod; bit 1: low pass; bit 2: band pass; bit 3: high pass)"; + break; + case 0x15: + return "15xx: Set frequency sweep period low byte"; + break; + case 0x16: + return "16xx: Set frequency sweep period high byte"; + break; + case 0x17: + return "17xx: Set volume sweep period low byte"; + break; + case 0x18: + return "18xx: Set volume sweep period high byte"; + break; + case 0x19: + return "19xx: Set cutoff sweep period low byte"; + break; + case 0x1a: + return "1Axx: Set cutoff sweep period high byte"; + break; + case 0x1b: + return "1Bxx: Set frequency sweep boundary"; + break; + case 0x1c: + return "1Cxx: Set volume sweep boundary"; + break; + case 0x1d: + return "1Dxx: Set cutoff sweep boundary"; + break; + case 0x20: + return "20xx: Toggle frequency sweep (bit 0-6: speed; bit 7: direction is up)"; + break; + case 0x21: + return "21xx: Toggle volume sweep (bit 0-4: speed; bit 5: direciton is up; bit 6: loop; bit 7: alternate)"; + break; + case 0x22: + return "22xx: Toggle cutoff sweep (bit 0-6: speed; bit 7: direction is up)"; + break; + case 0x40: case 0x41: case 0x42: case 0x43: + case 0x44: case 0x45: case 0x46: case 0x47: + case 0x48: case 0x49: case 0x4a: case 0x4b: + case 0x4c: case 0x4d: case 0x4e: case 0x4f: + return "4xxx: Set cutoff (0 to FFF)"; + break; + } + return NULL; +} + +void DivPlatformSoundUnit::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t h=start; hWrite(w.addr,w.val); + writes.pop(); + } + su->NextSample(&bufL[h],&bufR[h]); + for (int i=0; i<8; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=su->GetSample(i); + } + } +} + +void DivPlatformSoundUnit::writeControl(int ch) { + chWrite(ch,0x04,(chan[ch].wave&7)|(chan[ch].pcm<<3)|(chan[ch].control<<4)); +} + +void DivPlatformSoundUnit::writeControlUpper(int ch) { + chWrite(ch,0x05,((int)chan[ch].phaseReset)|(chan[ch].filterPhaseReset<<1)|(chan[ch].pcmLoop<<2)|(chan[ch].timerSync<<3)|(chan[ch].freqSweep<<4)|(chan[ch].volSweep<<5)|(chan[ch].cutSweep<<6)); + chan[ch].phaseReset=false; + chan[ch].filterPhaseReset=false; +} + +void DivPlatformSoundUnit::tick(bool sysTick) { + for (int i=0; i<8; i++) { + chan[i].std.next(); + if (chan[i].std.vol.had) { + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); + if (ins->type==DIV_INS_AMIGA) { + chan[i].outVol=((chan[i].vol&127)*MIN(64,chan[i].std.vol.val))>>6; + } else { + chan[i].outVol=((chan[i].vol&127)*MIN(127,chan[i].std.vol.val))>>7; + } + chWrite(i,0x02,chan[i].outVol); + } + if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); + } else { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.duty.had) { + chan[i].duty=chan[i].std.duty.val; + chWrite(i,0x08,chan[i].duty); + } + if (chan[i].std.wave.had) { + chan[i].wave=chan[i].std.wave.val&7; + writeControl(i); + } + if (chan[i].std.phaseReset.had) { + chan[i].phaseReset=chan[i].std.phaseReset.val; + writeControlUpper(i); + } + if (chan[i].std.panL.had) { + chan[i].pan=chan[i].std.panL.val; + chWrite(i,0x03,chan[i].pan); + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + if (chan[i].std.ex1.had) { + chan[i].cutoff=chan[i].std.ex1.val&16383; + chWrite(i,0x06,chan[i].cutoff&0xff); + chWrite(i,0x07,chan[i].cutoff>>8); + } + if (chan[i].std.ex2.had) { + chan[i].res=chan[i].std.ex2.val; + chWrite(i,0x09,chan[i].res); + } + if (chan[i].std.ex3.had) { + chan[i].control=chan[i].std.ex3.val&15; + writeControl(i); + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2); + if (chan[i].pcm) { + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); + DivSample* sample=parent->getSample(ins->amiga.initSample); + if (sample!=NULL) { + double off=0.25; + if (sample->centerRate<1) { + off=0.25; + } else { + off=(double)sample->centerRate/(8363.0*4.0); + } + chan[i].freq=(double)chan[i].freq*off; + } + } + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>65535) chan[i].freq=65535; + chWrite(i,0x00,chan[i].freq&0xff); + chWrite(i,0x01,chan[i].freq>>8); + if (chan[i].keyOn) { + if (chan[i].pcm) { + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); + DivSample* sample=parent->getSample(ins->amiga.initSample); + if (sample!=NULL) { + unsigned int sampleEnd=sample->offSU+sample->samples; + if (sampleEnd>=getSampleMemCapacity(0)) sampleEnd=getSampleMemCapacity(0)-1; + chWrite(i,0x0a,sample->offSU&0xff); + chWrite(i,0x0b,sample->offSU>>8); + chWrite(i,0x0c,sampleEnd&0xff); + chWrite(i,0x0d,sampleEnd>>8); + if (sample->loopStart>=0 && sample->loopStart<(int)sample->samples) { + unsigned int sampleLoop=sample->offSU+sample->loopStart; + if (sampleLoop>=getSampleMemCapacity(0)) sampleLoop=getSampleMemCapacity(0)-1; + chWrite(i,0x0e,sampleLoop&0xff); + chWrite(i,0x0f,sampleLoop>>8); + chan[i].pcmLoop=true; + } else { + chan[i].pcmLoop=false; + } + writeControl(i); + writeControlUpper(i); + } + } + } + if (chan[i].keyOff) { + chWrite(i,0x02,0); + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformSoundUnit::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SU); + if (chan[c.chan].pcm && ins->type!=DIV_INS_AMIGA) { + writeControl(c.chan); + writeControlUpper(c.chan); + } + chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chWrite(c.chan,0x02,chan[c.chan].vol); + chan[c.chan].macroInit(ins); + chan[c.chan].insChanged=false; + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + chan[c.chan].insChanged=true; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + if (chan[c.chan].active) chWrite(c.chan,0x02,chan[c.chan].outVol); + } + } + break; + case DIV_CMD_GET_VOLUME: + if (chan[c.chan].std.vol.has) { + return chan[c.chan].vol; + } + return chan[c.chan].outVol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_WAVE: + chan[c.chan].wave=c.value; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_FREQUENCY(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value*(1+(chan[c.chan].baseFreq>>9)); + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value*(1+(chan[c.chan].baseFreq>>9)); + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_PANNING: { + chan[c.chan].pan=parent->convertPanSplitToLinearLR(c.value,c.value2,254)-127; + chWrite(c.chan,0x03,chan[c.chan].pan); + break; + } + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SU)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 127; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformSoundUnit::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + su->muted[ch]=mute; +} + +void DivPlatformSoundUnit::forceIns() { + for (int i=0; i<8; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + } +} + +void* DivPlatformSoundUnit::getChanState(int ch) { + return &chan[ch]; +} + +DivDispatchOscBuffer* DivPlatformSoundUnit::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformSoundUnit::getRegisterPool() { + return (unsigned char*)su->chan; +} + +int DivPlatformSoundUnit::getRegisterPoolSize() { + return 256; +} + +void DivPlatformSoundUnit::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,128); + for (int i=0; i<8; i++) { + chan[i]=DivPlatformSoundUnit::Channel(); + chan[i].std.setEngine(parent); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + su->Reset(); + for (int i=0; i<8; i++) { + chWrite(i,0x08,0x3f); + } + lastPan=0xff; + cycles=0; + curChan=-1; + sampleBank=0; + lfoMode=0; + lfoSpeed=255; + delay=500; +} + +bool DivPlatformSoundUnit::isStereo() { + return true; +} + +bool DivPlatformSoundUnit::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformSoundUnit::notifyInsDeletion(void* ins) { + for (int i=0; i<8; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformSoundUnit::setFlags(unsigned int flags) { + if (flags&1) { + chipClock=1190000; + } else { + chipClock=1236000; + } + rate=chipClock/4; + for (int i=0; i<8; i++) { + oscBuf[i]->rate=rate; + } +} + +void DivPlatformSoundUnit::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformSoundUnit::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +const void* DivPlatformSoundUnit::getSampleMem(int index) { + return (index==0)?su->pcm:NULL; +} + +size_t DivPlatformSoundUnit::getSampleMemCapacity(int index) { + return (index==0)?8192:0; +} + +size_t DivPlatformSoundUnit::getSampleMemUsage(int index) { + return (index==0)?sampleMemLen:0; +} + +void DivPlatformSoundUnit::renderSamples() { + memset(su->pcm,0,getSampleMemCapacity(0)); + + size_t memPos=0; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + int paddedLen=s->samples; + if (memPos>=getSampleMemCapacity(0)) { + logW("out of PCM memory for sample %d!",i); + break; + } + if (memPos+paddedLen>=getSampleMemCapacity(0)) { + memcpy(su->pcm+memPos,s->data8,getSampleMemCapacity(0)-memPos); + logW("out of PCM memory for sample %d!",i); + } else { + memcpy(su->pcm+memPos,s->data8,paddedLen); + } + s->offSU=memPos; + memPos+=paddedLen; + } + sampleMemLen=memPos; + +} + +int DivPlatformSoundUnit::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<8; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + setFlags(flags); + su=new SoundUnit(); + su->Init(); + reset(); + return 6; +} + +void DivPlatformSoundUnit::quit() { + for (int i=0; i<8; i++) { + delete oscBuf[i]; + } + delete su; +} + +DivPlatformSoundUnit::~DivPlatformSoundUnit() { +} diff --git a/src/engine/platform/su.h b/src/engine/platform/su.h new file mode 100644 index 000000000..22ea1e78d --- /dev/null +++ b/src/engine/platform/su.h @@ -0,0 +1,123 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _SU_H +#define _SU_H + +#include "../dispatch.h" +#include +#include "../macroInt.h" +#include "sound/su.h" + +class DivPlatformSoundUnit: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, pitch2, note; + int ins, cutoff, res, control; + signed char pan; + unsigned char duty; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, phaseReset, filterPhaseReset; + bool pcmLoop, timerSync, freqSweep, volSweep, cutSweep; + signed char vol, outVol, wave; + DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): + freq(0), + baseFreq(0), + pitch(0), + pitch2(0), + note(0), + ins(-1), + cutoff(65535), + res(0), + control(0), + pan(0), + duty(63), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + noise(false), + pcm(false), + phaseReset(false), + filterPhaseReset(false), + pcmLoop(false), + timerSync(false), + freqSweep(false), + volSweep(false), + cutSweep(false), + vol(127), + outVol(127), + wave(0) {} + }; + Channel chan[8]; + DivDispatchOscBuffer* oscBuf[8]; + bool isMuted[8]; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + }; + std::queue writes; + unsigned char lastPan; + + int cycles, curChan, delay; + short tempL; + short tempR; + unsigned char sampleBank, lfoMode, lfoSpeed; + SoundUnit* su; + size_t sampleMemLen; + unsigned char regPool[128]; + void writeControl(int ch); + void writeControlUpper(int ch); + + friend void putDispatchChan(void*,int,int); + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + bool isStereo(); + bool keyOffAffectsArp(int ch); + void setFlags(unsigned int flags); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + const void* getSampleMem(int index); + size_t getSampleMemCapacity(int index); + size_t getSampleMemUsage(int index); + void renderSamples(); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformSoundUnit(); +}; + +#endif diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 802f27b86..97aa7b9ef 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -107,6 +107,9 @@ void DivPlatformSwan::acquire(short* bufL, short* bufR, size_t start, size_t len ws->SoundFlush(samp, 1); bufL[h]=samp[0]; bufR[h]=samp[1]; + for (int i=0; i<4; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(ws->sample_cache[i][0]+ws->sample_cache[i][1])<<6; + } } } @@ -141,13 +144,13 @@ void DivPlatformSwan::writeOutVol(int ch) { } } -void DivPlatformSwan::tick() { +void DivPlatformSwan::tick(bool sysTick) { unsigned char sndCtrl=(pcm?0x20:0)|(sweep?0x40:0)|((noise>0)?0x80:0); for (int i=0; i<4; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { int env=chan[i].std.vol.val; - if(parent->getIns(chan[i].ins)->type==DIV_INS_AMIGA) { + if(parent->getIns(chan[i].ins,DIV_INS_SWAN)->type==DIV_INS_AMIGA) { env=MIN(env/4,15); } calcAndWriteOutVol(i,env); @@ -173,6 +176,26 @@ void DivPlatformSwan::tick() { chan[i].ws.changeWave1(chan[i].wave); } } + if (chan[i].std.panL.had) { + chan[i].pan&=0x0f; + chan[i].pan|=(chan[i].std.panL.val&15)<<4; + } + if (chan[i].std.panR.had) { + chan[i].pan&=0xf0; + chan[i].pan|=chan[i].std.panR.val&15; + } + if (chan[i].std.panL.had || chan[i].std.panR.had) { + calcAndWriteOutVol(i,chan[i].std.vol.will?chan[i].std.vol.val:15); + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } if (chan[i].active) { sndCtrl|=(1<calcFreq(chan[i].baseFreq,chan[i].pitch,true); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); if (i==1 && pcm && furnaceDac) { double off=1.0; if (dacSample>=0 && dacSamplesong.sampleLen) { @@ -212,12 +235,14 @@ void DivPlatformSwan::tick() { } } if (chan[3].std.duty.had) { - noise=chan[3].std.duty.val; - if (noise>0) { - rWrite(0x0e,((noise-1)&0x07)|0x18); - sndCtrl|=0x80; - } else { - sndCtrl&=~0x80; + if (noise!=chan[3].std.duty.val) { + noise=chan[3].std.duty.val; + if (noise>0) { + rWrite(0x0e,((noise-1)&0x07)|0x18); + sndCtrl|=0x80; + } else { + sndCtrl&=~0x80; + } } } rWrite(0x10,sndCtrl); @@ -226,7 +251,7 @@ void DivPlatformSwan::tick() { int DivPlatformSwan::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SWAN); if (c.chan==1) { if (ins->type==DIV_INS_AMIGA) { pcm=true; @@ -255,7 +280,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { } chan[1].active=true; chan[1].keyOn=true; - chan[1].std.init(ins); + chan[1].macroInit(ins); furnaceDac=true; } else { if (c.value!=DIV_NOTE_NULL) { @@ -287,7 +312,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); if (chan[c.chan].wave<0) { chan[c.chan].wave=0; chan[c.chan].ws.changeWave1(chan[c.chan].wave); @@ -304,7 +329,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { } chan[c.chan].active=false; chan[c.chan].keyOff=true; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -390,7 +415,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { } break; case DIV_CMD_PANNING: { - chan[c.chan].pan=c.value; + chan[c.chan].pan=(c.value&0xf0)|(c.value2>>4); calcAndWriteOutVol(c.chan,chan[c.chan].std.vol.will?chan[c.chan].std.vol.val:15); break; } @@ -401,7 +426,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SWAN)); } chan[c.chan].inPorta=c.value; break; @@ -435,6 +460,10 @@ void* DivPlatformSwan::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformSwan::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformSwan::getRegisterPool() { // get Random from emulator regPool[0x12]=ws->SoundRead(0x92); @@ -453,6 +482,7 @@ void DivPlatformSwan::reset() { chan[i]=Channel(); chan[i].vol=15; chan[i].pan=0xff; + chan[i].std.setEngine(parent); chan[i].ws.setEngine(parent); chan[i].ws.init(NULL,32,15,false); rWrite(0x08+i,0xff); @@ -509,6 +539,8 @@ int DivPlatformSwan::init(DivEngine* p, int channels, int sugRate, unsigned int rate=chipClock/16; // = 192000kHz, should be enough for (int i=0; i<4; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + oscBuf[i]->rate=rate; } ws=new WSwan(); reset(); @@ -516,6 +548,9 @@ int DivPlatformSwan::init(DivEngine* p, int channels, int sugRate, unsigned int } void DivPlatformSwan::quit() { + for (int i=0; i<4; i++) { + delete oscBuf[i]; + } delete ws; } diff --git a/src/engine/platform/swan.h b/src/engine/platform/swan.h index 610884b00..32f400e5b 100644 --- a/src/engine/platform/swan.h +++ b/src/engine/platform/swan.h @@ -28,16 +28,21 @@ class DivPlatformSwan: public DivDispatch { struct Channel { - int freq, baseFreq, pitch, note; - unsigned char ins, pan; + int freq, baseFreq, pitch, pitch2, note, ins; + unsigned char pan; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; int vol, outVol, wave; DivMacroInt std; DivWaveSynth ws; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freq(0), baseFreq(0), pitch(0), + pitch2(0), note(0), ins(-1), pan(255), @@ -52,6 +57,7 @@ class DivPlatformSwan: public DivDispatch { wave(-1) {} }; Channel chan[4]; + DivDispatchOscBuffer* oscBuf[4]; bool isMuted[4]; bool pcm, sweep, furnaceDac; unsigned char sampleBank, noise; @@ -73,11 +79,12 @@ class DivPlatformSwan: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); void notifyWaveChange(int wave); void notifyInsDeletion(void* ins); diff --git a/src/engine/platform/tia.cpp b/src/engine/platform/tia.cpp index e06c5c74f..d3bd5ce2d 100644 --- a/src/engine/platform/tia.cpp +++ b/src/engine/platform/tia.cpp @@ -48,7 +48,7 @@ const char** DivPlatformTIA::getRegisterSheet() { } void DivPlatformTIA::acquire(short* bufL, short* bufR, size_t start, size_t len) { - tia.process(bufL+start,len); + tia.process(bufL+start,len,oscBuf); } unsigned char DivPlatformTIA::dealWithFreq(unsigned char shape, int base, int pitch) { @@ -84,7 +84,7 @@ unsigned char DivPlatformTIA::dealWithFreq(unsigned char shape, int base, int pi return 0; } -void DivPlatformTIA::tick() { +void DivPlatformTIA::tick(bool sysTick) { for (int i=0; i<2; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { @@ -116,6 +116,15 @@ void DivPlatformTIA::tick() { rWrite(0x15+i,chan[i].shape); chan[i].freqChanged=true; } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].insChanged) { if (!chan[i].std.wave.will) { @@ -124,14 +133,14 @@ void DivPlatformTIA::tick() { } chan[i].insChanged=false; } - chan[i].freq=dealWithFreq(chan[i].shape,chan[i].baseFreq,chan[i].pitch); + chan[i].freq=dealWithFreq(chan[i].shape,chan[i].baseFreq,chan[i].pitch)+chan[i].pitch2; if ((chan[i].shape==4 || chan[i].shape==5) && !(chan[i].baseFreq&0x80000000 && ((chan[i].baseFreq&0x7fffffff)<32))) { if (chan[i].baseFreq<39*256) { rWrite(0x15+i,6); - chan[i].freq=dealWithFreq(6,chan[i].baseFreq,chan[i].pitch); + chan[i].freq=dealWithFreq(6,chan[i].baseFreq,chan[i].pitch)+chan[i].pitch2; } else if (chan[i].baseFreq<59*256) { rWrite(0x15+i,12); - chan[i].freq=dealWithFreq(12,chan[i].baseFreq,chan[i].pitch); + chan[i].freq=dealWithFreq(12,chan[i].baseFreq,chan[i].pitch)+chan[i].pitch2; } else { rWrite(0x15+i,chan[i].shape); } @@ -151,7 +160,7 @@ void DivPlatformTIA::tick() { int DivPlatformTIA::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_TIA); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=c.value<<8; chan[c.chan].freqChanged=true; @@ -160,7 +169,7 @@ int DivPlatformTIA::dispatch(DivCommand c) { chan[c.chan].active=true; chan[c.chan].keyOn=true; rWrite(0x15+c.chan,chan[c.chan].shape); - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); if (isMuted[c.chan]) { rWrite(0x19+c.chan,0); } else { @@ -171,7 +180,7 @@ int DivPlatformTIA::dispatch(DivCommand c) { case DIV_CMD_NOTE_OFF: chan[c.chan].keyOff=true; chan[c.chan].active=false; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -245,7 +254,7 @@ int DivPlatformTIA::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_TIA)); } chan[c.chan].inPorta=c.value; break; @@ -281,6 +290,10 @@ void* DivPlatformTIA::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformTIA::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformTIA::getRegisterPool() { return regPool; } @@ -294,6 +307,7 @@ void DivPlatformTIA::reset() { memset(regPool,0,16); for (int i=0; i<2; i++) { chan[i]=DivPlatformTIA::Channel(); + chan[i].std.setEngine(parent); chan[i].vol=0x0f; } } @@ -327,6 +341,9 @@ void DivPlatformTIA::setFlags(unsigned int flags) { rate=31468; } chipClock=rate; + for (int i=0; i<2; i++) { + oscBuf[i]->rate=rate; + } } int DivPlatformTIA::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { @@ -335,6 +352,7 @@ int DivPlatformTIA::init(DivEngine* p, int channels, int sugRate, unsigned int f skipRegisterWrites=false; for (int i=0; i<2; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } tia.channels(1,false); setFlags(flags); diff --git a/src/engine/platform/tia.h b/src/engine/platform/tia.h index ea149ec34..76064d069 100644 --- a/src/engine/platform/tia.h +++ b/src/engine/platform/tia.h @@ -27,15 +27,20 @@ class DivPlatformTIA: public DivDispatch { protected: struct Channel { - int freq, baseFreq, pitch, note; - unsigned char ins, shape; + int freq, baseFreq, pitch, pitch2, note, ins; + unsigned char shape; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; int vol, outVol; DivMacroInt std; - Channel(): freq(0), baseFreq(0), pitch(0), note(0), ins(-1), shape(4), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(15) {} + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): freq(0), baseFreq(0), pitch(0), pitch2(0), note(0), ins(-1), shape(4), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(15) {} }; Channel chan[2]; + DivDispatchOscBuffer* oscBuf[2]; bool isMuted[2]; TIASound tia; unsigned char regPool[16]; @@ -47,11 +52,12 @@ class DivPlatformTIA: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); void setFlags(unsigned int flags); bool isStereo(); diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index 7ea634b26..c3f7e9991 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -137,8 +137,84 @@ const char* DivPlatformTX81Z::getEffectName(unsigned char effect) { case 0x1f: return "1Fxx: Set PM depth (0 to 7F)"; break; - case 0x30: - return "30xx: Toggle hard envelope reset on new notes"; + case 0x28: + return "28xy: Set reverb (x: operator from 1 to 4 (0 for all ops); y: reverb from 0 to 7)"; + break; + case 0x2a: + return "2Axy: Set waveform (x: operator from 1 to 4 (0 for all ops); y: waveform from 0 to 7)"; + break; + case 0x2b: + return "2Bxy: Set envelope generator shift (x: operator from 1 to 4 (0 for all ops); y: shift from 0 to 3)"; + break; + case 0x2c: + return "2Cxy: Set fine multiplier (x: operator from 1 to 4 (0 for all ops); y: fine)"; + break; + case 0x2f: + return "2Fxx: Toggle hard envelope reset on new notes"; + break; + case 0x30: case 0x31: case 0x32: case 0x33: + case 0x34: case 0x35: case 0x36: case 0x37: + return "3xyy: Set fixed frequency of operator 1 (x: octave from 0 to 7; y: frequency)"; + break; + case 0x38: case 0x39: case 0x3a: case 0x3b: + case 0x3c: case 0x3d: case 0x3e: case 0x3f: + return "3xyy: Set fixed frequency of operator 2 (x: octave from 8 to F; y: frequency)"; + break; + case 0x40: case 0x41: case 0x42: case 0x43: + case 0x44: case 0x45: case 0x46: case 0x47: + return "4xyy: Set fixed frequency of operator 3 (x: octave from 0 to 7; y: frequency)"; + break; + case 0x48: case 0x49: case 0x4a: case 0x4b: + case 0x4c: case 0x4d: case 0x4e: case 0x4f: + return "4xyy: Set fixed frequency of operator 4 (x: octave from 8 to F; y: frequency)"; + break; + case 0x50: + return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; + break; + case 0x51: + return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; + break; + case 0x52: + return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; + break; + case 0x53: + return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)"; + break; + case 0x54: + return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)"; + break; + case 0x55: + return "55xy: Set detune 2 (x: operator from 1 to 4 (0 for all ops); y: detune from 0 to 3)"; + break; + case 0x56: + return "56xx: Set decay of all operators (0 to 1F)"; + break; + case 0x57: + return "57xx: Set decay of operator 1 (0 to 1F)"; + break; + case 0x58: + return "58xx: Set decay of operator 2 (0 to 1F)"; + break; + case 0x59: + return "59xx: Set decay of operator 3 (0 to 1F)"; + break; + case 0x5a: + return "5Axx: Set decay of operator 4 (0 to 1F)"; + break; + case 0x5b: + return "5Bxx: Set decay 2 of all operators (0 to 1F)"; + break; + case 0x5c: + return "5Cxx: Set decay 2 of operator 1 (0 to 1F)"; + break; + case 0x5d: + return "5Dxx: Set decay 2 of operator 2 (0 to 1F)"; + break; + case 0x5e: + return "5Exx: Set decay 2 of operator 3 (0 to 1F)"; + break; + case 0x5f: + return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; break; } return NULL; @@ -147,6 +223,8 @@ const char* DivPlatformTX81Z::getEffectName(unsigned char effect) { void DivPlatformTX81Z::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; + ymfm::ym2414::fm_engine* fme=fm_ymfm->debug_engine(); + for (size_t h=start; hgenerate(&out_ymfm); + for (int i=0; i<8; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1)); + } + os[0]=out_ymfm.data[0]; if (os[0]<-32768) os[0]=-32768; if (os[0]>32767) os[0]=32767; @@ -183,7 +265,7 @@ inline int hScale(int note) { return ((note/12)<<4)+(noteMap[note%12]); } -void DivPlatformTX81Z::tick() { +void DivPlatformTX81Z::tick(bool sysTick) { for (int i=0; i<8; i++) { chan[i].std.next(); @@ -192,10 +274,14 @@ void DivPlatformTX81Z::tick() { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; - if (isOutput[chan[i].state.alg][j]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } } } } @@ -228,6 +314,22 @@ void DivPlatformTX81Z::tick() { rWrite(0x1b,chan[i].std.wave.val&3); } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + chan[i].keyOn=true; + } + } + if (chan[i].std.ex1.had) { amDepth=chan[i].std.ex1.val; immWrite(0x19,amDepth); @@ -244,11 +346,7 @@ void DivPlatformTX81Z::tick() { if (chan[i].std.alg.had) { chan[i].state.alg=chan[i].std.alg.val; - if (isMuted[i]) { - immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x40); - } else { - immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0:0x40)|(chan[i].chVolR<<7)); - } + immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0:0x40)|(chan[i].chVolR<<7)); if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -265,11 +363,7 @@ void DivPlatformTX81Z::tick() { } if (chan[i].std.fb.had) { chan[i].state.fb=chan[i].std.fb.val; - if (isMuted[i]) { - immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x40); - } else { - immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0:0x40)|(chan[i].chVolR<<7)); - } + immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0:0x40)|(chan[i].chVolR<<7)); } if (chan[i].std.fms.had) { chan[i].state.fms=chan[i].std.fms.val; @@ -297,7 +391,7 @@ void DivPlatformTX81Z::tick() { } if (m.mult.had) { op.mult=m.mult.val; - rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4)); } if (m.rr.had) { op.rr=m.rr.val; @@ -309,10 +403,14 @@ void DivPlatformTX81Z::tick() { } if (m.tl.had) { op.tl=127-m.tl.val; - if (isOutput[chan[i].state.alg][j]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } } } if (m.rs.had) { @@ -321,7 +419,7 @@ void DivPlatformTX81Z::tick() { } if (m.dt.had) { op.dt=m.dt.val; - rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4)); } if (m.d2r.had) { op.d2r=m.d2r.val; @@ -342,12 +440,8 @@ void DivPlatformTX81Z::tick() { oldWrites[baseAddr+ADDR_TL]=-1; } } - if (isMuted[i]) { - immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x00); - } else { - //if (chan[i].keyOn) immWrite(0x08,i); - immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x00|(chan[i].chVolR<<7)); - } + //if (chan[i].keyOn) immWrite(0x08,i); + immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x00|(chan[i].chVolR<<7)); if (chan[i].hardReset && chan[i].keyOn) { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; @@ -381,7 +475,7 @@ void DivPlatformTX81Z::tick() { for (int i=0; i<8; i++) { if (chan[i].freqChanged) { - chan[i].freq=chan[i].baseFreq+(chan[i].pitch>>1)-64; + chan[i].freq=chan[i].baseFreq+(chan[i].pitch>>1)-64+chan[i].pitch2; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>=(95<<6)) chan[i].freq=(95<<6)-1; immWrite(i+0x28,hScale(chan[i].freq>>6)); @@ -389,12 +483,8 @@ void DivPlatformTX81Z::tick() { chan[i].freqChanged=false; } if (chan[i].keyOn) { - if (isMuted[i]) { - immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - } else { - //immWrite(0x08,i); - immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x40|(chan[i].chVolR<<7)); - } + //immWrite(0x08,i); + immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x40|(chan[i].chVolR<<7)); chan[i].keyOn=false; } } @@ -402,25 +492,31 @@ void DivPlatformTX81Z::tick() { void DivPlatformTX81Z::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - // TODO: use volume registers! - /* - if (isMuted[ch]) { - immWrite(chanOffs[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&7)|(chan[ch].state.fb<<3)); - } else { - immWrite(chanOffs[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&7)|(chan[ch].state.fb<<3)|((chan[ch].chVolL&1)<<6)|((chan[ch].chVolR&1)<<7)); - }*/ + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[ch]|opOffs[i]; + DivInstrumentFM::Operator op=chan[ch].state.op[i]; + if (isMuted[ch]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[ch].state.alg][i]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[ch].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } } int DivPlatformTX81Z::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_OPZ); if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; } - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } @@ -428,17 +524,21 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator op=chan[c.chan].state.op[i]; - if (isOutput[chan[c.chan].state.alg][i]) { - if (!chan[c.chan].active || chan[c.chan].insChanged) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); - } + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); } else { - if (chan[c.chan].insChanged) { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[c.chan].state.alg][i]) { + if (!chan[c.chan].active || chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } + } else { + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,op.tl); + } } } if (chan[c.chan].insChanged) { - rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4)); rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6)); rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); @@ -490,10 +590,14 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; - if (isOutput[chan[c.chan].state.alg][i]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[c.chan].state.alg][i]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } } } break; @@ -509,8 +613,8 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { - chan[c.chan].chVolL=((c.value>>4)>0); - chan[c.chan].chVolR=((c.value&15)>0); + chan[c.chan].chVolL=(c.value>0); + chan[c.chan].chVolR=(c.value2>0); chan[c.chan].freqChanged=true; /* if (isMuted[c.chan]) { @@ -576,18 +680,24 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { case DIV_CMD_FM_MULT: { unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; - op.mult=c.value2&15; - rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + if (!op.egt) { + op.mult=c.value2&15; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4)); + } break; } case DIV_CMD_FM_TL: { unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; op.tl=c.value2; - if (isOutput[chan[c.chan].state.alg][c.value]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[c.chan].state.alg][c.value]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } } break; } @@ -607,6 +717,222 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { } break; } + case DIV_CMD_FM_RS: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_AM: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_DR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_SL: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_RR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_DT2: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dt2=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dt2=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); + } + break; + } + case DIV_CMD_FM_D2R: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); + } + break; + } + case DIV_CMD_FM_DT: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (!op.egt) { + op.dt=c.value&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4)); + } + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + if (!op.egt) { + op.dt=c.value2&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4)); + } + } + break; + } + case DIV_CMD_FM_EG_SHIFT: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ksl=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_EGS_REV,(op.dam&7)|(op.ksl<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ksl=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_EGS_REV,(op.dam&7)|(op.ksl<<6)); + } + break; + } + case DIV_CMD_FM_REV: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dam=c.value2&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_EGS_REV,(op.dam&7)|(op.ksl<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dam=c.value2&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_EGS_REV,(op.dam&7)|(op.ksl<<6)); + } + break; + } + case DIV_CMD_FM_WS: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ws=c.value2&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_WS_FINE,(op.dvb&15)|(op.ws<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ws=c.value2&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_WS_FINE,(op.dvb&15)|(op.ws<<4)); + } + break; + } + case DIV_CMD_FM_FINE: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (!op.egt) { + op.dvb=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_WS_FINE,(op.dvb&15)|(op.ws<<4)); + } + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + if (!op.egt) { + op.dvb=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_WS_FINE,(op.dvb&15)|(op.ws<<4)); + } + } + break; + } + case DIV_CMD_FM_FIXFREQ: { + if (c.value<0 || c.value>3) break; + printf("fixfreq %x\n",c.value2); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.egt=(c.value2>0); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6)); + if (op.egt) { + rWrite(baseAddr+ADDR_MULT_DT,((c.value2>>4)&15)|(((c.value2>>8)&7)<<4)); + rWrite(baseAddr+ADDR_WS_FINE,(c.value2&15)|(op.ws<<4)); + } else { + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4)); + rWrite(baseAddr+ADDR_WS_FINE,(op.dvb&15)|(op.ws<<4)); + } + break; + } case DIV_CMD_FM_AM_DEPTH: { amDepth=c.value; immWrite(0x19,amDepth); @@ -656,12 +982,16 @@ void DivPlatformTX81Z::forceIns() { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator op=chan[i].state.op[j]; - if (isOutput[chan[i].state.alg][j]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } } - rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4)); rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6)); rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); @@ -698,6 +1028,10 @@ void* DivPlatformTX81Z::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformTX81Z::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformTX81Z::getRegisterPool() { return regPool; } @@ -723,6 +1057,7 @@ void DivPlatformTX81Z::reset() { } for (int i=0; i<8; i++) { chan[i]=DivPlatformTX81Z::Channel(); + chan[i].std.setEngine(parent); chan[i].vol=0x7f; chan[i].outVol=0x7f; } @@ -760,6 +1095,9 @@ void DivPlatformTX81Z::setFlags(unsigned int flags) { baseFreqOff=0; } rate=chipClock/64; + for (int i=0; i<8; i++) { + oscBuf[i]->rate=rate; + } } bool DivPlatformTX81Z::isStereo() { @@ -772,6 +1110,7 @@ int DivPlatformTX81Z::init(DivEngine* p, int channels, int sugRate, unsigned int skipRegisterWrites=false; for (int i=0; i<8; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); fm_ymfm=new ymfm::ym2414(iface); @@ -781,6 +1120,9 @@ int DivPlatformTX81Z::init(DivEngine* p, int channels, int sugRate, unsigned int } void DivPlatformTX81Z::quit() { + for (int i=0; i<8; i++) { + delete oscBuf[i]; + } delete fm_ymfm; } diff --git a/src/engine/platform/tx81z.h b/src/engine/platform/tx81z.h index 363d5c234..60ea66ae0 100644 --- a/src/engine/platform/tx81z.h +++ b/src/engine/platform/tx81z.h @@ -35,18 +35,22 @@ class DivPlatformTX81Z: public DivDispatch { DivInstrumentFM state; DivMacroInt std; unsigned char freqH, freqL; - int freq, baseFreq, pitch, note; - unsigned char ins; + int freq, baseFreq, pitch, pitch2, note, ins; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset; int vol, outVol; unsigned char chVolL, chVolR; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), + pitch2(0), note(0), ins(-1), active(false), @@ -64,6 +68,7 @@ class DivPlatformTX81Z: public DivDispatch { chVolR(127) {} }; Channel chan[8]; + DivDispatchOscBuffer* oscBuf[8]; struct QueuedWrite { unsigned short addr; unsigned char val; @@ -98,11 +103,12 @@ class DivPlatformTX81Z: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); void notifyInsChange(int ins); void setFlags(unsigned int flags); diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 77f5c8050..4dcc8f6c5 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -111,12 +111,20 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len } int curLen=MIN(len,128); memset(buf,0,sizeof(buf)); - psg_render(psg,buf[0],buf[1],curLen); pcm_render(pcm,buf[2],buf[3],curLen); for (int i=0; idata[oscBuf[i]->needle++]=psg->channels[i].lastOut<<4; + } + int pcmOut=buf[2][i]+buf[3][i]; + if (pcmOut<-32768) pcmOut=-32768; + if (pcmOut>32767) pcmOut=32767; + oscBuf[16]->data[oscBuf[16]->needle++]=pcmOut; } len-=curLen; } @@ -125,6 +133,7 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len void DivPlatformVERA::reset() { for (int i=0; i<17; i++) { chan[i]=Channel(); + chan[i].std.setEngine(parent); } psg_reset(psg); pcm_reset(pcm); @@ -155,7 +164,7 @@ int DivPlatformVERA::calcNoteFreq(int ch, int note) { } } -void DivPlatformVERA::tick() { +void DivPlatformVERA::tick(bool sysTick) { for (int i=0; i<16; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { @@ -183,8 +192,23 @@ void DivPlatformVERA::tick() { if (chan[i].std.wave.had) { rWriteHi(i,3,chan[i].std.wave.val); } + if (i<16) { + if (chan[i].std.panL.had) { + chan[i].pan=chan[i].std.panL.val&3; + rWriteHi(i,2,isMuted[i]?0:chan[i].pan); + } + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2); if (chan[i].freq>65535) chan[i].freq=65535; rWrite(i,0,chan[i].freq&0xff); rWrite(i,1,(chan[i].freq>>8)&0xff); @@ -213,7 +237,7 @@ void DivPlatformVERA::tick() { } } if (chan[16].freqChanged) { - chan[16].freq=parent->calcFreq(chan[16].baseFreq,chan[16].pitch,false,8); + chan[16].freq=parent->calcFreq(chan[16].baseFreq,chan[16].pitch,false,8,chan[16].pitch2); if (chan[16].freq>128) chan[16].freq=128; rWritePCMRate(chan[16].freq&0xff); chan[16].freqChanged=false; @@ -227,7 +251,7 @@ int DivPlatformVERA::dispatch(DivCommand c) { if(c.chan<16) { rWriteLo(c.chan,2,chan[c.chan].vol) } else { - chan[16].pcm.sample=parent->getIns(chan[16].ins)->amiga.initSample; + chan[16].pcm.sample=parent->getIns(chan[16].ins,DIV_INS_VERA)->amiga.initSample; if (chan[16].pcm.sample<0 || chan[16].pcm.sample>=parent->song.sampleLen) { chan[16].pcm.sample=-1; } @@ -249,7 +273,7 @@ int DivPlatformVERA::dispatch(DivCommand c) { chan[c.chan].note=c.value; } chan[c.chan].active=true; - chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_VERA)); break; case DIV_CMD_NOTE_OFF: chan[c.chan].active=false; @@ -260,7 +284,7 @@ int DivPlatformVERA::dispatch(DivCommand c) { rWritePCMCtrl(0x80); rWritePCMRate(0); } - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -317,7 +341,7 @@ int DivPlatformVERA::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_VERA)); } chan[c.chan].inPorta=c.value; break; @@ -329,8 +353,8 @@ int DivPlatformVERA::dispatch(DivCommand c) { break; case DIV_CMD_PANNING: { tmp=0; - tmp|=(c.value&0x10)?1:0; - tmp|=(c.value&0x01)?2:0; + tmp|=(c.value>0)?1:0; + tmp|=(c.value2>0)?2:0; chan[c.chan].pan=tmp&3; if (c.chan<16) { rWriteHi(c.chan,2,isMuted[c.chan]?0:chan[c.chan].pan); @@ -357,6 +381,10 @@ void* DivPlatformVERA::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformVERA::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformVERA::getRegisterPool() { return regPool; } @@ -372,6 +400,10 @@ void DivPlatformVERA::muteChannel(int ch, bool mute) { } } +float DivPlatformVERA::getPostAmp() { + return 8.0f; +} + bool DivPlatformVERA::isStereo() { return true; } @@ -406,6 +438,7 @@ void DivPlatformVERA::poke(std::vector& wlist) { int DivPlatformVERA::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { for (int i=0; i<17; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } parent=p; psg=new struct VERA_PSG; @@ -414,11 +447,17 @@ int DivPlatformVERA::init(DivEngine* p, int channels, int sugRate, unsigned int skipRegisterWrites=false; chipClock=25000000; rate=chipClock/512; + for (int i=0; i<17; i++) { + oscBuf[i]->rate=rate; + } reset(); return 17; } void DivPlatformVERA::quit() { + for (int i=0; i<17; i++) { + delete oscBuf[i]; + } delete psg; delete pcm; } diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h index a3773cccb..734db020b 100644 --- a/src/engine/platform/vera.h +++ b/src/engine/platform/vera.h @@ -29,8 +29,8 @@ struct VERA_PCM; class DivPlatformVERA: public DivDispatch { protected: struct Channel { - int freq, baseFreq, pitch, note; - unsigned char ins, pan; + int freq, baseFreq, pitch, pitch2, note, ins; + unsigned char pan; bool active, freqChanged, inPorta; int vol, outVol; unsigned accum; @@ -45,9 +45,15 @@ class DivPlatformVERA: public DivDispatch { bool depth16; PCMChannel(): sample(-1), pos(0), len(0), freq(0), depth16(false) {} } pcm; - Channel(): freq(0), baseFreq(0), pitch(0), note(0), ins(-1), pan(0), active(false), freqChanged(false), inPorta(false), vol(0), outVol(0), accum(0), noiseval(0) {} + // somebody please split this into multiple lines! + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): freq(0), baseFreq(0), pitch(0), pitch2(0), note(0), ins(-1), pan(0), active(false), freqChanged(false), inPorta(false), vol(0), outVol(0), accum(0), noiseval(0) {} }; Channel chan[17]; + DivDispatchOscBuffer* oscBuf[17]; bool isMuted[17]; unsigned char regPool[67]; struct VERA_PSG* psg; @@ -60,12 +66,14 @@ class DivPlatformVERA: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); void notifyInsDeletion(void* ins); + float getPostAmp(); bool isStereo(); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index b45fecfc3..33250bfc6 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -77,6 +77,9 @@ void DivPlatformVIC20::acquire(short* bufL, short* bufR, size_t start, size_t le vic_sound_machine_calculate_samples(vic,&samp,1,1,0,SAMP_DIVIDER); bufL[h]=samp; bufR[h]=samp; + for (int i=0; i<4; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=vic->ch[i].out?(vic->volume<<11):0; + } } } @@ -91,7 +94,7 @@ void DivPlatformVIC20::writeOutVol(int ch) { } } -void DivPlatformVIC20::tick() { +void DivPlatformVIC20::tick(bool sysTick) { for (int i=0; i<4; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { @@ -119,8 +122,17 @@ void DivPlatformVIC20::tick() { chan[i].keyOn=true; } } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); if (i<3) { chan[i].freq>>=(2-i); } else { @@ -155,7 +167,7 @@ void DivPlatformVIC20::tick() { int DivPlatformVIC20::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_VIC); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); chan[c.chan].freqChanged=true; @@ -163,13 +175,13 @@ int DivPlatformVIC20::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); break; } case DIV_CMD_NOTE_OFF: chan[c.chan].active=false; chan[c.chan].keyOff=true; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -229,7 +241,7 @@ int DivPlatformVIC20::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_VIC)); } chan[c.chan].inPorta=c.value; break; @@ -266,6 +278,10 @@ void* DivPlatformVIC20::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformVIC20::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformVIC20::getRegisterPool() { return regPool; } @@ -278,6 +294,7 @@ void DivPlatformVIC20::reset() { memset(regPool,0,16); for (int i=0; i<4; i++) { chan[i]=Channel(); + chan[i].std.setEngine(parent); } vic_sound_machine_init(vic,rate,chipClock); hasWaveWrite=false; @@ -304,6 +321,9 @@ void DivPlatformVIC20::setFlags(unsigned int flags) { chipClock=COLOR_NTSC*2.0/7.0; } rate=chipClock/4; + for (int i=0; i<4; i++) { + oscBuf[i]->rate=rate; + } } void DivPlatformVIC20::poke(unsigned int addr, unsigned short val) { @@ -320,6 +340,7 @@ int DivPlatformVIC20::init(DivEngine* p, int channels, int sugRate, unsigned int skipRegisterWrites=false; for (int i=0; i<4; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); vic=new sound_vic20_t(); @@ -328,6 +349,9 @@ int DivPlatformVIC20::init(DivEngine* p, int channels, int sugRate, unsigned int } void DivPlatformVIC20::quit() { + for (int i=0; i<4; i++) { + delete oscBuf[i]; + } delete vic; } diff --git a/src/engine/platform/vic20.h b/src/engine/platform/vic20.h index 1c4d384b7..f05ad8f2b 100644 --- a/src/engine/platform/vic20.h +++ b/src/engine/platform/vic20.h @@ -27,15 +27,20 @@ class DivPlatformVIC20: public DivDispatch { struct Channel { - int freq, baseFreq, pitch, note; - unsigned char ins, pan; + int freq, baseFreq, pitch, pitch2, note, ins; + unsigned char pan; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; int vol, outVol, wave, waveWriteCycle; DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freq(0), baseFreq(0), pitch(0), + pitch2(0), note(0), ins(-1), pan(255), @@ -51,6 +56,7 @@ class DivPlatformVIC20: public DivDispatch { waveWriteCycle(-1) {} }; Channel chan[4]; + DivDispatchOscBuffer* oscBuf[4]; bool isMuted[4]; bool hasWaveWrite; @@ -62,11 +68,12 @@ class DivPlatformVIC20: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); void setFlags(unsigned int flags); void notifyInsDeletion(void* ins); diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 31694a62c..835acf4b8 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -97,6 +97,14 @@ void DivPlatformVRC6::acquire(short* bufL, short* bufR, size_t start, size_t len if (sample<-32768) sample=-32768; bufL[i]=bufR[i]=sample; + // Oscilloscope buffer part + if (++writeOscBuf>=32) { + writeOscBuf=0; + for (int i=0; i<3; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=vrc6.chan_out(i)<<10; + } + } + // Command part while (!writes.empty()) { QueuedWrite w=writes.front(); @@ -135,7 +143,7 @@ void DivPlatformVRC6::acquire(short* bufL, short* bufR, size_t start, size_t len } } -void DivPlatformVRC6::tick() { +void DivPlatformVRC6::tick(bool sysTick) { for (int i=0; i<3; i++) { // 16 for pulse; 14 for saw int CHIP_DIVIDER=(i==2)?14:16; @@ -178,11 +186,20 @@ void DivPlatformVRC6::tick() { chWrite(i,0,(chan[i].outVol&0xf)|((chan[i].duty&7)<<4)); } } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (i==2) { // sawtooth - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1; } else { // pulse - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1; if (chan[i].furnaceDac) { double off=1.0; if (chan[i].dacSample>=0 && chan[i].dacSamplesong.sampleLen) { @@ -217,7 +234,7 @@ int DivPlatformVRC6::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: if (c.chan!=2) { // pulse wave - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_VRC6); if (ins->type==DIV_INS_AMIGA) { chan[c.chan].pcm=true; } else if (chan[c.chan].furnaceDac) { @@ -246,7 +263,7 @@ int DivPlatformVRC6::dispatch(DivCommand c) { chan[c.chan].note=c.value; } chan[c.chan].active=true; - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); //chan[c.chan].keyOn=true; chan[c.chan].furnaceDac=true; } else { @@ -281,7 +298,7 @@ int DivPlatformVRC6::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_VRC6)); if (!isMuted[c.chan]) { if (c.chan==2) { // sawtooth chWrite(c.chan,0,chan[c.chan].vol); @@ -296,7 +313,7 @@ int DivPlatformVRC6::dispatch(DivCommand c) { chan[c.chan].pcm=false; chan[c.chan].active=false; chan[c.chan].keyOff=true; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -377,7 +394,7 @@ int DivPlatformVRC6::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_VRC6)); } chan[c.chan].inPorta=c.value; break; @@ -418,6 +435,10 @@ void* DivPlatformVRC6::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformVRC6::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformVRC6::getRegisterPool() { return regPool; } @@ -429,10 +450,11 @@ int DivPlatformVRC6::getRegisterPoolSize() { void DivPlatformVRC6::reset() { for (int i=0; i<3; i++) { chan[i]=DivPlatformVRC6::Channel(); + chan[i].std.setEngine(parent); } - // a poll may be necessary to decide the default - chan[2].vol=30; - chan[2].outVol=30; + // HELP + chan[2].vol=63; + chan[2].outVol=63; if (dumpWrites) { addWrite(0xffffffff,0); } @@ -461,6 +483,9 @@ void DivPlatformVRC6::setFlags(unsigned int flags) { rate=COLOR_NTSC/2.0; } chipClock=rate; + for (int i=0; i<3; i++) { + oscBuf[i]->rate=rate/32; + } } void DivPlatformVRC6::notifyInsDeletion(void* ins) { @@ -481,8 +506,10 @@ int DivPlatformVRC6::init(DivEngine* p, int channels, int sugRate, unsigned int parent=p; dumpWrites=false; skipRegisterWrites=false; + writeOscBuf=0; for (int i=0; i<3; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); reset(); @@ -490,6 +517,9 @@ int DivPlatformVRC6::init(DivEngine* p, int channels, int sugRate, unsigned int } void DivPlatformVRC6::quit() { + for (int i=0; i<3; i++) { + delete oscBuf[i]; + } } DivPlatformVRC6::~DivPlatformVRC6() { diff --git a/src/engine/platform/vrc6.h b/src/engine/platform/vrc6.h index 05cd89415..dd6863d5f 100644 --- a/src/engine/platform/vrc6.h +++ b/src/engine/platform/vrc6.h @@ -28,18 +28,23 @@ class DivPlatformVRC6: public DivDispatch { struct Channel { - int freq, baseFreq, pitch, note; + int freq, baseFreq, pitch, pitch2, note; int dacPeriod, dacRate, dacOut; unsigned int dacPos; - int dacSample; - unsigned char ins, duty; + int dacSample, ins; + unsigned char duty; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, pcm, furnaceDac; signed char vol, outVol; DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freq(0), baseFreq(0), pitch(0), + pitch2(0), note(0), dacPeriod(0), dacRate(0), @@ -60,6 +65,7 @@ class DivPlatformVRC6: public DivDispatch { outVol(15) {} }; Channel chan[3]; + DivDispatchOscBuffer* oscBuf[3]; bool isMuted[3]; struct QueuedWrite { unsigned short addr; @@ -68,6 +74,7 @@ class DivPlatformVRC6: public DivDispatch { }; std::queue writes; unsigned char sampleBank; + unsigned char writeOscBuf; vrcvi_intf intf; vrcvi_core vrc6; unsigned char regPool[13]; @@ -78,11 +85,12 @@ class DivPlatformVRC6: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool keyOffAffectsArp(int ch); void setFlags(unsigned int flags); diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 124e0ace6..03cd4f6be 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -19,6 +19,7 @@ #include "x1_010.h" #include "../engine.h" +#include "../../ta-log.h" #include //#define rWrite(a,v) pendingWrites[a]=v; @@ -252,6 +253,10 @@ void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t l //printf("tempL: %d tempR: %d\n",tempL,tempR); bufL[h]=stereo?tempL:((tempL+tempR)>>1); bufR[h]=stereo?tempR:bufL[h]; + + for (int i=0; i<16; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=x1_010->chan_out(i); + } } } @@ -336,7 +341,7 @@ void DivPlatformX1_010::updateEnvelope(int ch) { } } -void DivPlatformX1_010::tick() { +void DivPlatformX1_010::tick(bool sysTick) { for (int i=0; i<16; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { @@ -372,6 +377,25 @@ void DivPlatformX1_010::tick() { } } } + if (chan[i].std.panL.had) { + chan[i].pan&=0x0f; + chan[i].pan|=(chan[i].std.panL.val&15)<<4; + chan[i].envChanged=true; + } + if (chan[i].std.panR.had) { + chan[i].pan&=0xf0; + chan[i].pan|=chan[i].std.panR.val&15; + chan[i].envChanged=true; + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } if (chan[i].std.ex1.had) { bool nextEnable=(chan[i].std.ex1.val&1); if (nextEnable!=(chan[i].env.flag.envEnable)) { @@ -463,7 +487,7 @@ void DivPlatformX1_010::tick() { chan[i].envChanged=false; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2); if (chan[i].pcm) { if (chan[i].freq<1) chan[i].freq=1; if (chan[i].freq>255) chan[i].freq=255; @@ -512,7 +536,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { chWrite(c.chan,0,0); // reset previous note - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_X1_010); if ((ins->type==DIV_INS_AMIGA) || chan[c.chan].pcm) { if (ins->type==DIV_INS_AMIGA) { chan[c.chan].furnacePCM=true; @@ -522,7 +546,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { if (skipRegisterWrites) break; if (chan[c.chan].furnacePCM) { chan[c.chan].pcm=true; - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); chan[c.chan].sample=ins->amiga.initSample; if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); @@ -535,7 +559,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; } } else { - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); chan[c.chan].outVol=chan[c.chan].vol; if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { chWrite(c.chan,0,0); // reset @@ -547,7 +571,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } } } else { - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); chan[c.chan].outVol=chan[c.chan].vol; if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { chWrite(c.chan,0,0); // reset @@ -572,7 +596,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].active=true; chan[c.chan].keyOn=true; chan[c.chan].envChanged=true; - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); if (chan[c.chan].wave<0) { chan[c.chan].wave=0; chan[c.chan].ws.changeWave1(chan[c.chan].wave); @@ -586,7 +610,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].pcm=false; chan[c.chan].active=false; chan[c.chan].keyOff=true; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -675,8 +699,9 @@ int DivPlatformX1_010::dispatch(DivCommand c) { break; case DIV_CMD_PANNING: { if (!stereo) break; - if (chan[c.chan].pan!=c.value) { - chan[c.chan].pan=c.value; + unsigned char newPan=(c.value&0xf0)|(c.value2>>4); + if (chan[c.chan].pan!=newPan) { + chan[c.chan].pan=newPan; if (!isMuted[c.chan]) { chan[c.chan].envChanged=true; } @@ -690,7 +715,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_X1_010)); } chan[c.chan].inPorta=c.value; break; @@ -802,6 +827,10 @@ void* DivPlatformX1_010::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformX1_010::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformX1_010::getRegisterPool() { for (int i=0; i<0x2000; i++) { regPool[i]=x1_010->ram_r(i); @@ -818,6 +847,7 @@ void DivPlatformX1_010::reset() { for (int i=0; i<16; i++) { chan[i]=DivPlatformX1_010::Channel(); chan[i].reset(); + chan[i].std.setEngine(parent); chan[i].ws.setEngine(parent); chan[i].ws.init(NULL,128,255,false); } @@ -867,6 +897,9 @@ void DivPlatformX1_010::setFlags(unsigned int flags) { } rate=chipClock/512; stereo=flags&16; + for (int i=0; i<16; i++) { + oscBuf[i]->rate=rate; + } } void DivPlatformX1_010::poke(unsigned int addr, unsigned short val) { @@ -877,6 +910,48 @@ void DivPlatformX1_010::poke(std::vector& wlist) { for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); } +const void* DivPlatformX1_010::getSampleMem(int index) { + return index == 0 ? sampleMem : 0; +} + +size_t DivPlatformX1_010::getSampleMemCapacity(int index) { + return index == 0 ? 1048576 : 0; +} + +size_t DivPlatformX1_010::getSampleMemUsage(int index) { + return index == 0 ? sampleMemLen : 0; +} + +void DivPlatformX1_010::renderSamples() { + memset(sampleMem,0,getSampleMemCapacity()); + + size_t memPos=0; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + int paddedLen=(s->length8+4095)&(~0xfff); + // fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!) + if (paddedLen>131072) { + paddedLen=131072; + } + if ((memPos&0xfe0000)!=((memPos+paddedLen)&0xfe0000)) { + memPos=(memPos+0x1ffff)&0xfe0000; + } + if (memPos>=getSampleMemCapacity()) { + logW("out of X1-010 memory for sample %d!",i); + break; + } + if (memPos+paddedLen>=getSampleMemCapacity()) { + memcpy(sampleMem+memPos,s->data8,getSampleMemCapacity()-memPos); + logW("out of X1-010 memory for sample %d!",i); + } else { + memcpy(sampleMem+memPos,s->data8,paddedLen); + } + s->offX1_010=memPos; + memPos+=paddedLen; + } + sampleMemLen=memPos+256; +} + int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; @@ -884,9 +959,12 @@ int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned in stereo=false; for (int i=0; i<16; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); - intf.parent=parent; + sampleMem=new unsigned char[getSampleMemCapacity()]; + sampleMemLen=0; + intf.memory=sampleMem; x1_010=new x1_010_core(intf); x1_010->reset(); reset(); @@ -894,7 +972,11 @@ int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned in } void DivPlatformX1_010::quit() { + for (int i=0; i<16; i++) { + delete oscBuf[i]; + } delete x1_010; + delete[] sampleMem; } DivPlatformX1_010::~DivPlatformX1_010() { diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index 2bfb78cc9..939280ef9 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -28,13 +28,13 @@ class DivX1_010Interface: public x1_010_mem_intf { public: - DivEngine* parent; + unsigned char* memory; int sampleBank; virtual u8 read_byte(u32 address) override { - if (parent->x1_010Mem==NULL) return 0; - return parent->x1_010Mem[address & 0xfffff]; + if (memory==NULL) return 0; + return memory[address & 0xfffff]; } - DivX1_010Interface(): parent(NULL), sampleBank(0) {} + DivX1_010Interface(): memory(NULL), sampleBank(0) {} }; class DivPlatformX1_010: public DivDispatch { @@ -79,7 +79,7 @@ class DivPlatformX1_010: public DivDispatch { slide(0), slidefrac(0) {} }; - int freq, baseFreq, pitch, note; + int freq, baseFreq, pitch, pitch2, note; int wave, sample, ins; unsigned char pan, autoEnvNum, autoEnvDen; bool active, insChanged, envChanged, freqChanged, keyOn, keyOff, inPorta, furnacePCM, pcm; @@ -89,7 +89,7 @@ class DivPlatformX1_010: public DivDispatch { DivMacroInt std; DivWaveSynth ws; void reset() { - freq = baseFreq = pitch = note = 0; + freq = baseFreq = pitch = pitch2 = note = 0; wave = sample = ins = -1; pan = 255; autoEnvNum = autoEnvDen = 0; @@ -99,8 +99,12 @@ class DivPlatformX1_010: public DivDispatch { vol = outVol = lvol = rvol = 15; waveBank = 0; } + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): - freq(0), baseFreq(0), pitch(0), note(0), + freq(0), baseFreq(0), pitch(0), pitch2(0), note(0), wave(-1), sample(-1), ins(-1), pan(255), autoEnvNum(0), autoEnvDen(0), active(false), insChanged(true), envChanged(true), freqChanged(false), keyOn(false), keyOff(false), inPorta(false), furnacePCM(false), pcm(false), @@ -108,8 +112,11 @@ class DivPlatformX1_010: public DivDispatch { waveBank(0) {} }; Channel chan[16]; + DivDispatchOscBuffer* oscBuf[16]; bool isMuted[16]; bool stereo=false; + unsigned char* sampleMem; + size_t sampleMemLen; unsigned char sampleBank; DivX1_010Interface intf; x1_010_core* x1_010; @@ -122,11 +129,12 @@ class DivPlatformX1_010: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool isStereo(); bool keyOffAffectsArp(int ch); @@ -135,6 +143,10 @@ class DivPlatformX1_010: public DivDispatch { void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); + const void* getSampleMem(int index = 0); + size_t getSampleMemCapacity(int index = 0); + size_t getSampleMemUsage(int index = 0); + void renderSamples(); const char** getRegisterSheet(); const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index dd7220fa5..54bbbd213 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -18,7 +18,9 @@ */ #include "ym2610.h" +#include "sound/ymfm/ymfm.h" #include "../engine.h" +#include "../../ta-log.h" #include #include @@ -30,6 +32,10 @@ static unsigned char konOffs[4]={ 1, 2, 5, 6 }; +static unsigned char bchOffs[4]={ + 1, 2, 4, 5 +}; + #define CHIP_DIVIDER 32 const char* regCheatSheetYM2610[]={ @@ -240,6 +246,85 @@ const char* regCheatSheetYM2610[]={ NULL }; +const void* DivPlatformYM2610Base::getSampleMem(int index) { + return index == 0 ? adpcmAMem : index == 1 ? adpcmBMem : NULL; +} + +size_t DivPlatformYM2610Base::getSampleMemCapacity(int index) { + return index == 0 ? 16777216 : index == 1 ? 16777216 : 0; +} + +size_t DivPlatformYM2610Base::getSampleMemUsage(int index) { + return index == 0 ? adpcmAMemLen : index == 1 ? adpcmBMemLen : 0; +} + +void DivPlatformYM2610Base::renderSamples() { + memset(adpcmAMem,0,getSampleMemCapacity(0)); + + size_t memPos=0; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + int paddedLen=(s->lengthA+255)&(~0xff); + if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) { + memPos=(memPos+0xfffff)&0xf00000; + } + if (memPos>=getSampleMemCapacity(0)) { + logW("out of ADPCM-A memory for sample %d!",i); + break; + } + if (memPos+paddedLen>=getSampleMemCapacity(0)) { + memcpy(adpcmAMem+memPos,s->dataA,getSampleMemCapacity(0)-memPos); + logW("out of ADPCM-A memory for sample %d!",i); + } else { + memcpy(adpcmAMem+memPos,s->dataA,paddedLen); + } + s->offA=memPos; + memPos+=paddedLen; + } + adpcmAMemLen=memPos+256; + + memset(adpcmBMem,0,getSampleMemCapacity(1)); + + memPos=0; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + int paddedLen=(s->lengthB+255)&(~0xff); + if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) { + memPos=(memPos+0xfffff)&0xf00000; + } + if (memPos>=getSampleMemCapacity(1)) { + logW("out of ADPCM-B memory for sample %d!",i); + break; + } + if (memPos+paddedLen>=getSampleMemCapacity(1)) { + memcpy(adpcmBMem+memPos,s->dataB,getSampleMemCapacity(1)-memPos); + logW("out of ADPCM-B memory for sample %d!",i); + } else { + memcpy(adpcmBMem+memPos,s->dataB,paddedLen); + } + s->offB=memPos; + memPos+=paddedLen; + } + adpcmBMemLen=memPos+256; +} + +int DivPlatformYM2610Base::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + adpcmAMem=new unsigned char[getSampleMemCapacity(0)]; + adpcmAMemLen=0; + adpcmBMem=new unsigned char[getSampleMemCapacity(1)]; + adpcmBMemLen=0; + iface.adpcmAMem=adpcmAMem; + iface.adpcmBMem=adpcmBMem; + iface.sampleBank=0; + return 0; +} + +void DivPlatformYM2610Base::quit() { + delete[] adpcmAMem; + delete[] adpcmBMem; +} + const char** DivPlatformYM2610::getRegisterSheet() { return regCheatSheetYM2610; } @@ -312,6 +397,54 @@ const char* DivPlatformYM2610::getEffectName(unsigned char effect) { case 0x30: return "30xx: Toggle hard envelope reset on new notes"; break; + case 0x50: + return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; + break; + case 0x51: + return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; + break; + case 0x52: + return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; + break; + case 0x53: + return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)"; + break; + case 0x54: + return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)"; + break; + case 0x55: + return "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)"; + break; + case 0x56: + return "56xx: Set decay of all operators (0 to 1F)"; + break; + case 0x57: + return "57xx: Set decay of operator 1 (0 to 1F)"; + break; + case 0x58: + return "58xx: Set decay of operator 2 (0 to 1F)"; + break; + case 0x59: + return "59xx: Set decay of operator 3 (0 to 1F)"; + break; + case 0x5a: + return "5Axx: Set decay of operator 4 (0 to 1F)"; + break; + case 0x5b: + return "5Bxx: Set decay 2 of all operators (0 to 1F)"; + break; + case 0x5c: + return "5Cxx: Set decay 2 of operator 1 (0 to 1F)"; + break; + case 0x5d: + return "5Dxx: Set decay 2 of operator 2 (0 to 1F)"; + break; + case 0x5e: + return "5Exx: Set decay 2 of operator 3 (0 to 1F)"; + break; + case 0x5f: + return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; + break; } return NULL; } @@ -337,6 +470,22 @@ double DivPlatformYM2610::NOTE_ADPCMB(int note) { void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; + ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); + ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); + ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); + ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); + + ymfm::ssg_engine::output_data ssgOut; + + ymfm::fm_channel>* fmChan[6]; + ymfm::adpcm_a_channel* adpcmAChan[6]; + for (int i=0; i<4; i++) { + fmChan[i]=fme->debug_channel(bchOffs[i]); + } + for (int i=0; i<6; i++) { + adpcmAChan[i]=aae->debug_channel(i); + } + for (size_t h=start; hdata[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); + } + + ssge->get_last_out(ssgOut); + for (int i=4; i<7; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-4]; + } + + for (int i=7; i<13; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-7]->get_last_out(0)+adpcmAChan[i-7]->get_last_out(1); + } + + oscBuf[13]->data[oscBuf[13]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); } } -void DivPlatformYM2610::tick() { +void DivPlatformYM2610::tick(bool sysTick) { // PSG - ay->tick(); + ay->tick(sysTick); ay->flushWrites(); for (DivRegWrite& i: ay->getRegisterWrites()) { immWrite(i.addr&15,i.val); @@ -408,6 +572,27 @@ void DivPlatformYM2610::tick() { } } + if (chan[i].std.panL.had) { + chan[i].pan=chan[i].std.panL.val&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].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + chan[i].keyOn=true; + } + } + if (chan[i].std.alg.had) { chan[i].state.alg=chan[i].std.alg.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); @@ -557,9 +742,9 @@ void DivPlatformYM2610::tick() { for (int i=0; i<4; i++) { if (i==1 && extMode) continue; if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2); if (chan[i].freq>262143) chan[i].freq=262143; - int freqt=toFreq(chan[i].freq); + int freqt=toFreq(chan[i].freq)+chan[i].pitch2; immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8); immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff); chan[i].freqChanged=false; @@ -620,7 +805,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { if (c.chan>12) { // ADPCM-B - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); if (ins->type==DIV_INS_AMIGA) { chan[c.chan].furnacePCM=true; } else { @@ -628,7 +813,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { } if (skipRegisterWrites) break; if (chan[c.chan].furnacePCM) { - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(0x1b,chan[c.chan].outVol); @@ -660,7 +845,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { } } else { chan[c.chan].sample=-1; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); chan[c.chan].outVol=chan[c.chan].vol; if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { immWrite(0x10,0x01); // reset @@ -703,8 +888,8 @@ int DivPlatformYM2610::dispatch(DivCommand c) { immWrite(0x100,0x00|(1<<(c.chan-7))); break; } - DivInstrument* ins=parent->getIns(chan[c.chan].ins); - chan[c.chan].std.init(ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); + chan[c.chan].macroInit(ins); if (c.chan<4) { if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; @@ -764,7 +949,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: if (c.chan>12) { @@ -818,10 +1003,10 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { - if (c.value==0) { + if (c.value==0 && c.value2==0) { chan[c.chan].pan=3; } else { - chan[c.chan].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); + chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1); } if (c.chan>12) { immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); @@ -951,6 +1136,134 @@ int DivPlatformYM2610::dispatch(DivCommand c) { } break; } + case DIV_CMD_FM_RS: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_AM: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_DR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_SL: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_RR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_D2R: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + break; + } + case DIV_CMD_FM_DT: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dt=c.value&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dt=c.value2&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + break; + } + case DIV_CMD_FM_SSG: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + break; + } case DIV_CMD_FM_HARD_RESET: chan[c.chan].hardReset=c.value; break; @@ -966,7 +1279,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { case DIV_CMD_PRE_PORTA: if (c.chan>3) { if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FM)); } } chan[c.chan].inPorta=c.value; @@ -1037,6 +1350,10 @@ void* DivPlatformYM2610::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformYM2610::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformYM2610::getRegisterPool() { return regPool; } @@ -1062,6 +1379,7 @@ void DivPlatformYM2610::reset() { fm->reset(); for (int i=0; i<14; i++) { chan[i]=DivPlatformYM2610::Channel(); + chan[i].std.setEngine(parent); } for (int i=0; i<4; i++) { chan[i].vol=0x7f; @@ -1126,16 +1444,18 @@ void DivPlatformYM2610::setSkipRegisterWrites(bool value) { } int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { - parent=p; + DivPlatformYM2610Base::init(p, channels, sugRate, flags); dumpWrites=false; skipRegisterWrites=false; for (int i=0; i<14; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } chipClock=8000000; rate=chipClock/16; - iface.parent=parent; - iface.sampleBank=0; + for (int i=0; i<14; i++) { + oscBuf[i]->rate=rate; + } fm=new ymfm::ym2610(iface); // YM2149, 2MHz ay=new DivPlatformAY8910; @@ -1146,9 +1466,13 @@ int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, unsigned in } void DivPlatformYM2610::quit() { + for (int i=0; i<14; i++) { + delete oscBuf[i]; + } ay->quit(); delete ay; delete fm; + DivPlatformYM2610Base::quit(); } DivPlatformYM2610::~DivPlatformYM2610() { diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index 2fd525b08..45ecb335a 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -27,14 +27,32 @@ class DivYM2610Interface: public ymfm::ymfm_interface { public: - DivEngine* parent; + unsigned char* adpcmAMem; + unsigned char* adpcmBMem; int sampleBank; uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address); void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data); - DivYM2610Interface(): parent(NULL), sampleBank(0) {} + DivYM2610Interface(): adpcmAMem(NULL), adpcmBMem(NULL), sampleBank(0) {} }; -class DivPlatformYM2610: public DivDispatch { +class DivPlatformYM2610Base: public DivDispatch { + protected: + unsigned char* adpcmAMem; + size_t adpcmAMemLen; + unsigned char* adpcmBMem; + size_t adpcmBMemLen; + DivYM2610Interface iface; + + public: + const void* getSampleMem(int index); + size_t getSampleMemCapacity(int index); + size_t getSampleMemUsage(int index); + void renderSamples(); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); +}; + +class DivPlatformYM2610: public DivPlatformYM2610Base { protected: const unsigned short chanOffs[4]={ 0x01, 0x02, 0x101, 0x102 @@ -43,20 +61,25 @@ class DivPlatformYM2610: public DivDispatch { struct Channel { DivInstrumentFM state; unsigned char freqH, freqL; - int freq, baseFreq, pitch, note; - unsigned char ins, psgMode, autoEnvNum, autoEnvDen; + int freq, baseFreq, pitch, pitch2, note, ins; + unsigned char psgMode, autoEnvNum, autoEnvDen; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; int vol, outVol; int sample; unsigned char pan; DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), + pitch2(0), note(0), ins(-1), psgMode(1), @@ -77,6 +100,7 @@ class DivPlatformYM2610: public DivDispatch { pan(3) {} }; Channel chan[14]; + DivDispatchOscBuffer* oscBuf[14]; bool isMuted[14]; struct QueuedWrite { unsigned short addr; @@ -87,7 +111,6 @@ class DivPlatformYM2610: public DivDispatch { std::queue writes; ymfm::ym2610* fm; ymfm::ym2610::output_data fmout; - DivYM2610Interface iface; DivPlatformAY8910* ay; unsigned char regPool[512]; @@ -112,11 +135,12 @@ class DivPlatformYM2610: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool isStereo(); bool keyOffAffectsArp(int ch); diff --git a/src/engine/platform/ym2610Interface.cpp b/src/engine/platform/ym2610Interface.cpp index 9154b40e7..d442ce347 100644 --- a/src/engine/platform/ym2610Interface.cpp +++ b/src/engine/platform/ym2610Interface.cpp @@ -24,13 +24,11 @@ uint8_t DivYM2610Interface::ymfm_external_read(ymfm::access_class type, uint32_t address) { switch (type) { case ymfm::ACCESS_ADPCM_A: - if (parent->adpcmAMem==NULL) return 0; - if ((address&0xffffff)>=parent->adpcmAMemLen) return 0; - return parent->adpcmAMem[address&0xffffff]; + if (adpcmAMem==NULL) return 0; + return adpcmAMem[address&0xffffff]; case ymfm::ACCESS_ADPCM_B: - if (parent->adpcmBMem==NULL) return 0; - if ((address&0xffffff)>=parent->adpcmBMemLen) return 0; - return parent->adpcmBMem[address&0xffffff]; + if (adpcmBMem==NULL) return 0; + return adpcmBMem[address&0xffffff]; default: return 0; } diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index c51f41198..b36cc87f5 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -18,6 +18,7 @@ */ #include "ym2610b.h" +#include "sound/ymfm/ymfm.h" #include "../engine.h" #include #include @@ -376,6 +377,54 @@ const char* DivPlatformYM2610B::getEffectName(unsigned char effect) { case 0x30: return "30xx: Toggle hard envelope reset on new notes"; break; + case 0x50: + return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; + break; + case 0x51: + return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; + break; + case 0x52: + return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; + break; + case 0x53: + return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)"; + break; + case 0x54: + return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)"; + break; + case 0x55: + return "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)"; + break; + case 0x56: + return "56xx: Set decay of all operators (0 to 1F)"; + break; + case 0x57: + return "57xx: Set decay of operator 1 (0 to 1F)"; + break; + case 0x58: + return "58xx: Set decay of operator 2 (0 to 1F)"; + break; + case 0x59: + return "59xx: Set decay of operator 3 (0 to 1F)"; + break; + case 0x5a: + return "5Axx: Set decay of operator 4 (0 to 1F)"; + break; + case 0x5b: + return "5Bxx: Set decay 2 of all operators (0 to 1F)"; + break; + case 0x5c: + return "5Cxx: Set decay 2 of operator 1 (0 to 1F)"; + break; + case 0x5d: + return "5Dxx: Set decay 2 of operator 2 (0 to 1F)"; + break; + case 0x5e: + return "5Exx: Set decay 2 of operator 3 (0 to 1F)"; + break; + case 0x5f: + return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; + break; } return NULL; } @@ -401,6 +450,20 @@ double DivPlatformYM2610B::NOTE_ADPCMB(int note) { void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; + ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); + ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); + ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); + ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); + + ymfm::ssg_engine::output_data ssgOut; + + ymfm::fm_channel>* fmChan[6]; + ymfm::adpcm_a_channel* adpcmAChan[6]; + for (int i=0; i<6; i++) { + fmChan[i]=fme->debug_channel(i); + adpcmAChan[i]=aae->debug_channel(i); + } + for (size_t h=start; hdata[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); + } + + ssge->get_last_out(ssgOut); + for (int i=6; i<9; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-6]; + } + + for (int i=9; i<15; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-9]->get_last_out(0)+adpcmAChan[i-9]->get_last_out(1); + } + + oscBuf[15]->data[oscBuf[15]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); } } -void DivPlatformYM2610B::tick() { +void DivPlatformYM2610B::tick(bool sysTick) { // PSG - ay->tick(); + ay->tick(sysTick); ay->flushWrites(); for (DivRegWrite& i: ay->getRegisterWrites()) { immWrite(i.addr&15,i.val); @@ -472,6 +551,27 @@ void DivPlatformYM2610B::tick() { } } + if (chan[i].std.panL.had) { + chan[i].pan=chan[i].std.panL.val&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].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + chan[i].keyOn=true; + } + } + if (chan[i].std.alg.had) { chan[i].state.alg=chan[i].std.alg.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); @@ -620,9 +720,9 @@ void DivPlatformYM2610B::tick() { for (int i=0; i<6; i++) { if (i==2 && extMode) continue; if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2); if (chan[i].freq>262143) chan[i].freq=262143; - int freqt=toFreq(chan[i].freq); + int freqt=toFreq(chan[i].freq)+chan[i].pitch2; immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8); immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff); chan[i].freqChanged=false; @@ -683,7 +783,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { if (c.chan>14) { // ADPCM-B - DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); if (ins->type==DIV_INS_AMIGA) { chan[c.chan].furnacePCM=true; } else { @@ -691,7 +791,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } if (skipRegisterWrites) break; if (chan[c.chan].furnacePCM) { - chan[c.chan].std.init(ins); + chan[c.chan].macroInit(ins); if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(0x1b,chan[c.chan].outVol); @@ -723,7 +823,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } } else { chan[c.chan].sample=-1; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); chan[c.chan].outVol=chan[c.chan].vol; if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { immWrite(0x10,0x01); // reset @@ -766,8 +866,8 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { immWrite(0x100,0x00|(1<<(c.chan-9))); break; } - DivInstrument* ins=parent->getIns(chan[c.chan].ins); - chan[c.chan].std.init(ins); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); + chan[c.chan].macroInit(ins); if (c.chan<6) { if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; @@ -827,7 +927,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; - chan[c.chan].std.init(NULL); + chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: if (c.chan>14) { @@ -881,10 +981,10 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { - if (c.value==0) { + if (c.value==0 && c.value2==0) { chan[c.chan].pan=3; } else { - chan[c.chan].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); + chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1); } if (c.chan>14) { immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); @@ -1014,6 +1114,134 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } break; } + case DIV_CMD_FM_RS: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_AM: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_DR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_SL: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_RR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_D2R: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + break; + } + case DIV_CMD_FM_DT: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dt=c.value&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dt=c.value2&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + break; + } + case DIV_CMD_FM_SSG: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + break; + } case DIV_CMD_FM_HARD_RESET: chan[c.chan].hardReset=c.value; break; @@ -1029,7 +1257,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { case DIV_CMD_PRE_PORTA: if (c.chan>5) { if (chan[c.chan].active && c.value2) { - if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FM)); } } chan[c.chan].inPorta=c.value; @@ -1100,6 +1328,10 @@ void* DivPlatformYM2610B::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformYM2610B::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformYM2610B::getRegisterPool() { return regPool; } @@ -1125,6 +1357,7 @@ void DivPlatformYM2610B::reset() { fm->reset(); for (int i=0; i<16; i++) { chan[i]=DivPlatformYM2610B::Channel(); + chan[i].std.setEngine(parent); } for (int i=0; i<6; i++) { chan[i].vol=0x7f; @@ -1189,16 +1422,18 @@ void DivPlatformYM2610B::setSkipRegisterWrites(bool value) { } int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { - parent=p; + DivPlatformYM2610Base::init(p, channels, sugRate, flags); dumpWrites=false; skipRegisterWrites=false; for (int i=0; i<16; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } chipClock=8000000; rate=chipClock/16; - iface.parent=parent; - iface.sampleBank=0; + for (int i=0; i<16; i++) { + oscBuf[i]->rate=rate; + } fm=new ymfm::ym2610b(iface); // YM2149, 2MHz ay=new DivPlatformAY8910; @@ -1209,9 +1444,13 @@ int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned i } void DivPlatformYM2610B::quit() { + for (int i=0; i<16; i++) { + delete oscBuf[i]; + } ay->quit(); delete ay; delete fm; + DivPlatformYM2610Base::quit(); } DivPlatformYM2610B::~DivPlatformYM2610B() { diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index d6b616c50..d1af3069a 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -26,7 +26,7 @@ #include "ym2610.h" -class DivPlatformYM2610B: public DivDispatch { +class DivPlatformYM2610B: public DivPlatformYM2610Base { protected: const unsigned short chanOffs[6]={ 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 @@ -35,20 +35,25 @@ class DivPlatformYM2610B: public DivDispatch { struct Channel { DivInstrumentFM state; unsigned char freqH, freqL; - int freq, baseFreq, pitch, note; - unsigned char ins, psgMode, autoEnvNum, autoEnvDen; + int freq, baseFreq, pitch, pitch2, note, ins; + unsigned char psgMode, autoEnvNum, autoEnvDen; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; int vol, outVol; int sample; unsigned char pan; DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), + pitch2(0), note(0), ins(-1), psgMode(1), @@ -69,6 +74,7 @@ class DivPlatformYM2610B: public DivDispatch { pan(3) {} }; Channel chan[16]; + DivDispatchOscBuffer* oscBuf[16]; bool isMuted[16]; struct QueuedWrite { unsigned short addr; @@ -79,7 +85,6 @@ class DivPlatformYM2610B: public DivDispatch { std::queue writes; ymfm::ym2610b* fm; ymfm::ym2610b::output_data fmout; - DivYM2610Interface iface; unsigned char regPool[512]; unsigned char lastBusy; @@ -103,11 +108,12 @@ class DivPlatformYM2610B: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool isStereo(); bool keyOffAffectsArp(int ch); diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index 03e9f8699..03b91fccc 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -36,7 +36,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { int ordch=orderedOps[ch]; switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(opChan[ch].ins); + DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; @@ -78,7 +78,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { break; case DIV_CMD_VOLUME: { opChan[ch].vol=c.value; - DivInstrument* ins=parent->getIns(opChan[ch].ins); + DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; if (isOpMuted[ch]) { @@ -99,12 +99,12 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { opChan[ch].ins=c.value; break; case DIV_CMD_PANNING: { - if (c.value==0) { + if (c.value==0 && c.value2==0) { opChan[ch].pan=3; } else { - opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); + opChan[ch].pan=(c.value2>0)|((c.value>0)<<1); } - DivInstrument* ins=parent->getIns(opChan[ch].ins); + DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); if (parent->song.sharedExtStat) { for (int i=0; i<4; i++) { if (ch==i) continue; @@ -159,14 +159,14 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { } case DIV_CMD_FM_MULT: { // TODO unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; - DivInstrument* ins=parent->getIns(opChan[ch].ins); + DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4)); break; } case DIV_CMD_FM_TL: { // TODO unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; - DivInstrument* ins=parent->getIns(opChan[ch].ins); + DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); if (isOutput[ins->fm.alg][c.value]) { rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127)); } else { @@ -175,17 +175,146 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { break; } case DIV_CMD_FM_AR: { - DivInstrument* ins=parent->getIns(opChan[ch].ins); if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator op=ins->fm.op[i]; + DivInstrumentFM::Operator& op=chan[2].state.op[i]; + op.ar=c.value2&31; unsigned short baseAddr=chanOffs[2]|opOffs[i]; - rWrite(baseAddr+0x50,(c.value2&31)|(op.rs<<6)); + rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6)); } } else { - DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.ar=c.value2&31; unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; - rWrite(baseAddr+0x50,(c.value2&31)|(op.rs<<6)); + rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_RS: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[2].state.op[i]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_AM: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[2].state.op[i]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_DR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[2].state.op[i]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_SL: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[2].state.op[i]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_RR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[2].state.op[i]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_D2R: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[2].state.op[i]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + break; + } + case DIV_CMD_FM_DT: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[2].state.op[i]; + op.dt=c.value&7; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.dt=c.value2&7; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + break; + } + case DIV_CMD_FM_SSG: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[2].state.op[i]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } break; } @@ -212,7 +341,7 @@ static int opChanOffsH[4]={ 0xad, 0xae, 0xac, 0xa6 }; -void DivPlatformYM2610BExt::tick() { +void DivPlatformYM2610BExt::tick(bool sysTick) { if (extMode) { bool writeSomething=false; unsigned char writeMask=2; @@ -229,13 +358,13 @@ void DivPlatformYM2610BExt::tick() { } } - DivPlatformYM2610B::tick(); + DivPlatformYM2610B::tick(sysTick); bool writeNoteOn=false; unsigned char writeMask=2; if (extMode) for (int i=0; i<4; i++) { if (opChan[i].freqChanged) { - opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch); + opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,octave(opChan[i].baseFreq),opChan[i].pitch2); if (opChan[i].freq>262143) opChan[i].freq=262143; int freqt=toFreq(opChan[i].freq); opChan[i].freqH=freqt>>8; @@ -268,7 +397,7 @@ void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) { isOpMuted[ch-2]=mute; int ordch=orderedOps[ch-2]; - DivInstrument* ins=parent->getIns(opChan[ch-2].ins); + DivInstrument* ins=parent->getIns(opChan[ch-2].ins,DIV_INS_FM); unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; if (isOpMuted[ch-2]) { @@ -336,13 +465,18 @@ void DivPlatformYM2610BExt::forceIns() { } } - void* DivPlatformYM2610BExt::getChanState(int ch) { if (ch>=6) return &chan[ch-3]; if (ch>=2) return &opChan[ch-2]; return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformYM2610BExt::getOscBuffer(int ch) { + if (ch>=6) return oscBuf[ch-3]; + if (ch<3) return oscBuf[ch]; + return NULL; +} + void DivPlatformYM2610BExt::reset() { DivPlatformYM2610B::reset(); diff --git a/src/engine/platform/ym2610bext.h b/src/engine/platform/ym2610bext.h index 25ca59196..b416b5c70 100644 --- a/src/engine/platform/ym2610bext.h +++ b/src/engine/platform/ym2610bext.h @@ -25,13 +25,12 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B { struct OpChannel { DivMacroInt std; unsigned char freqH, freqL; - int freq, baseFreq, pitch; - unsigned char ins; + int freq, baseFreq, pitch, pitch2, ins; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause; int vol; unsigned char pan; - OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} + OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} }; OpChannel opChan[4]; bool isOpMuted[4]; @@ -39,9 +38,10 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B { public: int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool keyOffAffectsArp(int ch); void notifyInsChange(int ins); diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index 40294733d..1ffbd74fe 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -36,7 +36,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { int ordch=orderedOps[ch]; switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(opChan[ch].ins); + DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); unsigned short baseAddr=chanOffs[1]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; @@ -78,7 +78,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { break; case DIV_CMD_VOLUME: { opChan[ch].vol=c.value; - DivInstrument* ins=parent->getIns(opChan[ch].ins); + DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); unsigned short baseAddr=chanOffs[1]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; if (isOpMuted[ch]) { @@ -99,12 +99,12 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { opChan[ch].ins=c.value; break; case DIV_CMD_PANNING: { - if (c.value==0) { + if (c.value==0 && c.value2==0) { opChan[ch].pan=3; } else { - opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); + opChan[ch].pan=(c.value2>0)|((c.value>0)<<1); } - DivInstrument* ins=parent->getIns(opChan[ch].ins); + DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); if (parent->song.sharedExtStat) { for (int i=0; i<4; i++) { if (ch==i) continue; @@ -159,14 +159,14 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { } case DIV_CMD_FM_MULT: { // TODO unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; - DivInstrument* ins=parent->getIns(opChan[ch].ins); + DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4)); break; } case DIV_CMD_FM_TL: { // TODO unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; - DivInstrument* ins=parent->getIns(opChan[ch].ins); + DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); if (isOutput[ins->fm.alg][c.value]) { rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127)); } else { @@ -175,17 +175,146 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { break; } case DIV_CMD_FM_AR: { - DivInstrument* ins=parent->getIns(opChan[ch].ins); if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator op=ins->fm.op[i]; + DivInstrumentFM::Operator& op=chan[1].state.op[i]; + op.ar=c.value2&31; unsigned short baseAddr=chanOffs[1]|opOffs[i]; - rWrite(baseAddr+0x50,(c.value2&31)|(op.rs<<6)); + rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6)); } } else { - DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[1].state.op[orderedOps[c.value]]; + op.ar=c.value2&31; unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; - rWrite(baseAddr+0x50,(c.value2&31)|(op.rs<<6)); + rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_RS: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[1].state.op[i]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[1]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[1].state.op[orderedOps[c.value]]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_AM: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[1].state.op[i]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[1]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[1].state.op[orderedOps[c.value]]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_DR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[1].state.op[i]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[1]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[1].state.op[orderedOps[c.value]]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_SL: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[1].state.op[i]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[1]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[1].state.op[orderedOps[c.value]]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_RR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[1].state.op[i]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[1]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[1].state.op[orderedOps[c.value]]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_D2R: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[1].state.op[i]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[1]|opOffs[i]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[1].state.op[orderedOps[c.value]]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + break; + } + case DIV_CMD_FM_DT: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[1].state.op[i]; + op.dt=c.value&7; + unsigned short baseAddr=chanOffs[1]|opOffs[i]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[1].state.op[orderedOps[c.value]]; + op.dt=c.value2&7; + unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + break; + } + case DIV_CMD_FM_SSG: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[1].state.op[i]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[1]|opOffs[i]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[1].state.op[orderedOps[c.value]]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } break; } @@ -212,7 +341,7 @@ static int opChanOffsH[4]={ 0xad, 0xae, 0xac, 0xa6 }; -void DivPlatformYM2610Ext::tick() { +void DivPlatformYM2610Ext::tick(bool sysTick) { if (extMode) { bool writeSomething=false; unsigned char writeMask=2; @@ -229,13 +358,13 @@ void DivPlatformYM2610Ext::tick() { } } - DivPlatformYM2610::tick(); + DivPlatformYM2610::tick(sysTick); bool writeNoteOn=false; unsigned char writeMask=2; if (extMode) for (int i=0; i<4; i++) { if (opChan[i].freqChanged) { - opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch); + opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,octave(opChan[i].baseFreq),opChan[i].pitch2); if (opChan[i].freq>262143) opChan[i].freq=262143; int freqt=toFreq(opChan[i].freq); opChan[i].freqH=freqt>>8; @@ -268,7 +397,7 @@ void DivPlatformYM2610Ext::muteChannel(int ch, bool mute) { isOpMuted[ch-1]=mute; int ordch=orderedOps[ch-1]; - DivInstrument* ins=parent->getIns(opChan[ch].ins); + DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); unsigned short baseAddr=chanOffs[1]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; if (isOpMuted[ch]) { @@ -343,6 +472,12 @@ void* DivPlatformYM2610Ext::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformYM2610Ext::getOscBuffer(int ch) { + if (ch>=5) return oscBuf[ch-3]; + if (ch<2) return oscBuf[ch]; + return NULL; +} + void DivPlatformYM2610Ext::reset() { DivPlatformYM2610::reset(); diff --git a/src/engine/platform/ym2610ext.h b/src/engine/platform/ym2610ext.h index 37c9e328d..10be6638d 100644 --- a/src/engine/platform/ym2610ext.h +++ b/src/engine/platform/ym2610ext.h @@ -25,13 +25,12 @@ class DivPlatformYM2610Ext: public DivPlatformYM2610 { struct OpChannel { DivMacroInt std; unsigned char freqH, freqL; - int freq, baseFreq, pitch; - unsigned char ins; + int freq, baseFreq, pitch, pitch2, ins; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause; int vol; unsigned char pan; - OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} + OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} }; OpChannel opChan[4]; bool isOpMuted[4]; @@ -39,9 +38,10 @@ class DivPlatformYM2610Ext: public DivPlatformYM2610 { public: int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); void reset(); void forceIns(); - void tick(); + void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool keyOffAffectsArp(int ch); void notifyInsChange(int ins); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 97047b64d..224838e84 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include #define _USE_MATH_DEFINES #include "dispatch.h" #include "engine.h" @@ -40,7 +41,7 @@ const char* notes[12]={ }; // update this when adding new commands. -const char* cmdName[DIV_CMD_MAX]={ +const char* cmdName[]={ "NOTE_ON", "NOTE_OFF", "NOTE_OFF_ENV", @@ -65,9 +66,26 @@ const char* cmdName[DIV_CMD_MAX]={ "FM_LFO", "FM_LFO_WAVE", "FM_TL", + "FM_AM", "FM_AR", + "FM_DR", + "FM_SL", + "FM_D2R", + "FM_RR", + "FM_DT", + "FM_DT2", + "FM_RS", + "FM_KSR", + "FM_VIB", + "FM_SUS", + "FM_WS", + "FM_SSG", + "FM_REV", + "FM_EG_SHIFT", "FM_FB", "FM_MULT", + "FM_FINE", + "FM_FIXFREQ", "FM_EXTCH", "FM_AM_DEPTH", "FM_PM_DEPTH", @@ -88,6 +106,7 @@ const char* cmdName[DIV_CMD_MAX]={ "PCE_LFO_SPEED", "NES_SWEEP", + "NES_DMC", "C64_CUTOFF", "C64_RESONANCE", @@ -127,6 +146,7 @@ const char* cmdName[DIV_CMD_MAX]={ "QSOUND_ECHO_FEEDBACK", "QSOUND_ECHO_DELAY", "QSOUND_ECHO_LEVEL", + "QSOUND_SURROUND", "X1_010_ENVELOPE_SHAPE", "X1_010_ENVELOPE_ENABLE", @@ -144,6 +164,7 @@ const char* cmdName[DIV_CMD_MAX]={ "N163_WAVE_LOAD", "N163_WAVE_LOADPOS", "N163_WAVE_LOADLEN", + "N163_WAVE_LOADMODE", "N163_CHANNEL_LIMIT", "N163_GLOBAL_WAVE_LOAD", "N163_GLOBAL_WAVE_LOADPOS", @@ -153,6 +174,8 @@ const char* cmdName[DIV_CMD_MAX]={ "ALWAYS_SET_VOLUME" }; +static_assert((sizeof(cmdName)/sizeof(void*))==DIV_CMD_MAX,"update cmdName!"); + const char* formatNote(unsigned char note, unsigned char octave) { static char ret[4]; if (note==100) { @@ -231,675 +254,13 @@ int DivEngine::dispatchCmd(DivCommand c) { } bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effectVal) { - switch (sysOfChan[ch]) { - case DIV_SYSTEM_YM2612: - case DIV_SYSTEM_YM2612_EXT: - switch (effect) { - case 0x17: // DAC enable - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); - break; - case 0x20: // SN noise mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - case 0x30: // toggle hard-reset - dispatchCmd(DivCommand(DIV_CMD_FM_HARD_RESET,ch,effectVal)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_YM2151: - case DIV_SYSTEM_YM2610: - case DIV_SYSTEM_YM2610_EXT: - case DIV_SYSTEM_YM2610B: - case DIV_SYSTEM_YM2610B_EXT: - case DIV_SYSTEM_OPZ: - switch (effect) { - case 0x30: // toggle hard-reset - dispatchCmd(DivCommand(DIV_CMD_FM_HARD_RESET,ch,effectVal)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_SMS: - switch (effect) { - case 0x20: // SN noise mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_GB: - switch (effect) { - case 0x10: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - case 0x11: case 0x12: // duty or noise mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - case 0x13: // sweep params - dispatchCmd(DivCommand(DIV_CMD_GB_SWEEP_TIME,ch,effectVal)); - break; - case 0x14: // sweep direction - dispatchCmd(DivCommand(DIV_CMD_GB_SWEEP_DIR,ch,effectVal)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_PCE: - switch (effect) { - case 0x10: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - case 0x11: // noise mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - case 0x12: // LFO mode - dispatchCmd(DivCommand(DIV_CMD_PCE_LFO_MODE,ch,effectVal)); - break; - case 0x13: // LFO speed - dispatchCmd(DivCommand(DIV_CMD_PCE_LFO_SPEED,ch,effectVal)); - break; - case 0x17: // PCM enable - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); - break; - default: - return false; - } - break; - case DIV_SYSTEM_NES: - case DIV_SYSTEM_MMC5: - switch (effect) { - case 0x12: // duty or noise mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - case 0x13: // sweep up - dispatchCmd(DivCommand(DIV_CMD_NES_SWEEP,ch,0,effectVal)); - break; - case 0x14: // sweep down - dispatchCmd(DivCommand(DIV_CMD_NES_SWEEP,ch,1,effectVal)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_FDS: - switch (effect) { - case 0x10: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - case 0x11: // modulation depth - dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_DEPTH,ch,effectVal)); - break; - case 0x12: // modulation enable/high - dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_HIGH,ch,effectVal)); - break; - case 0x13: // modulation low - dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_LOW,ch,effectVal)); - break; - case 0x14: // modulation pos - dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_POS,ch,effectVal)); - break; - case 0x15: // modulation wave - dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_WAVE,ch,effectVal)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_OPLL_DRUMS: - case DIV_SYSTEM_OPL_DRUMS: - case DIV_SYSTEM_OPL2_DRUMS: - case DIV_SYSTEM_OPL3_DRUMS: - switch (effect) { - case 0x18: // drum mode toggle - dispatchCmd(DivCommand(DIV_CMD_FM_EXTCH,ch,effectVal)); - break; - case 0x30: // toggle hard-reset - dispatchCmd(DivCommand(DIV_CMD_FM_HARD_RESET,ch,effectVal)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_OPLL: - case DIV_SYSTEM_VRC7: - case DIV_SYSTEM_OPL: - case DIV_SYSTEM_OPL2: - case DIV_SYSTEM_OPL3: - switch (effect) { - case 0x30: // toggle hard-reset - dispatchCmd(DivCommand(DIV_CMD_FM_HARD_RESET,ch,effectVal)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_N163: - switch (effect) { - case 0x10: // select instrument waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - case 0x11: // select instrument waveform position in RAM - dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_POSITION,ch,effectVal)); - break; - case 0x12: // select instrument waveform length in RAM - dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LENGTH,ch,effectVal)); - break; - case 0x13: // change instrument waveform update mode - dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_MODE,ch,effectVal)); - break; - case 0x14: // select waveform for load to RAM - dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOAD,ch,effectVal)); - break; - case 0x15: // select waveform position for load to RAM - dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOADPOS,ch,effectVal)); - break; - case 0x16: // select waveform length for load to RAM - dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOADLEN,ch,effectVal)); - break; - case 0x17: // change waveform load mode - dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOADMODE,ch,effectVal)); - break; - case 0x18: // change channel limits - dispatchCmd(DivCommand(DIV_CMD_N163_CHANNEL_LIMIT,ch,effectVal)); - break; - case 0x20: // (global) select waveform for load to RAM - dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOAD,ch,effectVal)); - break; - case 0x21: // (global) select waveform position for load to RAM - dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOADPOS,ch,effectVal)); - break; - case 0x22: // (global) select waveform length for load to RAM - dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOADLEN,ch,effectVal)); - break; - case 0x23: // (global) change waveform load mode - dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOADMODE,ch,effectVal)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_QSOUND: - switch (effect) { - case 0x10: // echo feedback - dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_FEEDBACK,ch,effectVal)); - break; - case 0x11: // echo level - dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_LEVEL,ch,effectVal)); - break; - default: - if ((effect&0xf0)==0x30) { - dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_DELAY,ch,((effect & 0x0f) << 8) | effectVal)); - } else { - return false; - } - break; - } - break; - case DIV_SYSTEM_X1_010: - switch (effect) { - case 0x10: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - case 0x11: // select envelope shape - dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SHAPE,ch,effectVal)); - break; - case 0x17: // PCM enable - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); - break; - default: - return false; - } - break; - case DIV_SYSTEM_SWAN: - switch (effect) { - case 0x10: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - case 0x11: // noise mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - case 0x12: // sweep period - dispatchCmd(DivCommand(DIV_CMD_WS_SWEEP_TIME,ch,effectVal)); - break; - case 0x13: // sweep amount - dispatchCmd(DivCommand(DIV_CMD_WS_SWEEP_AMOUNT,ch,effectVal)); - break; - case 0x17: // PCM enable - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); - break; - default: - return false; - } - break; - case DIV_SYSTEM_VERA: - switch (effect) { - case 0x20: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - case 0x22: // duty - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_BUBSYS_WSG: - case DIV_SYSTEM_PET: - case DIV_SYSTEM_VIC20: - switch (effect) { - case 0x10: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_VRC6: - switch (effect) { - case 0x12: // duty or noise mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - case 0x17: // PCM enable - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); - break; - default: - return false; - } - break; - default: - return false; - } - return true; + if (sysDefs[sysOfChan[ch]]==NULL) return false; + return sysDefs[sysOfChan[ch]]->effectFunc(ch,effect,effectVal); } -#define IS_YM2610 (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) -#define IS_OPM_LIKE (sysOfChan[ch]==DIV_SYSTEM_YM2151 || sysOfChan[ch]==DIV_SYSTEM_OPZ) - bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char effectVal) { - switch (sysOfChan[ch]) { - case DIV_SYSTEM_YM2612: - case DIV_SYSTEM_YM2612_EXT: - case DIV_SYSTEM_YM2151: - case DIV_SYSTEM_YM2610: - case DIV_SYSTEM_YM2610_EXT: - case DIV_SYSTEM_YM2610_FULL: - case DIV_SYSTEM_YM2610_FULL_EXT: - case DIV_SYSTEM_YM2610B: - case DIV_SYSTEM_YM2610B_EXT: - case DIV_SYSTEM_OPZ: - switch (effect) { - case 0x10: // LFO or noise mode - if (IS_OPM_LIKE) { - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); - } else { - dispatchCmd(DivCommand(DIV_CMD_FM_LFO,ch,effectVal)); - } - break; - case 0x11: // FB - dispatchCmd(DivCommand(DIV_CMD_FM_FB,ch,effectVal&7)); - break; - case 0x12: // TL op1 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,0,effectVal&0x7f)); - break; - case 0x13: // TL op2 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,1,effectVal&0x7f)); - break; - case 0x14: // TL op3 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,2,effectVal&0x7f)); - break; - case 0x15: // TL op4 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,3,effectVal&0x7f)); - break; - case 0x16: // MULT - if ((effectVal>>4)>0 && (effectVal>>4)<5) { - dispatchCmd(DivCommand(DIV_CMD_FM_MULT,ch,(effectVal>>4)-1,effectVal&15)); - } - break; - case 0x17: // arcade LFO - if (IS_OPM_LIKE) { - dispatchCmd(DivCommand(DIV_CMD_FM_LFO,ch,effectVal)); - } - break; - case 0x18: // EXT or LFO waveform - if (IS_OPM_LIKE) { - dispatchCmd(DivCommand(DIV_CMD_FM_LFO_WAVE,ch,effectVal)); - } else { - dispatchCmd(DivCommand(DIV_CMD_FM_EXTCH,ch,effectVal)); - } - break; - case 0x19: // AR global - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,-1,effectVal&31)); - break; - case 0x1a: // AR op1 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,0,effectVal&31)); - break; - case 0x1b: // AR op2 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,1,effectVal&31)); - break; - case 0x1c: // AR op3 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,2,effectVal&31)); - break; - case 0x1d: // AR op4 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,3,effectVal&31)); - break; - case 0x1e: // UNOFFICIAL: Arcade AM depth - dispatchCmd(DivCommand(DIV_CMD_FM_AM_DEPTH,ch,effectVal&127)); - break; - case 0x1f: // UNOFFICIAL: Arcade PM depth - dispatchCmd(DivCommand(DIV_CMD_FM_PM_DEPTH,ch,effectVal&127)); - break; - case 0x20: // Neo Geo PSG mode - if (IS_YM2610) { - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - } - break; - case 0x21: // Neo Geo PSG noise freq - if (IS_YM2610) { - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); - } - break; - case 0x22: // UNOFFICIAL: Neo Geo PSG envelope enable - if (IS_YM2610) { - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SET,ch,effectVal)); - } - break; - case 0x23: // UNOFFICIAL: Neo Geo PSG envelope period low - if (IS_YM2610) { - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_LOW,ch,effectVal)); - } - break; - case 0x24: // UNOFFICIAL: Neo Geo PSG envelope period high - if (IS_YM2610) { - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_HIGH,ch,effectVal)); - } - break; - case 0x25: // UNOFFICIAL: Neo Geo PSG envelope slide up - if (IS_YM2610) { - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,-effectVal)); - } - break; - case 0x26: // UNOFFICIAL: Neo Geo PSG envelope slide down - if (IS_YM2610) { - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,effectVal)); - } - break; - case 0x29: // auto-envelope - if (IS_YM2610) { - dispatchCmd(DivCommand(DIV_CMD_AY_AUTO_ENVELOPE,ch,effectVal)); - } - break; - default: - return false; - } - break; - case DIV_SYSTEM_OPLL: - case DIV_SYSTEM_OPLL_DRUMS: - case DIV_SYSTEM_VRC7: - switch (effect) { - case 0x11: // FB - dispatchCmd(DivCommand(DIV_CMD_FM_FB,ch,effectVal&7)); - break; - case 0x12: // TL op1 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,0,effectVal&0x3f)); - break; - case 0x13: // TL op2 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,1,effectVal&0x0f)); - break; - case 0x16: // MULT - if ((effectVal>>4)>0 && (effectVal>>4)<3) { - dispatchCmd(DivCommand(DIV_CMD_FM_MULT,ch,(effectVal>>4)-1,effectVal&15)); - } - break; - case 0x19: // AR global - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,-1,effectVal&31)); - break; - case 0x1a: // AR op1 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,0,effectVal&31)); - break; - case 0x1b: // AR op2 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,1,effectVal&31)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_OPL: - case DIV_SYSTEM_OPL2: - case DIV_SYSTEM_OPL3: - case DIV_SYSTEM_OPL_DRUMS: - case DIV_SYSTEM_OPL2_DRUMS: - case DIV_SYSTEM_OPL3_DRUMS: - switch (effect) { - case 0x10: // DAM - dispatchCmd(DivCommand(DIV_CMD_FM_LFO,ch,effectVal&1)); - break; - case 0x11: // FB - dispatchCmd(DivCommand(DIV_CMD_FM_FB,ch,effectVal&7)); - break; - case 0x12: // TL op1 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,0,effectVal&0x3f)); - break; - case 0x13: // TL op2 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,1,effectVal&0x3f)); - break; - case 0x14: // TL op3 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,2,effectVal&0x3f)); - break; - case 0x15: // TL op4 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,3,effectVal&0x3f)); - break; - case 0x16: // MULT - if ((effectVal>>4)>0 && (effectVal>>4)<5) { - dispatchCmd(DivCommand(DIV_CMD_FM_MULT,ch,(effectVal>>4)-1,effectVal&15)); - } - break; - case 0x17: // DVB - dispatchCmd(DivCommand(DIV_CMD_FM_LFO,ch,2+(effectVal&1))); - break; - case 0x19: // AR global - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,-1,effectVal&15)); - break; - case 0x1a: // AR op1 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,0,effectVal&15)); - break; - case 0x1b: // AR op2 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,1,effectVal&15)); - break; - case 0x1c: // AR op3 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,2,effectVal&15)); - break; - case 0x1d: // AR op4 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,3,effectVal&15)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_8580: - switch (effect) { - case 0x10: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - case 0x11: // cutoff - dispatchCmd(DivCommand(DIV_CMD_C64_CUTOFF,ch,effectVal)); - break; - case 0x12: // duty - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - case 0x13: // resonance - dispatchCmd(DivCommand(DIV_CMD_C64_RESONANCE,ch,effectVal)); - break; - case 0x14: // filter mode - dispatchCmd(DivCommand(DIV_CMD_C64_FILTER_MODE,ch,effectVal)); - break; - case 0x15: // reset time - dispatchCmd(DivCommand(DIV_CMD_C64_RESET_TIME,ch,effectVal)); - break; - case 0x1a: // reset mask - dispatchCmd(DivCommand(DIV_CMD_C64_RESET_MASK,ch,effectVal)); - break; - case 0x1b: // cutoff reset - dispatchCmd(DivCommand(DIV_CMD_C64_FILTER_RESET,ch,effectVal)); - break; - case 0x1c: // duty reset - dispatchCmd(DivCommand(DIV_CMD_C64_DUTY_RESET,ch,effectVal)); - break; - case 0x1e: // extended - dispatchCmd(DivCommand(DIV_CMD_C64_EXTENDED,ch,effectVal)); - break; - case 0x30: case 0x31: case 0x32: case 0x33: - case 0x34: case 0x35: case 0x36: case 0x37: - case 0x38: case 0x39: case 0x3a: case 0x3b: - case 0x3c: case 0x3d: case 0x3e: case 0x3f: // fine duty - dispatchCmd(DivCommand(DIV_CMD_C64_FINE_DUTY,ch,((effect&0x0f)<<8)|effectVal)); - break; - case 0x40: case 0x41: case 0x42: case 0x43: - case 0x44: case 0x45: case 0x46: case 0x47: // fine cutoff - dispatchCmd(DivCommand(DIV_CMD_C64_FINE_CUTOFF,ch,((effect&0x07)<<8)|effectVal)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_AY8910: - case DIV_SYSTEM_AY8930: - switch (effect) { - case 0x12: // duty on 8930 - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,0x10+(effectVal&15))); - break; - case 0x20: // mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal&15)); - break; - case 0x21: // noise freq - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); - break; - case 0x22: // envelope enable - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SET,ch,effectVal)); - break; - case 0x23: // envelope period low - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_LOW,ch,effectVal)); - break; - case 0x24: // envelope period high - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_HIGH,ch,effectVal)); - break; - case 0x25: // envelope slide up - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,-effectVal)); - break; - case 0x26: // envelope slide down - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,effectVal)); - break; - case 0x27: // noise and mask - dispatchCmd(DivCommand(DIV_CMD_AY_NOISE_MASK_AND,ch,effectVal)); - break; - case 0x28: // noise or mask - dispatchCmd(DivCommand(DIV_CMD_AY_NOISE_MASK_OR,ch,effectVal)); - break; - case 0x29: // auto-envelope - dispatchCmd(DivCommand(DIV_CMD_AY_AUTO_ENVELOPE,ch,effectVal)); - break; - case 0x2d: // TEST - dispatchCmd(DivCommand(DIV_CMD_AY_IO_WRITE,ch,255,effectVal)); - break; - case 0x2e: // I/O port A - dispatchCmd(DivCommand(DIV_CMD_AY_IO_WRITE,ch,0,effectVal)); - break; - case 0x2f: // I/O port B - dispatchCmd(DivCommand(DIV_CMD_AY_IO_WRITE,ch,1,effectVal)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_SAA1099: - switch (effect) { - case 0x10: // select channel mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - case 0x11: // set noise freq - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); - break; - case 0x12: // setup envelope - dispatchCmd(DivCommand(DIV_CMD_SAA_ENVELOPE,ch,effectVal)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_TIA: - switch (effect) { - case 0x10: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_AMIGA: - switch (effect) { - case 0x10: // toggle filter - dispatchCmd(DivCommand(DIV_CMD_AMIGA_FILTER,ch,effectVal)); - break; - case 0x11: // toggle AM - dispatchCmd(DivCommand(DIV_CMD_AMIGA_AM,ch,effectVal)); - break; - case 0x12: // toggle PM - dispatchCmd(DivCommand(DIV_CMD_AMIGA_PM,ch,effectVal)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_SEGAPCM: - case DIV_SYSTEM_SEGAPCM_COMPAT: - switch (effect) { - case 0x20: // PCM frequency - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_FREQ,ch,effectVal)); - break; - default: - return false; - } - break; - case DIV_SYSTEM_LYNX: - if (effect>=0x30 && effect<0x40) { - int value = ((int)(effect&0x0f)<<8)|effectVal; - dispatchCmd(DivCommand(DIV_CMD_LYNX_LFSR_LOAD,ch,value)); - break; - } - return false; - break; - case DIV_SYSTEM_X1_010: - switch (effect) { - case 0x20: // PCM frequency - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_FREQ,ch,effectVal)); - break; - case 0x22: // envelope mode - dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_MODE,ch,effectVal)); - break; - case 0x23: // envelope period - dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_PERIOD,ch,effectVal)); - break; - case 0x25: // envelope slide up - dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SLIDE,ch,effectVal)); - break; - case 0x26: // envelope slide down - dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SLIDE,ch,-effectVal)); - break; - case 0x29: // auto-envelope - dispatchCmd(DivCommand(DIV_CMD_X1_010_AUTO_ENVELOPE,ch,effectVal)); - break; - default: - return false; - } - break; - default: - return false; - } - return true; + if (sysDefs[sysOfChan[ch]]==NULL) return false; + return sysDefs[sysOfChan[ch]]->postEffectFunc(ch,effect,effectVal); } void DivEngine::processRow(int i, bool afterDelay) { @@ -907,7 +268,7 @@ void DivEngine::processRow(int i, bool afterDelay) { int whatRow=afterDelay?chan[i].delayRow:curRow; DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][whatOrder],false); // pre effects - if (!afterDelay) for (int j=0; jdata[whatRow][4+(j<<1)]; short effectVal=pat->data[whatRow][5+(j<<1)]; @@ -1014,9 +375,10 @@ void DivEngine::processRow(int i, bool afterDelay) { short lastSlide=-1; bool calledPorta=false; + bool panChanged=false; // effects - for (int j=0; jdata[whatRow][4+(j<<1)]; short effectVal=pat->data[whatRow][5+(j<<1)]; @@ -1042,8 +404,25 @@ void DivEngine::processRow(int i, bool afterDelay) { changePos=effectVal; } break; - case 0x08: // panning - dispatchCmd(DivCommand(DIV_CMD_PANNING,i,effectVal)); + case 0x08: // panning (split 4-bit) + chan[i].panL=(effectVal>>4)|(effectVal&0xf0); + chan[i].panR=(effectVal&15)|((effectVal&15)<<4); + panChanged=true; + break; + case 0x80: { // panning (linear) + unsigned short pan=convertPanLinearToSplit(effectVal,8,255); + chan[i].panL=pan>>8; + chan[i].panR=pan&0xff; + panChanged=true; + break; + } + case 0x81: // panning left (split 8-bit) + chan[i].panL=effectVal; + panChanged=true; + break; + case 0x82: // panning right (split 8-bit) + chan[i].panR=effectVal; + panChanged=true; break; case 0x01: // ramp up if (song.ignoreDuplicateSlides && (lastSlide==0x01 || lastSlide==0x1337)) break; @@ -1174,6 +553,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].inPorta=true; chan[i].shorthandPorta=true; if (!song.brokenShortcutSlides) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0)); + if (song.e1e2AlsoTakePriority) lastSlide=0x1337; // ... } else { chan[i].inPorta=false; if (!song.brokenShortcutSlides) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); @@ -1190,6 +570,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].inPorta=true; chan[i].shorthandPorta=true; if (!song.brokenShortcutSlides) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0)); + if (song.e1e2AlsoTakePriority) lastSlide=0x1337; // ... } else { chan[i].inPorta=false; if (!song.brokenShortcutSlides) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); @@ -1295,6 +676,10 @@ void DivEngine::processRow(int i, bool afterDelay) { } } + if (panChanged) { + dispatchCmd(DivCommand(DIV_CMD_PANNING,i,chan[i].panL,chan[i].panR)); + } + if (insChanged && (chan[i].inPorta || calledPorta) && song.newInsTriggersInPorta) { dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,DIV_NOTE_NULL)); } @@ -1333,7 +718,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].noteOnInhibit=false; // post effects - for (int j=0; jdata[whatRow][4+(j<<1)]; short effectVal=pat->data[whatRow][5+(j<<1)]; @@ -1370,7 +755,7 @@ void DivEngine::nextRow() { snprintf(pb2,4095,"\x1b[0;36m%.2x",pat->data[curRow][2]); strcat(pb3,pb2); } - for (int j=0; jdata[curRow][4+(j<<1)]==-1) { strcat(pb3,"\x1b[m--"); } else { @@ -1444,7 +829,7 @@ void DivEngine::nextRow() { if (song.oneTickCut) { bool doPrepareCut=true; - for (int j=0; jdata[curRow][4+(j<<1)]==0x03) { doPrepareCut=false; break; @@ -1467,14 +852,21 @@ void DivEngine::nextRow() { firstTick=true; } -bool DivEngine::nextTick(bool noAccum) { +bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { bool ret=false; if (divider<10) divider=10; + + if (lowLatency && !skipping && !inhibitLowLat) { + tickMult=1000/divider; + if (tickMult<1) tickMult=1; + } else { + tickMult=1; + } - cycles=got.rate*pow(2,MASTER_CLOCK_PREC)/divider; - clockDrift+=fmod(got.rate*pow(2,MASTER_CLOCK_PREC),(double)divider); - if (clockDrift>=divider) { - clockDrift-=divider; + cycles=got.rate*pow(2,MASTER_CLOCK_PREC)/(divider*tickMult); + clockDrift+=fmod(got.rate*pow(2,MASTER_CLOCK_PREC),(double)(divider*tickMult)); + if (clockDrift>=(divider*tickMult)) { + clockDrift-=(divider*tickMult); cycles++; } @@ -1497,130 +889,133 @@ bool DivEngine::nextTick(bool noAccum) { } if (!freelance) { - if (stepPlay!=1) if (--ticks<=0) { - ret=endOfSong; - if (endOfSong) { - if (song.loopModality!=2) { - playSub(true); + if (--subticks<=0) { + subticks=tickMult; + if (stepPlay!=1) if (--ticks<=0) { + ret=endOfSong; + if (endOfSong) { + if (song.loopModality!=2) { + playSub(true); + } } + endOfSong=false; + if (stepPlay==2) stepPlay=1; + nextRow(); } - endOfSong=false; - if (stepPlay==2) stepPlay=1; - nextRow(); - } - // process stuff - for (int i=0; i0) { - if (--chan[i].rowDelay==0) { - processRow(i,true); + // process stuff + for (int i=0; i0) { + if (--chan[i].rowDelay==0) { + processRow(i,true); + } } - } - if (chan[i].retrigSpeed) { - if (--chan[i].retrigTick<0) { - chan[i].retrigTick=chan[i].retrigSpeed-1; - dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,DIV_NOTE_NULL)); - keyHit[i]=true; + if (chan[i].retrigSpeed) { + if (--chan[i].retrigTick<0) { + chan[i].retrigTick=chan[i].retrigSpeed-1; + dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,DIV_NOTE_NULL)); + keyHit[i]=true; + } } - } - if (!song.noSlidesOnFirstTick || !firstTick) { - if (chan[i].volSpeed!=0) { - chan[i].volume=(chan[i].volume&0xff)|(dispatchCmd(DivCommand(DIV_CMD_GET_VOLUME,i))<<8); - chan[i].volume+=chan[i].volSpeed; - if (chan[i].volume>chan[i].volMax) { - chan[i].volume=chan[i].volMax; - chan[i].volSpeed=0; - dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); - } else if (chan[i].volume<0) { - chan[i].volSpeed=0; - if (song.legacyVolumeSlides) { - chan[i].volume=chan[i].volMax+1; + if (!song.noSlidesOnFirstTick || !firstTick) { + if (chan[i].volSpeed!=0) { + chan[i].volume=(chan[i].volume&0xff)|(dispatchCmd(DivCommand(DIV_CMD_GET_VOLUME,i))<<8); + chan[i].volume+=chan[i].volSpeed; + if (chan[i].volume>chan[i].volMax) { + chan[i].volume=chan[i].volMax; + chan[i].volSpeed=0; + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + } else if (chan[i].volume<0) { + chan[i].volSpeed=0; + if (song.legacyVolumeSlides) { + chan[i].volume=chan[i].volMax+1; + } else { + chan[i].volume=0; + } + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); } else { - chan[i].volume=0; + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); } - dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); - } else { - dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); } } - } - if (chan[i].vibratoDepth>0) { - chan[i].vibratoPos+=chan[i].vibratoRate; - if (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64; - switch (chan[i].vibratoDir) { - case 1: // up - dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(MAX(0,(chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); - break; - case 2: // down - dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(MIN(0,(chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); - break; - default: // both - dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); - break; - } - } - if (!song.noSlidesOnFirstTick || !firstTick) { - if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) { - if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) { - chan[i].portaSpeed=0; - chan[i].oldNote=chan[i].note; - chan[i].note=chan[i].portaNote; - chan[i].inPorta=false; - dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + if (chan[i].vibratoDepth>0) { + chan[i].vibratoPos+=chan[i].vibratoRate; + if (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64; + switch (chan[i].vibratoDir) { + case 1: // up + dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(MAX(0,(chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); + break; + case 2: // down + dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(MIN(0,(chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); + break; + default: // both + dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); + break; } } - } - if (chan[i].cut>0) { - if (--chan[i].cut<1) { - chan[i].oldNote=chan[i].note; - //chan[i].note=-1; - if (chan[i].inPorta && song.noteOffResetsSlides) { - chan[i].keyOff=true; - chan[i].keyOn=false; - if (chan[i].stopOnOff) { - chan[i].portaNote=-1; - chan[i].portaSpeed=-1; - chan[i].stopOnOff=false; - } - if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { - chan[i].portaNote=-1; - chan[i].portaSpeed=-1; - /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { - chan[i+1].portaNote=-1; - chan[i+1].portaSpeed=-1; - }*/ - } - dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); - chan[i].scheduledSlideReset=true; - } - dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,i)); - } - } - if (chan[i].resetArp) { - dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); - chan[i].resetArp=false; - } - if (song.rowResetsArpPos && firstTick) { - chan[i].arpStage=-1; - } - if (chan[i].arp!=0 && !chan[i].arpYield && chan[i].portaSpeed<1) { - if (--chan[i].arpTicks<1) { - chan[i].arpTicks=song.arpLen; - chan[i].arpStage++; - if (chan[i].arpStage>2) chan[i].arpStage=0; - switch (chan[i].arpStage) { - case 0: + if (!song.noSlidesOnFirstTick || !firstTick) { + if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) { + if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) { + chan[i].portaSpeed=0; + chan[i].oldNote=chan[i].note; + chan[i].note=chan[i].portaNote; + chan[i].inPorta=false; dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); - break; - case 1: - dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note+(chan[i].arp>>4))); - break; - case 2: - dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note+(chan[i].arp&15))); - break; + } } } - } else { - chan[i].arpYield=false; + if (chan[i].cut>0) { + if (--chan[i].cut<1) { + chan[i].oldNote=chan[i].note; + //chan[i].note=-1; + if (chan[i].inPorta && song.noteOffResetsSlides) { + chan[i].keyOff=true; + chan[i].keyOn=false; + if (chan[i].stopOnOff) { + chan[i].portaNote=-1; + chan[i].portaSpeed=-1; + chan[i].stopOnOff=false; + } + if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { + chan[i].portaNote=-1; + chan[i].portaSpeed=-1; + /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { + chan[i+1].portaNote=-1; + chan[i+1].portaSpeed=-1; + }*/ + } + dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); + chan[i].scheduledSlideReset=true; + } + dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,i)); + } + } + if (chan[i].resetArp) { + dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + chan[i].resetArp=false; + } + if (song.rowResetsArpPos && firstTick) { + chan[i].arpStage=-1; + } + if (chan[i].arp!=0 && !chan[i].arpYield && chan[i].portaSpeed<1) { + if (--chan[i].arpTicks<1) { + chan[i].arpTicks=song.arpLen; + chan[i].arpStage++; + if (chan[i].arpStage>2) chan[i].arpStage=0; + switch (chan[i].arpStage) { + case 0: + dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + break; + case 1: + dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note+(chan[i].arp>>4))); + break; + case 2: + dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note+(chan[i].arp&15))); + break; + } + } + } else { + chan[i].arpYield=false; + } } } } @@ -1628,13 +1023,13 @@ bool DivEngine::nextTick(bool noAccum) { firstTick=false; // system tick - for (int i=0; itick(); + for (int i=0; itick(subticks==tickMult); if (!freelance) { if (stepPlay!=1) { if (!noAccum) { totalTicksR++; - totalTicks+=1000000/divider; + totalTicks+=1000000/(divider*tickMult); } if (totalTicks>=1000000) { totalTicks-=1000000; @@ -1644,7 +1039,7 @@ bool DivEngine::nextTick(bool noAccum) { } } - if (consoleMode) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000,curOrder,song.ordersLen,curRow,song.patLen,cmdsPerSecond); + if (consoleMode && subticks<=1) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000,curOrder,song.ordersLen,curRow,song.patLen,cmdsPerSecond); } if (haltOn==DIV_HALT_TICK) halted=true; @@ -1668,6 +1063,8 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi } got.bufsize=size; + std::chrono::steady_clock::time_point ts_processBegin=std::chrono::steady_clock::now(); + // process MIDI events (TODO: everything) if (output) if (output->midiIn) while (!output->midiIn->queue.empty()) { TAMidiMessage& msg=output->midiIn->queue.front(); @@ -1802,8 +1199,8 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi } runtotal[i]=blip_clocks_needed(disCont[i].bb[0],size-lastAvail[i]); if (runtotal[i]>disCont[i].bbInLen) { - delete disCont[i].bbIn[0]; - delete disCont[i].bbIn[1]; + delete[] disCont[i].bbIn[0]; + delete[] disCont[i].bbIn[1]; disCont[i].bbIn[0]=new short[runtotal[i]+256]; disCont[i].bbIn[1]=new short[runtotal[i]+256]; disCont[i].bbInLen=runtotal[i]+256; @@ -1831,7 +1228,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi // 2. check whether we gonna tick if (cycles<=0) { // we have to tick - if (!freelance && stepPlay!=-1) { + if (!freelance && stepPlay!=-1 && subticks==1) { unsigned int realPos=size-(runLeftG>>MASTER_CLOCK_PREC); if (realPos>=size) realPos=size-1; if (song.hilightA>0) { @@ -1923,8 +1320,8 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi metroAmp=0.7f; } if (metroAmp>0.0f) { - out[0][i]+=(sin(metroPos*2*M_PI))*metroAmp; - out[1][i]+=(sin(metroPos*2*M_PI))*metroAmp; + out[0][i]+=(sin(metroPos*2*M_PI))*metroAmp*metroVol; + out[1][i]+=(sin(metroPos*2*M_PI))*metroAmp*metroVol; } metroAmp-=0.0003f; if (metroAmp<0.0f) metroAmp=0.0f; @@ -1946,4 +1343,8 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi } } isBusy.unlock(); + + std::chrono::steady_clock::time_point ts_processEnd=std::chrono::steady_clock::now(); + + processTime=std::chrono::duration_cast(ts_processEnd-ts_processBegin).count(); } diff --git a/src/engine/safeReader.cpp b/src/engine/safeReader.cpp index 7fa66e8b3..36d6097d6 100644 --- a/src/engine/safeReader.cpp +++ b/src/engine/safeReader.cpp @@ -144,7 +144,9 @@ String SafeReader::readString(size_t stlen) { logD("SR: reading string len %d at %x",stlen,curSeek); #endif size_t curPos=0; - while (curPos= len; }; SafeReader(void* b, size_t l): buf((unsigned char*)b), diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 93e3ece6d..e46c267d1 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -71,7 +71,7 @@ bool DivSample::save(const char* path) { } sf_command(f, SFC_SET_INSTRUMENT, &inst, sizeof(inst)); - sf_write_short(f,data16,length16); + sf_writef_short(f,data16,samples); sf_close(f); @@ -323,6 +323,7 @@ bool DivSample::insert(unsigned int pos, unsigned int length) { #define RESAMPLE_END \ if (loopStart>=0) loopStart=(double)loopStart*(r/(double)rate); \ + centerRate=(int)((double)centerRate*(r/(double)rate)); \ rate=r; \ samples=finalCount; \ if (depth==16) { \ @@ -586,8 +587,8 @@ bool DivSample::resampleSinc(double r) { result+=s[j]*t2[7-j]; result+=s[8+j]*t1[j]; } - if (result<-32768) result=-32768; - if (result>32767) result=32767; + if (result<-128) result=-128; + if (result>127) result=127; if (i>=8) { data8[i-8]=result; } diff --git a/src/engine/sample.h b/src/engine/sample.h index 1a9e53d32..6e4de0941 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -86,7 +86,7 @@ struct DivSample { unsigned int length8, length16, length1, lengthDPCM, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX; unsigned int off8, off16, off1, offDPCM, offQSoundA, offA, offB, offX68, offBRR, offVOX; - unsigned int offSegaPCM, offQSound, offX1_010; + unsigned int offSegaPCM, offQSound, offX1_010, offSU; unsigned int samples; @@ -246,6 +246,8 @@ struct DivSample { offVOX(0), offSegaPCM(0), offQSound(0), + offX1_010(0), + offSU(0), samples(0) {} ~DivSample(); }; diff --git a/src/engine/song.cpp b/src/engine/song.cpp index 4d2aef8da..7a815eb16 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -19,21 +19,57 @@ #include "song.h" +void DivSong::clearSongData() { + for (int i=0; i game name + // writer -> ripper + // createdDate -> year String carrier, composer, vendor, category, writer, arranger, copyright, manGroup, manInfo, createdDate, revisionDate; + // more VGM specific stuff + String nameJ, authorJ, categoryJ; + // other things String chanName[DIV_MAX_CHANS]; String chanShortName[DIV_MAX_CHANS]; @@ -314,6 +331,12 @@ struct DivSong { bool buggyPortaAfterSlide; bool gbInsAffectsEnvelope; bool sharedExtStat; + bool ignoreDACModeOutsideIntendedChannel; + bool e1e2AlsoTakePriority; + bool newSegaPCM; + bool fbPortaPause; + bool snDutyReset; + bool pitchMacroIsLinear; DivOrders orders; std::vector ins; @@ -322,12 +345,33 @@ struct DivSong { std::vector sample; bool chanShow[DIV_MAX_CHANS]; - bool chanCollapse[DIV_MAX_CHANS]; + unsigned char chanCollapse[DIV_MAX_CHANS]; - DivInstrument nullIns; + DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsQSound; DivWavetable nullWave; DivSample nullSample; + /** + * clear orders and patterns. + */ + void clearSongData(); + + /** + * clear instruments. + */ + void clearInstruments(); + + /** + * clear wavetables. + */ + void clearWavetables(); + + /** + * clear samples. + */ + void clearSamples(); + + /** * unloads the song, freeing all memory associated with it. * use before destroying the object. @@ -393,7 +437,13 @@ struct DivSong { ignoreJumpAtEnd(false), buggyPortaAfterSlide(false), gbInsAffectsEnvelope(true), - sharedExtStat(true) { + sharedExtStat(true), + ignoreDACModeOutsideIntendedChannel(false), + e1e2AlsoTakePriority(false), + newSegaPCM(true), + fbPortaPause(false), + snDutyReset(false), + pitchMacroIsLinear(true) { for (int i=0; i<32; i++) { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; @@ -402,10 +452,55 @@ struct DivSong { } for (int i=0; iid; } DivSystem DivEngine::systemFromFileDMF(unsigned char val) { - switch (val) { - case 0x01: - return DIV_SYSTEM_YMU759; - case 0x02: - return DIV_SYSTEM_GENESIS; - case 0x03: - return DIV_SYSTEM_SMS; - case 0x04: - return DIV_SYSTEM_GB; - case 0x05: - return DIV_SYSTEM_PCE; - case 0x06: - return DIV_SYSTEM_NES; - case 0x07: - return DIV_SYSTEM_C64_8580; - case 0x08: - return DIV_SYSTEM_ARCADE; - case 0x09: - return DIV_SYSTEM_YM2610; - case 0x42: - return DIV_SYSTEM_GENESIS_EXT; - case 0x43: - return DIV_SYSTEM_SMS_OPLL; - case 0x46: - return DIV_SYSTEM_NES_VRC7; - case 0x47: - return DIV_SYSTEM_C64_6581; - case 0x49: - return DIV_SYSTEM_YM2610_EXT; - case 0x86: - return DIV_SYSTEM_NES_FDS; - } - return DIV_SYSTEM_NULL; + return sysFileMapDMF[val]; } unsigned char DivEngine::systemToFileDMF(DivSystem val) { - switch (val) { - case DIV_SYSTEM_YMU759: - return 0x01; - case DIV_SYSTEM_GENESIS: - return 0x02; - case DIV_SYSTEM_SMS: - return 0x03; - case DIV_SYSTEM_GB: - return 0x04; - case DIV_SYSTEM_PCE: - return 0x05; - case DIV_SYSTEM_NES: - return 0x06; - case DIV_SYSTEM_C64_8580: - return 0x07; - case DIV_SYSTEM_ARCADE: - return 0x08; - case DIV_SYSTEM_YM2610: - return 0x09; - case DIV_SYSTEM_GENESIS_EXT: - return 0x42; - case DIV_SYSTEM_SMS_OPLL: - return 0x43; - case DIV_SYSTEM_NES_VRC7: - return 0x46; - case DIV_SYSTEM_NES_FDS: - return 0x86; - case DIV_SYSTEM_C64_6581: - return 0x47; - case DIV_SYSTEM_YM2610_EXT: - return 0x49; - default: - return 0; - } - return 0; + if (sysDefs[val]==NULL) return 0; + return sysDefs[val]->id_DMF; } int DivEngine::getChannelCount(DivSystem sys) { - switch (sys) { - case DIV_SYSTEM_NULL: - return 0; - case DIV_SYSTEM_YMU759: - return 17; - case DIV_SYSTEM_GENESIS: - return 10; - case DIV_SYSTEM_SMS: - case DIV_SYSTEM_GB: - return 4; - case DIV_SYSTEM_PCE: - return 6; - case DIV_SYSTEM_NES: - return 5; - case DIV_SYSTEM_C64_6581: - case DIV_SYSTEM_C64_8580: - return 3; - case DIV_SYSTEM_ARCADE: - case DIV_SYSTEM_GENESIS_EXT: - case DIV_SYSTEM_YM2610: - case DIV_SYSTEM_SMS_OPLL: - return 13; - case DIV_SYSTEM_NES_VRC7: - return 11; - case DIV_SYSTEM_NES_FDS: - return 6; - case DIV_SYSTEM_YM2610_EXT: - return 16; - case DIV_SYSTEM_AY8910: - case DIV_SYSTEM_AY8930: - return 3; - case DIV_SYSTEM_AMIGA: - return 4; - case DIV_SYSTEM_YM2151: - return 8; - case DIV_SYSTEM_YM2612: - return 6; - case DIV_SYSTEM_TIA: - return 2; - case DIV_SYSTEM_VIC20: - return 4; - case DIV_SYSTEM_PET: - return 1; - case DIV_SYSTEM_SNES: - return 8; - case DIV_SYSTEM_VRC6: - return 3; - case DIV_SYSTEM_OPLL: - return 9; - case DIV_SYSTEM_FDS: - return 1; - case DIV_SYSTEM_MMC5: - return 3; - case DIV_SYSTEM_N163: - return 8; - case DIV_SYSTEM_OPN: - return 6; - case DIV_SYSTEM_PC98: - return 16; - case DIV_SYSTEM_OPL: - return 9; - case DIV_SYSTEM_OPL2: - return 9; - case DIV_SYSTEM_OPL3: - return 18; - case DIV_SYSTEM_MULTIPCM: - return 28; - case DIV_SYSTEM_PCSPKR: - return 1; - case DIV_SYSTEM_POKEY: - return 4; - case DIV_SYSTEM_RF5C68: - return 8; - case DIV_SYSTEM_SWAN: - return 4; - case DIV_SYSTEM_SAA1099: - return 6; - case DIV_SYSTEM_OPZ: - return 8; - case DIV_SYSTEM_POKEMINI: - return 1; - case DIV_SYSTEM_SEGAPCM: - case DIV_SYSTEM_X1_010: - return 16; - case DIV_SYSTEM_VBOY: - return 6; - case DIV_SYSTEM_VRC7: - return 6; - case DIV_SYSTEM_YM2610B: - return 16; - case DIV_SYSTEM_SFX_BEEPER: - return 6; - case DIV_SYSTEM_YM2612_EXT: - return 9; - case DIV_SYSTEM_SCC: - return 5; - case DIV_SYSTEM_OPL_DRUMS: - return 11; - case DIV_SYSTEM_OPL2_DRUMS: - return 11; - case DIV_SYSTEM_OPL3_DRUMS: - return 20; - case DIV_SYSTEM_YM2610_FULL: - return 14; - case DIV_SYSTEM_YM2610_FULL_EXT: - return 17; - case DIV_SYSTEM_OPLL_DRUMS: - return 11; - case DIV_SYSTEM_LYNX: - return 4; - case DIV_SYSTEM_SEGAPCM_COMPAT: - return 5; - case DIV_SYSTEM_YM2610B_EXT: - case DIV_SYSTEM_QSOUND: - return 19; - case DIV_SYSTEM_VERA: - return 17; - case DIV_SYSTEM_BUBSYS_WSG: - return 2; - } - return 0; + if (sysDefs[sys]==NULL) return 0; + return sysDefs[sys]->channels; } int DivEngine::getTotalChannelCount() { return chans; } +std::vector& DivEngine::getPossibleInsTypes() { + return possibleInsTypes; +} + // TODO: rewrite this function (again). it's an unreliable mess. -const char* DivEngine::getSongSystemName() { +String DivEngine::getSongSystemName(bool isMultiSystemAcceptable) { switch (song.systemLen) { case 0: return "help! what's going on!"; @@ -629,274 +198,27 @@ const char* DivEngine::getSongSystemName() { } break; } - return "multi-system"; + if (isMultiSystemAcceptable) return "multi-system"; + + String ret=""; + for (int i=0; i0) ret+=" + "; + ret+=getSystemName(song.system[i]); + } + + return ret; } const char* DivEngine::getSystemName(DivSystem sys) { - switch (sys) { - case DIV_SYSTEM_NULL: - return "Unknown"; - case DIV_SYSTEM_YMU759: - return "Yamaha YMU759"; - case DIV_SYSTEM_GENESIS: - return "Sega Genesis/Mega Drive"; - case DIV_SYSTEM_SMS: - return "TI SN76489"; - case DIV_SYSTEM_SMS_OPLL: - return "Sega Master System + FM Expansion"; - case DIV_SYSTEM_GB: - return "Game Boy"; - case DIV_SYSTEM_PCE: - return "PC Engine/TurboGrafx-16"; - case DIV_SYSTEM_NES: - return "NES"; - case DIV_SYSTEM_NES_VRC7: - return "NES + Konami VRC7"; - case DIV_SYSTEM_NES_FDS: - return "Famicom Disk System"; - case DIV_SYSTEM_C64_6581: - return "Commodore 64 with 6581"; - case DIV_SYSTEM_C64_8580: - return "Commodore 64 with 8580"; - case DIV_SYSTEM_ARCADE: - return "YM2151 + SegaPCM Arcade"; - case DIV_SYSTEM_GENESIS_EXT: - return "Sega Genesis Extended Channel 3"; - case DIV_SYSTEM_YM2610: - return "Neo Geo CD"; - case DIV_SYSTEM_YM2610_EXT: - return "Neo Geo CD Extended Channel 2"; - case DIV_SYSTEM_YM2610_FULL: - return "Neo Geo"; - case DIV_SYSTEM_YM2610_FULL_EXT: - return "Neo Geo Extended Channel 2"; - case DIV_SYSTEM_AY8910: - return "AY-3-8910"; - case DIV_SYSTEM_AMIGA: - return "Amiga"; - case DIV_SYSTEM_YM2151: - return "Yamaha YM2151"; - case DIV_SYSTEM_YM2612: - return "Yamaha YM2612"; - case DIV_SYSTEM_TIA: - return "Atari 2600"; - case DIV_SYSTEM_VIC20: - return "Commodore VIC-20"; - case DIV_SYSTEM_PET: - return "Commodore PET"; - case DIV_SYSTEM_SNES: - return "SNES"; - case DIV_SYSTEM_VRC6: - return "Konami VRC6"; - case DIV_SYSTEM_OPLL: - return "Yamaha OPLL"; - case DIV_SYSTEM_FDS: - return "Famicom Disk System (chip)"; - case DIV_SYSTEM_MMC5: - return "MMC5"; - case DIV_SYSTEM_N163: - return "Namco 163"; - case DIV_SYSTEM_OPN: - return "NEC PC-9801-26K"; - case DIV_SYSTEM_PC98: - return "PC-9801-86 + Chibi-oto"; - case DIV_SYSTEM_OPL: - return "Yamaha OPL"; - case DIV_SYSTEM_OPL2: - return "Yamaha OPL2"; - case DIV_SYSTEM_OPL3: - return "Yamaha OPL3"; - case DIV_SYSTEM_MULTIPCM: - return "MultiPCM"; - case DIV_SYSTEM_PCSPKR: - return "PC Speaker"; - case DIV_SYSTEM_POKEY: - return "Atari 400/800"; - case DIV_SYSTEM_RF5C68: - return "Ricoh RF5C68"; - case DIV_SYSTEM_SWAN: - return "WonderSwan"; - case DIV_SYSTEM_SAA1099: - return "Philips SAA1099"; - case DIV_SYSTEM_OPZ: - return "Yamaha TX81Z/YS200"; - case DIV_SYSTEM_POKEMINI: - return "Pokémon Mini"; - case DIV_SYSTEM_AY8930: - return "Microchip AY8930"; - case DIV_SYSTEM_SEGAPCM: - return "SegaPCM"; - case DIV_SYSTEM_VBOY: - return "Virtual Boy"; - case DIV_SYSTEM_VRC7: - return "Konami VRC7"; - case DIV_SYSTEM_YM2610B: - return "Taito Arcade"; - case DIV_SYSTEM_SFX_BEEPER: - return "ZX Spectrum Beeper"; - case DIV_SYSTEM_YM2612_EXT: - return "Yamaha YM2612 Extended Channel 3"; - case DIV_SYSTEM_SCC: - return "Konami SCC"; - case DIV_SYSTEM_OPL_DRUMS: - return "Yamaha OPL with drums"; - case DIV_SYSTEM_OPL2_DRUMS: - return "Yamaha OPL2 with drums"; - case DIV_SYSTEM_OPL3_DRUMS: - return "Yamaha OPL3 with drums"; - case DIV_SYSTEM_OPLL_DRUMS: - return "Yamaha OPLL with drums"; - case DIV_SYSTEM_LYNX: - return "Atari Lynx"; - case DIV_SYSTEM_SEGAPCM_COMPAT: - return "SegaPCM (compatible 5-channel mode)"; - case DIV_SYSTEM_YM2610B_EXT: - return "Taito Arcade Extended Channel 3"; - case DIV_SYSTEM_QSOUND: - return "Capcom QSound"; - case DIV_SYSTEM_VERA: - return "VERA"; - case DIV_SYSTEM_X1_010: - return "Seta/Allumer X1-010"; - case DIV_SYSTEM_BUBSYS_WSG: - return "Konami Bubble System WSG"; - } - return "Unknown"; -} - -const char* DivEngine::getSystemChips(DivSystem sys) { - switch (sys) { - case DIV_SYSTEM_NULL: - return "Unknown"; - case DIV_SYSTEM_YMU759: - return "Yamaha YMU759"; - case DIV_SYSTEM_GENESIS: - return "Yamaha YM2612 + TI SN76489"; - case DIV_SYSTEM_SMS: - return "TI SN76489"; - case DIV_SYSTEM_SMS_OPLL: - return "TI SN76489 + Yamaha YM2413"; - case DIV_SYSTEM_GB: - return "Sharp LR35902"; - case DIV_SYSTEM_PCE: - return "Hudson Soft HuC6280"; - case DIV_SYSTEM_NES: - return "Ricoh 2A03"; - case DIV_SYSTEM_NES_VRC7: - return "Ricoh 2A03 + Konami VRC7"; - case DIV_SYSTEM_NES_FDS: - return "Ricoh 2A03 + Famicom Disk System"; - case DIV_SYSTEM_C64_6581: - return "SID 6581"; - case DIV_SYSTEM_C64_8580: - return "SID 8580"; - case DIV_SYSTEM_ARCADE: - return "Yamaha YM2151 + SegaPCM"; - case DIV_SYSTEM_GENESIS_EXT: - return "Yamaha YM2612 (extended channel 3) + TI SN76489"; - case DIV_SYSTEM_YM2610: - return "Yamaha YM2610 no ADPCM-B"; - case DIV_SYSTEM_YM2610_EXT: - return "Yamaha YM2610 no ADPCM-B (extended channel 2)"; - case DIV_SYSTEM_AY8910: - return "AY-3-8910"; - case DIV_SYSTEM_AMIGA: - return "MOS 8364 Paula"; - case DIV_SYSTEM_YM2151: - return "Yamaha YM2151"; - case DIV_SYSTEM_YM2612: - return "Yamaha YM2612"; - case DIV_SYSTEM_TIA: - return "Atari TIA"; - case DIV_SYSTEM_VIC20: - return "VIC"; - case DIV_SYSTEM_PET: - return "Commodore PET"; - case DIV_SYSTEM_SNES: - return "SPC700"; - case DIV_SYSTEM_VRC6: - return "Konami VRC6"; - case DIV_SYSTEM_OPLL: - return "Yamaha YM2413"; - case DIV_SYSTEM_FDS: - return "Famicom Disk System"; - case DIV_SYSTEM_MMC5: - return "MMC5"; - case DIV_SYSTEM_N163: - return "Namco 163"; - case DIV_SYSTEM_OPN: - return "Yamaha YM2203"; - case DIV_SYSTEM_PC98: - return "Yamaha YM2608"; - case DIV_SYSTEM_OPL: - return "Yamaha YM3526"; - case DIV_SYSTEM_OPL2: - return "Yamaha YM3812"; - case DIV_SYSTEM_OPL3: - return "Yamaha YMF262"; - case DIV_SYSTEM_MULTIPCM: - return "Yamaha YMW258"; - case DIV_SYSTEM_PCSPKR: - return "Intel 8253"; - case DIV_SYSTEM_POKEY: - return "POKEY"; - case DIV_SYSTEM_RF5C68: - return "Ricoh RF5C68"; - case DIV_SYSTEM_SWAN: - return "WonderSwan"; - case DIV_SYSTEM_SAA1099: - return "Philips SAA1099"; - case DIV_SYSTEM_OPZ: - return "Yamaha YM2414"; - case DIV_SYSTEM_POKEMINI: - return "Pokémon Mini"; - case DIV_SYSTEM_AY8930: - return "Microchip AY8930"; - case DIV_SYSTEM_SEGAPCM: - return "SegaPCM"; - case DIV_SYSTEM_VBOY: - return "VSU"; - case DIV_SYSTEM_VRC7: - return "Konami VRC7"; - case DIV_SYSTEM_YM2610B: - return "Yamaha YM2610B"; - case DIV_SYSTEM_SFX_BEEPER: - return "ZX Spectrum Beeper"; - case DIV_SYSTEM_YM2612_EXT: - return "Yamaha YM2612 Extended Channel 3"; - case DIV_SYSTEM_SCC: - return "Konami K051649"; - case DIV_SYSTEM_OPL_DRUMS: - return "Yamaha YM3526 with drums"; - case DIV_SYSTEM_OPL2_DRUMS: - return "Yamaha YM3812 with drums"; - case DIV_SYSTEM_OPL3_DRUMS: - return "Yamaha YMF262 with drums"; - case DIV_SYSTEM_YM2610_FULL: - return "Yamaha YM2610"; - case DIV_SYSTEM_YM2610_FULL_EXT: - return "Yamaha YM2610 (extended channel 2)"; - case DIV_SYSTEM_OPLL_DRUMS: - return "Yamaha YM2413 with drums"; - case DIV_SYSTEM_LYNX: - return "Mikey"; - case DIV_SYSTEM_SEGAPCM_COMPAT: - return "SegaPCM (compatible 5-channel mode)"; - case DIV_SYSTEM_YM2610B_EXT: - return "Yamaha YM2610B Extended Channel 3"; - case DIV_SYSTEM_QSOUND: - return "Capcom DL-1425"; - case DIV_SYSTEM_VERA: - return "VERA"; - case DIV_SYSTEM_X1_010: - return "Seta/Allumer X1-010"; - case DIV_SYSTEM_BUBSYS_WSG: - return "Konami Bubble System WSG"; - } - return "Unknown"; + if (sysDefs[sys]==NULL) return "Unknown"; + return sysDefs[sys]->name; } const char* DivEngine::getSystemNameJ(DivSystem sys) { + if (sysDefs[sys]==NULL) return "不明"; + if (sysDefs[sys]->nameJ==NULL) return ""; + return sysDefs[sys]->nameJ; + /* switch (sys) { case DIV_SYSTEM_NULL: return "不明"; @@ -946,851 +268,1527 @@ const char* DivEngine::getSystemNameJ(DivSystem sys) { default: // TODO return ""; } - return "不明"; + */ } bool DivEngine::isFMSystem(DivSystem sys) { - return (sys==DIV_SYSTEM_GENESIS || - sys==DIV_SYSTEM_GENESIS_EXT || - sys==DIV_SYSTEM_SMS_OPLL || - sys==DIV_SYSTEM_NES_VRC7 || - sys==DIV_SYSTEM_ARCADE || - sys==DIV_SYSTEM_YM2610 || - sys==DIV_SYSTEM_YM2610_EXT || - sys==DIV_SYSTEM_YM2610_FULL || - sys==DIV_SYSTEM_YM2610_FULL_EXT || - sys==DIV_SYSTEM_YM2610B || - sys==DIV_SYSTEM_YM2610B_EXT || - sys==DIV_SYSTEM_YMU759 || - sys==DIV_SYSTEM_YM2151 || - sys==DIV_SYSTEM_YM2612); + if (sysDefs[sys]==NULL) return false; + return sysDefs[sys]->isFM; } bool DivEngine::isSTDSystem(DivSystem sys) { - return (sys!=DIV_SYSTEM_ARCADE && - sys!=DIV_SYSTEM_YMU759 && - sys!=DIV_SYSTEM_YM2612 && - sys!=DIV_SYSTEM_YM2151); + if (sysDefs[sys]==NULL) return false; + return sysDefs[sys]->isSTD; } -const char* chanNames[40][32]={ - {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM - {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis - {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis (extended channel 3) - {"Square 1", "Square 2", "Square 3", "Noise"}, // SMS - {"Pulse 1", "Pulse 2", "Wavetable", "Noise"}, // GB - {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6"}, // PCE/ZX Beeper - {"Pulse 1", "Pulse 2", "Triangle", "Noise", "PCM"}, // NES - {"Channel 1", "Channel 2", "Channel 3"}, // C64 - {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5"}, // Arcade - {"FM 1", "FM 2", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610 - {"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610 (extended channel 2) - {"PSG 1", "PSG 2", "PSG 3"}, // AY-3-8910 - {"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, // Amiga/POKEY/Lynx - {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8"}, // YM2151/YM2414 - {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"}, // YM2612 - {"Channel 1", "Channel 2"}, // TIA - {"PSG 1", "PSG 2", "PSG 3", "PSG 4", "PSG 5", "PSG 6"}, // SAA1099 - {"PSG 1", "PSG 2", "PSG 3"}, // AY8930 - {"Low", "Mid", "High", "Noise"}, // VIC-20 - {"Wave"}, // PET - {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"}, // SNES/N163/RF5C68 - {"VRC6 1", "VRC6 2", "VRC6 Saw"}, // VRC6 - {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9"}, // OPLL/OPL/OPL2/VRC7 - {"FDS"}, // FDS - {"Pulse 1", "Pulse 2", "PCM"}, // MMC5 - {"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"}, // OPN - {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"}, // PC-98 - {"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18"}, // OPL3 - {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28"}, // MultiPCM - {"Square"}, // PC Speaker/Pokémon Mini - {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Noise"}, // Virtual Boy/SCC - {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B - {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPLL/OPL/OPL2 drums - {"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 drums - {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6"}, // OPL3 4-op (UNUSED) - {"FM 1", "FM 2", "FM 3", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 4-op + drums (UNUSED) - {"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "ADPCM 1", "ADPCM 2", "ADPCM 3"}, // QSound - {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B (extended channel 3) - {"Wave", "Wave/PCM", "Wave", "Wave/Noise"}, // Swan - {"PSG 1", "PSG 2", "PSG 3", "PSG 4", "PSG 5", "PSG 6", "PSG 7", "PSG 8", "PSG 9", "PSG 10", "PSG 11", "PSG 12", "PSG 13", "PSG 14", "PSG 15", "PSG 16", "PCM"}, // VERA -}; - -const char* chanShortNames[38][32]={ - {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759 - {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "NO"}, // Genesis - {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "S4"}, // Genesis (extended channel 3) - {"S1", "S2", "S3", "NO"}, // SMS - {"S1", "S2", "WA", "NO"}, // GB - {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6"}, // PCE - {"S1", "S2", "TR", "NO", "PCM"}, // NES - {"CH1", "CH2", "CH3"}, // C64 - {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "P1", "P2", "P3", "P4", "P5"}, // Arcade - {"F1", "F2", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610 - {"F1", "O1", "O2", "O3", "O4", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610 (extended channel 2) - {"S1", "S2", "S3"}, // AY-3-8910 - {"CH1", "CH2", "CH3", "CH4"}, // Amiga/Lynx - {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8"}, // YM2151 - {"F1", "F2", "F3", "F4", "F5", "F6"}, // YM2612 - {"CH1", "CH2"}, // TIA - {"S1", "S2", "S3", "S4", "S5", "S6"}, // SAA1099 - {"S1", "S2", "S3"}, // AY8930 - {"LO", "MID", "HI", "NO"}, // VIC-20 - {"PET"}, // PET - {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, // SNES/N163/RF5C68 - {"V1", "V2", "VS"}, // VRC6 - {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"}, // OPLL/OPL/OPL2/VRC7 - {"FDS"}, // FDS - {"S1", "S2", "PCM"}, // MMC5 - {"F1", "F2", "F3", "S1", "S2", "S3"}, // OPN - {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"}, // PC-98 - {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18"}, // OPL3 - {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28"}, // MultiPCM - {"SQ"}, // PC Speaker/Pokémon Mini - {"CH1", "CH2", "CH3", "CH4", "CH5", "NO"}, // Virtual Boy/SCC - {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B - {"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"}, // OPLL/OPL/OPL2 drums - {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "BD", "SD", "TM", "TP", "HH"}, // OPL3 drums - {"F1", "F2", "F3", "F4", "F5", "F6", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6"}, // OPL3 4-op (UNUSED) - {"F1", "F2", "F3", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6", "BD", "SD", "TM", "TP", "HH"}, // OPL3 4-op + drums (UNUSED) - {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "A1", "A2", "A3"}, // QSound - {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B (extended channel 3) -}; - -const int chanTypes[41][32]={ - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, // YMU759 - {0, 0, 0, 0, 0, 0, 1, 1, 1, 2}, // Genesis - {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 2}, // Genesis (extended channel 3) - {1, 1, 1, 2}, // SMS - {1, 1, 3, 2}, // GB - {3, 3, 3, 3, 3, 3}, // PCE - {1, 1, 3, 2, 4}, // NES - {2, 2, 2}, // C64 - {0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4}, // Arcade - {0, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610 - {0, 5, 5, 5, 5, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610 (extended channel 2) - {1, 1, 1}, // AY-3-8910 - {4, 4, 4, 4}, // Amiga - {0, 0, 0, 0, 0, 0, 0, 0}, // YM2151 - {0, 0, 0, 0, 0, 0}, // YM2612 - {3, 3}, // TIA - {1, 1, 1, 1, 1, 1}, // SAA1099 - {1, 1, 1}, // AY8930 - {1, 1, 1, 2}, // VIC-20 - {1}, // PET - {4, 4, 4, 4, 4, 4, 4, 4}, // SNES/N163/RF5C68 - {1, 1, 3}, // VRC6 - {0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPLL/OPL/OPL2/VRC7 - {3}, // FDS - {1, 1, 4}, // MMC5 - {0, 0, 0, 1, 1, 1}, // OPN - {0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 4}, // PC-98 - {5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 0, 0, 0, 0, 0, 0}, // OPL3 - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM/QSound - {1}, // PC Speaker/Pokémon Mini - {3, 3, 3, 3, 3, 2}, // Virtual Boy/SCC - {0, 0, 0, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B - {0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPLL/OPL/OPL2 drums - {5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 drums - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3 4-op (UNUSED) - {0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums (UNUSED) - {3, 3, 3, 3}, // Lynx - {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B (extended channel 3) - {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4}, // VERA - {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, // X1-010 - {3, 4, 3, 2}, // Swan -}; - -const DivInstrumentType chanPrefType[47][28]={ - {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, // YMU759 - {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis - {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis (extended channel 3) - {DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // SMS - {DIV_INS_GB, DIV_INS_GB, DIV_INS_GB, DIV_INS_GB}, // GB - {DIV_INS_PCE, DIV_INS_PCE, DIV_INS_PCE, DIV_INS_PCE, DIV_INS_PCE, DIV_INS_PCE}, // PCE - {DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // NES - {DIV_INS_C64, DIV_INS_C64, DIV_INS_C64}, // C64 - {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // Arcade - {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610 - {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610 (extended channel 2) - {DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}, // AY-3-8910 - {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // Amiga - {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, // YM2151 - {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, // YM2612 - {DIV_INS_TIA, DIV_INS_TIA}, // TIA - {DIV_INS_SAA1099, DIV_INS_SAA1099, DIV_INS_SAA1099, DIV_INS_SAA1099, DIV_INS_SAA1099, DIV_INS_SAA1099}, // SAA1099 - {DIV_INS_AY8930, DIV_INS_AY8930, DIV_INS_AY8930}, // AY8930 - {DIV_INS_VIC, DIV_INS_VIC, DIV_INS_VIC, DIV_INS_VIC}, // VIC-20 - {DIV_INS_PET}, // PET - {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // SNES/N163/RF5C68 - {DIV_INS_VRC6, DIV_INS_VRC6, DIV_INS_VRC6}, // VRC6 - {DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL}, // OPLL/VRC7 - {DIV_INS_FDS}, // FDS - {DIV_INS_STD, DIV_INS_STD, DIV_INS_AMIGA}, // MMC5 - {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}, // OPN - {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // PC-98 - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, // OPL/OPL2/OPL3 - {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // MultiPCM/QSound - {DIV_INS_STD}, // PC Speaker/Pokémon Mini - {DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY}, // Virtual Boy - {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B - {DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL}, // OPLL drums - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, // OPL3 drums - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ}, // OPL3 4-op - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, // OPL3 4-op + drums - {DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC}, // SCC - {DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163}, // N163 - {DIV_INS_POKEY, DIV_INS_POKEY, DIV_INS_POKEY, DIV_INS_POKEY}, // POKEY - {DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER}, // ZX beeper - {DIV_INS_SWAN, DIV_INS_SWAN, DIV_INS_SWAN, DIV_INS_SWAN}, // Swan - {DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ}, // Z - {DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY}, // Lynx - {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B (extended channel 3) - {DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_AMIGA}, // VERA - {DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010}, // X1-010 - {DIV_INS_SCC, DIV_INS_SCC}, // Bubble System WSG -}; - const char* DivEngine::getChannelName(int chan) { if (chan<0 || chan>chans) return "??"; if (!song.chanName[chan].empty()) return song.chanName[chan].c_str(); - switch (sysOfChan[chan]) { - case DIV_SYSTEM_NULL: case DIV_SYSTEM_YMU759: - return chanNames[0][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_GENESIS: - return chanNames[1][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_GENESIS_EXT: - case DIV_SYSTEM_YM2612_EXT: - return chanNames[2][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SMS: - return chanNames[3][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL. - case DIV_SYSTEM_NES_VRC7: // this is flattened to NES + VRC7. - case DIV_SYSTEM_NES_FDS: // this is flattened to NES + FDS. - return "??"; - break; - case DIV_SYSTEM_GB: - return chanNames[4][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_PCE: - case DIV_SYSTEM_SFX_BEEPER: - case DIV_SYSTEM_BUBSYS_WSG: - return chanNames[5][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_NES: - return chanNames[6][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_8580: - return chanNames[7][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_ARCADE: - case DIV_SYSTEM_OPZ: - return chanNames[8][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2610: - case DIV_SYSTEM_YM2610_FULL: - return chanNames[9][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2610_EXT: - case DIV_SYSTEM_YM2610_FULL_EXT: - return chanNames[10][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_AY8910: - return chanNames[11][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_AMIGA: - case DIV_SYSTEM_POKEY: - case DIV_SYSTEM_LYNX: - return chanNames[12][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SWAN: - return chanNames[38][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2151: - return chanNames[13][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2612: - return chanNames[14][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_TIA: - return chanNames[15][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_VIC20: - return chanNames[18][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_PET: - return chanNames[19][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SNES: - case DIV_SYSTEM_N163: - case DIV_SYSTEM_RF5C68: - return chanNames[20][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_VRC6: - return chanNames[21][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPLL: - case DIV_SYSTEM_OPL: - case DIV_SYSTEM_OPL2: - case DIV_SYSTEM_VRC7: - return chanNames[22][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_FDS: - return chanNames[23][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_MMC5: - return chanNames[24][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPN: - return chanNames[25][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_PC98: - return chanNames[26][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPL3: - return chanNames[27][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_MULTIPCM: - case DIV_SYSTEM_SEGAPCM: - case DIV_SYSTEM_SEGAPCM_COMPAT: - case DIV_SYSTEM_X1_010: - return chanNames[28][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_PCSPKR: - case DIV_SYSTEM_POKEMINI: - return chanNames[29][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_VBOY: - case DIV_SYSTEM_SCC: - return chanNames[30][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2610B: - return chanNames[31][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2610B_EXT: - return chanNames[37][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPLL_DRUMS: - case DIV_SYSTEM_OPL_DRUMS: - case DIV_SYSTEM_OPL2_DRUMS: - return chanNames[32][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPL3_DRUMS: - return chanNames[33][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SAA1099: - return chanNames[16][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_AY8930: - return chanNames[17][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_QSOUND: - return chanNames[36][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_VERA: - return chanNames[39][dispatchChanOfChan[chan]]; - break; - } - return "??"; + if (sysDefs[sysOfChan[chan]]==NULL) return "??"; + + const char* ret=sysDefs[sysOfChan[chan]]->chanNames[dispatchChanOfChan[chan]]; + if (ret==NULL) return "??"; + return ret; } const char* DivEngine::getChannelShortName(int chan) { if (chan<0 || chan>chans) return "??"; if (!song.chanShortName[chan].empty()) return song.chanShortName[chan].c_str(); - switch (sysOfChan[chan]) { - case DIV_SYSTEM_NULL: case DIV_SYSTEM_YMU759: - return chanShortNames[0][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_GENESIS: - return chanShortNames[1][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_GENESIS_EXT: - case DIV_SYSTEM_YM2612_EXT: - return chanShortNames[2][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SMS: - return chanShortNames[3][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL. - case DIV_SYSTEM_NES_VRC7: // this is flattened to NES + VRC7. - case DIV_SYSTEM_NES_FDS: // this is flattened to NES + FDS. - return "??"; - break; - case DIV_SYSTEM_GB: - return chanShortNames[4][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_PCE: - case DIV_SYSTEM_SFX_BEEPER: - case DIV_SYSTEM_BUBSYS_WSG: - return chanShortNames[5][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_NES: - return chanShortNames[6][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_8580: - return chanShortNames[7][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_ARCADE: - case DIV_SYSTEM_OPZ: - return chanShortNames[8][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2610: - case DIV_SYSTEM_YM2610_FULL: - return chanShortNames[9][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2610_EXT: - case DIV_SYSTEM_YM2610_FULL_EXT: - return chanShortNames[10][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_AY8910: - return chanShortNames[11][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_AMIGA: - case DIV_SYSTEM_POKEY: - case DIV_SYSTEM_SWAN: - case DIV_SYSTEM_LYNX: - return chanShortNames[12][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2151: - return chanShortNames[13][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2612: - return chanShortNames[14][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_TIA: - return chanShortNames[15][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_VIC20: - return chanShortNames[18][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_PET: - return chanShortNames[19][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SNES: - case DIV_SYSTEM_N163: - case DIV_SYSTEM_RF5C68: - return chanShortNames[20][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_VRC6: - return chanShortNames[21][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPLL: - case DIV_SYSTEM_OPL: - case DIV_SYSTEM_OPL2: - case DIV_SYSTEM_VRC7: - return chanShortNames[22][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_FDS: - return chanShortNames[23][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_MMC5: - return chanShortNames[24][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPN: - return chanShortNames[25][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_PC98: - return chanShortNames[26][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPL3: - return chanShortNames[27][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_MULTIPCM: - case DIV_SYSTEM_SEGAPCM: - case DIV_SYSTEM_SEGAPCM_COMPAT: - case DIV_SYSTEM_X1_010: - return chanShortNames[28][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_PCSPKR: - case DIV_SYSTEM_POKEMINI: - return chanShortNames[29][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_VBOY: - case DIV_SYSTEM_SCC: - return chanShortNames[30][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2610B: - return chanShortNames[31][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2610B_EXT: - return chanShortNames[37][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPLL_DRUMS: - case DIV_SYSTEM_OPL_DRUMS: - case DIV_SYSTEM_OPL2_DRUMS: - return chanShortNames[32][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPL3_DRUMS: - return chanShortNames[33][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SAA1099: - return chanShortNames[16][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_AY8930: - return chanShortNames[17][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_QSOUND: - return chanShortNames[36][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_VERA: - return chanShortNames[0][dispatchChanOfChan[chan]]; - break; - } - return "??"; + if (sysDefs[sysOfChan[chan]]==NULL) return "??"; + + const char* ret=sysDefs[sysOfChan[chan]]->chanShortNames[dispatchChanOfChan[chan]]; + if (ret==NULL) return "??"; + return ret; } int DivEngine::getChannelType(int chan) { - switch (sysOfChan[chan]) { - case DIV_SYSTEM_NULL: case DIV_SYSTEM_YMU759: - return chanTypes[0][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_GENESIS: - return chanTypes[1][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_GENESIS_EXT: - case DIV_SYSTEM_YM2612_EXT: - return chanTypes[2][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SMS: - return chanTypes[3][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL. - case DIV_SYSTEM_NES_VRC7: // this is flattened to NES + VRC7. - case DIV_SYSTEM_NES_FDS: // this is flattened to NES + FDS. - return 0; - break; - case DIV_SYSTEM_GB: - return chanTypes[4][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_PCE: - case DIV_SYSTEM_SFX_BEEPER: - case DIV_SYSTEM_BUBSYS_WSG: - return chanTypes[5][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_NES: - return chanTypes[6][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_8580: - return chanTypes[7][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_ARCADE: - case DIV_SYSTEM_OPZ: - return chanTypes[8][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2610: - case DIV_SYSTEM_YM2610_FULL: - return chanTypes[9][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2610_EXT: - case DIV_SYSTEM_YM2610_FULL_EXT: - return chanTypes[10][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_AY8910: - return chanTypes[11][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_AMIGA: - case DIV_SYSTEM_POKEY: - return chanTypes[12][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2151: - return chanTypes[13][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2612: - return chanTypes[14][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_TIA: - return chanTypes[15][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_VIC20: - return chanTypes[18][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_PET: - return chanTypes[19][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SNES: - case DIV_SYSTEM_N163: - case DIV_SYSTEM_RF5C68: - return chanTypes[20][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_VRC6: - return chanTypes[21][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPLL: - case DIV_SYSTEM_OPL: - case DIV_SYSTEM_OPL2: - case DIV_SYSTEM_VRC7: - return chanTypes[22][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_FDS: - return chanTypes[23][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_MMC5: - return chanTypes[24][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPN: - return chanTypes[25][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_PC98: - return chanTypes[26][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPL3: - return chanTypes[27][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_MULTIPCM: - case DIV_SYSTEM_SEGAPCM: - case DIV_SYSTEM_SEGAPCM_COMPAT: - case DIV_SYSTEM_QSOUND: - return chanTypes[28][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_PCSPKR: - case DIV_SYSTEM_POKEMINI: - return chanTypes[29][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_VBOY: - case DIV_SYSTEM_SCC: - return chanTypes[30][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2610B: - return chanTypes[31][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2610B_EXT: - return chanTypes[37][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPLL_DRUMS: - case DIV_SYSTEM_OPL_DRUMS: - case DIV_SYSTEM_OPL2_DRUMS: - return chanTypes[32][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPL3_DRUMS: - return chanTypes[33][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SAA1099: - return chanTypes[16][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_AY8930: - return chanTypes[17][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_LYNX: - return chanTypes[36][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_VERA: - return chanTypes[38][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_X1_010: - return chanTypes[39][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SWAN: - return chanTypes[40][dispatchChanOfChan[chan]]; - break; - } - return 1; + if (chan<0 || chan>chans) return DIV_CH_NOISE; + if (sysDefs[sysOfChan[chan]]==NULL) return DIV_CH_NOISE; + return sysDefs[sysOfChan[chan]]->chanTypes[dispatchChanOfChan[chan]]; } DivInstrumentType DivEngine::getPreferInsType(int chan) { - switch (sysOfChan[chan]) { - case DIV_SYSTEM_NULL: case DIV_SYSTEM_YMU759: - return chanPrefType[0][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_GENESIS: - return chanPrefType[1][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_GENESIS_EXT: - case DIV_SYSTEM_YM2612_EXT: - return chanPrefType[2][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SMS: - return chanPrefType[3][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL. - case DIV_SYSTEM_NES_VRC7: // this is flattened to NES + VRC7. - return DIV_INS_OPLL; - break; - case DIV_SYSTEM_NES_FDS: // this is flattened to NES + FDS. - return DIV_INS_STD; - break; - case DIV_SYSTEM_GB: - return chanPrefType[4][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_PCE: - return chanPrefType[5][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_NES: - return chanPrefType[6][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_8580: - return chanPrefType[7][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_ARCADE: - return chanPrefType[8][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2610: - case DIV_SYSTEM_YM2610_FULL: - return chanPrefType[9][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2610_EXT: - case DIV_SYSTEM_YM2610_FULL_EXT: - return chanPrefType[10][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_AY8910: - return chanPrefType[11][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_AMIGA: - return chanPrefType[12][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2151: - return chanPrefType[13][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2612: - return chanPrefType[14][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_TIA: - return chanPrefType[15][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_VIC20: - return chanPrefType[18][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_PET: - return chanPrefType[19][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SNES: - case DIV_SYSTEM_RF5C68: - return chanPrefType[20][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_VRC6: - return chanPrefType[21][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPLL: - case DIV_SYSTEM_VRC7: - return chanPrefType[22][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_FDS: - return chanPrefType[23][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_MMC5: - return chanPrefType[24][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPN: - return chanPrefType[25][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_PC98: - return chanPrefType[26][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPL: - case DIV_SYSTEM_OPL2: - case DIV_SYSTEM_OPL3: - return chanPrefType[27][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_MULTIPCM: - case DIV_SYSTEM_SEGAPCM: - case DIV_SYSTEM_SEGAPCM_COMPAT: - case DIV_SYSTEM_QSOUND: - return chanPrefType[28][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_PCSPKR: - case DIV_SYSTEM_POKEMINI: - return chanPrefType[29][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_VBOY: - return chanPrefType[30][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SCC: - return chanPrefType[36][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_N163: - return chanPrefType[37][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2610B: - return chanPrefType[31][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_YM2610B_EXT: - return chanPrefType[43][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPLL_DRUMS: - return chanPrefType[32][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPL_DRUMS: - case DIV_SYSTEM_OPL2_DRUMS: - case DIV_SYSTEM_OPL3_DRUMS: - return chanPrefType[33][dispatchChanOfChan[chan]]; - return chanPrefType[33][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SAA1099: - return chanPrefType[16][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_AY8930: - return chanPrefType[17][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_POKEY: - return chanPrefType[38][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SFX_BEEPER: - return chanPrefType[39][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_SWAN: - return chanPrefType[40][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_OPZ: - return chanPrefType[41][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_LYNX: - return chanPrefType[42][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_VERA: - return chanPrefType[44][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_X1_010: - return chanPrefType[45][dispatchChanOfChan[chan]]; - break; - case DIV_SYSTEM_BUBSYS_WSG: - return chanPrefType[46][dispatchChanOfChan[chan]]; - break; - } - return DIV_INS_FM; + if (chan<0 || chan>chans) return DIV_INS_STD; + if (sysDefs[sysOfChan[chan]]==NULL) return DIV_INS_STD; + return sysDefs[sysOfChan[chan]]->chanInsType[dispatchChanOfChan[chan]][0]; +} + +DivInstrumentType DivEngine::getPreferInsSecondType(int chan) { + if (chan<0 || chan>chans) return DIV_INS_NULL; + if (sysDefs[sysOfChan[chan]]==NULL) return DIV_INS_NULL; + return sysDefs[sysOfChan[chan]]->chanInsType[dispatchChanOfChan[chan]][1]; } int DivEngine::minVGMVersion(DivSystem which) { - switch (which) { - case DIV_SYSTEM_YM2612: - case DIV_SYSTEM_YM2612_EXT: - case DIV_SYSTEM_SMS: - case DIV_SYSTEM_OPLL: - case DIV_SYSTEM_OPLL_DRUMS: - case DIV_SYSTEM_VRC7: - case DIV_SYSTEM_YM2151: - return 0x150; // due to usage of data blocks - case DIV_SYSTEM_SEGAPCM: - case DIV_SYSTEM_SEGAPCM_COMPAT: - case DIV_SYSTEM_YM2610: - case DIV_SYSTEM_YM2610_EXT: - case DIV_SYSTEM_YM2610_FULL: - case DIV_SYSTEM_YM2610_FULL_EXT: - case DIV_SYSTEM_YM2610B: - case DIV_SYSTEM_YM2610B_EXT: - case DIV_SYSTEM_OPL: - case DIV_SYSTEM_OPL_DRUMS: - case DIV_SYSTEM_OPL2: - case DIV_SYSTEM_OPL2_DRUMS: - case DIV_SYSTEM_OPL3: - case DIV_SYSTEM_OPL3_DRUMS: - case DIV_SYSTEM_AY8910: - case DIV_SYSTEM_AY8930: - return 0x151; - case DIV_SYSTEM_GB: - case DIV_SYSTEM_PCE: - case DIV_SYSTEM_NES: - case DIV_SYSTEM_FDS: - case DIV_SYSTEM_QSOUND: - return 0x161; - case DIV_SYSTEM_SAA1099: - case DIV_SYSTEM_X1_010: - case DIV_SYSTEM_SWAN: - return 0x171; - default: - return 0; - } - return 0; + if (sysDefs[which]==NULL) return 0; + return sysDefs[which]->vgmVersion; +} + +#define IS_YM2610 (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) +#define IS_OPM_LIKE (sysOfChan[ch]==DIV_SYSTEM_YM2151 || sysOfChan[ch]==DIV_SYSTEM_OPZ) + +#define OP_EFFECT_MULTI(x,c,op,mask) \ + case x: \ + dispatchCmd(DivCommand(c,ch,op,effectVal&mask)); \ + break; + +#define OP_EFFECT_SINGLE(x,c,maxOp,mask) \ + case x: \ + if ((effectVal>>4)>=0 && (effectVal>>4)<=maxOp) { \ + dispatchCmd(DivCommand(c,ch,(effectVal>>4)-1,effectVal&mask)); \ + } \ + break; + +// define systems like: +// sysDefs[DIV_SYSTEM_ID]=new DivSysDef( +// "Name", "Name (japanese, optional)", fileID, fileID_DMF, channels, isFM, isSTD, vgmVersion, +// {"Channel Names", ...}, +// {"Channel Short Names", ...}, +// {chanTypes, ...}, +// {chanPreferInsType, ...}, +// {chanPreferInsType2, ...}, (optional) +// [this](int ch, unsigned char effect, unsigned char effectVal) -> bool {}, (effect handler, optional) +// [this](int ch, unsigned char effect, unsigned char effectVal) -> bool {} (post effect handler, optional) +// ); + +void DivEngine::registerSystems() { + logD("registering systems..."); + + auto fmPostEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x10: // LFO or noise mode + if (IS_OPM_LIKE) { + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); + } else { + dispatchCmd(DivCommand(DIV_CMD_FM_LFO,ch,effectVal)); + } + break; + case 0x11: // FB + dispatchCmd(DivCommand(DIV_CMD_FM_FB,ch,effectVal&7)); + break; + case 0x12: // TL op1 + dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,0,effectVal&0x7f)); + break; + case 0x13: // TL op2 + dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,1,effectVal&0x7f)); + break; + case 0x14: // TL op3 + dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,2,effectVal&0x7f)); + break; + case 0x15: // TL op4 + dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,3,effectVal&0x7f)); + break; + case 0x16: // MULT + if ((effectVal>>4)>0 && (effectVal>>4)<5) { + dispatchCmd(DivCommand(DIV_CMD_FM_MULT,ch,(effectVal>>4)-1,effectVal&15)); + } + break; + case 0x17: // arcade LFO + if (IS_OPM_LIKE) { + dispatchCmd(DivCommand(DIV_CMD_FM_LFO,ch,effectVal)); + } + break; + case 0x18: // EXT or LFO waveform + if (IS_OPM_LIKE) { + dispatchCmd(DivCommand(DIV_CMD_FM_LFO_WAVE,ch,effectVal)); + } else { + dispatchCmd(DivCommand(DIV_CMD_FM_EXTCH,ch,effectVal)); + } + break; + case 0x19: // AR global + dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,-1,effectVal&31)); + break; + case 0x1a: // AR op1 + dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,0,effectVal&31)); + break; + case 0x1b: // AR op2 + dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,1,effectVal&31)); + break; + case 0x1c: // AR op3 + dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,2,effectVal&31)); + break; + case 0x1d: // AR op4 + dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,3,effectVal&31)); + break; + case 0x1e: // UNOFFICIAL: Arcade AM depth + dispatchCmd(DivCommand(DIV_CMD_FM_AM_DEPTH,ch,effectVal&127)); + break; + case 0x1f: // UNOFFICIAL: Arcade PM depth + dispatchCmd(DivCommand(DIV_CMD_FM_PM_DEPTH,ch,effectVal&127)); + break; + case 0x20: // Neo Geo PSG mode + if (IS_YM2610) { + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + } + break; + case 0x21: // Neo Geo PSG noise freq + if (IS_YM2610) { + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); + } + break; + case 0x22: // UNOFFICIAL: Neo Geo PSG envelope enable + if (IS_YM2610) { + dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SET,ch,effectVal)); + } + break; + case 0x23: // UNOFFICIAL: Neo Geo PSG envelope period low + if (IS_YM2610) { + dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_LOW,ch,effectVal)); + } + break; + case 0x24: // UNOFFICIAL: Neo Geo PSG envelope period high + if (IS_YM2610) { + dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_HIGH,ch,effectVal)); + } + break; + case 0x25: // UNOFFICIAL: Neo Geo PSG envelope slide up + if (IS_YM2610) { + dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,-effectVal)); + } + break; + case 0x26: // UNOFFICIAL: Neo Geo PSG envelope slide down + if (IS_YM2610) { + dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,effectVal)); + } + break; + case 0x29: // auto-envelope + if (IS_YM2610) { + dispatchCmd(DivCommand(DIV_CMD_AY_AUTO_ENVELOPE,ch,effectVal)); + } + break; + // fixed frequency effects on OPZ + case 0x30: case 0x31: case 0x32: case 0x33: + case 0x34: case 0x35: case 0x36: case 0x37: + if (sysOfChan[ch]==DIV_SYSTEM_OPZ) { + dispatchCmd(DivCommand(DIV_CMD_FM_FIXFREQ,ch,0,((effect&7)<<8)|effectVal)); + } + break; + case 0x38: case 0x39: case 0x3a: case 0x3b: + case 0x3c: case 0x3d: case 0x3e: case 0x3f: + if (sysOfChan[ch]==DIV_SYSTEM_OPZ) { + dispatchCmd(DivCommand(DIV_CMD_FM_FIXFREQ,ch,1,((effect&7)<<8)|effectVal)); + } + break; + case 0x40: case 0x41: case 0x42: case 0x43: + case 0x44: case 0x45: case 0x46: case 0x47: + if (sysOfChan[ch]==DIV_SYSTEM_OPZ) { + dispatchCmd(DivCommand(DIV_CMD_FM_FIXFREQ,ch,2,((effect&7)<<8)|effectVal)); + } + break; + case 0x48: case 0x49: case 0x4a: case 0x4b: + case 0x4c: case 0x4d: case 0x4e: case 0x4f: + if (sysOfChan[ch]==DIV_SYSTEM_OPZ) { + dispatchCmd(DivCommand(DIV_CMD_FM_FIXFREQ,ch,3,((effect&7)<<8)|effectVal)); + } + break; + // extra FM effects here + OP_EFFECT_SINGLE(0x50,DIV_CMD_FM_AM,4,1); + OP_EFFECT_SINGLE(0x51,DIV_CMD_FM_SL,4,15); + OP_EFFECT_SINGLE(0x52,DIV_CMD_FM_RR,4,15); + OP_EFFECT_SINGLE(0x53,DIV_CMD_FM_DT,4,7); + OP_EFFECT_SINGLE(0x54,DIV_CMD_FM_RS,4,3); + OP_EFFECT_SINGLE(0x55,DIV_CMD_FM_SSG,4,(IS_OPM_LIKE?3:15)); + + OP_EFFECT_MULTI(0x56,DIV_CMD_FM_DR,-1,31); + OP_EFFECT_MULTI(0x57,DIV_CMD_FM_DR,0,31); + OP_EFFECT_MULTI(0x58,DIV_CMD_FM_DR,1,31); + OP_EFFECT_MULTI(0x59,DIV_CMD_FM_DR,2,31); + OP_EFFECT_MULTI(0x5a,DIV_CMD_FM_DR,3,31); + + OP_EFFECT_MULTI(0x5b,DIV_CMD_FM_D2R,-1,31); + OP_EFFECT_MULTI(0x5c,DIV_CMD_FM_D2R,0,31); + OP_EFFECT_MULTI(0x5d,DIV_CMD_FM_D2R,1,31); + OP_EFFECT_MULTI(0x5e,DIV_CMD_FM_D2R,2,31); + OP_EFFECT_MULTI(0x5f,DIV_CMD_FM_D2R,3,31); + + OP_EFFECT_SINGLE(0x28,DIV_CMD_FM_REV,4,7); + OP_EFFECT_SINGLE(0x2a,DIV_CMD_FM_WS,4,7); + OP_EFFECT_SINGLE(0x2b,DIV_CMD_FM_EG_SHIFT,4,3); + OP_EFFECT_SINGLE(0x2c,DIV_CMD_FM_FINE,4,15); + default: + return false; + } + return true; + }; + + auto fmOPLLPostEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x11: // FB + dispatchCmd(DivCommand(DIV_CMD_FM_FB,ch,effectVal&7)); + break; + case 0x12: // TL op1 + dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,0,effectVal&0x3f)); + break; + case 0x13: // TL op2 + dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,1,effectVal&0x0f)); + break; + case 0x16: // MULT + if ((effectVal>>4)>0 && (effectVal>>4)<3) { + dispatchCmd(DivCommand(DIV_CMD_FM_MULT,ch,(effectVal>>4)-1,effectVal&15)); + } + break; + case 0x19: // AR global + dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,-1,effectVal&31)); + break; + case 0x1a: // AR op1 + dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,0,effectVal&31)); + break; + case 0x1b: // AR op2 + dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,1,effectVal&31)); + break; + + // extra FM effects here + OP_EFFECT_SINGLE(0x50,DIV_CMD_FM_AM,2,1); + OP_EFFECT_SINGLE(0x51,DIV_CMD_FM_SL,2,15); + OP_EFFECT_SINGLE(0x52,DIV_CMD_FM_RR,2,15); + OP_EFFECT_SINGLE(0x53,DIV_CMD_FM_VIB,2,1); + OP_EFFECT_SINGLE(0x54,DIV_CMD_FM_RS,2,3); + OP_EFFECT_SINGLE(0x55,DIV_CMD_FM_SUS,2,1); + + OP_EFFECT_MULTI(0x56,DIV_CMD_FM_DR,-1,15); + OP_EFFECT_MULTI(0x57,DIV_CMD_FM_DR,0,15); + OP_EFFECT_MULTI(0x58,DIV_CMD_FM_DR,1,15); + + OP_EFFECT_SINGLE(0x5b,DIV_CMD_FM_KSR,2,1); + default: + return false; + } + return true; + }; + + auto fmOPLPostEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x10: // DAM + dispatchCmd(DivCommand(DIV_CMD_FM_LFO,ch,effectVal&1)); + break; + case 0x11: // FB + dispatchCmd(DivCommand(DIV_CMD_FM_FB,ch,effectVal&7)); + break; + case 0x12: // TL op1 + dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,0,effectVal&0x3f)); + break; + case 0x13: // TL op2 + dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,1,effectVal&0x3f)); + break; + case 0x14: // TL op3 + dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,2,effectVal&0x3f)); + break; + case 0x15: // TL op4 + dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,3,effectVal&0x3f)); + break; + case 0x16: // MULT + if ((effectVal>>4)>0 && (effectVal>>4)<5) { + dispatchCmd(DivCommand(DIV_CMD_FM_MULT,ch,(effectVal>>4)-1,effectVal&15)); + } + break; + case 0x17: // DVB + dispatchCmd(DivCommand(DIV_CMD_FM_LFO,ch,2+(effectVal&1))); + break; + case 0x19: // AR global + dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,-1,effectVal&15)); + break; + case 0x1a: // AR op1 + dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,0,effectVal&15)); + break; + case 0x1b: // AR op2 + dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,1,effectVal&15)); + break; + case 0x1c: // AR op3 + dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,2,effectVal&15)); + break; + case 0x1d: // AR op4 + dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,3,effectVal&15)); + break; + + // extra FM effects here + OP_EFFECT_SINGLE(0x50,DIV_CMD_FM_AM,4,1); + OP_EFFECT_SINGLE(0x51,DIV_CMD_FM_SL,4,15); + OP_EFFECT_SINGLE(0x52,DIV_CMD_FM_RR,4,15); + OP_EFFECT_SINGLE(0x53,DIV_CMD_FM_VIB,4,1); + OP_EFFECT_SINGLE(0x54,DIV_CMD_FM_RS,4,3); + OP_EFFECT_SINGLE(0x55,DIV_CMD_FM_SUS,4,1); + + OP_EFFECT_MULTI(0x56,DIV_CMD_FM_DR,-1,15); + OP_EFFECT_MULTI(0x57,DIV_CMD_FM_DR,0,15); + OP_EFFECT_MULTI(0x58,DIV_CMD_FM_DR,1,15); + OP_EFFECT_MULTI(0x59,DIV_CMD_FM_DR,2,15); + OP_EFFECT_MULTI(0x5a,DIV_CMD_FM_DR,3,15); + + OP_EFFECT_SINGLE(0x5b,DIV_CMD_FM_KSR,4,1); + OP_EFFECT_SINGLE(0x2a,DIV_CMD_FM_WS,4,7); + + default: + return false; + } + return true; + }; + + auto c64PostEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x10: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x11: // cutoff + dispatchCmd(DivCommand(DIV_CMD_C64_CUTOFF,ch,effectVal)); + break; + case 0x12: // duty + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + break; + case 0x13: // resonance + dispatchCmd(DivCommand(DIV_CMD_C64_RESONANCE,ch,effectVal)); + break; + case 0x14: // filter mode + dispatchCmd(DivCommand(DIV_CMD_C64_FILTER_MODE,ch,effectVal)); + break; + case 0x15: // reset time + dispatchCmd(DivCommand(DIV_CMD_C64_RESET_TIME,ch,effectVal)); + break; + case 0x1a: // reset mask + dispatchCmd(DivCommand(DIV_CMD_C64_RESET_MASK,ch,effectVal)); + break; + case 0x1b: // cutoff reset + dispatchCmd(DivCommand(DIV_CMD_C64_FILTER_RESET,ch,effectVal)); + break; + case 0x1c: // duty reset + dispatchCmd(DivCommand(DIV_CMD_C64_DUTY_RESET,ch,effectVal)); + break; + case 0x1e: // extended + dispatchCmd(DivCommand(DIV_CMD_C64_EXTENDED,ch,effectVal)); + break; + case 0x30: case 0x31: case 0x32: case 0x33: + case 0x34: case 0x35: case 0x36: case 0x37: + case 0x38: case 0x39: case 0x3a: case 0x3b: + case 0x3c: case 0x3d: case 0x3e: case 0x3f: // fine duty + dispatchCmd(DivCommand(DIV_CMD_C64_FINE_DUTY,ch,((effect&0x0f)<<8)|effectVal)); + break; + case 0x40: case 0x41: case 0x42: case 0x43: + case 0x44: case 0x45: case 0x46: case 0x47: // fine cutoff + dispatchCmd(DivCommand(DIV_CMD_C64_FINE_CUTOFF,ch,((effect&0x07)<<8)|effectVal)); + break; + default: + return false; + } + return true; + }; + + auto ayPostEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x12: // duty on 8930 + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,0x10+(effectVal&15))); + break; + case 0x20: // mode + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal&15)); + break; + case 0x21: // noise freq + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); + break; + case 0x22: // envelope enable + dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SET,ch,effectVal)); + break; + case 0x23: // envelope period low + dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_LOW,ch,effectVal)); + break; + case 0x24: // envelope period high + dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_HIGH,ch,effectVal)); + break; + case 0x25: // envelope slide up + dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,-effectVal)); + break; + case 0x26: // envelope slide down + dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,effectVal)); + break; + case 0x27: // noise and mask + dispatchCmd(DivCommand(DIV_CMD_AY_NOISE_MASK_AND,ch,effectVal)); + break; + case 0x28: // noise or mask + dispatchCmd(DivCommand(DIV_CMD_AY_NOISE_MASK_OR,ch,effectVal)); + break; + case 0x29: // auto-envelope + dispatchCmd(DivCommand(DIV_CMD_AY_AUTO_ENVELOPE,ch,effectVal)); + break; + case 0x2d: // TEST + dispatchCmd(DivCommand(DIV_CMD_AY_IO_WRITE,ch,255,effectVal)); + break; + case 0x2e: // I/O port A + dispatchCmd(DivCommand(DIV_CMD_AY_IO_WRITE,ch,0,effectVal)); + break; + case 0x2f: // I/O port B + dispatchCmd(DivCommand(DIV_CMD_AY_IO_WRITE,ch,1,effectVal)); + break; + default: + return false; + } + return true; + }; + + auto segaPCMPostEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x20: // PCM frequency + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_FREQ,ch,effectVal)); + break; + default: + return false; + } + return true; + }; + + sysDefs[DIV_SYSTEM_YMU759]=new DivSysDef( + "Yamaha YMU759 (MA-2)", NULL, 0x01, 0x01, 17, true, false, 0, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM" }, // name + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM" }, // short + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM }, // type + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA} // ins + ); + + sysDefs[DIV_SYSTEM_GENESIS]=new DivSysDef( + "Sega Genesis/Mega Drive", "セガメガドライブ", 0x02, 0x02, 10, true, true, 0, true, + {}, {}, {}, {} + ); + + sysDefs[DIV_SYSTEM_GENESIS_EXT]=new DivSysDef( + "Sega Genesis Extended Channel 3", NULL, 0x42, 0x42, 13, true, true, 0, true, + {}, {}, {}, {} + ); + + sysDefs[DIV_SYSTEM_SMS]=new DivSysDef( + "TI SN76489", NULL, 0x03, 0x03, 4, false, true, 0x150, false, + {"Square 1", "Square 2", "Square 3", "Noise"}, + {"S1", "S2", "S3", "NO"}, + {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE}, + {DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, + {}, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x20: // SN noise mode + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + break; + default: + return false; + } + return true; + } + ); + + sysDefs[DIV_SYSTEM_SMS_OPLL]=new DivSysDef( + "Sega Master System + FM Expansion", NULL, 0x43, 0x43, 13, true, true, 0, true, + {}, {}, {}, {} + ); + + sysDefs[DIV_SYSTEM_GB]=new DivSysDef( + "Game Boy", NULL, 0x04, 0x04, 4, false, true, 0x161, false, + {"Pulse 1", "Pulse 2", "Wavetable", "Noise"}, + {"S1", "S2", "WA", "NO"}, + {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_WAVE, DIV_CH_NOISE}, + {DIV_INS_GB, DIV_INS_GB, DIV_INS_GB, DIV_INS_GB}, + {}, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x10: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x11: case 0x12: // duty or noise mode + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + break; + case 0x13: // sweep params + dispatchCmd(DivCommand(DIV_CMD_GB_SWEEP_TIME,ch,effectVal)); + break; + case 0x14: // sweep direction + dispatchCmd(DivCommand(DIV_CMD_GB_SWEEP_DIR,ch,effectVal)); + break; + default: + return false; + } + return true; + } + ); + + sysDefs[DIV_SYSTEM_PCE]=new DivSysDef( + "PC Engine/TurboGrafx-16", NULL, 0x05, 0x05, 6, false, true, 0x161, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6"}, + {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6"}, + {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, + {DIV_INS_PCE, DIV_INS_PCE, DIV_INS_PCE, DIV_INS_PCE, DIV_INS_PCE, DIV_INS_PCE}, + {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x10: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x11: // noise mode + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + break; + case 0x12: // LFO mode + dispatchCmd(DivCommand(DIV_CMD_PCE_LFO_MODE,ch,effectVal)); + break; + case 0x13: // LFO speed + dispatchCmd(DivCommand(DIV_CMD_PCE_LFO_SPEED,ch,effectVal)); + break; + case 0x17: // PCM enable + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); + break; + default: + return false; + } + return true; + } + ); + + sysDefs[DIV_SYSTEM_NES]=new DivSysDef( + "NES (Ricoh 2A03)", NULL, 0x06, 0x06, 5, false, true, 0x161, false, + {"Pulse 1", "Pulse 2", "Triangle", "Noise", "PCM"}, + {"S1", "S2", "TR", "NO", "PCM"}, + {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_WAVE, DIV_CH_NOISE, DIV_CH_PCM}, + {DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_AMIGA}, + {}, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x11: // DMC write + dispatchCmd(DivCommand(DIV_CMD_NES_DMC,ch,effectVal)); + break; + case 0x12: // duty or noise mode + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + break; + case 0x13: // sweep up + dispatchCmd(DivCommand(DIV_CMD_NES_SWEEP,ch,0,effectVal)); + break; + case 0x14: // sweep down + dispatchCmd(DivCommand(DIV_CMD_NES_SWEEP,ch,1,effectVal)); + break; + case 0x18: // DPCM mode + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,effectVal)); + break; + default: + return false; + } + return true; + } + ); + + sysDefs[DIV_SYSTEM_NES_VRC7]=new DivSysDef( + "NES + Konami VRC7", NULL, 0x46, 0x46, 11, true, true, 0, true, + {}, {}, {}, {} + ); + + sysDefs[DIV_SYSTEM_NES_FDS]=new DivSysDef( + "Famicom Disk System", NULL, 0, 0x86, 6, false, true, 0, true, + {}, {}, {}, {} + ); + + sysDefs[DIV_SYSTEM_C64_6581]=new DivSysDef( + "Commodore 64 (6581)", NULL, 0x47, 0x47, 3, false, true, 0, false, + {"Channel 1", "Channel 2", "Channel 3"}, + {"CH1", "CH2", "CH3"}, + {DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, + {DIV_INS_C64, DIV_INS_C64, DIV_INS_C64}, + {}, + [](int,unsigned char,unsigned char) -> bool {return false;}, + c64PostEffectHandler + ); + + sysDefs[DIV_SYSTEM_C64_8580]=new DivSysDef( + "Commodore 64 (8580)", NULL, 0x07, 0x07, 3, false, true, 0, false, + {"Channel 1", "Channel 2", "Channel 3"}, + {"CH1", "CH2", "CH3"}, + {DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, + {DIV_INS_C64, DIV_INS_C64, DIV_INS_C64}, + {}, + [](int,unsigned char,unsigned char) -> bool {return false;}, + c64PostEffectHandler + ); + + sysDefs[DIV_SYSTEM_ARCADE]=new DivSysDef( + "DefleCade", NULL, 0x08, 0x08, 13, true, false, 0, true, + {}, {}, {}, {} + ); + + auto fmHardResetEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x30: // toggle hard-reset + dispatchCmd(DivCommand(DIV_CMD_FM_HARD_RESET,ch,effectVal)); + break; + default: + return false; + } + return true; + }; + + sysDefs[DIV_SYSTEM_YM2610]=new DivSysDef( + "Neo Geo CD", NULL, 0x09, 0x09, 13, true, true, 0x151, false, + {"FM 1", "FM 2", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6"}, + {"F1", "F2", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + {}, + fmHardResetEffectHandler, + fmPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_YM2610_EXT]=new DivSysDef( + "Neo Geo CD Extended Channel 2", NULL, 0x49, 0x49, 16, true, true, 0x151, false, + {"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6"}, + {"F1", "O1", "O2", "O3", "O4", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6"}, + {DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + {}, + fmHardResetEffectHandler, + fmPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_AY8910]=new DivSysDef( + "AY-3-8910", NULL, 0x80, 0, 3, false, true, 0x151, false, + {"PSG 1", "PSG 2", "PSG 3"}, + {"S1", "S2", "S3"}, + {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, + {DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}, + {}, + [](int,unsigned char,unsigned char) -> bool {return false;}, + ayPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_AMIGA]=new DivSysDef( + "Amiga", NULL, 0x81, 0, 4, false, true, 0, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, + {"CH1", "CH2", "CH3", "CH4"}, + {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + {}, + [](int,unsigned char,unsigned char) -> bool {return false;}, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x10: // toggle filter + dispatchCmd(DivCommand(DIV_CMD_AMIGA_FILTER,ch,effectVal)); + break; + case 0x11: // toggle AM + dispatchCmd(DivCommand(DIV_CMD_AMIGA_AM,ch,effectVal)); + break; + case 0x12: // toggle PM + dispatchCmd(DivCommand(DIV_CMD_AMIGA_PM,ch,effectVal)); + break; + default: + return false; + } + return true; + } + ); + + sysDefs[DIV_SYSTEM_YM2151]=new DivSysDef( + "Yamaha YM2151 (OPM)", NULL, 0x82, 0, 8, true, false, 0x150, false, + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8"}, + {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, + {}, + fmHardResetEffectHandler, + fmPostEffectHandler + ); + + auto opn2EffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x17: // DAC enable + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); + break; + case 0x30: // toggle hard-reset + dispatchCmd(DivCommand(DIV_CMD_FM_HARD_RESET,ch,effectVal)); + break; + default: + return false; + } + return true; + }; + + sysDefs[DIV_SYSTEM_YM2612]=new DivSysDef( + "Yamaha YM2612 (OPN2)", NULL, 0x83, 0, 6, true, false, 0x150, false, + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"}, + {"F1", "F2", "F3", "F4", "F5", "F6"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, + {DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA}, + opn2EffectHandler, + fmPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_TIA]=new DivSysDef( + "Atari 2600", NULL, 0x84, 0, 2, false, true, 0, false, + {"Channel 1", "Channel 2"}, + {"CH1", "CH2"}, + {DIV_CH_WAVE, DIV_CH_WAVE}, + {DIV_INS_TIA, DIV_INS_TIA}, + {}, + [](int,unsigned char,unsigned char) -> bool {return false;}, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x10: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + default: + return false; + } + return true; + } + ); + + sysDefs[DIV_SYSTEM_SAA1099]=new DivSysDef( + "Philips SAA1099", NULL, 0x97, 0, 6, false, true, 0x171, false, + {"PSG 1", "PSG 2", "PSG 3", "PSG 4", "PSG 5", "PSG 6"}, + {"S1", "S2", "S3", "S4", "S5", "S6"}, + {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, + {DIV_INS_SAA1099, DIV_INS_SAA1099, DIV_INS_SAA1099, DIV_INS_SAA1099, DIV_INS_SAA1099, DIV_INS_SAA1099}, + {}, + [](int,unsigned char,unsigned char) -> bool {return false;}, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x10: // select channel mode + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + break; + case 0x11: // set noise freq + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); + break; + case 0x12: // setup envelope + dispatchCmd(DivCommand(DIV_CMD_SAA_ENVELOPE,ch,effectVal)); + break; + default: + return false; + } + return true; + } + ); + + sysDefs[DIV_SYSTEM_AY8930]=new DivSysDef( + "Microchip AY8930", NULL, 0x9a, 0, 3, false, true, 0x151, false, + {"PSG 1", "PSG 2", "PSG 3"}, + {"S1", "S2", "S3"}, + {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, + {DIV_INS_AY8930, DIV_INS_AY8930, DIV_INS_AY8930}, + {}, + [](int,unsigned char,unsigned char) -> bool {return false;}, + ayPostEffectHandler + ); + + auto waveOnlyEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x10: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + default: + return false; + } + return true; + }; + + sysDefs[DIV_SYSTEM_VIC20]=new DivSysDef( + "Commodore VIC-20", NULL, 0x85, 0, 4, false, true, 0, false, + {"Low", "Mid", "High", "Noise"}, + {"LO", "MID", "HI", "NO"}, + {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE}, + {DIV_INS_VIC, DIV_INS_VIC, DIV_INS_VIC, DIV_INS_VIC}, + {}, + waveOnlyEffectHandler + ); + + sysDefs[DIV_SYSTEM_PET]=new DivSysDef( + "Commodore PET", NULL, 0x86, 0, 1, false, true, 0, false, + {"Wave"}, + {"PET"}, + {DIV_CH_PULSE}, + {DIV_INS_PET}, + {}, + waveOnlyEffectHandler + ); + + sysDefs[DIV_SYSTEM_SNES]=new DivSysDef( + "SNES", NULL, 0x87, 0, 8, false, true, 0, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"}, + {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, + {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES} + ); + + sysDefs[DIV_SYSTEM_VRC6]=new DivSysDef( + "Konami VRC6", NULL, 0x88, 0, 3, false, true, 0, false, + {"VRC6 1", "VRC6 2", "VRC6 Saw"}, + {"V1", "V2", "VS"}, + {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_WAVE}, + {DIV_INS_VRC6, DIV_INS_VRC6, DIV_INS_VRC6_SAW}, + {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_NULL}, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x12: // duty or noise mode + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + break; + case 0x17: // PCM enable + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); + break; + default: + return false; + } + return true; + } + ); + + auto oplEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x30: // toggle hard-reset + dispatchCmd(DivCommand(DIV_CMD_FM_HARD_RESET,ch,effectVal)); + break; + default: + return false; + } + return true; + }; + + sysDefs[DIV_SYSTEM_OPLL]=new DivSysDef( + "Yamaha YM2413 (OPLL)", NULL, 0x89, 0, 9, true, false, 0x150, false, + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9"}, + {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, + {DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL}, + {}, + oplEffectHandler, + fmOPLLPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_FDS]=new DivSysDef( + "Famicom Disk System (chip)", NULL, 0x8a, 0, 1, false, true, 0x161, false, + {"FDS"}, + {"FDS"}, + {DIV_CH_WAVE}, + {DIV_INS_FDS}, + {}, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x10: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x11: // modulation depth + dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_DEPTH,ch,effectVal)); + break; + case 0x12: // modulation enable/high + dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_HIGH,ch,effectVal)); + break; + case 0x13: // modulation low + dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_LOW,ch,effectVal)); + break; + case 0x14: // modulation pos + dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_POS,ch,effectVal)); + break; + case 0x15: // modulation wave + dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_WAVE,ch,effectVal)); + break; + default: + return false; + } + return true; + } + ); + + sysDefs[DIV_SYSTEM_MMC5]=new DivSysDef( + "MMC5", NULL, 0x8b, 0, 3, false, true, 0, false, + {"Pulse 1", "Pulse 2", "PCM"}, + {"S1", "S2", "PCM"}, + {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM}, + {DIV_INS_STD, DIV_INS_STD, DIV_INS_AMIGA}, + {}, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x11: // DMC write + dispatchCmd(DivCommand(DIV_CMD_NES_DMC,ch,effectVal)); + break; + case 0x12: // duty or noise mode + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + break; + default: + return false; + } + return true; + } + ); + + sysDefs[DIV_SYSTEM_N163]=new DivSysDef( + "Namco 163", NULL, 0x8c, 0, 8, false, true, 0, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"}, + {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, + {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, + {DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163}, + {}, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x10: // select instrument waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x11: // select instrument waveform position in RAM + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_POSITION,ch,effectVal)); + break; + case 0x12: // select instrument waveform length in RAM + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LENGTH,ch,effectVal)); + break; + case 0x13: // change instrument waveform update mode + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_MODE,ch,effectVal)); + break; + case 0x14: // select waveform for load to RAM + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOAD,ch,effectVal)); + break; + case 0x15: // select waveform position for load to RAM + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOADPOS,ch,effectVal)); + break; + case 0x16: // select waveform length for load to RAM + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOADLEN,ch,effectVal)); + break; + case 0x17: // change waveform load mode + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOADMODE,ch,effectVal)); + break; + case 0x18: // change channel limits + dispatchCmd(DivCommand(DIV_CMD_N163_CHANNEL_LIMIT,ch,effectVal)); + break; + case 0x20: // (global) select waveform for load to RAM + dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOAD,ch,effectVal)); + break; + case 0x21: // (global) select waveform position for load to RAM + dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOADPOS,ch,effectVal)); + break; + case 0x22: // (global) select waveform length for load to RAM + dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOADLEN,ch,effectVal)); + break; + case 0x23: // (global) change waveform load mode + dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOADMODE,ch,effectVal)); + break; + default: + return false; + } + return true; + } + ); + + sysDefs[DIV_SYSTEM_OPN]=new DivSysDef( + "Yamaha YM2203 (OPN)", NULL, 0x8d, 0, 6, true, false, 0, false, + {"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"}, + {"F1", "F2", "F3", "S1", "S2", "S3"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY} + ); + + sysDefs[DIV_SYSTEM_PC98]=new DivSysDef( + "Yamaha YM2608 (OPNA)", NULL, 0x8e, 0, 16, true, false, 0, false, + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"}, + {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM}, + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA} + ); + + sysDefs[DIV_SYSTEM_OPL]=new DivSysDef( + "Yamaha YM3526 (OPL)", NULL, 0x8f, 0, 9, true, false, 0x151, false, + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9"}, + {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, + {}, + oplEffectHandler, + fmOPLPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_OPL2]=new DivSysDef( + "Yamaha YM3812 (OPL2)", NULL, 0x90, 0, 9, true, false, 0x151, false, + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9"}, + {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, + {}, + oplEffectHandler, + fmOPLPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_OPL3]=new DivSysDef( + "Yamaha YMF262 (OPL3)", NULL, 0x91, 0, 18, true, false, 0x151, false, + {"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18"}, + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18"}, + {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, + {}, + oplEffectHandler, + fmOPLPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_MULTIPCM]=new DivSysDef( + "MultiPCM", NULL, 0x92, 0, 28, false, true, 0, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28"}, + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28"}, + {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM} + ); + + sysDefs[DIV_SYSTEM_PCSPKR]=new DivSysDef( + "PC Speaker", NULL, 0x93, 0, 1, false, true, 0, false, + {"Square"}, + {"SQ"}, + {DIV_CH_PULSE}, + {DIV_INS_STD} + ); + + sysDefs[DIV_SYSTEM_POKEY]=new DivSysDef( + "POKEY", NULL, 0x94, 0, 4, false, true, 0, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, + {"CH1", "CH2", "CH3", "CH4"}, + {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, + {DIV_INS_POKEY, DIV_INS_POKEY, DIV_INS_POKEY, DIV_INS_POKEY} + ); + + sysDefs[DIV_SYSTEM_RF5C68]=new DivSysDef( + "Ricoh RF5C68", NULL, 0x95, 0, 8, false, true, 0, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"}, + {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, + {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA} + ); + + sysDefs[DIV_SYSTEM_SWAN]=new DivSysDef( + "WonderSwan", NULL, 0x96, 0, 4, false, true, 0x171, false, + {"Wave", "Wave/PCM", "Wave", "Wave/Noise"}, + {"CH1", "CH2", "CH3", "CH4"}, + {DIV_CH_WAVE, DIV_CH_PCM, DIV_CH_WAVE, DIV_CH_NOISE}, + {DIV_INS_SWAN, DIV_INS_SWAN, DIV_INS_SWAN, DIV_INS_SWAN}, + {DIV_INS_NULL, DIV_INS_AMIGA, DIV_INS_NULL, DIV_INS_NULL}, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x10: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x11: // noise mode + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + break; + case 0x12: // sweep period + dispatchCmd(DivCommand(DIV_CMD_WS_SWEEP_TIME,ch,effectVal)); + break; + case 0x13: // sweep amount + dispatchCmd(DivCommand(DIV_CMD_WS_SWEEP_AMOUNT,ch,effectVal)); + break; + case 0x17: // PCM enable + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); + break; + default: + return false; + } + return true; + } + ); + + sysDefs[DIV_SYSTEM_OPZ]=new DivSysDef( + "Yamaha YM2414 (OPZ)", NULL, 0x98, 0, 8, true, false, 0, false, + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8"}, + {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, + {DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ}, + {}, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x2f: // toggle hard-reset + dispatchCmd(DivCommand(DIV_CMD_FM_HARD_RESET,ch,effectVal)); + break; + default: + return false; + } + return true; + }, + fmPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_POKEMINI]=new DivSysDef( + "Pokémon Mini", NULL, 0x99, 0, 1, false, true, 0, false, + {"Square"}, + {"SQ"}, + {DIV_CH_PULSE}, + {DIV_INS_STD} + ); + + sysDefs[DIV_SYSTEM_SEGAPCM]=new DivSysDef( + "SegaPCM", NULL, 0x9b, 0, 16, false, true, 0x151, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16"}, + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"}, + {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + {}, + [](int,unsigned char,unsigned char) -> bool {return false;}, + segaPCMPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_VBOY]=new DivSysDef( + "Virtual Boy", NULL, 0x9c, 0, 6, false, true, 0, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Noise"}, + {"CH1", "CH2", "CH3", "CH4", "CH5", "NO"}, + {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_NOISE}, + {DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY} + ); + + sysDefs[DIV_SYSTEM_VRC7]=new DivSysDef( + "Konami VRC7", NULL, 0x9d, 0, 6, true, false, 0x151, false, + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"}, + {"F1", "F2", "F3", "F4", "F5", "F6"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, + {DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL}, + {}, + oplEffectHandler, + fmOPLLPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_YM2610B]=new DivSysDef( + "Yamaha YM2610B (OPNB-B)", NULL, 0x9e, 0, 16, true, false, 0x151, false, + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, + {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + {}, + fmHardResetEffectHandler, + fmPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_SFX_BEEPER]=new DivSysDef( + "ZX Spectrum Beeper", NULL, 0x9f, 0, 6, false, true, 0, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6"}, + {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6"}, + {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, + {DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER} + ); + + sysDefs[DIV_SYSTEM_YM2612_EXT]=new DivSysDef( + "Yamaha YM2612 Extended Channel 3", NULL, 0xa0, 0, 9, true, false, 0x150, false, + {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6"}, + {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, + {DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA}, + opn2EffectHandler, + fmPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_SCC]=new DivSysDef( + "Konami SCC", NULL, 0xa1, 0, 5, false, true, 0, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"}, + {"CH1", "CH2", "CH3", "CH4", "CH5"}, + {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, + {DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC} + ); + + auto oplDrumsEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x18: // drum mode toggle + dispatchCmd(DivCommand(DIV_CMD_FM_EXTCH,ch,effectVal)); + break; + case 0x30: // toggle hard-reset + dispatchCmd(DivCommand(DIV_CMD_FM_HARD_RESET,ch,effectVal)); + break; + default: + return false; + } + return true; + }; + + sysDefs[DIV_SYSTEM_OPL_DRUMS]=new DivSysDef( + "Yamaha OPL with drums", NULL, 0xa2, 0, 11, true, false, 0x151, false, + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, + {"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, + {}, + oplDrumsEffectHandler, + fmOPLPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_OPL2_DRUMS]=new DivSysDef( + "Yamaha OPL2 with drums", NULL, 0xa3, 0, 11, true, false, 0x151, false, + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, + {"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, + {}, + oplDrumsEffectHandler, + fmOPLPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_OPL3_DRUMS]=new DivSysDef( + "Yamaha OPL3 with drums", NULL, 0xa4, 0, 20, true, false, 0x151, false, + {"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick", "Snare", "Tom", "Top", "HiHat"}, + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "BD", "SD", "TM", "TP", "HH"}, + {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, + {}, + oplDrumsEffectHandler, + fmOPLPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_YM2610_FULL]=new DivSysDef( + "Yamaha YM2610 (OPNB)", NULL, 0xa5, 0, 14, true, false, 0x151, false, + {"FM 1", "FM 2", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, + {"F1", "F2", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + {}, + fmHardResetEffectHandler, + fmPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_YM2610_FULL_EXT]=new DivSysDef( + "Yamaha YM2610 Extended Channel 2", NULL, 0xa6, 0, 17, true, false, 0x151, false, + {"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, + {"F1", "O1", "O2", "O3", "O4", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, + {DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + {}, + fmHardResetEffectHandler, + fmPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_OPLL_DRUMS]=new DivSysDef( + "Yamaha OPLL with drums", NULL, 0xa7, 0, 11, true, false, 0x150, false, + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, + {"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, + {DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL}, + {}, + oplDrumsEffectHandler, + fmOPLLPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_LYNX]=new DivSysDef( + "Atari Lynx", NULL, 0xa8, 0, 4, false, true, 0, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, + {"CH1", "CH2", "CH3", "CH4"}, + {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, + {DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY}, + {}, + [](int,unsigned char,unsigned char) -> bool {return false;}, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + if (effect>=0x30 && effect<0x40) { + int value=((int)(effect&0x0f)<<8)|effectVal; + dispatchCmd(DivCommand(DIV_CMD_LYNX_LFSR_LOAD,ch,value)); + return true; + } + return false; + } + ); + + sysDefs[DIV_SYSTEM_QSOUND]=new DivSysDef( + "Capcom QSound", NULL, 0xe0, 0, 19, false, true, 0x161, false, + {"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "ADPCM 1", "ADPCM 2", "ADPCM 3"}, + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "A1", "A2", "A3"}, + {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, + {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + {}, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x10: // echo feedback + dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_FEEDBACK,ch,effectVal)); + break; + case 0x11: // echo level + dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_LEVEL,ch,effectVal)); + break; + case 0x12: // surround + dispatchCmd(DivCommand(DIV_CMD_QSOUND_SURROUND,ch,effectVal)); + break; + default: + if ((effect&0xf0)==0x30) { + dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_DELAY,ch,((effect & 0x0f) << 8) | effectVal)); + } else { + return false; + } + break; + } + return true; + } + ); + + sysDefs[DIV_SYSTEM_VERA]=new DivSysDef( + "VERA", NULL, 0xac, 0, 17, false, true, 0, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, + {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM}, + {DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_AMIGA}, + {}, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x20: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x22: // duty + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + break; + default: + return false; + } + return true; + } + ); + + sysDefs[DIV_SYSTEM_YM2610B_EXT]=new DivSysDef( + "Yamaha YM2610B Extended Channel 3", NULL, 0xde, 0, 19, true, false, 0x151, false, + {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, + {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + {}, + fmHardResetEffectHandler, + fmPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_SEGAPCM_COMPAT]=new DivSysDef( + "SegaPCM (compatible 5-channel mode)", NULL, 0xa9, 0, 5, false, true, 0x151, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"}, + {"P1", "P2", "P3", "P4", "P5"}, + {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + {}, + [](int,unsigned char,unsigned char) -> bool {return false;}, + segaPCMPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_X1_010]=new DivSysDef( + "Seta/Allumer X1-010", NULL, 0xb0, 0, 16, false, true, 0x171, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16"}, + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"}, + {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, + {DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010}, + {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x10: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x11: // select envelope shape + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SHAPE,ch,effectVal)); + break; + case 0x17: // PCM enable + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); + break; + default: + return false; + } + return true; + }, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x20: // PCM frequency + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_FREQ,ch,effectVal)); + break; + case 0x22: // envelope mode + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_MODE,ch,effectVal)); + break; + case 0x23: // envelope period + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_PERIOD,ch,effectVal)); + break; + case 0x25: // envelope slide up + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SLIDE,ch,effectVal)); + break; + case 0x26: // envelope slide down + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SLIDE,ch,-effectVal)); + break; + case 0x29: // auto-envelope + dispatchCmd(DivCommand(DIV_CMD_X1_010_AUTO_ENVELOPE,ch,effectVal)); + break; + default: + return false; + } + return true; + } + ); + + sysDefs[DIV_SYSTEM_BUBSYS_WSG]=new DivSysDef( + "Konami Bubble System WSG", NULL, 0xad, 0, 2, false, true, 0, false, + {"Channel 1", "Channel 2"}, + {"CH1", "CH2"}, + {DIV_CH_WAVE, DIV_CH_WAVE}, + {DIV_INS_SCC, DIV_INS_SCC}, + {}, + waveOnlyEffectHandler + ); + + // to Grauw: feel free to change this to 24 during development of OPL4's PCM part. + sysDefs[DIV_SYSTEM_OPL4]=new DivSysDef( + "Yamaha OPL4", NULL, 0xae, 0, 42, true, true, 0, false, + {"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18", "PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "PCM 17", "PCM 18", "PCM 19", "PCM 20", "PCM 21", "PCM 22", "PCM 23", "PCM 24"}, + {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P8", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"}, + {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM} + ); + + sysDefs[DIV_SYSTEM_OPL4_DRUMS]=new DivSysDef( + "Yamaha OPL4 with drums", NULL, 0xaf, 0, 44, true, true, 0, false, + {"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick", "Snare", "Tom", "Top", "HiHat", "PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "PCM 17", "PCM 18", "PCM 19", "PCM 20", "PCM 21", "PCM 22", "PCM 23", "PCM 24"}, + {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "BD", "SD", "TM", "TP", "HH", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P8", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"}, + {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM} + ); + + sysDefs[DIV_SYSTEM_ES5506]=new DivSysDef( + "Ensoniq ES5506", NULL, 0xb1, 0, 32, false, true, 0, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28", "Channel 29", "Channel 30", "Channel 31", "Channel 32"}, + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32"}, + {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506} + ); + + sysDefs[DIV_SYSTEM_Y8950]=new DivSysDef( + "Yamaha Y8950", NULL, 0xb2, 0, 10, true, false, 0, false, + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "PCM"}, + {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "PCM"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM}, + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA} + ); + + sysDefs[DIV_SYSTEM_Y8950_DRUMS]=new DivSysDef( + "Yamaha Y8950 with drums", NULL, 0xb3, 0, 12, true, false, 0, false, + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat", "PCM"}, + {"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH", "PCM"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM}, + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA} + ); + + sysDefs[DIV_SYSTEM_SCC_PLUS]=new DivSysDef( + "Konami SCC+", NULL, 0xb4, 0, 5, false, true, 0, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"}, + {"CH1", "CH2", "CH3", "CH4", "CH5"}, + {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, + {DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC} + ); + + sysDefs[DIV_SYSTEM_SOUND_UNIT]=new DivSysDef( + "tildearrow Sound Unit", NULL, 0xb5, 0, 8, false, true, 0, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"}, + {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, + {DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, + {DIV_INS_SU, DIV_INS_SU, DIV_INS_SU, DIV_INS_SU, DIV_INS_SU, DIV_INS_SU, DIV_INS_SU, DIV_INS_SU}, + {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA} + ); + + sysDefs[DIV_SYSTEM_MSM6295]=new DivSysDef( + "OKI MSM6295", NULL, 0xaa, 0, 4, false, true, 0, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, + {"CH1", "CH2", "CH3", "CH4"}, + {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA} + ); + + sysDefs[DIV_SYSTEM_MSM6258]=new DivSysDef( + "OKI MSM6258", NULL, 0xab, 0, 1, false, true, 0, false, + {"Sample"}, + {"PCM"}, + {DIV_CH_PCM}, + {DIV_INS_AMIGA} + ); + + sysDefs[DIV_SYSTEM_DUMMY]=new DivSysDef( + "Dummy System", NULL, 0xfd, 0, 8, false, true, 0, false, + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"}, + {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, + {DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, + {DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD} + ); + + for (int i=0; i<256; i++) { + if (sysDefs[i]==NULL) continue; + if (sysDefs[i]->id!=0) { + sysFileMapFur[sysDefs[i]->id]=(DivSystem)i; + } + if (sysDefs[i]->id_DMF!=0) { + sysFileMapDMF[sysDefs[i]->id_DMF]=(DivSystem)i; + } + } + + systemsRegistered=true; } diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index b20813489..53800a525 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -713,10 +713,10 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { bool writeDACSamples=false; bool writeNESSamples=false; bool writePCESamples=false; - bool writeADPCM=false; - bool writeSegaPCM=false; - bool writeX1010=false; - bool writeQSound=false; + DivDispatch* writeADPCM[2]={NULL,NULL}; + int writeSegaPCM=0; + DivDispatch* writeX1010[2]={NULL,NULL}; + DivDispatch* writeQSound[2]={NULL,NULL}; for (int i=0; ichipClock; willExport[i]=true; - writeX1010=true; + writeX1010[0]=disCont[i].dispatch; } else if (!(hasX1&0x40000000)) { isSecond[i]=true; willExport[i]=true; + writeX1010[1]=disCont[i].dispatch; hasX1|=0x40000000; howManyChips++; } @@ -821,10 +823,11 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { if (!hasOPNB) { hasOPNB=disCont[i].dispatch->chipClock; willExport[i]=true; - writeADPCM=true; + writeADPCM[0]=disCont[i].dispatch; } else if (!(hasOPNB&0x40000000)) { isSecond[i]=true; willExport[i]=true; + writeADPCM[1]=disCont[i].dispatch; hasOPNB|=0x40000000; howManyChips++; } @@ -926,10 +929,11 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { // not be able to handle the 64kb sample bank trick hasQSound=disCont[i].dispatch->chipClock; willExport[i]=true; - writeQSound=true; + writeQSound[0]=disCont[i].dispatch; } else if (!(hasQSound&0x40000000)) { isSecond[i]=true; willExport[i]=false; + writeQSound[1]=disCont[i].dispatch; addWarning("dual QSound is not supported by the VGM format"); } break; @@ -1200,7 +1204,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { } } - if (writeSegaPCM) { + if (writeSegaPCM>0) { unsigned char* pcmMem=new unsigned char[16777216]; size_t memPos=0; @@ -1232,60 +1236,69 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { if (memPos>=16777216) break; } - w->writeC(0x67); - w->writeC(0x66); - w->writeC(0x80); - w->writeI(memPos+8); - w->writeI(memPos); - w->writeI(0); - w->write(pcmMem,memPos); + for (int i=0; iwriteC(0x67); + w->writeC(0x66); + w->writeC(0x80); + w->writeI((memPos+8)|(i*0x80000000)); + w->writeI(memPos); + w->writeI(0); + w->write(pcmMem,memPos); + } delete[] pcmMem; } - if (writeADPCM && adpcmAMemLen>0) { - w->writeC(0x67); - w->writeC(0x66); - w->writeC(0x82); - w->writeI(adpcmAMemLen+8); - w->writeI(adpcmAMemLen); - w->writeI(0); - w->write(adpcmAMem,adpcmAMemLen); - } - - if (writeADPCM && adpcmBMemLen>0) { - w->writeC(0x67); - w->writeC(0x66); - w->writeC(0x83); - w->writeI(adpcmBMemLen+8); - w->writeI(adpcmBMemLen); - w->writeI(0); - w->write(adpcmBMem,adpcmBMemLen); - } - - if (writeQSound && qsoundMemLen>0) { - // always write a whole bank - unsigned int blockSize=(qsoundMemLen+0xffff)&(~0xffff); - if (blockSize > 0x1000000) { - blockSize = 0x1000000; + for (int i=0; i<2; i++) { + if (writeADPCM[i]!=NULL && writeADPCM[i]->getSampleMemUsage(0)>0) { + w->writeC(0x67); + w->writeC(0x66); + w->writeC(0x82); + w->writeI((writeADPCM[i]->getSampleMemUsage(0)+8)|(i*0x80000000)); + w->writeI(writeADPCM[i]->getSampleMemCapacity(0)); + w->writeI(0); + w->write(writeADPCM[i]->getSampleMem(0),writeADPCM[i]->getSampleMemUsage(0)); } - w->writeC(0x67); - w->writeC(0x66); - w->writeC(0x8F); - w->writeI(blockSize+8); - w->writeI(0x1000000); - w->writeI(0); - w->write(qsoundMem,blockSize); } - if (writeX1010 && x1_010MemLen>0) { - w->writeC(0x67); - w->writeC(0x66); - w->writeC(0x91); - w->writeI(x1_010MemLen+8); - w->writeI(x1_010MemLen); - w->writeI(0); - w->write(x1_010Mem,x1_010MemLen); + for (int i=0; i<2; i++) { + if (writeADPCM[i]!=NULL && writeADPCM[i]->getSampleMemUsage(1)>0) { + w->writeC(0x67); + w->writeC(0x66); + w->writeC(0x83); + w->writeI((writeADPCM[i]->getSampleMemUsage(1)+8)|(i*0x80000000)); + w->writeI(writeADPCM[i]->getSampleMemCapacity(1)); + w->writeI(0); + w->write(writeADPCM[i]->getSampleMem(1),writeADPCM[i]->getSampleMemUsage(1)); + } + } + + for (int i=0; i<2; i++) { + if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage()>0) { + unsigned int blockSize=(writeQSound[i]->getSampleMemUsage()+0xffff)&(~0xffff); + if (blockSize > 0x1000000) { + blockSize = 0x1000000; + } + w->writeC(0x67); + w->writeC(0x66); + w->writeC(0x8F); + w->writeI((blockSize+8)|(i*0x80000000)); + w->writeI(writeQSound[i]->getSampleMemCapacity()); + w->writeI(0); + w->write(writeQSound[i]->getSampleMem(),blockSize); + } + } + + for (int i=0; i<2; i++) { + if (writeX1010[i]!=NULL && writeX1010[i]->getSampleMemUsage()>0) { + w->writeC(0x67); + w->writeC(0x66); + w->writeC(0x91); + w->writeI((writeX1010[i]->getSampleMemUsage()+8)|(i*0x80000000)); + w->writeI(writeX1010[i]->getSampleMemCapacity()); + w->writeI(0); + w->write(writeX1010[i]->getSampleMem(),writeX1010[i]->getSampleMemUsage()); + } } // initialize streams @@ -1384,7 +1397,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { writeLoop=true; } } - if (nextTick() || !playing) { + if (nextTick(false,true) || !playing) { done=true; if (!loop) { for (int i=0; i0) return false; + bool updated=first; first=false; + subDivCounter=e->tickMult; if (!state.enabled) return updated; + if (width<1) return false; if (--divCounter<=0) { // run effect @@ -80,8 +103,15 @@ bool DivWaveSynth::tick() { return updated; } +void DivWaveSynth::setWidth(int val) { + width=val; + if (width<0) width=0; + if (width>256) width=256; +} + void DivWaveSynth::changeWave1(int num) { DivWavetable* w1=e->getWave(num); + if (width<1) return; for (int i=0; imax<1 || w1->len<1) { wave1[i]=0; @@ -99,6 +129,7 @@ void DivWaveSynth::changeWave1(int num) { void DivWaveSynth::changeWave2(int num) { DivWavetable* w2=e->getWave(num); + if (width<1) return; for (int i=0; imax<1 || w2->len<1) { wave2[i]=0; @@ -139,6 +170,7 @@ void DivWaveSynth::init(DivInstrument* which, int w, int h, bool insChanged) { pos=0; stage=0; divCounter=1+state.rateDivider; + subDivCounter=0; first=true; changeWave1(state.wave1); diff --git a/src/engine/waveSynth.h b/src/engine/waveSynth.h index ccd8b23de..f5c89e3ac 100644 --- a/src/engine/waveSynth.h +++ b/src/engine/waveSynth.h @@ -28,7 +28,7 @@ class DivEngine; class DivWaveSynth { DivEngine* e; DivInstrumentWaveSynth state; - int pos, stage, divCounter, width, height; + int pos, stage, divCounter, width, height, subDivCounter; bool first, activeChangedB; unsigned char wave1[256]; unsigned char wave2[256]; @@ -47,6 +47,11 @@ class DivWaveSynth { * @return whether the wave has changed. */ bool tick(); + /** + * set the wave width. + * @param value the width. + */ + void setWidth(int val); /** * change the first wave. * @param num wavetable number. @@ -73,6 +78,7 @@ class DivWaveSynth { divCounter(0), width(32), height(31), + subDivCounter(0), first(false), activeChangedB(false) { memset(wave1,0,256); diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 386b01b0c..ce543f5f4 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -27,7 +27,7 @@ const char* aboutLine[]={ "", ("Furnace " DIV_VERSION), "", - "the free software chiptune tracker,", + "the free software multi-system chiptune tracker,", "compatible with DefleMask modules.", "", "zero disassembly.", @@ -42,6 +42,7 @@ const char* aboutLine[]={ "cam900", "djtuBIG-MaliceX", "laoo", + "OPNA2608", "superctr", "", "-- graphics/UI design --", @@ -73,7 +74,7 @@ const char* aboutLine[]={ "", "-- additional feedback/fixes --", "fd", - "OPNA2608", + "GENATARi", "plane", "TheEssem", "", @@ -83,25 +84,30 @@ const char* aboutLine[]={ "zlib by Jean-loup Gailly", "and Mark Adler", "libsndfile by Erik de Castro Lopo", - "Nuked-OPM & Nuked-OPN2 by Nuke.YKT", + "Portable File Dialogs by Sam Hocevar", + "RtMidi by Gary P. Scavone", + "adpcm by superctr", + "Nuked-OPL3/OPLL/OPM/OPN2 by Nuke.YKT", "ymfm by Aaron Giles", "MAME SN76496 by Nicola Salmoria", "MAME AY-3-8910 by Couriersud", "with AY8930 fixes by Eulous", "MAME SAA1099 by Juergen Buchmueller and Manuel Abadia", - "SAASound", + "SAASound by Dave Hooper and Simon Owen", "SameBoy by Lior Halphon", - "Mednafen PCE", - "puNES by FHorse", + "Mednafen PCE and WonderSwan audio cores", + "puNES (NES, MMC5 and FDS) by FHorse", "reSID by Dag Lem", "Stella by Stella Team", "QSound emulator by Ian Karlsson and Valley Bell", + "VICE VIC-20 sound core by Rami Rasanen and viznut", + "VERA sound core by Frank van den Hoef", + "K005289 emulator by cam900", + "Namco 163 emulator by cam900", + "Seta X1-010 emulator by cam900", + "Konami VRC6 emulator by cam900", "", - "greetings to:", - "Delek", - "fd", - "ILLUMIDARO", - "all members of Deflers of Noice!", + "greetings to all members of Deflers of Noice!", "", "copyright © 2021-2022 tildearrow", "(and contributors).", @@ -208,6 +214,8 @@ void FurnaceGUI::drawAbout() { while (aboutHue>1) aboutHue--; while (aboutSin>=2400) aboutSin-=2400; if (aboutScroll>(42*aboutCount+scrH)) aboutScroll=-20; + + WAKE_UP; } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_ABOUT; ImGui::End(); diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp new file mode 100644 index 000000000..dc392a884 --- /dev/null +++ b/src/gui/chanOsc.cpp @@ -0,0 +1,153 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" +#include "imgui.h" +#include "imgui_internal.h" + +void FurnaceGUI::drawChanOsc() { + if (nextWindow==GUI_WINDOW_CHAN_OSC) { + chanOscOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!chanOscOpen) return; + ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + if (ImGui::Begin("Oscilloscope (per-channel)",&chanOscOpen)) { + if (ImGui::BeginTable("ChanOscSettings",3)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Columns"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##COSColumns",&chanOscCols,1,1)) { + if (chanOscCols<1) chanOscCols=1; + if (chanOscCols>64) chanOscCols=64; + } + + ImGui::TableNextColumn(); + ImGui::Text("Size (ms)"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputFloat("##COSWinSize",&chanOscWindowSize,1.0f,1.0f)) { + if (chanOscWindowSize<1.0f) chanOscWindowSize=1.0f; + if (chanOscWindowSize>50.0f) chanOscWindowSize=50.0f; + } + + ImGui::TableNextColumn(); + ImGui::Checkbox("Center waveform",&chanOscWaveCorr); + + ImGui::EndTable(); + } + + + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f)); + float availY=ImGui::GetContentRegionAvail().y; + if (ImGui::BeginTable("ChanOsc",chanOscCols,ImGuiTableFlags_Borders)) { + std::vector oscBufs; + std::vector oscChans; + int chans=e->getTotalChannelCount(); + ImDrawList* dl=ImGui::GetWindowDrawList(); + ImGuiWindow* window=ImGui::GetCurrentWindow(); + ImVec2 waveform[512]; + + ImGuiStyle& style=ImGui::GetStyle(); + ImU32 color=ImGui::GetColorU32(uiColors[GUI_COLOR_OSC_WAVE]); + + for (int i=0; igetOscBuffer(i); + if (buf!=NULL) { + oscBufs.push_back(buf); + oscChans.push_back(i); + } + } + int rows=(oscBufs.size()+(chanOscCols-1))/chanOscCols; + + for (size_t i=0; irate)*(chanOscWindowSize/1000.0f); + + ImVec2 minArea=window->DC.CursorPos; + ImVec2 maxArea=ImVec2( + minArea.x+size.x, + minArea.y+size.y + ); + ImRect rect=ImRect(minArea,maxArea); + ImRect inRect=rect; + inRect.Min.x+=dpiScale; + inRect.Min.y+=dpiScale; + inRect.Max.x-=dpiScale; + inRect.Max.y-=dpiScale; + ImGui::ItemSize(size,style.FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID("chOscDisplay"))) { + if (!e->isPlaying()) { + for (unsigned short i=0; i<512; i++) { + float x=(float)i/512.0f; + waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f)); + } + } else { + unsigned short needlePos=buf->needle; + if (chanOscWaveCorr) { + float cutoff=0.01f; + while (buf->readNeedle!=needlePos) { + //float old=chanOscLP1[ch]; + chanOscLP0[ch]+=cutoff*((float)buf->data[buf->readNeedle]-chanOscLP0[ch]); + chanOscLP1[ch]+=cutoff*(chanOscLP0[ch]-chanOscLP1[ch]); + if (chanOscLP1[ch]>=20) { + lastCorrPos[ch]=buf->readNeedle; + } + buf->readNeedle++; + } + needlePos=lastCorrPos[ch]; + /* + for (unsigned short i=0; idata[needlePos--]; + if (buf->data[needlePos]>old) break; + }*/ + } + needlePos-=displaySize; + for (unsigned short i=0; i<512; i++) { + float x=(float)i/512.0f; + float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f; + if (y<-0.5f) y=-0.5f; + if (y>0.5f) y=0.5f; + waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y)); + } + } + dl->AddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale); + } + } + } + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_CHAN_OSC; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/channels.cpp b/src/gui/channels.cpp index 5b2a7e0c6..397ac2f65 100644 --- a/src/gui/channels.cpp +++ b/src/gui/channels.cpp @@ -19,6 +19,8 @@ #include "gui.h" #include "misc/cpp/imgui_stdlib.h" +#include "IconsFontAwesome4.h" +#include void FurnaceGUI::drawChannels() { if (nextWindow==GUI_WINDOW_CHANNELS) { @@ -37,6 +39,18 @@ void FurnaceGUI::drawChannels() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Checkbox("##Visible",&e->song.chanShow[i]); + ImGui::SameLine(); + ImGui::BeginDisabled(i==0); + if (ImGui::Button(ICON_FA_CHEVRON_UP)) { + e->swapChannelsP(i,i-1); + } + ImGui::EndDisabled(); + ImGui::SameLine(); + ImGui::BeginDisabled(i==(e->getTotalChannelCount()-1)); + if (ImGui::Button(ICON_FA_CHEVRON_DOWN)) { + e->swapChannelsP(i,i+1); + } + ImGui::EndDisabled(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::InputTextWithHint("##ChanName",e->getChannelName(i),&e->song.chanName[i]); diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index 15a900b2d..52f465027 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -101,10 +101,30 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("simulates a bug in where portamento does not work after sliding."); } + ImGui::Checkbox("FM pitch slide octave boundary odd behavior",&e->song.fbPortaPause); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("if this is on, a pitch slide that crosses the octave boundary will stop for one tick and then continue from the nearest octave boundary.\nfor .dmf compatibility."); + } ImGui::Checkbox("Apply Game Boy envelope on note-less instrument change",&e->song.gbInsAffectsEnvelope); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("if this is on, an instrument change will also affect the envelope."); } + ImGui::Checkbox("Ignore DAC mode change outside of intended channel in ExtCh mode",&e->song.ignoreDACModeOutsideIntendedChannel); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("if this is on, 17xx has no effect on the operator channels in YM2612."); + } + ImGui::Checkbox("E1xy/E2xy also take priority over slide stops",&e->song.e1e2AlsoTakePriority); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("does this make any sense by now?"); + } + ImGui::Checkbox("SN76489 duty macro always resets phase",&e->song.snDutyReset); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, duty macro will always reset phase, even if its value hasn't changed."); + } + ImGui::Checkbox("Pitch macro is linear",&e->song.pitchMacroIsLinear); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, the pitch macro of an instrument is in linear space."); + } ImGui::Text("Loop modality:"); if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { @@ -158,7 +178,11 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("behavior changed in 0.6"); } + ImGui::Checkbox("New SegaPCM features (macros and better panning)",&e->song.newSegaPCM); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.6"); + } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_COMPAT_FLAGS; ImGui::End(); -} \ No newline at end of file +} diff --git a/src/gui/cursor.cpp b/src/gui/cursor.cpp index 421b6d486..6bf4e76ff 100644 --- a/src/gui/cursor.cpp +++ b/src/gui/cursor.cpp @@ -83,11 +83,11 @@ void FurnaceGUI::finishSelection() { if (cursor.y<0) cursor.y=0; if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; - if (e->song.chanCollapse[selEnd.xCoarse]) { + if (e->song.chanCollapse[selStart.xCoarse]==3) { selStart.xFine=0; } - if (e->song.chanCollapse[selEnd.xCoarse]) { - selEnd.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; + if (e->song.chanCollapse[selEnd.xCoarse] && selEnd.xFine>=(3-e->song.chanCollapse[selEnd.xCoarse])) { + selEnd.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2; } e->setMidiBaseChan(cursor.xCoarse); @@ -105,7 +105,7 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { demandScrollX=true; if (x>0) { for (int i=0; i=(e->song.chanCollapse[cursor.xCoarse]?1:(3+e->song.pat[cursor.xCoarse].effectRows*2))) { + if (++cursor.xFine>=(e->song.chanCollapse[cursor.xCoarse]?(4-e->song.chanCollapse[cursor.xCoarse]):(3+e->song.pat[cursor.xCoarse].effectCols*2))) { cursor.xFine=0; if (++cursor.xCoarse>=lastChannel) { if (settings.wrapHorizontal!=0 && !select) { @@ -113,7 +113,7 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { if (settings.wrapHorizontal==2) y++; } else { cursor.xCoarse=lastChannel-1; - cursor.xFine=e->song.chanCollapse[cursor.xCoarse]?0:(2+e->song.pat[cursor.xCoarse].effectRows*2); + cursor.xFine=e->song.chanCollapse[cursor.xCoarse]?(3-e->song.chanCollapse[cursor.xCoarse]):(2+e->song.pat[cursor.xCoarse].effectCols*2); } } else { while (!e->song.chanShow[cursor.xCoarse]) { @@ -129,7 +129,7 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { if (--cursor.xCoarsesong.pat[cursor.xCoarse].effectRows*2; + cursor.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2; if (settings.wrapHorizontal==2) y--; } else { cursor.xCoarse=firstChannel; @@ -141,9 +141,9 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { if (cursor.xCoarse<0) break; } if (e->song.chanCollapse[cursor.xCoarse]) { - cursor.xFine=0; + cursor.xFine=3-e->song.chanCollapse[cursor.xCoarse]; } else { - cursor.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; + cursor.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2; } } } @@ -158,8 +158,8 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { if (settings.wrapVertical!=0 && !select) { cursor.y=0; if (settings.wrapVertical==2) { - if (!e->isPlaying() && e->getOrder()<(e->song.ordersLen-1)) { - e->setOrder(e->getOrder()+1); + if ((!e->isPlaying() || !followPattern) && curOrder<(e->song.ordersLen-1)) { + setOrder(curOrder+1); } else { cursor.y=e->song.patLen-1; } @@ -176,8 +176,8 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { if (settings.wrapVertical!=0 && !select) { cursor.y=e->song.patLen-1; if (settings.wrapVertical==2) { - if (!e->isPlaying() && e->getOrder()>0) { - e->setOrder(e->getOrder()-1); + if ((!e->isPlaying() || !followPattern) && curOrder>0) { + setOrder(curOrder-1); } else { cursor.y=0; } @@ -193,7 +193,9 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { selStart=cursor; } selEnd=cursor; - updateScroll(cursor.y); + if (!settings.cursorMoveNoScroll) { + updateScroll(cursor.y); + } e->setMidiBaseChan(cursor.xCoarse); } @@ -271,7 +273,7 @@ void FurnaceGUI::moveCursorBottom(bool select) { DETERMINE_LAST; cursor.xCoarse=lastChannel-1; if (cursor.xCoarse<0) cursor.xCoarse=0; - cursor.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; + cursor.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2; demandScrollX=true; } else { cursor.y=e->song.patLen-1; diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 9557cc57d..0de552054 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -23,6 +23,7 @@ #include "plot_nolerp.h" #include "guiConst.h" #include +#include const char* sampleNote[12]={ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" @@ -36,6 +37,7 @@ void FurnaceGUI::drawInsList() { } if (!insListOpen) return; if (ImGui::Begin("Instruments",&insListOpen)) { + if (settings.unifiedDataView) settings.horizontalDataView=0; if (ImGui::Button(ICON_FA_PLUS "##InsAdd")) { doAction(GUI_ACTION_INS_LIST_ADD); } @@ -45,7 +47,7 @@ void FurnaceGUI::drawInsList() { } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FOLDER_OPEN "##InsLoad")) { - doAction(GUI_ACTION_INS_LIST_OPEN); + doAction((settings.insLoadAlwaysReplace && curIns>=0 && curIns<(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FLOPPY_O "##InsSave")) { @@ -64,7 +66,12 @@ void FurnaceGUI::drawInsList() { doAction(GUI_ACTION_INS_LIST_DELETE); } ImGui::Separator(); - if (ImGui::BeginTable("InsListScroll",1,ImGuiTableFlags_ScrollY)) { + int availableRows=ImGui::GetContentRegionAvail().y/ImGui::GetFrameHeight(); + if (availableRows<1) availableRows=1; + int columns=settings.horizontalDataView?(int)(ceil((double)(e->song.ins.size()+1)/(double)availableRows)):1; + if (columns<1) columns=1; + if (columns>64) columns=64; + if (ImGui::BeginTable("InsListScroll",columns,(settings.horizontalDataView?ImGuiTableFlags_ScrollX:0)|ImGuiTableFlags_ScrollY)) { if (settings.unifiedDataView) { ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -72,126 +79,157 @@ void FurnaceGUI::drawInsList() { ImGui::Indent(); } - for (int i=0; i<(int)e->song.ins.size(); i++) { - DivInstrument* ins=e->song.ins[i]; - String name; - switch (ins->type) { - case DIV_INS_FM: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FM]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_STD: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_STD]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_GB: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_GB]); - name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_C64: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_C64]); - name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_AMIGA: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AMIGA]); - name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_PCE: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PCE]); - name=fmt::sprintf(ICON_FA_ID_BADGE " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_AY: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_AY8930: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY8930]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_TIA: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_TIA]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_SAA1099: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SAA1099]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_VIC: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VIC]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_PET: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PET]); - name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_VRC6: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_VRC6_SAW: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6_SAW]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_OPLL: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPLL]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_OPL: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPL]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_FDS: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FDS]); - name=fmt::sprintf(ICON_FA_FLOPPY_O " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_VBOY: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VBOY]); - name=fmt::sprintf(ICON_FA_BINOCULARS " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_N163: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_N163]); - name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_SCC: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SCC]); - name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_OPZ: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPZ]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_POKEY: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_POKEY]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_BEEPER: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_BEEPER]); - name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_SWAN: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SWAN]); - name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_MIKEY: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MIKEY]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_VERA: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VERA]); - name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_X1_010: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_X1_010]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - default: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); - name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d",i,ins->name,i); - break; - } + if (settings.horizontalDataView) { ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Selectable(name.c_str(),curIns==i)) { + } + + int curRow=0; + for (int i=-1; i<(int)e->song.ins.size(); i++) { + String name=ICON_FA_CIRCLE_O " - None -"; + const char* insType="Bug!"; + if (i>=0) { + DivInstrument* ins=e->song.ins[i]; + insType=(ins->type>DIV_INS_MAX)?"Unknown":insTypes[ins->type]; + switch (ins->type) { + case DIV_INS_FM: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FM]); + name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_STD: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_STD]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_GB: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_GB]); + name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_C64: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_C64]); + name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_AMIGA: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AMIGA]); + name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_PCE: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PCE]); + name=fmt::sprintf(ICON_FA_ID_BADGE " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_AY: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_AY8930: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY8930]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_TIA: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_TIA]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_SAA1099: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SAA1099]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_VIC: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VIC]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_PET: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PET]); + name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_VRC6: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_VRC6_SAW: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6_SAW]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_OPLL: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPLL]); + name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_OPL: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPL]); + name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_FDS: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FDS]); + name=fmt::sprintf(ICON_FA_FLOPPY_O " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_VBOY: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VBOY]); + name=fmt::sprintf(ICON_FA_BINOCULARS " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_N163: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_N163]); + name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_SCC: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SCC]); + name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_OPZ: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPZ]); + name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_POKEY: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_POKEY]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_BEEPER: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_BEEPER]); + name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_SWAN: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SWAN]); + name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_MIKEY: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MIKEY]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_VERA: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VERA]); + name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_X1_010: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_X1_010]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_ES5506: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_ES5506]); + name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_MULTIPCM: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MULTIPCM]); + name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_SNES: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SNES]); + name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_SU: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SU]); + name=fmt::sprintf(ICON_FA_MICROCHIP " %.2X: %s##_INS%d",i,ins->name,i); + break; + default: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); + name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d",i,ins->name,i); + break; + } + } else { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); + } + if (!settings.horizontalDataView) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + } else if (curRow==0) { + ImGui::TableNextColumn(); + } + if (ImGui::Selectable(name.c_str(),(i==-1)?(curIns<0 || curIns>=e->song.insLen):(curIns==i))) { curIns=i; } if (settings.insFocusesPattern && patternOpen && ImGui::IsItemActivated()) { @@ -199,13 +237,16 @@ void FurnaceGUI::drawInsList() { curIns=i; } ImGui::PopStyleColor(); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("%s",(ins->type>DIV_INS_MAX)?"Unknown":insTypes[ins->type]); + if (ImGui::IsItemHovered() && i>=0) { + ImGui::SetTooltip("%s",insType); if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { insEditOpen=true; nextWindow=GUI_WINDOW_INS_EDIT; } } + if (settings.horizontalDataView) { + if (++curRow>=availableRows) curRow=0; + } } if (settings.unifiedDataView) { @@ -290,6 +331,10 @@ void FurnaceGUI::drawSampleList() { doAction(GUI_ACTION_SAMPLE_LIST_ADD); } ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FILES_O "##SampleClone")) { + doAction(GUI_ACTION_SAMPLE_LIST_DUPLICATE); + } + ImGui::SameLine(); if (ImGui::Button(ICON_FA_FOLDER_OPEN "##SampleLoad")) { doAction(GUI_ACTION_SAMPLE_LIST_OPEN); } diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 31830681f..71856c8fc 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -21,6 +21,7 @@ #include "debug.h" #include "IconsFontAwesome4.h" #include +#include void FurnaceGUI::drawDebug() { static int bpOrder; @@ -141,6 +142,104 @@ void FurnaceGUI::drawDebug() { ImGui::Columns(); ImGui::TreePop(); } + if (ImGui::TreeNode("Sample Debug")) { + for (int i=0; isong.sampleLen; i++) { + DivSample* sample=e->getSample(i); + if (sample==NULL) { + ImGui::Text("%d: ",i); + continue; + } + if (ImGui::TreeNode(fmt::sprintf("%d: %s",i,sample->name).c_str())) { + ImGui::Text("rate: %d",sample->rate); + ImGui::Text("centerRate: %d",sample->centerRate); + ImGui::Text("loopStart: %d",sample->loopStart); + ImGui::Text("loopOffP: %d",sample->loopOffP); + ImGui::Text("depth: %d",sample->depth); + ImGui::Text("length8: %d",sample->length8); + ImGui::Text("length16: %d",sample->length16); + ImGui::Text("length1: %d",sample->length1); + ImGui::Text("lengthDPCM: %d",sample->lengthDPCM); + ImGui::Text("lengthQSoundA: %d",sample->lengthQSoundA); + ImGui::Text("lengthA: %d",sample->lengthA); + ImGui::Text("lengthB: %d",sample->lengthB); + ImGui::Text("lengthX68: %d",sample->lengthX68); + ImGui::Text("lengthBRR: %d",sample->lengthBRR); + ImGui::Text("lengthVOX: %d",sample->lengthVOX); + + ImGui::Text("off8: %x",sample->off8); + ImGui::Text("off16: %x",sample->off16); + ImGui::Text("off1: %x",sample->off1); + ImGui::Text("offDPCM: %x",sample->offDPCM); + ImGui::Text("offQSoundA: %x",sample->offQSoundA); + ImGui::Text("offA: %x",sample->offA); + ImGui::Text("offB: %x",sample->offB); + ImGui::Text("offX68: %x",sample->offX68); + ImGui::Text("offBRR: %x",sample->offBRR); + ImGui::Text("offVOX: %x",sample->offVOX); + ImGui::Text("offSegaPCM: %x",sample->offSegaPCM); + ImGui::Text("offQSound: %x",sample->offQSound); + ImGui::Text("offX1_010: %x",sample->offX1_010); + ImGui::Text("offSU: %x",sample->offSU); + + ImGui::Text("samples: %d",sample->samples); + ImGui::TreePop(); + } + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Oscilloscope Debug")) { + int c=0; + for (int i=0; isong.systemLen; i++) { + DivSystem system=e->song.system[i]; + if (e->getChannelCount(system)>0) { + if (ImGui::TreeNode(fmt::sprintf("%d: %s",i,e->getSystemName(system)).c_str())) { + if (ImGui::BeginTable("OscilloscopeTable",4,ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Channel"); + ImGui::TableNextColumn(); + ImGui::Text("Follow"); + ImGui::TableNextColumn(); + ImGui::Text("Address"); + ImGui::TableNextColumn(); + ImGui::Text("Data"); + + for (int j=0; jgetChannelCount(system); j++, c++) { + ImGui::TableNextRow(); + // channel + ImGui::TableNextColumn(); + ImGui::Text("%d",j); + // follow + ImGui::TableNextColumn(); + ImGui::Checkbox(fmt::sprintf("##%d_OSCFollow_%d",i,c).c_str(),&e->getOscBuffer(c)->follow); + // address + ImGui::TableNextColumn(); + int needle=e->getOscBuffer(c)->follow?e->getOscBuffer(c)->needle:e->getOscBuffer(c)->followNeedle; + ImGui::BeginDisabled(e->getOscBuffer(c)->follow); + if (ImGui::InputInt(fmt::sprintf("##%d_OSCFollowNeedle_%d",i,c).c_str(),&needle,1,100)) { + e->getOscBuffer(c)->followNeedle=MIN(MAX(needle,0),65535); + } + ImGui::EndDisabled(); + // data + ImGui::TableNextColumn(); + ImGui::Text("%d",e->getOscBuffer(c)->data[needle]); + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + } else { + ImGui::Text("%d: ",i); + continue; + } + } + ImGui::TreePop(); + } if (ImGui::TreeNode("Playground")) { if (pgSys<0 || pgSys>=e->song.systemLen) pgSys=0; if (ImGui::BeginCombo("System",fmt::sprintf("%d. %s",pgSys+1,e->getSystemName(e->song.system[pgSys])).c_str())) { @@ -226,6 +325,26 @@ void FurnaceGUI::drawDebug() { } ImGui::TreePop(); } + if (ImGui::TreeNode("ADSR Test Area")) { + static int tl, ar, dr, d2r, sl, rr, sus, egt, algOrGlobalSus, instType; + static float maxArDr, maxTl; + ImGui::Text("This window was done out of frustration"); + drawFMEnv(tl,ar,dr,d2r,rr,sl,sus,egt,algOrGlobalSus,maxTl,maxArDr,ImVec2(200.0f*dpiScale,100.0f*dpiScale),instType); + + ImGui::InputInt("tl",&tl); + ImGui::InputInt("ar",&ar); + ImGui::InputInt("dr",&dr); + ImGui::InputInt("d2r",&d2r); + ImGui::InputInt("sl",&sl); + ImGui::InputInt("rr",&rr); + ImGui::InputInt("sus",&sus); + ImGui::InputInt("egt",&egt); + ImGui::InputInt("algOrGlobalSus",&algOrGlobalSus); + ImGui::InputInt("instType",&instType); + ImGui::InputFloat("maxArDr",&maxArDr); + ImGui::InputFloat("maxTl",&maxTl); + ImGui::TreePop(); + } if (ImGui::TreeNode("User Interface")) { if (ImGui::Button("Inspect")) { inspectorOpen=!inspectorOpen; @@ -247,4 +366,4 @@ void FurnaceGUI::drawDebug() { } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_DEBUG; ImGui::End(); -} \ No newline at end of file +} diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index a1b974e36..d966b797e 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -18,6 +18,7 @@ */ #include "gui.h" +#include "../ta-log.h" #include #include @@ -28,14 +29,14 @@ void FurnaceGUI::doAction(int what) { switch (what) { case GUI_ACTION_OPEN: if (modified) { - showWarning("Unsaved changes! Are you sure?",GUI_WARN_OPEN); + showWarning("Unsaved changes! Save changes before opening another file?",GUI_WARN_OPEN); } else { openFileDialog(GUI_FILE_OPEN); } break; case GUI_ACTION_OPEN_BACKUP: if (modified) { - showWarning("Unsaved changes! Are you sure?",GUI_WARN_OPEN_BACKUP); + showWarning("Unsaved changes! Save changes before opening backup?",GUI_WARN_OPEN_BACKUP); } else { if (load(backupPath)>0) { showError("No backup available! (or unable to open it)"); @@ -99,20 +100,14 @@ void FurnaceGUI::doAction(int what) { if (++curOctave>7) { curOctave=7; } else { - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); + e->autoNoteOffAll(); } break; case GUI_ACTION_OCTAVE_DOWN: if (--curOctave<-5) { curOctave=-5; } else { - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); + e->autoNoteOffAll(); } break; case GUI_ACTION_INS_UP: @@ -142,6 +137,10 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_FOLLOW_PATTERN: followPattern=!followPattern; break; + case GUI_ACTION_FULLSCREEN: + fullScreen=!fullScreen; + SDL_SetWindowFullscreen(sdlWin,fullScreen?(SDL_WINDOW_FULLSCREEN|SDL_WINDOW_FULLSCREEN_DESKTOP):0); + break; case GUI_ACTION_PANIC: e->syncReset(); break; @@ -215,6 +214,12 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_WINDOW_LOG: nextWindow=GUI_WINDOW_LOG; break; + case GUI_ACTION_WINDOW_EFFECT_LIST: + nextWindow=GUI_WINDOW_EFFECT_LIST; + break; + case GUI_ACTION_WINDOW_CHAN_OSC: + nextWindow=GUI_WINDOW_CHAN_OSC; + break; case GUI_ACTION_COLLAPSE_WINDOW: collapseWindow=true; @@ -287,6 +292,15 @@ void FurnaceGUI::doAction(int what) { case GUI_WINDOW_REGISTER_VIEW: regViewOpen=false; break; + case GUI_WINDOW_LOG: + logOpen=false; + break; + case GUI_WINDOW_EFFECT_LIST: + effectListOpen=false; + break; + case GUI_WINDOW_CHAN_OSC: + chanOscOpen=false; + break; default: break; } @@ -294,16 +308,28 @@ void FurnaceGUI::doAction(int what) { break; case GUI_ACTION_PAT_NOTE_UP: - doTranspose(1); + doTranspose(1,opMaskTransposeNote); break; case GUI_ACTION_PAT_NOTE_DOWN: - doTranspose(-1); + doTranspose(-1,opMaskTransposeNote); break; case GUI_ACTION_PAT_OCTAVE_UP: - doTranspose(12); + doTranspose(12,opMaskTransposeNote); break; case GUI_ACTION_PAT_OCTAVE_DOWN: - doTranspose(-12); + doTranspose(-12,opMaskTransposeNote); + break; + case GUI_ACTION_PAT_VALUE_UP: + doTranspose(1,opMaskTransposeValue); + break; + case GUI_ACTION_PAT_VALUE_DOWN: + doTranspose(-1,opMaskTransposeValue); + break; + case GUI_ACTION_PAT_VALUE_UP_COARSE: + doTranspose(16,opMaskTransposeValue); + break; + case GUI_ACTION_PAT_VALUE_DOWN_COARSE: + doTranspose(-16,opMaskTransposeValue); break; case GUI_ACTION_PAT_SELECT_ALL: doSelectAll(); @@ -428,28 +454,32 @@ void FurnaceGUI::doAction(int what) { e->unmuteAll(); break; case GUI_ACTION_PAT_NEXT_ORDER: - if (e->getOrder()song.ordersLen-1) { - e->setOrder(e->getOrder()+1); + if (curOrdersong.ordersLen-1) { + setOrder(curOrder+1); } break; case GUI_ACTION_PAT_PREV_ORDER: - if (e->getOrder()>0) { - e->setOrder(e->getOrder()-1); + if (curOrder>0) { + setOrder(curOrder-1); } break; case GUI_ACTION_PAT_COLLAPSE: if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->song.chanCollapse[cursor.xCoarse]=!e->song.chanCollapse[cursor.xCoarse]; + if (e->song.chanCollapse[cursor.xCoarse]==0) { + e->song.chanCollapse[cursor.xCoarse]=3; + } else if (e->song.chanCollapse[cursor.xCoarse]>0) { + e->song.chanCollapse[cursor.xCoarse]--; + } break; case GUI_ACTION_PAT_INCREASE_COLUMNS: if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->song.pat[cursor.xCoarse].effectRows++; - if (e->song.pat[cursor.xCoarse].effectRows>8) e->song.pat[cursor.xCoarse].effectRows=8; + e->song.pat[cursor.xCoarse].effectCols++; + if (e->song.pat[cursor.xCoarse].effectCols>8) e->song.pat[cursor.xCoarse].effectCols=8; break; case GUI_ACTION_PAT_DECREASE_COLUMNS: if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->song.pat[cursor.xCoarse].effectRows--; - if (e->song.pat[cursor.xCoarse].effectRows<1) e->song.pat[cursor.xCoarse].effectRows=1; + e->song.pat[cursor.xCoarse].effectCols--; + if (e->song.pat[cursor.xCoarse].effectCols<1) e->song.pat[cursor.xCoarse].effectCols=1; break; case GUI_ACTION_PAT_INTERPOLATE: doInterpolate(); @@ -492,6 +522,9 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_INS_LIST_OPEN: openFileDialog(GUI_FILE_INS_OPEN); break; + case GUI_ACTION_INS_LIST_OPEN_REPLACE: + openFileDialog(GUI_FILE_INS_OPEN_REPLACE); + break; case GUI_ACTION_INS_LIST_SAVE: if (curIns>=0 && curIns<(int)e->song.ins.size()) openFileDialog(GUI_FILE_INS_SAVE); break; @@ -565,8 +598,33 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_LIST_ADD: curSample=e->addSample(); + updateSampleTex=true; MARK_MODIFIED; break; + case GUI_ACTION_SAMPLE_LIST_DUPLICATE: + if (curSample>=0 && curSample<(int)e->song.sample.size()) { + DivSample* prevSample=e->getSample(curSample); + curSample=e->addSample(); + updateSampleTex=true; + e->lockEngine([this,prevSample]() { + DivSample* sample=e->getSample(curSample); + if (sample!=NULL) { + sample->rate=prevSample->rate; + sample->centerRate=prevSample->centerRate; + sample->name=prevSample->name; + sample->loopStart=prevSample->loopStart; + sample->depth=prevSample->depth; + if (sample->init(prevSample->samples)) { + if (prevSample->getCurBuf()!=NULL) { + memcpy(sample->getCurBuf(),prevSample->getCurBuf(),prevSample->getCurBufLen()); + } + } + } + e->renderSamples(); + }); + MARK_MODIFIED; + } + break; case GUI_ACTION_SAMPLE_LIST_OPEN: openFileDialog(GUI_FILE_SAMPLE_OPEN); break; @@ -574,16 +632,23 @@ void FurnaceGUI::doAction(int what) { if (curSample>=0 && curSample<(int)e->song.sample.size()) openFileDialog(GUI_FILE_SAMPLE_SAVE); break; case GUI_ACTION_SAMPLE_LIST_MOVE_UP: - if (e->moveSampleUp(curSample)) curSample--; + if (e->moveSampleUp(curSample)) { + curSample--; + updateSampleTex=true; + } break; case GUI_ACTION_SAMPLE_LIST_MOVE_DOWN: - if (e->moveSampleDown(curSample)) curSample++; + if (e->moveSampleDown(curSample)) { + curSample++; + updateSampleTex=true; + } break; case GUI_ACTION_SAMPLE_LIST_DELETE: e->delSample(curSample); MARK_MODIFIED; if (curSample>=(int)e->song.sample.size()) { curSample--; + updateSampleTex=true; } break; case GUI_ACTION_SAMPLE_LIST_EDIT: @@ -591,9 +656,11 @@ void FurnaceGUI::doAction(int what) { break; case GUI_ACTION_SAMPLE_LIST_UP: if (--curSample<0) curSample=0; + updateSampleTex=true; break; case GUI_ACTION_SAMPLE_LIST_DOWN: if (++curSample>=(int)e->song.sample.size()) curSample=((int)e->song.sample.size())-1; + updateSampleTex=true; break; case GUI_ACTION_SAMPLE_LIST_PREVIEW: e->previewSample(curSample); @@ -659,6 +726,9 @@ void FurnaceGUI::doAction(int what) { DivSample* sample=e->song.sample[curSample]; sample->prepareUndo(true); int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?sample->samples:sampleSelStart; + if (pos>=(int)sample->samples) pos=sample->samples-1; + if (pos<0) pos=0; + logV("paste position: %d",pos); e->lockEngine([this,sample,pos]() { if (!sample->insert(pos,sampleClipboardLen)) { @@ -686,6 +756,8 @@ void FurnaceGUI::doAction(int what) { DivSample* sample=e->song.sample[curSample]; sample->prepareUndo(true); int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?0:sampleSelStart; + if (pos>=(int)sample->samples) pos=sample->samples-1; + if (pos<0) pos=0; e->lockEngine([this,sample,pos]() { if (sample->depth==8) { @@ -714,6 +786,8 @@ void FurnaceGUI::doAction(int what) { DivSample* sample=e->song.sample[curSample]; sample->prepareUndo(true); int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?0:sampleSelStart; + if (pos>=(int)sample->samples) pos=sample->samples-1; + if (pos<0) pos=0; e->lockEngine([this,sample,pos]() { if (sample->depth==8) { @@ -1060,15 +1134,26 @@ void FurnaceGUI::doAction(int what) { updateSampleTex=true; } break; + case GUI_ACTION_SAMPLE_MAKE_INS: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + curIns=e->addInstrument(cursor.xCoarse); + e->song.ins[curIns]->type=DIV_INS_AMIGA; + e->song.ins[curIns]->name=sample->name; + e->song.ins[curIns]->amiga.initSample=curSample; + nextWindow=GUI_WINDOW_INS_EDIT; + MARK_MODIFIED; + break; + } case GUI_ACTION_ORDERS_UP: - if (e->getOrder()>0) { - e->setOrder(e->getOrder()-1); + if (curOrder>0) { + setOrder(curOrder-1); } break; case GUI_ACTION_ORDERS_DOWN: - if (e->getOrder()song.ordersLen-1) { - e->setOrder(e->getOrder()+1); + if (curOrdersong.ordersLen-1) { + setOrder(curOrder+1); } break; case GUI_ACTION_ORDERS_LEFT: { @@ -1097,7 +1182,6 @@ void FurnaceGUI::doAction(int what) { } case GUI_ACTION_ORDERS_INCREASE: { if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break; - int curOrder=e->getOrder(); if (e->song.orders.ord[orderCursor][curOrder]<0x7f) { e->song.orders.ord[orderCursor][curOrder]++; } @@ -1105,7 +1189,6 @@ void FurnaceGUI::doAction(int what) { } case GUI_ACTION_ORDERS_DECREASE: { if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break; - int curOrder=e->getOrder(); if (e->song.orders.ord[orderCursor][curOrder]>0) { e->song.orders.ord[orderCursor][curOrder]--; } @@ -1165,7 +1248,7 @@ void FurnaceGUI::doAction(int what) { makeUndo(GUI_UNDO_CHANGE_ORDER); break; case GUI_ACTION_ORDERS_REPLAY: - e->setOrder(e->getOrder()); + setOrder(curOrder); break; } } \ No newline at end of file diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 5e8d72574..722c38f2a 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -35,10 +35,7 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##Octave",&curOctave,1,1)) { if (curOctave>7) curOctave=7; if (curOctave<-5) curOctave=-5; - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); + e->autoNoteOffAll(); if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { nextWindow=GUI_WINDOW_PATTERN; @@ -86,6 +83,7 @@ void FurnaceGUI::drawEditControls() { ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { e->stepOne(cursor.y); + pendingStepUpdate=true; } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; @@ -105,6 +103,7 @@ void FurnaceGUI::drawEditControls() { ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { e->stepOne(cursor.y); + pendingStepUpdate=true; } ImGui::SameLine(); @@ -137,10 +136,7 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##Octave",&curOctave,1,1)) { if (curOctave>7) curOctave=7; if (curOctave<-5) curOctave=-5; - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); + e->autoNoteOffAll(); if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { nextWindow=GUI_WINDOW_PATTERN; @@ -182,6 +178,7 @@ void FurnaceGUI::drawEditControls() { } if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { e->stepOne(cursor.y); + pendingStepUpdate=true; } bool repeatPattern=e->getRepeatPattern(); @@ -210,10 +207,7 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##Octave",&curOctave,0,0)) { if (curOctave>7) curOctave=7; if (curOctave<-5) curOctave=-5; - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); + e->autoNoteOffAll(); if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { nextWindow=GUI_WINDOW_PATTERN; @@ -256,16 +250,23 @@ void FurnaceGUI::drawEditControls() { ImGui::PopStyleColor(); } else { if (ImGui::Button(ICON_FA_PLAY "##Play")) { - play(); + play(oldRow); } } ImGui::SameLine(); if (ImGui::Button(ICON_FA_PLAY_CIRCLE "##PlayAgain")) { + e->setRepeatPattern(false); + play(); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_STEP_FORWARD "##PlayRepeat")) { + e->setRepeatPattern(true); play(); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { e->stepOne(cursor.y); + pendingStepUpdate=true; } ImGui::SameLine(); @@ -304,10 +305,7 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##Octave",&curOctave,1,1)) { if (curOctave>7) curOctave=7; if (curOctave<-5) curOctave=-5; - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); + e->autoNoteOffAll(); if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { nextWindow=GUI_WINDOW_PATTERN; diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 96f827f32..8832b7fbb 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -42,7 +42,6 @@ const char* noteNameNormal(short note, short octave) { } void FurnaceGUI::prepareUndo(ActionType action) { - int order=e->getOrder(); switch (action) { case GUI_UNDO_CHANGE_ORDER: oldOrders=e->song.orders; @@ -64,7 +63,7 @@ void FurnaceGUI::prepareUndo(ActionType action) { case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: for (int i=0; igetTotalChannelCount(); i++) { - e->song.pat[i].getPattern(e->song.orders.ord[i][order],false)->copyOn(oldPat[i]); + e->song.pat[i].getPattern(e->song.orders.ord[i][curOrder],false)->copyOn(oldPat[i]); } break; } @@ -77,8 +76,7 @@ void FurnaceGUI::makeUndo(ActionType action) { s.cursor=cursor; s.selStart=selStart; s.selEnd=selEnd; - int order=e->getOrder(); - s.order=order; + s.order=curOrder; s.nibble=curNibble; switch (action) { case GUI_UNDO_CHANGE_ORDER: @@ -114,11 +112,11 @@ void FurnaceGUI::makeUndo(ActionType action) { case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: for (int i=0; igetTotalChannelCount(); i++) { - DivPattern* p=e->song.pat[i].getPattern(e->song.orders.ord[i][order],false); + DivPattern* p=e->song.pat[i].getPattern(e->song.orders.ord[i][curOrder],false); for (int j=0; jsong.patLen; j++) { for (int k=0; k<32; k++) { if (p->data[j][k]!=oldPat[i]->data[j][k]) { - s.pat.push_back(UndoPatternData(i,e->song.orders.ord[i][order],j,k,oldPat[i]->data[j][k],p->data[j][k])); + s.pat.push_back(UndoPatternData(i,e->song.orders.ord[i][curOrder],j,k,oldPat[i]->data[j][k],p->data[j][k])); } } } @@ -139,12 +137,12 @@ void FurnaceGUI::makeUndo(ActionType action) { void FurnaceGUI::doSelectAll() { finishSelection(); curNibble=false; - if (selStart.xFine==0 && selEnd.xFine==2+e->song.pat[selEnd.xCoarse].effectRows*2) { + if (selStart.xFine==0 && selEnd.xFine==2+e->song.pat[selEnd.xCoarse].effectCols*2) { if (selStart.y==0 && selEnd.y==e->song.patLen-1) { // select entire pattern selStart.xCoarse=0; selStart.xFine=0; selEnd.xCoarse=e->getTotalChannelCount()-1; - selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectRows*2; + selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2; } else { // select entire column selStart.y=0; selEnd.y=e->song.patLen-1; @@ -155,14 +153,14 @@ void FurnaceGUI::doSelectAll() { // find row position for (SelectionPoint i; i.xCoarse!=selStart.xCoarse || i.xFine!=selStart.xFine; selStartX++) { i.xFine++; - if (i.xFine>=3+e->song.pat[i.xCoarse].effectRows*2) { + if (i.xFine>=3+e->song.pat[i.xCoarse].effectCols*2) { i.xFine=0; i.xCoarse++; } } for (SelectionPoint i; i.xCoarse!=selEnd.xCoarse || i.xFine!=selEnd.xFine; selEndX++) { i.xFine++; - if (i.xFine>=3+e->song.pat[i.xCoarse].effectRows*2) { + if (i.xFine>=3+e->song.pat[i.xCoarse].effectCols*2) { i.xFine=0; i.xCoarse++; } @@ -174,22 +172,22 @@ void FurnaceGUI::doSelectAll() { selEnd.y=e->song.patLen-1; } else { // left-right selStart.xFine=0; - selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectRows*2; + selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2; } } } -#define maskOut(x) \ +#define maskOut(m,x) \ if (x==0) { \ - if (!opMaskNote) continue; \ + if (!m.note) continue; \ } else if (x==1) { \ - if (!opMaskIns) continue; \ + if (!m.ins) continue; \ } else if (x==2) { \ - if (!opMaskVol) continue; \ + if (!m.vol) continue; \ } else if (((x)&1)==0) { \ - if (!opMaskEffectVal) continue; \ + if (!m.effectVal) continue; \ } else if (((x)&1)==1) { \ - if (!opMaskEffect) continue; \ + if (!m.effect) continue; \ } void FurnaceGUI::doDelete() { @@ -199,12 +197,11 @@ void FurnaceGUI::doDelete() { int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; - int ord=e->getOrder(); for (; iCoarse<=selEnd.xCoarse; iCoarse++) { if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsedata[j][iFine]=0; @@ -237,12 +234,11 @@ void FurnaceGUI::doPullDelete() { int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; - int ord=e->getOrder(); for (; iCoarse<=selEnd.xCoarse; iCoarse++) { if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsesong.patLen; j++) { if (jsong.patLen-1) { if (iFine==0) { @@ -270,12 +266,11 @@ void FurnaceGUI::doInsert() { int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; - int ord=e->getOrder(); for (; iCoarse<=selEnd.xCoarse; iCoarse++) { if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsesong.patLen-1; j>=selStart.y; j--) { if (j==selStart.y) { if (iFine==0) { @@ -296,19 +291,18 @@ void FurnaceGUI::doInsert() { makeUndo(GUI_UNDO_PATTERN_PUSH); } -void FurnaceGUI::doTranspose(int amount) { +void FurnaceGUI::doTranspose(int amount, OperationMask& mask) { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_DELETE); curNibble=false; int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; - int ord=e->getOrder(); for (; iCoarse<=selEnd.xCoarse; iCoarse++) { if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsedata[j][0]; @@ -371,12 +365,11 @@ void FurnaceGUI::doCopy(bool cut) { if (iFine>3 && !(iFine&1)) { iFine--; } - int ord=e->getOrder(); clipboard+='\n'; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsedata[j][0],pat->data[j][1]); if (cut) { @@ -439,7 +432,6 @@ void FurnaceGUI::doPaste(PasteMode mode) { int j=cursor.y; char note[4]; - int ord=e->getOrder(); for (size_t i=2; isong.patLen; i++) { size_t charPos=0; int iCoarse=cursor.xCoarse; @@ -448,7 +440,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { String& line=data[i]; while (charPossong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); if (line[charPos]=='|') { iCoarse++; if (iCoarsesong.chanShow[iCoarse]) { @@ -477,7 +469,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { note[2]=line[charPos++]; note[3]=0; - if (iFine==0 && !opMaskNote) { + if (iFine==0 && !opMaskPaste.note) { iFine++; continue; } @@ -506,22 +498,22 @@ void FurnaceGUI::doPaste(PasteMode mode) { note[2]=0; if (iFine==1) { - if (!opMaskIns) { + if (!opMaskPaste.ins) { iFine++; continue; } } else if (iFine==2) { - if (!opMaskVol) { + if (!opMaskPaste.vol) { iFine++; continue; } } else if ((iFine&1)==0) { - if (!opMaskEffectVal) { + if (!opMaskPaste.effectVal) { iFine++; continue; } } else if ((iFine&1)==1) { - if (!opMaskEffect) { + if (!opMaskPaste.effect) { iFine++; continue; } @@ -538,7 +530,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { break; } if (mode!=GUI_PASTE_MODE_MIX_BG || pat->data[j][iFine+1]==-1) { - if (iFine<(3+e->song.pat[iCoarse].effectRows*2)) pat->data[j][iFine+1]=val; + if (iFine<(3+e->song.pat[iCoarse].effectCols*2)) pat->data[j][iFine+1]=val; } } } @@ -551,9 +543,9 @@ void FurnaceGUI::doPaste(PasteMode mode) { break; } j++; - if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->song.patLen && ordsong.ordersLen-1) { + if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->song.patLen && curOrdersong.ordersLen-1) { j=0; - ord++; + curOrder++; } if (mode==GUI_PASTE_MODE_FLOOD && i==data.size()-1) { @@ -562,6 +554,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { } if (settings.cursorPastePos) { cursor.y=j; + if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; updateScroll(cursor.y); } @@ -573,10 +566,9 @@ void FurnaceGUI::doChangeIns(int ins) { prepareUndo(GUI_UNDO_PATTERN_CHANGE_INS); int iCoarse=selStart.xCoarse; - int ord=e->getOrder(); for (; iCoarse<=selEnd.xCoarse; iCoarse++) { if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); for (int j=selStart.y; j<=selEnd.y; j++) { if (pat->data[j][2]!=-1 || !(pat->data[j][0]==0 && pat->data[j][1]==0)) { pat->data[j][2]=ins; @@ -594,12 +586,11 @@ void FurnaceGUI::doInterpolate() { std::vector> points; int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; - int ord=e->getOrder(); for (; iCoarse<=selEnd.xCoarse; iCoarse++) { if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsegetOrder(); for (; iCoarse<=selEnd.xCoarse; iCoarse++) { if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsegetOrder(); for (; iCoarse<=selEnd.xCoarse; iCoarse++) { if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsegetOrder(); for (; iCoarse<=selEnd.xCoarse; iCoarse++) { if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsegetOrder(); for (; iCoarse<=selEnd.xCoarse; iCoarse++) { if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsegetOrder(); for (; iCoarse<=selEnd.xCoarse; iCoarse++) { if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsedata[j][0]; @@ -836,12 +822,11 @@ void FurnaceGUI::doCollapse(int divider) { DivPattern patBuffer; int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; - int ord=e->getOrder(); for (; iCoarse<=selEnd.xCoarse; iCoarse++) { if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsedata[j][0]; @@ -894,12 +879,11 @@ void FurnaceGUI::doExpand(int multiplier) { DivPattern patBuffer; int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; - int ord=e->getOrder(); for (; iCoarse<=selEnd.xCoarse; iCoarse++) { if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsedata[j][0]; @@ -961,13 +945,13 @@ void FurnaceGUI::doUndo() { DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); p->data[i.row][i.col]=i.oldVal; } - if (!e->isPlaying()) { + if (!e->isPlaying() || !followPattern) { cursor=us.cursor; selStart=us.selStart; selEnd=us.selEnd; curNibble=us.nibble; updateScroll(cursor.y); - e->setOrder(us.order); + setOrder(us.order); } break; } @@ -1013,11 +997,11 @@ void FurnaceGUI::doRedo() { selEnd=us.selEnd; curNibble=us.nibble; updateScroll(cursor.y); - e->setOrder(us.order); + setOrder(us.order); } break; } redoHist.pop_back(); -} \ No newline at end of file +} diff --git a/src/gui/effectList.cpp b/src/gui/effectList.cpp new file mode 100644 index 000000000..032b87c33 --- /dev/null +++ b/src/gui/effectList.cpp @@ -0,0 +1,53 @@ +#include "gui.h" +#include "guiConst.h" +#include + +void FurnaceGUI::drawEffectList() { + if (nextWindow==GUI_WINDOW_EFFECT_LIST) { + effectListOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!effectListOpen) return; + if (ImGui::Begin("Effect List",&effectListOpen)) { + ImGui::Text("System at cursor: %s",e->getSystemName(e->sysOfChan[cursor.xCoarse])); + if (ImGui::BeginTable("effectList",2)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Name"); + ImGui::TableNextColumn(); + ImGui::Text("Description"); + + const char* prevName=NULL; + for (int i=0; i<256; i++) { + const char* name=e->getEffectDesc(i,cursor.xCoarse); + if (name==prevName) { + continue; + } + prevName=name; + if (name!=NULL) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushFont(patFont); + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[fxColors[i]]); + ImGui::Text("%c%c%c%c",name[0],name[1],name[2],name[3]); + ImGui::PopStyleColor(); + ImGui::PopFont(); + + ImGui::TableNextColumn(); + if (strlen(name)>6) { + ImGui::TextWrapped("%s",&name[6]); + } else { + ImGui::Text("ERROR"); + } + } + } + ImGui::EndTable(); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EFFECT_LIST; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index 7bfafb4a3..e50298c1a 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -4,7 +4,7 @@ #include "../../extern/pfd-fixed/portable-file-dialogs.h" -bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale) { +bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale, FileDialogSelectCallback clickCallback) { if (opened) return false; saving=false; curPath=path; @@ -13,7 +13,7 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, c dialogO=new pfd::open_file(header,path,filter); } else { ImGuiFileDialog::Instance()->DpiScale=dpiScale; - ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path); + ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,1,nullptr,0,clickCallback); } opened=true; return true; @@ -95,6 +95,10 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { } } +bool FurnaceGUIFileDialog::isOpen() { + return opened; +} + String FurnaceGUIFileDialog::getPath() { if (sysDialog) { if (curPath.size()>1) { diff --git a/src/gui/fileDialog.h b/src/gui/fileDialog.h index b7f21abf0..5eb67d853 100644 --- a/src/gui/fileDialog.h +++ b/src/gui/fileDialog.h @@ -1,5 +1,6 @@ #include "../ta-utils.h" #include "imgui.h" +#include #include namespace pfd { @@ -7,6 +8,8 @@ namespace pfd { class save_file; } +typedef std::function FileDialogSelectCallback; + class FurnaceGUIFileDialog { bool sysDialog; bool opened; @@ -16,14 +19,15 @@ class FurnaceGUIFileDialog { pfd::open_file* dialogO; pfd::save_file* dialogS; public: - bool openLoad(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale); + bool openLoad(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale, FileDialogSelectCallback clickCallback=NULL); bool openSave(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale); bool accepted(); void close(); bool render(const ImVec2& min, const ImVec2& max); + bool isOpen(); String getPath(); String getFileName(); - FurnaceGUIFileDialog(bool system): + explicit FurnaceGUIFileDialog(bool system): sysDialog(system), opened(false), saving(false), diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 948489775..700e55d09 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -505,20 +505,24 @@ bool FurnaceGUI::CWVSliderInt(const char* label, const ImVec2& size, int* v, int } const char* FurnaceGUI::getSystemName(DivSystem which) { + /* if (settings.chipNames) { return e->getSystemChips(which); } + */ return e->getSystemName(which); } void FurnaceGUI::updateScroll(int amount) { float lineHeight=(patFont->FontSize+2*dpiScale); nextScroll=lineHeight*amount; + haveHitBounds=false; } void FurnaceGUI::addScroll(int amount) { float lineHeight=(patFont->FontSize+2*dpiScale); nextAddScroll=lineHeight*amount; + haveHitBounds=false; } void FurnaceGUI::setFileName(String name) { @@ -584,7 +588,7 @@ void FurnaceGUI::updateWindowTitle() { } if (settings.titleBarSys) { - title+=fmt::sprintf(" (%s)",e->getSongSystemName()); + title+=fmt::sprintf(" (%s)",e->getSongSystemName(!settings.noMultiSystem)); } if (sdlWin!=NULL) SDL_SetWindowTitle(sdlWin,title.c_str()); @@ -839,6 +843,7 @@ float FurnaceGUI::calcBPM(int s1, int s2, float hz) { void FurnaceGUI::play(int row) { e->walkSong(loopOrder,loopRow,loopEnd); memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS); + if (!followPattern) e->setOrder(curOrder); if (row>0) { e->playToRow(row); } else { @@ -849,6 +854,13 @@ void FurnaceGUI::play(int row) { activeNotes.clear(); } +void FurnaceGUI::setOrder(unsigned char order, bool forced) { + curOrder=order; + if (followPattern || forced) { + e->setOrder(order); + } +} + void FurnaceGUI::stop() { e->walkSong(loopOrder,loopRow,loopEnd); e->stop(); @@ -858,37 +870,13 @@ void FurnaceGUI::stop() { } void FurnaceGUI::previewNote(int refChan, int note, bool autoNote) { - if (autoNote) { - e->setMidiBaseChan(refChan); - e->synchronized([this,note]() { - e->autoNoteOn(-1,curIns,note); - }); - return; - } - - bool chanBusy[DIV_MAX_CHANS]; - memset(chanBusy,0,DIV_MAX_CHANS*sizeof(bool)); - for (ActiveNote& i: activeNotes) { - if (i.chan<0 || i.chan>=DIV_MAX_CHANS) continue; - chanBusy[i.chan]=true; - } - int chanCount=e->getTotalChannelCount(); - int i=refChan; - do { - if (!chanBusy[i]) { - e->noteOn(i,curIns,note); - activeNotes.push_back(ActiveNote(i,note)); - //printf("PUSHING: %d NOTE %d\n",i,note); - return; - } - i++; - if (i>=chanCount) i=0; - } while (i!=refChan); - //printf("FAILED TO FIND CHANNEL!\n"); + e->setMidiBaseChan(refChan); + e->synchronized([this,note]() { + e->autoNoteOn(-1,curIns,note); + }); } void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode, bool autoNote) { - if (activeNotes.empty() && !autoNote) return; try { int key=noteKeys.at(scancode); int num=12*curOctave+key; @@ -899,27 +887,15 @@ void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode, bool autoNote) { if (key==101) return; if (key==102) return; - if (autoNote) { - e->synchronized([this,num]() { - e->autoNoteOff(-1,num); - }); - return; - } - - for (size_t i=0; inoteOff(activeNotes[i].chan); - //printf("REMOVING %d\n",activeNotes[i].chan); - activeNotes.erase(activeNotes.begin()+i); - break; - } - } + e->synchronized([this,num]() { + e->autoNoteOff(-1,num); + }); } catch (std::out_of_range& e) { } } void FurnaceGUI::noteInput(int num, int key, int vol) { - DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); + DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true); prepareUndo(GUI_UNDO_PATTERN_EDIT); @@ -941,7 +917,10 @@ void FurnaceGUI::noteInput(int num, int key, int vol) { } pat->data[cursor.y][1]=(unsigned char)pat->data[cursor.y][1]; if (latchIns==-2) { - pat->data[cursor.y][2]=curIns; + if (curIns>=(int)e->song.ins.size()) curIns=-1; + if (curIns>=0) { + pat->data[cursor.y][2]=curIns; + } } else if (latchIns!=-1 && !e->song.ins.empty()) { pat->data[cursor.y][2]=MIN(((int)e->song.ins.size())-1,latchIns); } @@ -960,14 +939,18 @@ void FurnaceGUI::noteInput(int num, int key, int vol) { } void FurnaceGUI::valueInput(int num, bool direct, int target) { - DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); + DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true); prepareUndo(GUI_UNDO_PATTERN_EDIT); if (target==-1) target=cursor.xFine+1; if (direct) { pat->data[cursor.y][target]=num&0xff; } else { if (pat->data[cursor.y][target]==-1) pat->data[cursor.y][target]=0; - pat->data[cursor.y][target]=((pat->data[cursor.y][target]<<4)|num)&0xff; + if (!settings.pushNibble && !curNibble) { + pat->data[cursor.y][target]=num; + } else { + pat->data[cursor.y][target]=((pat->data[cursor.y][target]<<4)|num)&0xff; + } } if (cursor.xFine==1) { // instrument if (pat->data[cursor.y][target]>=(int)e->song.ins.size()) { @@ -976,6 +959,9 @@ void FurnaceGUI::valueInput(int num, bool direct, int target) { pat->data[cursor.y][target]=(int)e->song.ins.size()-1; } } + if (settings.absorbInsInput) { + curIns=pat->data[cursor.y][target]; + } makeUndo(GUI_UNDO_PATTERN_EDIT); if (direct) { curNibble=false; @@ -1017,7 +1003,7 @@ void FurnaceGUI::valueInput(int num, bool direct, int target) { editAdvance(); } else { if (settings.effectCursorDir==2) { - if (++cursor.xFine>=(3+(e->song.pat[cursor.xCoarse].effectRows*2))) { + if (++cursor.xFine>=(3+(e->song.pat[cursor.xCoarse].effectCols*2))) { cursor.xFine=3; } } else { @@ -1034,6 +1020,15 @@ void FurnaceGUI::valueInput(int num, bool direct, int target) { } } +#define changeLatch(x) \ + if (x<0) x=0; \ + if (!latchNibble && !settings.pushNibble) x=0; \ + x=(x<<4)|num; \ + latchNibble=!latchNibble; \ + if (!latchNibble) { \ + if (++latchTarget>4) latchTarget=0; \ + } + void FurnaceGUI::keyDown(SDL_Event& ev) { if (ImGuiFileDialog::Instance()->IsOpened()) return; if (aboutOpen) return; @@ -1075,6 +1070,45 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { return; } + if (latchTarget) { + if (mapped==SDLK_DELETE || mapped==SDLK_BACKSPACE) { + switch (latchTarget) { + case 1: + latchIns=-1; + break; + case 2: + latchVol=-1; + break; + case 3: + latchEffect=-1; + break; + case 4: + latchEffectVal=-1; + break; + } + } else { + try { + int num=valueKeys.at(ev.key.keysym.sym); + switch (latchTarget) { + case 1: // instrument + changeLatch(latchIns); + break; + case 2: // volume + changeLatch(latchVol); + break; + case 3: // effect + changeLatch(latchEffect); + break; + case 4: // effect value + changeLatch(latchEffectVal); + break; + } + } catch (std::out_of_range& e) { + } + } + return; + } + // PER-WINDOW KEYS switch (curWindow) { case GUI_WINDOW_PATTERN: @@ -1100,9 +1134,6 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { if (edit) { noteInput(num,key); } - if (key!=100 && key!=101 && key!=102) { - previewNote(cursor.xCoarse,num); - } } catch (std::out_of_range& e) { } } else if (edit) { // value @@ -1129,8 +1160,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { try { int num=valueKeys.at(ev.key.keysym.sym); if (orderCursor>=0 && orderCursorgetTotalChannelCount()) { - int curOrder=e->getOrder(); - e->lockSave([this,curOrder,num]() { + e->lockSave([this,num]() { e->song.orders.ord[orderCursor][curOrder]=((e->song.orders.ord[orderCursor][curOrder]<<4)|num); }); if (orderEditMode==2 || orderEditMode==3) { @@ -1141,7 +1171,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { if (orderCursor>=e->getTotalChannelCount()) orderCursor=0; } else if (orderEditMode==3) { if (curOrdersong.ordersLen-1) { - e->setOrder(curOrder+1); + setOrder(curOrder+1); } } } @@ -1205,75 +1235,10 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { } } catch (std::out_of_range& e) { } - - // PER-WINDOW PREVIEW KEYS - switch (curWindow) { - case GUI_WINDOW_INS_EDIT: - case GUI_WINDOW_INS_LIST: - case GUI_WINDOW_EDIT_CONTROLS: - case GUI_WINDOW_SONG_INFO: - if (!ev.key.repeat) { - try { - int key=noteKeys.at(ev.key.keysym.scancode); - int num=12*curOctave+key; - if (key!=100 && key!=101 && key!=102) { - previewNote(cursor.xCoarse,num,true); - } - } catch (std::out_of_range& e) { - } - } - break; - case GUI_WINDOW_SAMPLE_EDIT: - case GUI_WINDOW_SAMPLE_LIST: - if (!ev.key.repeat) { - try { - int key=noteKeys.at(ev.key.keysym.scancode); - int num=12*curOctave+key; - if (key!=100 && key!=101 && key!=102) { - e->previewSample(curSample,num); - samplePreviewOn=true; - samplePreviewKey=ev.key.keysym.scancode; - samplePreviewNote=num; - } - } catch (std::out_of_range& e) { - } - } - break; - case GUI_WINDOW_WAVE_LIST: - case GUI_WINDOW_WAVE_EDIT: - if (!ev.key.repeat) { - try { - int key=noteKeys.at(ev.key.keysym.scancode); - int num=12*curOctave+key; - if (key!=100 && key!=101 && key!=102) { - e->previewWave(curWave,num); - wavePreviewOn=true; - wavePreviewKey=ev.key.keysym.scancode; - wavePreviewNote=num; - } - } catch (std::out_of_range& e) { - } - } - break; - default: - break; - } } void FurnaceGUI::keyUp(SDL_Event& ev) { - stopPreviewNote(ev.key.keysym.scancode,curWindow!=GUI_WINDOW_PATTERN); - if (wavePreviewOn) { - if (ev.key.keysym.scancode==wavePreviewKey) { - wavePreviewOn=false; - e->stopWavePreview(); - } - } - if (samplePreviewOn) { - if (ev.key.keysym.scancode==samplePreviewKey) { - samplePreviewOn=false; - e->stopSamplePreview(); - } - } + // nothing for now } bool dirExists(String what) { @@ -1294,9 +1259,9 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openLoad( "Open File", - {"compatible files", "*.fur *.dmf *.mod", + {"compatible files", "*.fur *.dmf *.mod *.ftm", "all files", ".*"}, - "compatible files{.fur,.dmf,.mod},.*", + "compatible files{.fur,.dmf,.mod,.ftm},.*", workingDirSong, dpiScale ); @@ -1323,14 +1288,42 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { ); break; case GUI_FILE_INS_OPEN: + case GUI_FILE_INS_OPEN_REPLACE: + prevIns=-3; + if (prevInsData!=NULL) { + delete prevInsData; + prevInsData=NULL; + } + prevInsData=new DivInstrument; + *prevInsData=*e->getIns(curIns); if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); hasOpened=fileDialog->openLoad( "Load Instrument", - {"compatible files", "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi", + {"compatible files", "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi *.opli *.opni *.y12 *.bnk *.ff *.opm", "all files", ".*"}, - "compatible files{.fui,.dmp,.tfi,.vgi,.s3i,.sbi},.*", + "compatible files{.fui,.dmp,.tfi,.vgi,.s3i,.sbi,.opli,.opni,.y12,.bnk,.ff,.opm},.*", workingDirIns, - dpiScale + dpiScale, + [this](const char* path) { + std::vector instruments=e->instrumentFromFile(path); + if (!instruments.empty()) { + if (curFileDialog==GUI_FILE_INS_OPEN_REPLACE) { + if (prevIns==-3) { + prevIns=curIns; + } + if (prevIns>=0 && prevIns<=(int)e->song.ins.size()) { + *e->song.ins[prevIns]=*instruments[0]; + } + } else { + e->loadTempIns(instruments[0]); + if (curIns!=-2) { + prevIns=curIns; + } + curIns=-2; + } + } + for (DivInstrument* i: instruments) delete i; + } ); break; case GUI_FILE_INS_SAVE: @@ -1368,9 +1361,9 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); hasOpened=fileDialog->openLoad( "Load Sample", - {"Wave file", "*.wav", + {"compatible files", "*.wav *.dmc", "all files", ".*"}, - "Wave file{.wav},.*", + "compatible files{.wav,.dmc},.*", workingDirSample, dpiScale ); @@ -1644,14 +1637,13 @@ int FurnaceGUI::load(String path) { fclose(f); return 1; } - unsigned char* file=new unsigned char[len]; if (fseek(f,0,SEEK_SET)<0) { perror("size error"); lastError=fmt::sprintf("on get size: %s",strerror(errno)); fclose(f); - delete[] file; return 1; } + unsigned char* file=new unsigned char[len]; if (fread(file,1,(size_t)len,f)!=(size_t)len) { perror("read error"); lastError=fmt::sprintf("on read: %s",strerror(errno)); @@ -1721,7 +1713,31 @@ void FurnaceGUI::showError(String what) { t[x]&=(1<0) { int x=(dragX-macroLoopDragStart.x)*macroLoopDragLen/MAX(1,macroLoopDragAreaSize.x); if (x<0) x=0; - if (x>=macroLoopDragLen) x=-1; - x+=macroDragScroll; + if (x>=macroLoopDragLen) { + x=-1; + } else { + x+=macroDragScroll; + } *macroLoopDragTarget=x; } } @@ -1812,7 +1831,7 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { #define sysChangeOption(x,y) \ if (ImGui::MenuItem(getSystemName(y),NULL,e->song.system[x]==y)) { \ - e->changeSystem(x,y); \ + e->changeSystem(x,y,preserveChanPos); \ updateWindowTitle(); \ } @@ -1834,8 +1853,47 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { fileName+=fallback; \ } +#define drawOpMask(m) \ + ImGui::PushFont(patFont); \ + ImGui::PushID("om_" #m); \ + if (ImGui::BeginTable("opMaskTable",5,ImGuiTableFlags_Borders|ImGuiTableFlags_SizingFixedFit|ImGuiTableFlags_NoHostExtendX)) { \ + ImGui::TableNextRow(); \ + ImGui::TableNextColumn(); \ + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_ACTIVE]); \ + if (ImGui::Selectable(m.note?"C-4##opMaskNote":"---##opMaskNote",m.note,ImGuiSelectableFlags_DontClosePopups)) { \ + m.note=!m.note; \ + } \ + ImGui::PopStyleColor(); \ + ImGui::TableNextColumn(); \ + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS]); \ + if (ImGui::Selectable(m.ins?"01##opMaskIns":"--##opMaskIns",m.ins,ImGuiSelectableFlags_DontClosePopups)) { \ + m.ins=!m.ins; \ + } \ + ImGui::PopStyleColor(); \ + ImGui::TableNextColumn(); \ + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_VOLUME_MAX]); \ + if (ImGui::Selectable(m.vol?"7F##opMaskVol":"--##opMaskVol",m.vol,ImGuiSelectableFlags_DontClosePopups)) { \ + m.vol=!m.vol; \ + } \ + ImGui::PopStyleColor(); \ + ImGui::TableNextColumn(); \ + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_PITCH]); \ + if (ImGui::Selectable(m.effect?"04##opMaskEffect":"--##opMaskEffect",m.effect,ImGuiSelectableFlags_DontClosePopups)) { \ + m.effect=!m.effect; \ + } \ + ImGui::TableNextColumn(); \ + if (ImGui::Selectable(m.effectVal?"72##opMaskEffectVal":"--##opMaskEffectVal",m.effectVal,ImGuiSelectableFlags_DontClosePopups)) { \ + m.effectVal=!m.effectVal; \ + } \ + ImGui::PopStyleColor(); \ + ImGui::EndTable(); \ + } \ + ImGui::PopID(); \ + ImGui::PopFont(); + void FurnaceGUI::editOptions(bool topMenu) { char id[4096]; + editOptsVisible=true; if (ImGui::MenuItem("cut",BIND_FOR(GUI_ACTION_PAT_CUT))) doCopy(true); if (ImGui::MenuItem("copy",BIND_FOR(GUI_ACTION_PAT_COPY))) doCopy(false); if (ImGui::MenuItem("paste",BIND_FOR(GUI_ACTION_PAT_PASTE))) doPaste(); @@ -1852,61 +1910,188 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::Separator(); - ImGui::Text("operation mask"); - ImGui::SameLine(); + if (ImGui::BeginMenu("operation mask...")) { + drawOpMask(opMaskDelete); + ImGui::SameLine(); + ImGui::Text("delete"); + drawOpMask(opMaskPullDelete); + ImGui::SameLine(); + ImGui::Text("pull delete"); + + drawOpMask(opMaskInsert); + ImGui::SameLine(); + ImGui::Text("insert"); + + drawOpMask(opMaskPaste); + ImGui::SameLine(); + ImGui::Text("paste"); + + drawOpMask(opMaskTransposeNote); + ImGui::SameLine(); + ImGui::Text("transpose (note)"); + + drawOpMask(opMaskTransposeValue); + ImGui::SameLine(); + ImGui::Text("transpose (value)"); + + drawOpMask(opMaskInterpolate); + ImGui::SameLine(); + ImGui::Text("interpolate"); + + drawOpMask(opMaskFade); + ImGui::SameLine(); + ImGui::Text("fade"); + + drawOpMask(opMaskInvertVal); + ImGui::SameLine(); + ImGui::Text("invert values"); + + drawOpMask(opMaskScale); + ImGui::SameLine(); + ImGui::Text("scale"); + + drawOpMask(opMaskRandomize); + ImGui::SameLine(); + ImGui::Text("randomize"); + + drawOpMask(opMaskFlip); + ImGui::SameLine(); + ImGui::Text("flip"); + + drawOpMask(opMaskCollapseExpand); + ImGui::SameLine(); + ImGui::Text("collapse/expand"); + + ImGui::EndMenu(); + } + + ImGui::Text("input latch"); ImGui::PushFont(patFont); - if (ImGui::BeginTable("opMaskTable",5,ImGuiTableFlags_Borders|ImGuiTableFlags_SizingFixedFit|ImGuiTableFlags_NoHostExtendX)) { + if (ImGui::BeginTable("inputLatchTable",5,ImGuiTableFlags_Borders|ImGuiTableFlags_SizingFixedFit|ImGuiTableFlags_NoHostExtendX)) { + static char id[64]; ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_ACTIVE]); - if (ImGui::Selectable(opMaskNote?"C-4##opMaskNote":"---##opMaskNote",opMaskNote,ImGuiSelectableFlags_DontClosePopups)) { - opMaskNote=!opMaskNote; - } + ImGui::Text("C-4"); ImGui::PopStyleColor(); ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS]); - if (ImGui::Selectable(opMaskIns?"01##opMaskIns":"--##opMaskIns",opMaskIns,ImGuiSelectableFlags_DontClosePopups)) { - opMaskIns=!opMaskIns; + if (latchIns==-2) { + strcpy(id,"&&##LatchIns"); + } else if (latchIns==-1) { + strcpy(id,"..##LatchIns"); + } else { + snprintf(id,63,"%.2x##LatchIns",latchIns&0xff); + } + if (ImGui::Selectable(id,latchTarget==1,ImGuiSelectableFlags_DontClosePopups)) { + latchTarget=1; + latchNibble=false; + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + latchIns=-2; + } + if (ImGui::IsItemHovered()) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); + ImGui::SetTooltip("&&: selected instrument\n..: no instrument"); + ImGui::PopStyleColor(); } ImGui::PopStyleColor(); ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_VOLUME_MAX]); - if (ImGui::Selectable(opMaskVol?"7F##opMaskVol":"--##opMaskVol",opMaskVol,ImGuiSelectableFlags_DontClosePopups)) { - opMaskVol=!opMaskVol; + if (latchVol==-1) { + strcpy(id,"..##LatchVol"); + } else { + snprintf(id,63,"%.2x##LatchVol",latchVol&0xff); + } + if (ImGui::Selectable(id,latchTarget==2,ImGuiSelectableFlags_DontClosePopups)) { + latchTarget=2; + latchNibble=false; + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + latchVol=-1; } ImGui::PopStyleColor(); ImGui::TableNextColumn(); - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_PITCH]); - if (ImGui::Selectable(opMaskEffect?"04##opMaskEffect":"--##opMaskEffect",opMaskEffect,ImGuiSelectableFlags_DontClosePopups)) { - opMaskEffect=!opMaskEffect; + if (latchEffect==-1) { + strcpy(id,"..##LatchFX"); + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INACTIVE]); + } else { + const unsigned char data=latchEffect; + snprintf(id,63,"%.2x##LatchFX",data); + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[fxColors[data]]); + } + + if (ImGui::Selectable(id,latchTarget==3,ImGuiSelectableFlags_DontClosePopups)) { + latchTarget=3; + latchNibble=false; + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + latchEffect=-1; } ImGui::TableNextColumn(); - if (ImGui::Selectable(opMaskEffectVal?"72##opMaskEffectVal":"--##opMaskEffectVal",opMaskEffectVal,ImGuiSelectableFlags_DontClosePopups)) { - opMaskEffectVal=!opMaskEffectVal; + if (latchEffectVal==-1) { + strcpy(id,"..##LatchFXV"); + } else { + snprintf(id,63,"%.2x##LatchFXV",latchEffectVal&0xff); + } + if (ImGui::Selectable(id,latchTarget==4,ImGuiSelectableFlags_DontClosePopups)) { + latchTarget=4; + latchNibble=false; + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + latchEffectVal=-1; } ImGui::PopStyleColor(); ImGui::EndTable(); } ImGui::PopFont(); - - ImGui::Text("input latch"); - if (ImGui::MenuItem("set latch",BIND_FOR(GUI_ACTION_PAT_LATCH))) { - // TODO + ImGui::SameLine(); + if (ImGui::Button("Set")) { + DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true); + latchIns=pat->data[cursor.y][2]; + latchVol=pat->data[cursor.y][3]; + latchEffect=pat->data[cursor.y][4]; + latchEffectVal=pat->data[cursor.y][5]; + latchTarget=0; + latchNibble=false; + } + ImGui::SameLine(); + if (ImGui::Button("Reset")) { + latchIns=-2; + latchVol=-1; + latchEffect=-1; + latchEffectVal=-1; + latchTarget=0; + latchNibble=false; } ImGui::Separator(); - if (ImGui::MenuItem("note up",BIND_FOR(GUI_ACTION_PAT_NOTE_UP))) doTranspose(1); - if (ImGui::MenuItem("note down",BIND_FOR(GUI_ACTION_PAT_NOTE_DOWN))) doTranspose(-1); - if (ImGui::MenuItem("octave up",BIND_FOR(GUI_ACTION_PAT_OCTAVE_UP))) doTranspose(12); - if (ImGui::MenuItem("octave down",BIND_FOR(GUI_ACTION_PAT_OCTAVE_DOWN))) doTranspose(-12); + if (ImGui::MenuItem("note up",BIND_FOR(GUI_ACTION_PAT_NOTE_UP))) doTranspose(1,opMaskTransposeNote); + if (ImGui::MenuItem("note down",BIND_FOR(GUI_ACTION_PAT_NOTE_DOWN))) doTranspose(-1,opMaskTransposeNote); + if (ImGui::MenuItem("octave up",BIND_FOR(GUI_ACTION_PAT_OCTAVE_UP))) doTranspose(12,opMaskTransposeNote); + if (ImGui::MenuItem("octave down",BIND_FOR(GUI_ACTION_PAT_OCTAVE_DOWN))) doTranspose(-12,opMaskTransposeNote); + ImGui::Separator(); + if (ImGui::MenuItem("values up",BIND_FOR(GUI_ACTION_PAT_VALUE_UP))) doTranspose(1,opMaskTransposeValue); + if (ImGui::MenuItem("values down",BIND_FOR(GUI_ACTION_PAT_VALUE_DOWN))) doTranspose(-1,opMaskTransposeValue); + if (ImGui::MenuItem("values up (+16)",BIND_FOR(GUI_ACTION_PAT_VALUE_UP_COARSE))) doTranspose(16,opMaskTransposeValue); + if (ImGui::MenuItem("values down (-16)",BIND_FOR(GUI_ACTION_PAT_VALUE_DOWN_COARSE))) doTranspose(-16,opMaskTransposeValue); + ImGui::Separator(); + ImGui::Text("transpose"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(120.0f*dpiScale); if (ImGui::InputInt("##TransposeAmount",&transposeAmount,1,1)) { if (transposeAmount<-96) transposeAmount=-96; if (transposeAmount>96) transposeAmount=96; } ImGui::SameLine(); - if (ImGui::Button("Transpose")) { - doTranspose(transposeAmount); + if (ImGui::Button("Notes")) { + doTranspose(transposeAmount,opMaskTransposeNote); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Values")) { + doTranspose(transposeAmount,opMaskTransposeValue); ImGui::CloseCurrentPopup(); } @@ -2021,10 +2206,100 @@ void FurnaceGUI::editOptions(bool topMenu) { } } +int _processEvent(void* instance, SDL_Event* event) { + return ((FurnaceGUI*)instance)->processEvent(event); +} + +int FurnaceGUI::processEvent(SDL_Event* ev) { + if (ev->type==SDL_KEYDOWN) { + if (!ev->key.repeat && latchTarget==0 && !wantCaptureKeyboard && (ev->key.keysym.mod&(~(KMOD_NUM|KMOD_CAPS|KMOD_SCROLL)))==0) { + if (settings.notePreviewBehavior==0) return 1; + switch (curWindow) { + case GUI_WINDOW_SAMPLE_EDIT: + case GUI_WINDOW_SAMPLE_LIST: + try { + int key=noteKeys.at(ev->key.keysym.scancode); + int num=12*curOctave+key; + if (key!=100 && key!=101 && key!=102) { + e->previewSample(curSample,num); + samplePreviewOn=true; + samplePreviewKey=ev->key.keysym.scancode; + samplePreviewNote=num; + } + } catch (std::out_of_range& e) { + } + break; + case GUI_WINDOW_WAVE_LIST: + case GUI_WINDOW_WAVE_EDIT: + try { + int key=noteKeys.at(ev->key.keysym.scancode); + int num=12*curOctave+key; + if (key!=100 && key!=101 && key!=102) { + e->previewWave(curWave,num); + wavePreviewOn=true; + wavePreviewKey=ev->key.keysym.scancode; + wavePreviewNote=num; + } + } catch (std::out_of_range& e) { + } + break; + case GUI_WINDOW_ORDERS: // ignore here + break; + case GUI_WINDOW_PATTERN: + if (settings.notePreviewBehavior==1) { + if (cursor.xFine!=0) break; + } else if (settings.notePreviewBehavior==2) { + if (edit && cursor.xFine!=0) break; + } + // fall-through + default: + try { + int key=noteKeys.at(ev->key.keysym.scancode); + int num=12*curOctave+key; + + if (num<-60) num=-60; // C-(-5) + if (num>119) num=119; // B-9 + + if (key!=100 && key!=101 && key!=102) { + previewNote(cursor.xCoarse,num); + } + } catch (std::out_of_range& e) { + } + break; + } + } + } else if (ev->type==SDL_KEYUP) { + stopPreviewNote(ev->key.keysym.scancode,true); + if (wavePreviewOn) { + if (ev->key.keysym.scancode==wavePreviewKey) { + wavePreviewOn=false; + e->stopWavePreview(); + } + } + if (samplePreviewOn) { + if (ev->key.keysym.scancode==samplePreviewKey) { + samplePreviewOn=false; + e->stopSamplePreview(); + } + } + } + return 1; +} + bool FurnaceGUI::loop() { + SDL_SetEventFilter(_processEvent,this); + while (!quit) { SDL_Event ev; + if (e->isPlaying()) { + WAKE_UP; + } + if (--drawHalt<=0) { + drawHalt=0; + if (settings.powerSave) SDL_WaitEventTimeout(NULL,500); + } while (SDL_PollEvent(&ev)) { + WAKE_UP; ImGui_ImplSDL2_ProcessEvent(&ev); switch (ev.type) { case SDL_MOUSEMOTION: { @@ -2094,7 +2369,9 @@ bool FurnaceGUI::loop() { demandScrollX=true; if (cursor.xCoarse==selStart.xCoarse && cursor.xFine==selStart.xFine && cursor.y==selStart.y && cursor.xCoarse==selEnd.xCoarse && cursor.xFine==selEnd.xFine && cursor.y==selEnd.y) { - updateScroll(cursor.y); + if (!settings.cursorMoveNoScroll) { + updateScroll(cursor.y); + } } } break; @@ -2131,29 +2408,13 @@ bool FurnaceGUI::loop() { } break; case SDL_KEYUP: - if (!ImGui::GetIO().WantCaptureKeyboard) { - keyUp(ev); - } else { - stopPreviewNote(ev.key.keysym.scancode); - if (wavePreviewOn) { - if (ev.key.keysym.scancode==wavePreviewKey) { - wavePreviewOn=false; - e->stopWavePreview(); - } - } - if (samplePreviewOn) { - if (ev.key.keysym.scancode==samplePreviewKey) { - samplePreviewOn=false; - e->stopSamplePreview(); - } - } - } + // for now break; case SDL_DROPFILE: if (ev.drop.file!=NULL) { if (modified) { nextFile=ev.drop.file; - showWarning("Unsaved changes! Are you sure?",GUI_WARN_OPEN_DROP); + showWarning("Unsaved changes! Save changes before opening file?",GUI_WARN_OPEN_DROP); } else { if (load(ev.drop.file)>0) { showError(fmt::sprintf("Error while loading file! (%s)",lastError)); @@ -2164,7 +2425,7 @@ bool FurnaceGUI::loop() { break; case SDL_QUIT: if (modified) { - showWarning("Unsaved changes! Are you sure you want to quit?",GUI_WARN_QUIT); + showWarning("Unsaved changes! Save changes before quitting?",GUI_WARN_QUIT); } else { quit=true; return true; @@ -2172,6 +2433,15 @@ bool FurnaceGUI::loop() { break; } } + + wantCaptureKeyboard=ImGui::GetIO().WantTextInput; + + if (wantCaptureKeyboard) { + WAKE_UP; + } + if (ImGui::GetIO().MouseDown[0] || ImGui::GetIO().MouseDown[1] || ImGui::GetIO().MouseDown[2] || ImGui::GetIO().MouseDown[3] || ImGui::GetIO().MouseDown[4]) { + WAKE_UP; + } while (true) { midiLock.lock(); @@ -2315,20 +2585,22 @@ bool FurnaceGUI::loop() { ImGui_ImplSDL2_NewFrame(sdlWin); ImGui::NewFrame(); + curWindowLast=curWindow; curWindow=GUI_WINDOW_NOTHING; + editOptsVisible=false; ImGui::BeginMainMenuBar(); if (ImGui::BeginMenu("file")) { if (ImGui::MenuItem("new...")) { if (modified) { - showWarning("Unsaved changes! Are you sure?",GUI_WARN_NEW); + showWarning("Unsaved changes! Save changes before creating a new song?",GUI_WARN_NEW); } else { displayNew=true; } } if (ImGui::MenuItem("open...",BIND_FOR(GUI_ACTION_OPEN))) { if (modified) { - showWarning("Unsaved changes! Are you sure?",GUI_WARN_OPEN); + showWarning("Unsaved changes! Save changes before opening another file?",GUI_WARN_OPEN); } else { openFileDialog(GUI_FILE_OPEN); } @@ -2409,6 +2681,7 @@ bool FurnaceGUI::loop() { ImGui::Separator(); if (ImGui::BeginMenu("add system...")) { for (int j=0; availableSystems[j]; j++) { + if (!settings.hiddenSystems && (availableSystems[j]==DIV_SYSTEM_YMU759 || availableSystems[j]==DIV_SYSTEM_DUMMY || availableSystems[j]==DIV_SYSTEM_SOUND_UNIT)) continue; sysAddOption((DivSystem)availableSystems[j]); } ImGui::EndMenu(); @@ -2416,16 +2689,18 @@ bool FurnaceGUI::loop() { if (ImGui::BeginMenu("configure system...")) { for (int i=0; isong.systemLen; i++) { if (ImGui::TreeNode(fmt::sprintf("%d. %s##_SYSP%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { - drawSysConf(i); + drawSysConf(i,e->song.system[i],e->song.systemFlags[i],true); ImGui::TreePop(); } } ImGui::EndMenu(); } if (ImGui::BeginMenu("change system...")) { + ImGui::Checkbox("Preserve channel positions",&preserveChanPos); for (int i=0; isong.systemLen; i++) { if (ImGui::BeginMenu(fmt::sprintf("%d. %s##_SYSC%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { for (int j=0; availableSystems[j]; j++) { + if (!settings.hiddenSystems && (availableSystems[j]==DIV_SYSTEM_YMU759 || availableSystems[j]==DIV_SYSTEM_DUMMY || availableSystems[j]==DIV_SYSTEM_SOUND_UNIT)) continue; sysChangeOption(i,(DivSystem)availableSystems[j]); } ImGui::EndMenu(); @@ -2434,9 +2709,10 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } if (ImGui::BeginMenu("remove system...")) { + ImGui::Checkbox("Preserve channel positions",&preserveChanPos); for (int i=0; isong.systemLen; i++) { if (ImGui::MenuItem(fmt::sprintf("%d. %s##_SYSR%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { - if (!e->removeSystem(i)) { + if (!e->removeSystem(i,preserveChanPos)) { showError("cannot remove system! ("+e->getLastError()+")"); } } @@ -2450,7 +2726,7 @@ bool FurnaceGUI::loop() { ImGui::Separator(); if (ImGui::MenuItem("exit")) { if (modified) { - showWarning("Unsaved changes! Are you sure you want to quit?",GUI_WARN_QUIT); + showWarning("Unsaved changes! Save before quitting?",GUI_WARN_QUIT); } else { quit=true; } @@ -2462,11 +2738,16 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("redo",BIND_FOR(GUI_ACTION_REDO))) doRedo(); ImGui::Separator(); editOptions(true); - /*ImGui::Separator(); - ImGui::MenuItem("clear...");*/ + ImGui::Separator(); + if (ImGui::MenuItem("clear...")) { + showWarning("Are you sure you want to clear... (cannot be undone!)",GUI_WARN_CLEAR); + } ImGui::EndMenu(); } if (ImGui::BeginMenu("settings")) { + if (ImGui::MenuItem("full screen",BIND_FOR(GUI_ACTION_FULLSCREEN),fullScreen)) { + doAction(GUI_ACTION_FULLSCREEN); + } if (ImGui::MenuItem("lock layout",NULL,lockLayout)) { lockLayout=!lockLayout; } @@ -2503,7 +2784,8 @@ bool FurnaceGUI::loop() { ImGui::Separator(); if (ImGui::MenuItem("play/edit controls",BIND_FOR(GUI_ACTION_WINDOW_EDIT_CONTROLS),editControlsOpen)) editControlsOpen=!editControlsOpen; if (ImGui::MenuItem("piano/input pad",BIND_FOR(GUI_ACTION_WINDOW_PIANO),pianoOpen)) pianoOpen=!pianoOpen; - if (ImGui::MenuItem("oscilloscope",BIND_FOR(GUI_ACTION_WINDOW_OSCILLOSCOPE),oscOpen)) oscOpen=!oscOpen; + if (ImGui::MenuItem("oscilloscope (master)",BIND_FOR(GUI_ACTION_WINDOW_OSCILLOSCOPE),oscOpen)) oscOpen=!oscOpen; + if (ImGui::MenuItem("oscilloscope (per-channel)",BIND_FOR(GUI_ACTION_WINDOW_CHAN_OSC),chanOscOpen)) chanOscOpen=!chanOscOpen; if (ImGui::MenuItem("volume meter",BIND_FOR(GUI_ACTION_WINDOW_VOL_METER),volMeterOpen)) volMeterOpen=!volMeterOpen; if (ImGui::MenuItem("register view",BIND_FOR(GUI_ACTION_WINDOW_REGISTER_VIEW),regViewOpen)) regViewOpen=!regViewOpen; if (ImGui::MenuItem("log viewer",BIND_FOR(GUI_ACTION_WINDOW_LOG),logOpen)) logOpen=!logOpen; @@ -2512,6 +2794,7 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } if (ImGui::BeginMenu("help")) { + if (ImGui::MenuItem("effect list",BIND_FOR(GUI_ACTION_WINDOW_EFFECT_LIST),effectListOpen)) effectListOpen=!effectListOpen; if (ImGui::MenuItem("debug menu",BIND_FOR(GUI_ACTION_WINDOW_DEBUG))) debugOpen=!debugOpen; if (ImGui::MenuItem("panic",BIND_FOR(GUI_ACTION_PANIC))) e->syncReset(); if (ImGui::MenuItem("about...",BIND_FOR(GUI_ACTION_WINDOW_ABOUT))) { @@ -2529,7 +2812,7 @@ bool FurnaceGUI::loop() { bool hasInfo=false; String info; if (cursor.xCoarse>=0 && cursor.xCoarsegetTotalChannelCount()) { - DivPattern* p=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],false); + DivPattern* p=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],false); if (cursor.xFine>=0) switch (cursor.xFine) { case 0: // note if (p->data[cursor.y][0]>0) { @@ -2570,7 +2853,7 @@ bool FurnaceGUI::loop() { default: // effect int actualCursor=((cursor.xFine+1)&(~1)); if (p->data[cursor.y][actualCursor]>-1) { - info=e->getEffectDesc(p->data[cursor.y][actualCursor],cursor.xCoarse); + info=e->getEffectDesc(p->data[cursor.y][actualCursor],cursor.xCoarse,true); hasInfo=true; } break; @@ -2605,6 +2888,7 @@ bool FurnaceGUI::loop() { readOsc(); drawOsc(); + drawChanOsc(); drawVolMeter(); drawSettings(); drawDebug(); @@ -2615,6 +2899,7 @@ bool FurnaceGUI::loop() { drawChannels(); drawRegView(); drawLog(); + drawEffectList(); if (inspectorOpen) ImGui::ShowMetricsWindow(&inspectorOpen); @@ -2626,8 +2911,34 @@ bool FurnaceGUI::loop() { #endif } + if (fileDialog->isOpen() && settings.sysFileDialog) { + ImGui::OpenPopup("System File Dialog Pending"); + } + + if (ImGui::BeginPopupModal("System File Dialog Pending",NULL,ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoBackground|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove)) { + if (!fileDialog->isOpen()) { + ImGui::CloseCurrentPopup(); + } + ImDrawList* dl=ImGui::GetForegroundDrawList(); + dl->AddRectFilled(ImVec2(0.0f,0.0f),ImVec2(scrW*dpiScale,scrH*dpiScale),ImGui::ColorConvertFloat4ToU32(uiColors[GUI_COLOR_MODAL_BACKDROP])); + ImGui::EndPopup(); + } + if (fileDialog->render(ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale))) { + bool openOpen=false; //ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NavEnableKeyboard; + if ((curFileDialog==GUI_FILE_INS_OPEN || curFileDialog==GUI_FILE_INS_OPEN_REPLACE) && prevIns!=-3) { + if (curFileDialog==GUI_FILE_INS_OPEN_REPLACE) { + if (prevInsData!=NULL) { + if (prevIns>=0 && prevIns<(int)e->song.ins.size()) { + *e->song.ins[prevIns]=*prevInsData; + } + } + } else { + curIns=prevIns; + } + prevIns=-3; + } switch (curFileDialog) { case GUI_FILE_OPEN: case GUI_FILE_SAVE: @@ -2635,6 +2946,7 @@ bool FurnaceGUI::loop() { workingDirSong=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_INS_OPEN: + case GUI_FILE_INS_OPEN_REPLACE: case GUI_FILE_INS_SAVE: workingDirIns=fileDialog->getPath()+DIR_SEPARATOR_STR; break; @@ -2720,15 +3032,47 @@ bool FurnaceGUI::loop() { for (char& i: lowerCase) { if (i>='A' && i<='Z') i+='a'-'A'; } + bool saveWasSuccessful=true; if ((lowerCase.size()<4 || lowerCase.rfind(".dmf")!=lowerCase.size()-4)) { if (save(copyOfName,0)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); + saveWasSuccessful=false; } } else { if (save(copyOfName,26)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); + saveWasSuccessful=false; } } + if (saveWasSuccessful && postWarnAction!=GUI_WARN_GENERIC) { + switch (postWarnAction) { + case GUI_WARN_QUIT: + quit=true; + break; + case GUI_WARN_NEW: + displayNew=true; + break; + case GUI_WARN_OPEN: + openOpen=true; + break; + case GUI_WARN_OPEN_DROP: + if (load(nextFile)>0) { + showError(fmt::sprintf("Error while loading file! (%s)",lastError)); + } + nextFile=""; + break; + case GUI_WARN_OPEN_BACKUP: + if (load(backupPath)>0) { + showError("No backup available! (or unable to open it)"); + } + break; + default: + break; + } + postWarnAction=GUI_WARN_GENERIC; + } else if (postWarnAction==GUI_WARN_OPEN_DROP) { + nextFile=""; + } break; } case GUI_FILE_SAVE_DMF_LEGACY: @@ -2782,6 +3126,25 @@ bool FurnaceGUI::loop() { } break; } + case GUI_FILE_INS_OPEN_REPLACE: { + std::vector instruments=e->instrumentFromFile(copyOfName.c_str()); + if (!instruments.empty()) { + if (!e->getWarnings().empty()) { + showWarning(e->getWarnings(),GUI_WARN_GENERIC); + } + if (curIns>=0 && curIns<(int)e->song.ins.size()) { + *e->song.ins[curIns]=*instruments[0]; + } else { + showError("...but you haven't selected an instrument!"); + } + for (DivInstrument* i: instruments) { + delete i; + } + } else { + showError("cannot load instrument! ("+e->getLastError()+")"); + } + break; + } case GUI_FILE_WAVE_OPEN: e->addWaveFromFile(copyOfName.c_str()); MARK_MODIFIED; @@ -2838,6 +3201,11 @@ bool FurnaceGUI::loop() { } } fileDialog->close(); + postWarnAction=GUI_WARN_GENERIC; + + if (openOpen) { + openFileDialog(GUI_FILE_OPEN); + } } if (warnQuit) { @@ -2896,49 +3264,256 @@ bool FurnaceGUI::loop() { if (ImGui::BeginPopupModal("Warning",NULL,ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("%s",warnString.c_str()); - if (ImGui::Button(warnAction==GUI_WARN_GENERIC?"OK":"Yes")) { - ImGui::CloseCurrentPopup(); - switch (warnAction) { - case GUI_WARN_QUIT: + switch (warnAction) { + case GUI_WARN_QUIT: + if (ImGui::Button("Yes")) { + ImGui::CloseCurrentPopup(); + if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { + openFileDialog(GUI_FILE_SAVE); + postWarnAction=GUI_WARN_QUIT; + } else { + if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { + showError(fmt::sprintf("Error while saving file! (%s)",lastError)); + } else { + quit=true; + } + } + } + ImGui::SameLine(); + if (ImGui::Button("No")) { + ImGui::CloseCurrentPopup(); quit=true; - break; - case GUI_WARN_NEW: + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + ImGui::CloseCurrentPopup(); + } + break; + case GUI_WARN_NEW: + if (ImGui::Button("Yes")) { + ImGui::CloseCurrentPopup(); + if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { + openFileDialog(GUI_FILE_SAVE); + postWarnAction=GUI_WARN_NEW; + } else { + if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { + showError(fmt::sprintf("Error while saving file! (%s)",lastError)); + } else { + displayNew=true; + } + } + } + ImGui::SameLine(); + if (ImGui::Button("No")) { + ImGui::CloseCurrentPopup(); displayNew=true; - break; - case GUI_WARN_OPEN: + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + ImGui::CloseCurrentPopup(); + } + break; + case GUI_WARN_OPEN: + if (ImGui::Button("Yes")) { + ImGui::CloseCurrentPopup(); + if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { + openFileDialog(GUI_FILE_SAVE); + postWarnAction=GUI_WARN_OPEN; + } else { + if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { + showError(fmt::sprintf("Error while saving file! (%s)",lastError)); + } else { + openFileDialog(GUI_FILE_OPEN); + } + } + } + ImGui::SameLine(); + if (ImGui::Button("No")) { + ImGui::CloseCurrentPopup(); openFileDialog(GUI_FILE_OPEN); - break; - case GUI_WARN_OPEN_BACKUP: + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + ImGui::CloseCurrentPopup(); + } + break; + case GUI_WARN_OPEN_BACKUP: + if (ImGui::Button("Yes")) { + ImGui::CloseCurrentPopup(); + if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { + openFileDialog(GUI_FILE_SAVE); + postWarnAction=GUI_WARN_OPEN_BACKUP; + } else { + if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { + showError(fmt::sprintf("Error while saving file! (%s)",lastError)); + } else { + if (load(backupPath)>0) { + showError("No backup available! (or unable to open it)"); + } + } + } + } + ImGui::SameLine(); + if (ImGui::Button("No")) { + ImGui::CloseCurrentPopup(); if (load(backupPath)>0) { showError("No backup available! (or unable to open it)"); } - break; - case GUI_WARN_OPEN_DROP: + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + ImGui::CloseCurrentPopup(); + } + break; + case GUI_WARN_OPEN_DROP: + if (ImGui::Button("Yes")) { + ImGui::CloseCurrentPopup(); + if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { + openFileDialog(GUI_FILE_SAVE); + postWarnAction=GUI_WARN_OPEN_DROP; + } else { + if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { + showError(fmt::sprintf("Error while saving file! (%s)",lastError)); + nextFile=""; + } else { + if (load(nextFile)>0) { + showError(fmt::sprintf("Error while loading file! (%s)",lastError)); + } + nextFile=""; + } + } + } + ImGui::SameLine(); + if (ImGui::Button("No")) { + ImGui::CloseCurrentPopup(); if (load(nextFile)>0) { showError(fmt::sprintf("Error while loading file! (%s)",lastError)); } nextFile=""; - break; - case GUI_WARN_RESET_LAYOUT: + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + ImGui::CloseCurrentPopup(); + nextFile=""; + } + break; + case GUI_WARN_RESET_LAYOUT: + if (ImGui::Button("Yes")) { + ImGui::CloseCurrentPopup(); ImGui::LoadIniSettingsFromMemory(defaultLayout); ImGui::SaveIniSettingsToDisk(finalLayoutPath); - break; - case GUI_WARN_RESET_KEYBINDS: + } + ImGui::SameLine(); + if (ImGui::Button("No")) { + ImGui::CloseCurrentPopup(); + } + break; + case GUI_WARN_RESET_KEYBINDS: + if (ImGui::Button("Yes")) { + ImGui::CloseCurrentPopup(); resetKeybinds(); - break; - case GUI_WARN_RESET_COLORS: + } + ImGui::SameLine(); + if (ImGui::Button("No")) { + ImGui::CloseCurrentPopup(); + } + break; + case GUI_WARN_RESET_COLORS: + if (ImGui::Button("Yes")) { + ImGui::CloseCurrentPopup(); resetColors(); applyUISettings(false); - break; - case GUI_WARN_GENERIC: - break; - } - } - if (warnAction!=GUI_WARN_GENERIC) { - ImGui::SameLine(); - if (ImGui::Button("No")) { - ImGui::CloseCurrentPopup(); - } + } + ImGui::SameLine(); + if (ImGui::Button("No")) { + ImGui::CloseCurrentPopup(); + } + break; + case GUI_WARN_CLOSE_SETTINGS: + if (ImGui::Button("Yes")) { + ImGui::CloseCurrentPopup(); + settingsOpen=false; + willCommit=true; + } + ImGui::SameLine(); + if (ImGui::Button("No")) { + ImGui::CloseCurrentPopup(); + settingsOpen=false; + syncSettings(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + ImGui::CloseCurrentPopup(); + } + break; + case GUI_WARN_CLEAR: + if (ImGui::Button("Song (orders and patterns)")) { + stop(); + e->lockEngine([this]() { + e->song.clearSongData(); + }); + e->setOrder(0); + curOrder=0; + oldOrder=0; + oldOrder1=0; + MARK_MODIFIED; + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Pattern")) { + stop(); + e->lockEngine([this]() { + for (int i=0; igetTotalChannelCount(); i++) { + DivPattern* pat=e->song.pat[i].getPattern(e->song.orders.ord[i][curOrder],true); + memset(pat->data,-1,256*32*sizeof(short)); + for (int j=0; j<256; j++) { + pat->data[j][0]=0; + pat->data[j][1]=0; + } + } + }); + MARK_MODIFIED; + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Instruments")) { + stop(); + e->lockEngine([this]() { + e->song.clearInstruments(); + }); + curIns=-1; + MARK_MODIFIED; + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Wavetables")) { + stop(); + e->lockEngine([this]() { + e->song.clearWavetables(); + }); + curWave=0; + MARK_MODIFIED; + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Samples")) { + stop(); + e->lockEngine([this]() { + e->song.clearSamples(); + }); + curSample=0; + ImGui::CloseCurrentPopup(); + } + + if (ImGui::Button("Wait! What am I doing? Cancel!")) { + ImGui::CloseCurrentPopup(); + } + break; + case GUI_WARN_GENERIC: + if (ImGui::Button("OK")) { + ImGui::CloseCurrentPopup(); + } + break; } ImGui::EndPopup(); } @@ -2995,6 +3570,11 @@ bool FurnaceGUI::loop() { willCommit=false; } + if (!editOptsVisible) { + latchTarget=0; + latchNibble=false; + } + if (SDL_GetWindowFlags(sdlWin)&SDL_WINDOW_MINIMIZED) { SDL_Delay(100); } @@ -3033,6 +3613,7 @@ bool FurnaceGUI::init() { settingsOpen=e->getConfBool("settingsOpen",false); mixerOpen=e->getConfBool("mixerOpen",false); oscOpen=e->getConfBool("oscOpen",true); + chanOscOpen=e->getConfBool("chanOscOpen",false); volMeterOpen=e->getConfBool("volMeterOpen",true); statsOpen=e->getConfBool("statsOpen",false); compatFlagsOpen=e->getConfBool("compatFlagsOpen",false); @@ -3041,10 +3622,12 @@ bool FurnaceGUI::init() { channelsOpen=e->getConfBool("channelsOpen",false); regViewOpen=e->getConfBool("regViewOpen",false); logOpen=e->getConfBool("logOpen",false); + effectListOpen=e->getConfBool("effectListOpen",false); tempoView=e->getConfBool("tempoView",true); waveHex=e->getConfBool("waveHex",false); lockLayout=e->getConfBool("lockLayout",false); + fullScreen=e->getConfBool("fullScreen",false); syncSettings(); @@ -3068,7 +3651,7 @@ bool FurnaceGUI::init() { SDL_Init(SDL_INIT_VIDEO); - sdlWin=SDL_CreateWindow("Furnace",SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,scrW*dpiScale,scrH*dpiScale,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI); + sdlWin=SDL_CreateWindow("Furnace",SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,scrW*dpiScale,scrH*dpiScale,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|(fullScreen?SDL_WINDOW_FULLSCREEN_DESKTOP:0)); if (sdlWin==NULL) { logE("could not open window! %s",SDL_GetError()); return false; @@ -3079,12 +3662,22 @@ bool FurnaceGUI::init() { SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(sdlWin),&dpiScaleF,NULL,NULL); dpiScale=round(dpiScaleF/96.0f); if (dpiScale<1) dpiScale=1; - if (dpiScale!=1) SDL_SetWindowSize(sdlWin,scrW*dpiScale,scrH*dpiScale); + if (dpiScale!=1) { + if (!fullScreen) { + SDL_SetWindowSize(sdlWin,scrW*dpiScale,scrH*dpiScale); + } + } if (SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(sdlWin),&displaySize)==0) { + if (scrW>((displaySize.w/dpiScale)-48) && scrH>((displaySize.h/dpiScale)-64)) { + // maximize + SDL_MaximizeWindow(sdlWin); + } if (scrW>displaySize.w/dpiScale) scrW=(displaySize.w/dpiScale)-32; if (scrH>displaySize.h/dpiScale) scrH=(displaySize.h/dpiScale)-32; - SDL_SetWindowSize(sdlWin,scrW*dpiScale,scrH*dpiScale); + if (!fullScreen) { + SDL_SetWindowSize(sdlWin,scrW*dpiScale,scrH*dpiScale); + } } } #endif @@ -3196,6 +3789,7 @@ bool FurnaceGUI::finish() { e->setConf("settingsOpen",settingsOpen); e->setConf("mixerOpen",mixerOpen); e->setConf("oscOpen",oscOpen); + e->setConf("chanOscOpen",chanOscOpen); e->setConf("volMeterOpen",volMeterOpen); e->setConf("statsOpen",statsOpen); e->setConf("compatFlagsOpen",compatFlagsOpen); @@ -3204,6 +3798,7 @@ bool FurnaceGUI::finish() { e->setConf("channelsOpen",channelsOpen); e->setConf("regViewOpen",regViewOpen); e->setConf("logOpen",logOpen); + e->setConf("effectListOpen",effectListOpen); // commit last window size e->setConf("lastWindowWidth",scrW); @@ -3212,6 +3807,7 @@ bool FurnaceGUI::finish() { e->setConf("tempoView",tempoView); e->setConf("waveHex",waveHex); e->setConf("lockLayout",lockLayout); + e->setConf("fullScreen",fullScreen); for (int i=0; i definition; @@ -669,6 +723,34 @@ struct FurnaceGUISysCategory { name(NULL) {} }; +struct FurnaceGUIMacroDesc { + DivInstrumentMacro* macro; + int min, max; + float height; + const char* displayName; + const char** bitfieldBits; + const char* modeName; + ImVec4 color; + unsigned int bitOffset; + bool isBitfield, blockMode; + String (*hoverFunc)(int,float); + + FurnaceGUIMacroDesc(const char* name, DivInstrumentMacro* m, int macroMin, int macroMax, float macroHeight, ImVec4 col=ImVec4(1.0f,1.0f,1.0f,1.0f), bool block=false, const char* mName=NULL, String (*hf)(int,float)=NULL, bool bitfield=false, const char** bfVal=NULL, unsigned int bitOff=0): + macro(m), + min(macroMin), + max(macroMax), + height(macroHeight), + displayName(name), + bitfieldBits(bfVal), + modeName(mName), + color(col), + bitOffset(bitOff), + isBitfield(bitfield), + blockMode(block), + hoverFunc(hf) { + } +}; + class FurnaceGUI { DivEngine* e; @@ -681,16 +763,19 @@ class FurnaceGUI { String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile; String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout; - String mmlString[13]; + String mmlString[17]; String mmlStringW; - bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop; - bool displayNew; + bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard; + bool displayNew, fullScreen, preserveChanPos; bool willExport[32]; int vgmExportVersion; + int drawHalt; + int macroPointSize; FurnaceGUIFileDialogs curFileDialog; FurnaceGUIWarnings warnAction; + FurnaceGUIWarnings postWarnAction; FurnaceGUIFileDialog* fileDialog; @@ -733,6 +818,8 @@ class FurnaceGUI { int arcadeCore; int ym2612Core; int saaCore; + int nesCore; + int fdsCore; int mainFont; int patFont; int audioRate; @@ -772,6 +859,8 @@ class FurnaceGUI { int roundedMenus; int loadJapanese; int fmLayout; + int sampleLayout; + int waveLayout; int susPosition; int effectCursorDir; int cursorPastePos; @@ -782,12 +871,30 @@ class FurnaceGUI { int oscRoundedCorners; int oscTakesEntireWindow; int oscBorder; + int separateFMColors; + int insEditColorize; + int metroVol; + int pushNibble; + int scrollChangesOrder; + int oplStandardWaveNames; + int cursorMoveNoScroll; + int lowLatency; + int notePreviewBehavior; + int powerSave; + int absorbInsInput; + int eventDelay; + int moveWindowTitle; + int hiddenSystems; + int insLoadAlwaysReplace; + int horizontalDataView; + int noMultiSystem; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; String audioDevice; String midiInDevice; String midiOutDevice; + std::vector initialSys; Settings(): mainFontSize(18), @@ -798,6 +905,8 @@ class FurnaceGUI { arcadeCore(0), ym2612Core(0), saaCore(1), + nesCore(0), + fdsCore(0), mainFont(0), patFont(0), audioRate(44100), @@ -835,6 +944,8 @@ class FurnaceGUI { roundedMenus(0), loadJapanese(0), fmLayout(0), + sampleLayout(0), + waveLayout(0), susPosition(0), effectCursorDir(1), cursorPastePos(1), @@ -845,6 +956,23 @@ class FurnaceGUI { oscRoundedCorners(1), oscTakesEntireWindow(0), oscBorder(1), + separateFMColors(0), + insEditColorize(0), + metroVol(100), + pushNibble(0), + scrollChangesOrder(0), + oplStandardWaveNames(0), + cursorMoveNoScroll(0), + lowLatency(0), + notePreviewBehavior(1), + powerSave(1), + absorbInsInput(0), + eventDelay(0), + moveWindowTitle(0), + hiddenSystems(0), + insLoadAlwaysReplace(1), + horizontalDataView(0), + noMultiSystem(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), @@ -855,32 +983,36 @@ class FurnaceGUI { char finalLayoutPath[4096]; - int curIns, curWave, curSample, curOctave, oldRow, oldOrder, oldOrder1, editStep, exportLoops, soloChan, soloTimeout, orderEditMode, orderCursor; - int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, patNameTarget, newSongCategory; + DivInstrument* prevInsData; + + int curIns, curWave, curSample, curOctave, curOrder, prevIns, oldRow, oldOrder, oldOrder1, editStep, exportLoops, soloChan, soloTimeout, orderEditMode, orderCursor; + int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, patNameTarget, newSongCategory, latchTarget; int wheelX, wheelY; bool editControlsOpen, ordersOpen, insListOpen, songInfoOpen, patternOpen, insEditOpen; bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen; bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; - bool pianoOpen, notesOpen, channelsOpen, regViewOpen, logOpen; + bool pianoOpen, notesOpen, channelsOpen, regViewOpen, logOpen, effectListOpen, chanOscOpen; /* there ought to be a better way... bool editControlsDocked, ordersDocked, insListDocked, songInfoDocked, patternDocked, insEditDocked; bool waveListDocked, waveEditDocked, sampleListDocked, sampleEditDocked, aboutDocked, settingsDocked; bool mixerDocked, debugDocked, inspectorDocked, oscDocked, volMeterDocked, statsDocked, compatFlagsDocked; - bool pianoDocked, notesDocked, channelsDocked, regViewDocked; + bool pianoDocked, notesDocked, channelsDocked, regViewDocked, logDocked, effectListDocked, chanOscDocked; */ SelectionPoint selStart, selEnd, cursor; bool selecting, curNibble, orderNibble, followOrders, followPattern, changeAllOrders; - bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, lockLayout; - FurnaceGUIWindows curWindow, nextWindow; + bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, lockLayout, editOptsVisible, latchNibble, nonLatchNibble; + FurnaceGUIWindows curWindow, nextWindow, curWindowLast; float peak[2]; float patChanX[DIV_MAX_CHANS+1]; float patChanSlideY[DIV_MAX_CHANS+1]; const int* nextDesc; - bool opMaskNote, opMaskIns, opMaskVol, opMaskEffect, opMaskEffectVal; + OperationMask opMaskDelete, opMaskPullDelete, opMaskInsert, opMaskPaste, opMaskTransposeNote, opMaskTransposeValue; + OperationMask opMaskInterpolate, opMaskFade, opMaskInvertVal, opMaskScale; + OperationMask opMaskRandomize, opMaskFlip, opMaskCollapseExpand; short latchNote, latchIns, latchVol, latchEffect, latchEffectVal; // bit 31: ctrl @@ -912,6 +1044,7 @@ class FurnaceGUI { std::vector activeNotes; std::vector cmdStream; std::vector particles; + std::vector pendingIns; std::vector sysCategories; @@ -929,6 +1062,7 @@ class FurnaceGUI { std::map valueKeys; int arpMacroScroll; + int pitchMacroScroll; ImVec2 macroDragStart; ImVec2 macroDragAreaSize; @@ -943,7 +1077,8 @@ class FurnaceGUI { bool macroDragInitialValueSet; bool macroDragInitialValue; bool macroDragChar; - bool macroDragLineMode; // TODO + bool macroDragLineMode; + ImVec2 macroDragLineInitial; bool macroDragActive; ImVec2 macroLoopDragStart; @@ -972,7 +1107,7 @@ class FurnaceGUI { int dummyRows, demandX; int transposeAmount, randomizeMin, randomizeMax, fadeMin, fadeMax; float scaleMax; - bool fadeMode, randomMode; + bool fadeMode, randomMode, haveHitBounds, pendingStepUpdate; int oldOrdersLen; DivOrders oldOrders; @@ -1006,6 +1141,15 @@ class FurnaceGUI { float oscZoom; bool oscZoomSlider; + // per-channel oscilloscope + int chanOscCols; + float chanOscWindowSize; + bool chanOscWaveCorr; + float chanOscLP0[DIV_MAX_CHANS]; + float chanOscLP1[DIV_MAX_CHANS]; + unsigned short lastNeedlePos[DIV_MAX_CHANS]; + unsigned short lastCorrPos[DIV_MAX_CHANS]; + // visualizer float keyHit[DIV_MAX_CHANS]; int lastIns[DIV_MAX_CHANS]; @@ -1013,11 +1157,18 @@ class FurnaceGUI { // log window bool followLog; + // piano + int pianoOctaves; + bool pianoOptions; + float pianoKeyHit[180]; + int pianoOffset; + void drawSSGEnv(unsigned char type, const ImVec2& size); void drawWaveform(unsigned char type, bool opz, const ImVec2& size); void drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size); - void drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, float maxTl, float maxArDr, const ImVec2& size); - void drawSysConf(int i); + void drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, unsigned char sus, unsigned char egt, unsigned char algOrGlobalSus, float maxTl, float maxArDr, const ImVec2& size, unsigned short instType); + void drawGBEnv(unsigned char vol, unsigned char len, unsigned char sLen, bool dir, const ImVec2& size); + void drawSysConf(int chan, DivSystem type, unsigned int& flags, bool modifyOnChange); // these ones offer ctrl-wheel fine value changes. bool CWSliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format=NULL, ImGuiSliderFlags flags=0); @@ -1031,9 +1182,14 @@ class FurnaceGUI { void readOsc(); + void pushAccentColors(const ImVec4& one, const ImVec4& two, const ImVec4& border, const ImVec4& borderShadow); + void popAccentColors(); + float calcBPM(int s1, int s2, float hz); - void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache); + void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache, bool inhibitSel); + + void drawMacros(std::vector& macros); void actualWaveList(); void actualSampleList(); @@ -1050,6 +1206,7 @@ class FurnaceGUI { void drawSampleEdit(); void drawMixer(); void drawOsc(); + void drawChanOsc(); void drawVolMeter(); void drawStats(); void drawCompatFlags(); @@ -1062,6 +1219,7 @@ class FurnaceGUI { void drawDebug(); void drawNewSong(); void drawLog(); + void drawEffectList(); void parseKeybinds(); void promptKey(int which); @@ -1097,7 +1255,7 @@ class FurnaceGUI { void doDelete(); void doPullDelete(); void doInsert(); - void doTranspose(int amount); + void doTranspose(int amount, OperationMask& mask); void doCopy(bool cut); void doPaste(PasteMode mode=GUI_PASTE_MODE_NORMAL); void doChangeIns(int ins); @@ -1119,6 +1277,7 @@ class FurnaceGUI { void doRedoSample(); void play(int row=0); + void setOrder(unsigned char order, bool forced=false); void stop(); void previewNote(int refChan, int note, bool autoNote=false); @@ -1156,6 +1315,7 @@ class FurnaceGUI { void addScroll(int amount); void setFileName(String name); void runBackupThread(); + int processEvent(SDL_Event* ev); bool loop(); bool finish(); bool init(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 1b292638c..3cbd82046 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -18,8 +18,8 @@ */ // guiConst: constants used in the GUI like arrays, strings and other stuff -#include "guiConst.h" #include "gui.h" +#include "guiConst.h" #include "../engine/song.h" const int opOrder[4]={ @@ -80,11 +80,11 @@ const int vgmVersions[6]={ }; const char* insTypes[DIV_INS_MAX]={ - "Standard", + "Standard (SMS/NES)", "FM (4-operator)", "Game Boy", "C64", - "Amiga/Sample", + "Sample", "PC Engine", "AY-3-8910/SSG", "AY8930", @@ -106,7 +106,11 @@ const char* insTypes[DIV_INS_MAX]={ "Atari Lynx", "VERA", "X1-010", - "VRC6 (saw)" + "VRC6 (saw)", + "ES5506", + "MultiPCM", + "SNES", + "Sound Unit", }; const char* sampleDepths[17]={ @@ -138,6 +142,293 @@ const char* resampleStrats[]={ "best possible" }; +const FurnaceGUIColors fxColors[256]={ + GUI_COLOR_PATTERN_EFFECT_MISC, // 00 + GUI_COLOR_PATTERN_EFFECT_PITCH, // 01 + GUI_COLOR_PATTERN_EFFECT_PITCH, // 02 + GUI_COLOR_PATTERN_EFFECT_PITCH, // 03 + GUI_COLOR_PATTERN_EFFECT_PITCH, // 04 + GUI_COLOR_PATTERN_EFFECT_VOLUME, // 05 + GUI_COLOR_PATTERN_EFFECT_VOLUME, // 06 + GUI_COLOR_PATTERN_EFFECT_VOLUME, // 07 + GUI_COLOR_PATTERN_EFFECT_PANNING, // 08 + GUI_COLOR_PATTERN_EFFECT_SPEED, // 09 + GUI_COLOR_PATTERN_EFFECT_VOLUME, // 0A + GUI_COLOR_PATTERN_EFFECT_SONG, // 0B + GUI_COLOR_PATTERN_EFFECT_TIME, // 0C + GUI_COLOR_PATTERN_EFFECT_SONG, // 0D + GUI_COLOR_PATTERN_EFFECT_INVALID, // 0E + GUI_COLOR_PATTERN_EFFECT_SPEED, // 0F + + // 10-1F + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + + // 20-2F + GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, + GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, + GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, + GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, + GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, + GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, + GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, + GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, + GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, + GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, + GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, + GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, + GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, + GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, + GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, + GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, + + // 30-3F + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + + // 40-4F + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + + // 50-5F + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, + + // 60-6F + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + + // 70-7F + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + + // 80-8F + GUI_COLOR_PATTERN_EFFECT_PANNING, + GUI_COLOR_PATTERN_EFFECT_PANNING, + GUI_COLOR_PATTERN_EFFECT_PANNING, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + + // 90-9F + GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_PATTERN_EFFECT_MISC, + + // A0-AF + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + + // B0-BF + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + + // C0-CF + GUI_COLOR_PATTERN_EFFECT_SPEED, + GUI_COLOR_PATTERN_EFFECT_SPEED, + GUI_COLOR_PATTERN_EFFECT_SPEED, + GUI_COLOR_PATTERN_EFFECT_SPEED, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + + // D0-DF + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_INVALID, + + // E0-FF extended effects + GUI_COLOR_PATTERN_EFFECT_MISC, // E0 + GUI_COLOR_PATTERN_EFFECT_PITCH, // E1 + GUI_COLOR_PATTERN_EFFECT_PITCH, // E2 + GUI_COLOR_PATTERN_EFFECT_MISC, // E3 + GUI_COLOR_PATTERN_EFFECT_MISC, // E4 + GUI_COLOR_PATTERN_EFFECT_PITCH, // E5 + GUI_COLOR_PATTERN_EFFECT_INVALID, // E6 + GUI_COLOR_PATTERN_EFFECT_INVALID, // E7 + GUI_COLOR_PATTERN_EFFECT_INVALID, // E8 + GUI_COLOR_PATTERN_EFFECT_INVALID, // E9 + GUI_COLOR_PATTERN_EFFECT_MISC, // EA + GUI_COLOR_PATTERN_EFFECT_MISC, // EB + GUI_COLOR_PATTERN_EFFECT_TIME, // EC + GUI_COLOR_PATTERN_EFFECT_TIME, // ED + GUI_COLOR_PATTERN_EFFECT_SONG, // EE + GUI_COLOR_PATTERN_EFFECT_SONG, // EF + GUI_COLOR_PATTERN_EFFECT_SPEED, // F0 + GUI_COLOR_PATTERN_EFFECT_PITCH, // F1 + GUI_COLOR_PATTERN_EFFECT_PITCH, // F2 + GUI_COLOR_PATTERN_EFFECT_VOLUME, // F3 + GUI_COLOR_PATTERN_EFFECT_VOLUME, // F4 + GUI_COLOR_PATTERN_EFFECT_INVALID, // F5 + GUI_COLOR_PATTERN_EFFECT_INVALID, // F6 + GUI_COLOR_PATTERN_EFFECT_INVALID, // F7 + GUI_COLOR_PATTERN_EFFECT_VOLUME, // F8 + GUI_COLOR_PATTERN_EFFECT_VOLUME, // F9 + GUI_COLOR_PATTERN_EFFECT_VOLUME, // FA + GUI_COLOR_PATTERN_EFFECT_INVALID, // FB + GUI_COLOR_PATTERN_EFFECT_INVALID, // FC + GUI_COLOR_PATTERN_EFFECT_INVALID, // FD + GUI_COLOR_PATTERN_EFFECT_INVALID, // FE + GUI_COLOR_PATTERN_EFFECT_SONG // FF +}; + #define D FurnaceGUIActionDef #define NOT_AN_ACTION -1 @@ -167,6 +458,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("REPEAT_PATTERN", "Toggle repeat pattern", 0), D("FOLLOW_ORDERS", "Follow orders", 0), D("FOLLOW_PATTERN", "Follow pattern", 0), + D("FULLSCREEN", "Toggle full-screen", SDLK_F11), D("PANIC", "Panic", SDLK_F12), D("WINDOW_EDIT_CONTROLS", "Edit Controls", 0), @@ -183,7 +475,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WINDOW_SETTINGS", "Settings", 0), D("WINDOW_MIXER", "Mixer", 0), D("WINDOW_DEBUG", "Debug Menu", 0), - D("WINDOW_OSCILLOSCOPE", "Oscilloscope", 0), + D("WINDOW_OSCILLOSCOPE", "Oscilloscope (master)", 0), D("WINDOW_VOL_METER", "Volume Meter", 0), D("WINDOW_STATS", "Statistics", 0), D("WINDOW_COMPAT_FLAGS", "Compatibility Flags", 0), @@ -192,6 +484,8 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WINDOW_CHANNELS", "Channels", 0), D("WINDOW_REGISTER_VIEW", "Register View", 0), D("WINDOW_LOG", "Log Viewer", 0), + D("EFFECT_LIST", "Effect List", 0), + D("WINDOW_CHAN_OSC", "Oscilloscope (per-channel)", 0), D("COLLAPSE_WINDOW", "Collapse/expand current window", 0), D("CLOSE_WINDOW", "Close current window", FURKMOD_SHIFT|SDLK_ESCAPE), @@ -202,6 +496,10 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("PAT_NOTE_DOWN", "Transpose (-1)", FURKMOD_CMD|SDLK_F1), D("PAT_OCTAVE_UP", "Transpose (+1 octave)", FURKMOD_CMD|SDLK_F4), D("PAT_OCTAVE_DOWN", "Transpose (-1 octave)", FURKMOD_CMD|SDLK_F3), + D("PAT_VALUE_UP", "Increase values (+1)", FURKMOD_CMD|FURKMOD_SHIFT|SDLK_F2), + D("PAT_VALUE_DOWN", "Increase values (-1)", FURKMOD_CMD|FURKMOD_SHIFT|SDLK_F1), + D("PAT_VALUE_UP_COARSE", "Increase values (+16)", FURKMOD_CMD|FURKMOD_SHIFT|SDLK_F4), + D("PAT_VALUE_DOWN_COARSE", "Increase values (-16)", FURKMOD_CMD|FURKMOD_SHIFT|SDLK_F3), D("PAT_SELECT_ALL", "Select all", FURKMOD_CMD|SDLK_a), D("PAT_CUT", "Cut", FURKMOD_CMD|SDLK_x), D("PAT_COPY", "Copy", FURKMOD_CMD|SDLK_c), @@ -262,6 +560,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("INS_LIST_ADD", "Add", SDLK_INSERT), D("INS_LIST_DUPLICATE", "Duplicate", FURKMOD_CMD|SDLK_d), D("INS_LIST_OPEN", "Open", 0), + D("INS_LIST_OPEN_REPLACE", "Open (replace current)", 0), D("INS_LIST_SAVE", "Save", 0), D("INS_LIST_MOVE_UP", "Move up", FURKMOD_SHIFT|SDLK_UP), D("INS_LIST_MOVE_DOWN", "Move down", FURKMOD_SHIFT|SDLK_DOWN), @@ -327,6 +626,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("SAMPLE_ZOOM_IN", "Zoom in", FURKMOD_CMD|SDLK_EQUALS), D("SAMPLE_ZOOM_OUT", "Zoom out", FURKMOD_CMD|SDLK_MINUS), D("SAMPLE_ZOOM_AUTO", "Toggle auto-zoom", FURKMOD_CMD|SDLK_0), + D("SAMPLE_MAKE_INS", "Create instrument from sample", 0), D("SAMPLE_MAX", "", NOT_AN_ACTION), D("ORDERS_MIN", "---Orders", NOT_AN_ACTION), @@ -385,8 +685,8 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_OSC_BORDER,"",ImVec4(0.4f,0.6f,0.95f,1.0f)), D(GUI_COLOR_OSC_WAVE,"",ImVec4(0.95f,0.95f,1.0f,1.0f)), D(GUI_COLOR_OSC_WAVE_PEAK,"",ImVec4(0.95f,0.95f,1.0f,1.0f)), - D(GUI_COLOR_OSC_REF,"",ImVec4(0.3f,0.3f,0.3f,1.0f)), - D(GUI_COLOR_OSC_GUIDE,"",ImVec4(0.3,0.3f,0.3f,1.0f)), + D(GUI_COLOR_OSC_REF,"",ImVec4(0.3,0.65f,1.0f,0.15f)), + D(GUI_COLOR_OSC_GUIDE,"",ImVec4(0.3,0.65f,1.0f,0.13f)), D(GUI_COLOR_VOLMETER_LOW,"",ImVec4(0.2f,0.6f,0.2f,1.0f)), D(GUI_COLOR_VOLMETER_HIGH,"",ImVec4(1.0f,0.9f,0.2f,1.0f)), @@ -397,13 +697,32 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_ORDER_SIMILAR,"",ImVec4(0.5f,1.0f,1.0f,1.0f)), D(GUI_COLOR_ORDER_INACTIVE,"",ImVec4(1.0f,1.0f,1.0f,1.0f)), + D(GUI_COLOR_FM_ALG_BG,"",ImVec4(0.12f,0.12f,0.12f,1.0f)), + D(GUI_COLOR_FM_ALG_LINE,"",ImVec4(1.0f,1.0f,1.0f,0.33f)), + D(GUI_COLOR_FM_MOD,"",ImVec4(1.0f,1.0f,1.0f,1.0f)), + D(GUI_COLOR_FM_PRIMARY_MOD,"",ImVec4(0.06f,0.53f,0.98f,1.0f)), + D(GUI_COLOR_FM_SECONDARY_MOD,"",ImVec4(0.06f,0.53f,0.98f,1.0f)), + D(GUI_COLOR_FM_BORDER_MOD,"",ImVec4(0.43f,0.43f,0.5f,0.5f)), + D(GUI_COLOR_FM_BORDER_SHADOW_MOD,"",ImVec4(0.0f,0.0f,0.0f,0.0f)), + D(GUI_COLOR_FM_CAR,"",ImVec4(0.5f,0.8f,1.0f,1.0f)), + D(GUI_COLOR_FM_PRIMARY_CAR,"",ImVec4(0.06f,0.8f,0.98f,1.0f)), + D(GUI_COLOR_FM_SECONDARY_CAR,"",ImVec4(0.06f,0.8f,0.98f,1.0f)), + D(GUI_COLOR_FM_BORDER_CAR,"",ImVec4(0.43f,0.5f,0.5f,0.5f)), + D(GUI_COLOR_FM_BORDER_SHADOW_CAR,"",ImVec4(0.0f,0.0f,0.0f,0.0f)), + + D(GUI_COLOR_FM_ENVELOPE,"",ImVec4(1.0f,1.0f,1.0f,1.0f)), + D(GUI_COLOR_FM_ENVELOPE_SUS_GUIDE,"",ImVec4(0.3f,0.5f,0.8f,0.4f)), + D(GUI_COLOR_FM_ENVELOPE_RELEASE,"",ImVec4(0.3f,0.5f,0.8f,0.4f)), + D(GUI_COLOR_FM_SSG,"",ImVec4(1.0f,1.0f,1.0f,1.0f)), + D(GUI_COLOR_FM_WAVE,"",ImVec4(1.0f,1.0f,1.0f,1.0f)), + D(GUI_COLOR_MACRO_VOLUME,"",ImVec4(0.2f,1.0f,0.0f,1.0f)), D(GUI_COLOR_MACRO_PITCH,"",ImVec4(1.0f,0.8f,0.0f,1.0f)), D(GUI_COLOR_MACRO_OTHER,"",ImVec4(0.0f,0.9f,1.0f,1.0f)), D(GUI_COLOR_MACRO_WAVE,"",ImVec4(1.0f,0.4f,0.0f,1.0f)), - D(GUI_COLOR_INSTR_FM,"",ImVec4(0.6f,0.9f,1.0f,1.0f)), D(GUI_COLOR_INSTR_STD,"",ImVec4(0.6f,1.0f,0.5f,1.0f)), + D(GUI_COLOR_INSTR_FM,"",ImVec4(0.6f,0.9f,1.0f,1.0f)), D(GUI_COLOR_INSTR_GB,"",ImVec4(1.0f,1.0f,0.5f,1.0f)), D(GUI_COLOR_INSTR_C64,"",ImVec4(0.85f,0.8f,1.0f,1.0f)), D(GUI_COLOR_INSTR_AMIGA,"",ImVec4(1.0f,0.5f,0.5f,1.0f)), @@ -415,7 +734,6 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_INSTR_VIC,"",ImVec4(0.2f,1.0f,0.6f,1.0f)), D(GUI_COLOR_INSTR_PET,"",ImVec4(1.0f,1.0f,0.8f,1.0f)), D(GUI_COLOR_INSTR_VRC6,"",ImVec4(1.0f,0.9f,0.5f,1.0f)), - D(GUI_COLOR_INSTR_VRC6_SAW,"",ImVec4(0.8f,0.3f,0.0f,1.0f)), D(GUI_COLOR_INSTR_OPLL,"",ImVec4(0.6f,0.7f,1.0f,1.0f)), D(GUI_COLOR_INSTR_OPL,"",ImVec4(0.3f,1.0f,0.9f,1.0f)), D(GUI_COLOR_INSTR_FDS,"",ImVec4(0.8f,0.5f,1.0f,1.0f)), @@ -429,13 +747,18 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_INSTR_MIKEY,"",ImVec4(0.5f,1.0f,0.3f,1.0f)), D(GUI_COLOR_INSTR_VERA,"",ImVec4(0.4f,0.6f,1.0f,1.0f)), D(GUI_COLOR_INSTR_X1_010,"",ImVec4(0.3f,0.5f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_VRC6_SAW,"",ImVec4(0.8f,0.3f,0.0f,1.0f)), + D(GUI_COLOR_INSTR_ES5506,"",ImVec4(0.5f,0.5f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_MULTIPCM,"",ImVec4(1.0f,0.8f,0.1f,1.0f)), + D(GUI_COLOR_INSTR_SNES,"",ImVec4(0.8f,0.7f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_SU,"",ImVec4(0.95f,0.98f,1.0f,1.0f)), D(GUI_COLOR_INSTR_UNKNOWN,"",ImVec4(0.3f,0.3f,0.3f,1.0f)), D(GUI_COLOR_CHANNEL_FM,"",ImVec4(0.2f,0.8f,1.0f,1.0f)), D(GUI_COLOR_CHANNEL_PULSE,"",ImVec4(0.4f,1.0f,0.2f,1.0f)), D(GUI_COLOR_CHANNEL_NOISE,"",ImVec4(0.8f,0.8f,0.8f,1.0f)), - D(GUI_COLOR_CHANNEL_WAVE,"",ImVec4(1.0f,0.9f,0.2f,1.0f)), - D(GUI_COLOR_CHANNEL_PCM,"",ImVec4(1.0f,0.5f,0.2f,1.0f)), + D(GUI_COLOR_CHANNEL_WAVE,"",ImVec4(1.0f,0.5f,0.2f,1.0f)), + D(GUI_COLOR_CHANNEL_PCM,"",ImVec4(1.0f,0.9f,0.2f,1.0f)), D(GUI_COLOR_CHANNEL_OP,"",ImVec4(0.2f,0.4f,1.0f,1.0f)), D(GUI_COLOR_CHANNEL_MUTED,"",ImVec4(0.5f,0.5f,0.5f,1.0f)), @@ -507,6 +830,9 @@ const int availableSystems[]={ DIV_SYSTEM_AY8910, DIV_SYSTEM_AMIGA, DIV_SYSTEM_PCSPKR, + DIV_SYSTEM_YMU759, + DIV_SYSTEM_DUMMY, + DIV_SYSTEM_SOUND_UNIT, DIV_SYSTEM_OPLL, DIV_SYSTEM_OPLL_DRUMS, DIV_SYSTEM_VRC7, diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index 93dd143a0..69085c324 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -46,4 +46,5 @@ extern const int availableSystems[]; extern const FurnaceGUIActionDef guiActions[]; extern const FurnaceGUIColorDef guiColors[]; extern const int altValues[24]; -extern const int vgmVersions[6]; \ No newline at end of file +extern const int vgmVersions[6]; +extern const FurnaceGUIColors fxColors[256]; \ No newline at end of file diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 3e1416128..47de22e9c 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -67,10 +67,32 @@ const char* oplWaveforms[8]={ "Sine", "Half Sine", "Absolute Sine", "Quarter Sine", "Squished Sine", "Squished AbsSine", "Square", "Derived Square" }; +const char* oplWaveformsStandard[8]={ + "Sine", "Half Sine", "Absolute Sine", "Pulse Sine", "Sine (Even Periods)", "AbsSine (Even Periods)", "Square", "Derived Square" +}; + const char* opzWaveforms[8]={ "Sine", "Triangle", "Cut Sine", "Cut Triangle", "Squished Sine", "Squished Triangle", "Squished AbsSine", "Squished AbsTriangle" }; +const bool opIsOutput[8][4]={ + {false,false,false,true}, + {false,false,false,true}, + {false,false,false,true}, + {false,false,false,true}, + {false,true,false,true}, + {false,true,true,true}, + {false,true,true,true}, + {true,true,true,true} +}; + +const bool opIsOutputOPL[4][4]={ + {false,false,false,true}, + {true,false,false,true}, + {false,true,false,true}, + {true,false,true,true} +}; + enum FMParams { FM_ALG=0, FM_FB=1, @@ -153,6 +175,14 @@ const char* n163UpdateBits[8]={ "now", "every waveform changed", NULL }; +const char* suControlBits[5]={ + "ring mod", "low pass", "band pass", "high pass", NULL +}; + +const char* panBits[3]={ + "right", "left", NULL +}; + const char* oneBit[2]={ "on", NULL }; @@ -180,15 +210,11 @@ const char* dualWSEffects[7]={ "Phase (dual)", }; -const char* macroAbsoluteMode[2]={ - "Relative", - "Absolute" -}; +const char* macroAbsoluteMode="Fixed"; +const char* macroRelativeMode="Relative"; +const char* macroQSoundMode="QSound"; -const char* macroDummyMode[2]={ - "Bug", - "Bug" -}; +const char* macroDummyMode="Bug"; String macroHoverNote(int id, float val) { if (val<-60 || val>=120) return "???"; @@ -239,7 +265,7 @@ void FurnaceGUI::drawSSGEnv(unsigned char type, const ImVec2& size) { ); ImRect rect=ImRect(minArea,maxArea); ImGuiStyle& style=ImGui::GetStyle(); - ImU32 color=ImGui::GetColorU32(uiColors[GUI_COLOR_TEXT]); + ImU32 color=ImGui::GetColorU32(uiColors[GUI_COLOR_FM_SSG]); ImGui::ItemSize(size,style.FramePadding.y); if (ImGui::ItemAdd(rect,ImGui::GetID("ssgEnvDisplay"))) { ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding); @@ -352,7 +378,7 @@ void FurnaceGUI::drawWaveform(unsigned char type, bool opz, const ImVec2& size) ); ImRect rect=ImRect(minArea,maxArea); ImGuiStyle& style=ImGui::GetStyle(); - ImU32 color=ImGui::GetColorU32(uiColors[GUI_COLOR_TEXT]); + ImU32 color=ImGui::GetColorU32(uiColors[GUI_COLOR_FM_WAVE]); ImGui::ItemSize(size,style.FramePadding.y); if (ImGui::ItemAdd(rect,ImGui::GetID("wsDisplay"))) { ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding); @@ -492,11 +518,12 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ); ImRect rect=ImRect(minArea,maxArea); ImGuiStyle& style=ImGui::GetStyle(); - ImU32 color=ImGui::GetColorU32(uiColors[GUI_COLOR_TEXT]); - ImU32 colorL=ImGui::GetColorU32(ImVec4(uiColors[GUI_COLOR_TEXT].x,uiColors[GUI_COLOR_TEXT].y,uiColors[GUI_COLOR_TEXT].z,uiColors[GUI_COLOR_TEXT].w*0.33)); + ImU32 colorM=ImGui::GetColorU32(uiColors[GUI_COLOR_FM_MOD]); + ImU32 colorC=ImGui::GetColorU32(uiColors[GUI_COLOR_FM_CAR]); + ImU32 colorL=ImGui::GetColorU32(uiColors[GUI_COLOR_FM_ALG_LINE]); ImGui::ItemSize(size,style.FramePadding.y); if (ImGui::ItemAdd(rect,ImGui::GetID("alg"))) { - ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding); + ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(uiColors[GUI_COLOR_FM_ALG_BG]),true,style.FrameRounding); const float circleRadius=6.0f*dpiScale+1.0f; switch (algType) { case FM_ALGS_4OP: @@ -506,14 +533,14 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.4,0.5)); ImVec2 pos3=ImLerp(rect.Min,rect.Max,ImVec2(0.6,0.5)); ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.8,0.5)); - dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); - dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,colorM); + dl->AddCircle(pos1,6.0f*dpiScale+1.0f,colorM); addAALine(dl,pos1,pos2,colorL); - dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,colorM); addAALine(dl,pos2,pos3,colorL); - dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,colorM); addAALine(dl,pos3,pos4,colorL); - dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,colorC); pos1.x-=ImGui::CalcTextSize("1").x*0.5; pos2.x-=ImGui::CalcTextSize("2").x*0.5; @@ -523,10 +550,10 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons pos2.y-=ImGui::CalcTextSize("2").y+circleRadius; pos3.y-=ImGui::CalcTextSize("3").y+circleRadius; pos4.y-=ImGui::CalcTextSize("4").y+circleRadius; - dl->AddText(pos1,color,"1"); - dl->AddText(pos2,color,"2"); - dl->AddText(pos3,color,"3"); - dl->AddText(pos4,color,"4"); + dl->AddText(pos1,colorM,"1"); + dl->AddText(pos2,colorM,"2"); + dl->AddText(pos3,colorM,"3"); + dl->AddText(pos4,colorC,"4"); break; } case 1: { // (1+2) > 3 > 4 @@ -534,14 +561,14 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.7)); ImVec2 pos3=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.5)); ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.5)); - dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); - dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,colorM); + dl->AddCircle(pos1,6.0f*dpiScale+1.0f,colorM); addAALine(dl,pos1,pos3,colorL); - dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,colorM); addAALine(dl,pos2,pos3,colorL); - dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,colorM); addAALine(dl,pos3,pos4,colorL); - dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,colorC); pos2.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; pos1.x=pos2.x; @@ -551,10 +578,10 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons pos2.y-=ImGui::CalcTextSize("2").y*0.5; pos3.y-=ImGui::CalcTextSize("3").y+circleRadius; pos4.y-=ImGui::CalcTextSize("4").y+circleRadius; - dl->AddText(pos1,color,"1"); - dl->AddText(pos2,color,"2"); - dl->AddText(pos3,color,"3"); - dl->AddText(pos4,color,"4"); + dl->AddText(pos1,colorM,"1"); + dl->AddText(pos2,colorM,"2"); + dl->AddText(pos3,colorM,"3"); + dl->AddText(pos4,colorC,"4"); break; } case 2: { // 1+(2>3) > 4 @@ -562,14 +589,14 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.7)); ImVec2 pos3=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.7)); ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.5)); - dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); - dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,colorM); + dl->AddCircle(pos1,6.0f*dpiScale+1.0f,colorM); addAALine(dl,pos1,pos4,colorL); - dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,colorM); addAALine(dl,pos2,pos3,colorL); - dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,colorM); addAALine(dl,pos3,pos4,colorL); - dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,colorC); pos1.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; pos2.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; @@ -579,10 +606,10 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons pos2.y-=ImGui::CalcTextSize("2").y*0.5; pos3.y-=ImGui::CalcTextSize("3").y*0.5; pos4.y-=ImGui::CalcTextSize("4").y+circleRadius; - dl->AddText(pos1,color,"1"); - dl->AddText(pos2,color,"2"); - dl->AddText(pos3,color,"3"); - dl->AddText(pos4,color,"4"); + dl->AddText(pos1,colorM,"1"); + dl->AddText(pos2,colorM,"2"); + dl->AddText(pos3,colorM,"3"); + dl->AddText(pos4,colorC,"4"); break; } case 3: { // (1>2)+3 > 4 @@ -590,14 +617,14 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.3)); ImVec2 pos3=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.7)); ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.5)); - dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); - dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,colorM); + dl->AddCircle(pos1,6.0f*dpiScale+1.0f,colorM); addAALine(dl,pos1,pos2,colorL); - dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,colorM); addAALine(dl,pos2,pos4,colorL); - dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,colorM); addAALine(dl,pos3,pos4,colorL); - dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,colorC); pos1.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; pos2.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; @@ -607,10 +634,10 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons pos2.y-=ImGui::CalcTextSize("2").y*0.5; pos3.y-=ImGui::CalcTextSize("3").y*0.5; pos4.y-=ImGui::CalcTextSize("4").y+circleRadius; - dl->AddText(pos1,color,"1"); - dl->AddText(pos2,color,"2"); - dl->AddText(pos3,color,"3"); - dl->AddText(pos4,color,"4"); + dl->AddText(pos1,colorM,"1"); + dl->AddText(pos2,colorM,"2"); + dl->AddText(pos3,colorM,"3"); + dl->AddText(pos4,colorC,"4"); break; } case 4: { // (1>2) + (3>4) @@ -619,13 +646,13 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos3=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.7)); ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.7)); ImVec2 pos5=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.5)); - dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); - dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,colorM); + dl->AddCircle(pos1,6.0f*dpiScale+1.0f,colorM); addAALine(dl,pos1,pos2,colorL); - dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); - dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,colorC); + dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,colorM); addAALine(dl,pos3,pos4,colorL); - dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,colorC); addAALine(dl,pos2,pos5,colorL); addAALine(dl,pos4,pos5,colorL); @@ -637,10 +664,10 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons pos2.y-=ImGui::CalcTextSize("2").y*0.5; pos3.y-=ImGui::CalcTextSize("3").y*0.5; pos4.y-=ImGui::CalcTextSize("4").y*0.5; - dl->AddText(pos1,color,"1"); - dl->AddText(pos2,color,"2"); - dl->AddText(pos3,color,"3"); - dl->AddText(pos4,color,"4"); + dl->AddText(pos1,colorM,"1"); + dl->AddText(pos2,colorC,"2"); + dl->AddText(pos3,colorM,"3"); + dl->AddText(pos4,colorC,"4"); break; } case 5: { // 1 > (2+3+4) @@ -649,14 +676,14 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos3=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.5)); ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.75)); ImVec2 pos5=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.5)); - dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); - dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,colorM); + dl->AddCircle(pos1,6.0f*dpiScale+1.0f,colorM); addAALine(dl,pos1,pos2,colorL); addAALine(dl,pos1,pos3,colorL); addAALine(dl,pos1,pos4,colorL); - dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); - dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); - dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,colorC); + dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,colorC); + dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,colorC); addAALine(dl,pos2,pos5,colorL); addAALine(dl,pos3,pos5,colorL); addAALine(dl,pos4,pos5,colorL); @@ -669,10 +696,10 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons pos2.y-=ImGui::CalcTextSize("2").y*0.5; pos3.y-=ImGui::CalcTextSize("3").y*0.5; pos4.y-=ImGui::CalcTextSize("4").y*0.5; - dl->AddText(pos1,color,"1"); - dl->AddText(pos2,color,"2"); - dl->AddText(pos3,color,"3"); - dl->AddText(pos4,color,"4"); + dl->AddText(pos1,colorM,"1"); + dl->AddText(pos2,colorC,"2"); + dl->AddText(pos3,colorC,"3"); + dl->AddText(pos4,colorC,"4"); break; } case 6: { // (1>2) + 3 + 4 @@ -681,12 +708,12 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos3=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.5)); ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.75)); ImVec2 pos5=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.5)); - dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); - dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,colorM); + dl->AddCircle(pos1,6.0f*dpiScale+1.0f,colorM); addAALine(dl,pos1,pos2,colorL); - dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); - dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); - dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,colorC); + dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,colorC); + dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,colorC); addAALine(dl,pos2,pos5,colorL); addAALine(dl,pos3,pos5,colorL); addAALine(dl,pos4,pos5,colorL); @@ -699,10 +726,10 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons pos2.y-=ImGui::CalcTextSize("2").y*0.5; pos3.y-=ImGui::CalcTextSize("3").y*0.5; pos4.y-=ImGui::CalcTextSize("4").y*0.5; - dl->AddText(pos1,color,"1"); - dl->AddText(pos2,color,"2"); - dl->AddText(pos3,color,"3"); - dl->AddText(pos4,color,"4"); + dl->AddText(pos1,colorM,"1"); + dl->AddText(pos2,colorC,"2"); + dl->AddText(pos3,colorC,"3"); + dl->AddText(pos4,colorC,"4"); break; } case 7: { // 1 + 2 + 3 + 4 @@ -711,11 +738,11 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos3=ImLerp(rect.Min,rect.Max,ImVec2(0.45,0.6)); ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.55,0.8)); ImVec2 pos5=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.5)); - dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); - dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); - dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); - dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); - dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,colorC); + dl->AddCircle(pos1,6.0f*dpiScale+1.0f,colorC); + dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,colorC); + dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,colorC); + dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,colorC); addAALine(dl,pos1,pos5,colorL); addAALine(dl,pos2,pos5,colorL); addAALine(dl,pos3,pos5,colorL); @@ -729,10 +756,10 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons pos2.y-=ImGui::CalcTextSize("2").y*0.5; pos3.y-=ImGui::CalcTextSize("3").y*0.5; pos4.y-=ImGui::CalcTextSize("4").y*0.5; - dl->AddText(pos1,color,"1"); - dl->AddText(pos2,color,"2"); - dl->AddText(pos3,color,"3"); - dl->AddText(pos4,color,"4"); + dl->AddText(pos1,colorC,"1"); + dl->AddText(pos2,colorC,"2"); + dl->AddText(pos3,colorC,"3"); + dl->AddText(pos4,colorC,"4"); break; } } @@ -742,32 +769,32 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons case 0: { // 1 > 2 ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.33,0.5)); ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.67,0.5)); - dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); - dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,colorM); + dl->AddCircle(pos1,6.0f*dpiScale+1.0f,colorM); addAALine(dl,pos1,pos2,colorL); - dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,colorC); pos1.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; pos2.x+=circleRadius+3.0*dpiScale; pos1.y-=ImGui::CalcTextSize("1").y*0.5; pos2.y-=ImGui::CalcTextSize("2").y*0.5; - dl->AddText(pos1,color,"1"); - dl->AddText(pos2,color,"2"); + dl->AddText(pos1,colorM,"1"); + dl->AddText(pos2,colorC,"2"); break; } case 1: { // 1 + 2 ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.33,0.5)); ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.67,0.5)); - dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); - dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); - dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,colorC); + dl->AddCircle(pos1,6.0f*dpiScale+1.0f,colorC); + dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,colorC); pos1.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; pos2.x+=circleRadius+3.0*dpiScale; pos1.y-=ImGui::CalcTextSize("1").y*0.5; pos2.y-=ImGui::CalcTextSize("2").y*0.5; - dl->AddText(pos1,color,"1"); - dl->AddText(pos2,color,"2"); + dl->AddText(pos1,colorC,"1"); + dl->AddText(pos2,colorC,"2"); break; } } @@ -779,14 +806,14 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.4,0.5)); ImVec2 pos3=ImLerp(rect.Min,rect.Max,ImVec2(0.6,0.5)); ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.8,0.5)); - dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); - dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,colorM); + dl->AddCircle(pos1,6.0f*dpiScale+1.0f,colorM); addAALine(dl,pos1,pos2,colorL); - dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,colorM); addAALine(dl,pos2,pos3,colorL); - dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,colorM); addAALine(dl,pos3,pos4,colorL); - dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,colorC); pos1.x-=ImGui::CalcTextSize("1").x*0.5; pos2.x-=ImGui::CalcTextSize("2").x*0.5; @@ -796,10 +823,10 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons pos2.y-=ImGui::CalcTextSize("2").y+circleRadius; pos3.y-=ImGui::CalcTextSize("3").y+circleRadius; pos4.y-=ImGui::CalcTextSize("4").y+circleRadius; - dl->AddText(pos1,color,"1"); - dl->AddText(pos2,color,"2"); - dl->AddText(pos3,color,"3"); - dl->AddText(pos4,color,"4"); + dl->AddText(pos1,colorM,"1"); + dl->AddText(pos2,colorM,"2"); + dl->AddText(pos3,colorM,"3"); + dl->AddText(pos4,colorC,"4"); break; } case 1: { // 1 + (2 > 3 > 4) @@ -808,14 +835,14 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos3=ImLerp(rect.Min,rect.Max,ImVec2(0.4,0.7)); ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.6,0.7)); ImVec2 pos5=ImLerp(rect.Min,rect.Max,ImVec2(0.8,0.7)); - dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); - dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,colorC); + dl->AddCircle(pos1,6.0f*dpiScale+1.0f,colorC); addAALine(dl,pos1,pos5,colorL); - dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,colorM); addAALine(dl,pos2,pos3,colorL); - dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,colorM); addAALine(dl,pos3,pos4,colorL); - dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,colorC); addAALine(dl,pos4,pos5,colorL); @@ -827,10 +854,10 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons pos2.y-=ImGui::CalcTextSize("2").y*0.5; pos3.y-=ImGui::CalcTextSize("3").y*0.5; pos4.y-=ImGui::CalcTextSize("4").y+circleRadius; - dl->AddText(pos1,color,"1"); - dl->AddText(pos2,color,"2"); - dl->AddText(pos3,color,"3"); - dl->AddText(pos4,color,"4"); + dl->AddText(pos1,colorC,"1"); + dl->AddText(pos2,colorM,"2"); + dl->AddText(pos3,colorM,"3"); + dl->AddText(pos4,colorC,"4"); break; } case 2: { // (1>2) + (3>4) @@ -839,13 +866,13 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos3=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.7)); ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.7)); ImVec2 pos5=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.5)); - dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); - dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,colorM); + dl->AddCircle(pos1,6.0f*dpiScale+1.0f,colorM); addAALine(dl,pos1,pos2,colorL); - dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); - dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,colorC); + dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,colorM); addAALine(dl,pos3,pos4,colorL); - dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,colorC); addAALine(dl,pos2,pos5,colorL); addAALine(dl,pos4,pos5,colorL); @@ -857,10 +884,10 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons pos2.y-=ImGui::CalcTextSize("2").y*0.5; pos3.y-=ImGui::CalcTextSize("3").y*0.5; pos4.y-=ImGui::CalcTextSize("4").y*0.5; - dl->AddText(pos1,color,"1"); - dl->AddText(pos2,color,"2"); - dl->AddText(pos3,color,"3"); - dl->AddText(pos4,color,"4"); + dl->AddText(pos1,colorM,"1"); + dl->AddText(pos2,colorC,"2"); + dl->AddText(pos3,colorM,"3"); + dl->AddText(pos4,colorC,"4"); break; } case 3: { // 1 + (2 > 3) + 4 @@ -869,12 +896,12 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos3=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.5)); ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.75)); ImVec2 pos5=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.5)); - dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); - dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,colorC); + dl->AddCircle(pos1,6.0f*dpiScale+1.0f,colorC); addAALine(dl,pos2,pos3,colorL); - dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); - dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); - dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,colorM); + dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,colorC); + dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,colorC); addAALine(dl,pos1,pos5,colorL); addAALine(dl,pos3,pos5,colorL); addAALine(dl,pos4,pos5,colorL); @@ -887,10 +914,10 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons pos2.y-=ImGui::CalcTextSize("2").y*0.5; pos3.y-=ImGui::CalcTextSize("3").y*0.5; pos4.y-=ImGui::CalcTextSize("4").y*0.5; - dl->AddText(pos1,color,"1"); - dl->AddText(pos2,color,"2"); - dl->AddText(pos3,color,"3"); - dl->AddText(pos4,color,"4"); + dl->AddText(pos1,colorC,"1"); + dl->AddText(pos2,colorM,"2"); + dl->AddText(pos3,colorC,"3"); + dl->AddText(pos4,colorC,"4"); break; } } @@ -901,7 +928,7 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons } } -void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, float maxTl, float maxArDr, const ImVec2& size) { +void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, unsigned char sus, unsigned char egt, unsigned char algOrGlobalSus, float maxTl, float maxArDr, const ImVec2& size, unsigned short instType) { ImDrawList* dl=ImGui::GetWindowDrawList(); ImGuiWindow* window=ImGui::GetCurrentWindow(); @@ -912,12 +939,17 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, ); ImRect rect=ImRect(minArea,maxArea); ImGuiStyle& style=ImGui::GetStyle(); - ImU32 color=ImGui::GetColorU32(uiColors[GUI_COLOR_TEXT]); - ImU32 colorS=ImGui::GetColorU32(uiColors[GUI_COLOR_SONG_LOOP]); //Relsease triangle and sustain horiz/vert line color + ImU32 color=ImGui::GetColorU32(uiColors[GUI_COLOR_FM_ENVELOPE]); + ImU32 colorR=ImGui::GetColorU32(uiColors[GUI_COLOR_FM_ENVELOPE_RELEASE]); // Relsease triangle + ImU32 colorS=ImGui::GetColorU32(uiColors[GUI_COLOR_FM_ENVELOPE_SUS_GUIDE]); // Sustain horiz/vert line color ImGui::ItemSize(size,style.FramePadding.y); - if (ImGui::ItemAdd(rect,ImGui::GetID("alg"))) { + if (ImGui::ItemAdd(rect,ImGui::GetID("fmEnv"))) { ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding); + //Adjust for OPLL global sustain setting + if (instType==DIV_INS_OPLL && algOrGlobalSus==1.0){ + rr = 5.0; + } //calculate x positions float arPos=float(maxArDr-ar)/maxArDr; //peak of AR, start of DR float drPos=arPos+((sl/15.0)*(float(maxArDr-dr)/maxArDr)); //end of DR, start of D2R @@ -951,17 +983,17 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, //addAALine(dl,pos3,posSLineVEnd,colorS); //draw vert. line through sustain level addAALine(dl,pos1,pos2,color); //A addAALine(dl,pos2,posDecayRate0Pt,color); //Line from A to end of graph - } else if (d2r==0.0) { //if D2R = 0, the envelope stays at the sustain level forever + } else if (d2r==0.0 || (instType==DIV_INS_OPL && sus==1.0) || (instType==DIV_INS_OPLL && egt!=0.0)) { //envelope stays at the sustain level forever dl->AddTriangleFilled(posRStart,posREnd,pos1,colorS); //draw release as shaded triangle behind everything - addAALine(dl,pos3,posSLineHEnd,colorS); //draw horiz line through sustain level - addAALine(dl,pos3,posSLineVEnd,colorS); //draw vert. line through sustain level + addAALine(dl,pos3,posSLineHEnd,colorR); //draw horiz line through sustain level + addAALine(dl,pos3,posSLineVEnd,colorR); //draw vert. line through sustain level addAALine(dl,pos1,pos2,color); //A addAALine(dl,pos2,pos3,color); //D addAALine(dl,pos3,posDecay2Rate0Pt,color); //Line from D to end of graph } else { //draw graph normally dl->AddTriangleFilled(posRStart,posREnd,pos1,colorS); //draw release as shaded triangle behind everything - addAALine(dl,pos3,posSLineHEnd,colorS); //draw horiz line through sustain level - addAALine(dl,pos3,posSLineVEnd,colorS); //draw vert. line through sustain level + addAALine(dl,pos3,posSLineHEnd,colorR); //draw horiz line through sustain level + addAALine(dl,pos3,posSLineVEnd,colorR); //draw vert. line through sustain level addAALine(dl,pos1,pos2,color); //A addAALine(dl,pos2,pos3,color); //D addAALine(dl,pos3,pos4,color); //D2 @@ -970,6 +1002,64 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, } } +void FurnaceGUI::drawGBEnv(unsigned char vol, unsigned char len, unsigned char sLen, bool dir, const ImVec2& size) { + ImDrawList* dl=ImGui::GetWindowDrawList(); + ImGuiWindow* window=ImGui::GetCurrentWindow(); + + ImVec2 minArea=window->DC.CursorPos; + ImVec2 maxArea=ImVec2( + minArea.x+size.x, + minArea.y+size.y + ); + ImRect rect=ImRect(minArea,maxArea); + ImGuiStyle& style=ImGui::GetStyle(); + ImU32 color=ImGui::GetColorU32(uiColors[GUI_COLOR_FM_ENVELOPE]); + //ImU32 colorS=ImGui::GetColorU32(uiColors[GUI_COLOR_FM_ENVELOPE_SUS_GUIDE]); // Sustain horiz/vert line color + ImGui::ItemSize(size,style.FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID("gbEnv"))) { + ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding); + + float volY=1.0-((float)vol/15.0); + float lenPos=(sLen>62)?1.0:((float)sLen/384.0); + float envEndPoint=((float)len/7.0)*((float)(dir?(15-vol):vol)/15.0); + + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.0,volY)); + ImVec2 pos2; + if (dir) { + if (len>0) { + if (lenPos0) { + if (lenPos0 || sLen<63)?((dir && sLen>62)?0.0:1.0):volY)); + + addAALine(dl,pos1,pos2,color); + if (lenPos>=envEndPoint && sLen<63 && dir) { + pos3=ImLerp(rect.Min,rect.Max,ImVec2(lenPos,0.0)); + addAALine(dl,pos2,pos3,color); + ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(lenPos,1.0)); + addAALine(dl,pos3,pos4,color); + } else { + addAALine(dl,pos2,pos3,color); + } + } +} + #define P(x) if (x) { \ MARK_MODIFIED; \ e->notifyInsChange(curIns); \ @@ -977,240 +1067,153 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, #define PARAMETER MARK_MODIFIED; e->notifyInsChange(curIns); -#define NORMAL_MACRO(macro,macroMin,macroHeight,macroName,displayName,displayHeight,displayLoop,bitfield,bfVal,drawSlider,sliderVal,sliderLow,macroDispMin,bitOff,macroMode,macroModeMax,displayModeName,macroColor,mmlStr,macroAMin,macroAMax,hoverFunc,blockMode) \ - ImGui::TableNextRow(); \ - ImGui::TableNextColumn(); \ - ImGui::Text("%s",displayName); \ - ImGui::SameLine(); \ - if (ImGui::SmallButton(displayLoop?(ICON_FA_CHEVRON_UP "##IMacroOpen_" macroName):(ICON_FA_CHEVRON_DOWN "##IMacroOpen_" macroName))) { \ - displayLoop=!displayLoop; \ - } \ - if (displayLoop) { \ - ImGui::SetNextItemWidth(lenAvail); \ - if (ImGui::InputScalar("##IMacroLen_" macroName,ImGuiDataType_U8,¯o.len,&_ONE,&_THREE)) { MARK_MODIFIED \ - if (macro.len>127) macro.len=127; \ - } \ - if (macroMode) { \ - String modeName; \ - if (macro.mode>macroModeMax) { \ - modeName="none selected"; \ - } else { \ - modeName=displayModeName[macro.mode]; \ - } \ - if (ImGui::BeginCombo("TODO: Improve##IMacroMode_" macroName,modeName.c_str())) { \ - String id; \ - for (unsigned int i=0; i<=macroModeMax; i++) { \ - id=fmt::sprintf("%d: %s",i,displayModeName[i]); \ - if (ImGui::Selectable(id.c_str(),macro.mode==i)) { PARAMETER \ - macro.mode=i; \ - } \ - } \ - ImGui::EndCombo(); \ - } \ - } \ - } \ - ImGui::TableNextColumn(); \ - for (int j=0; j<256; j++) { \ - if (j+macroDragScroll>=macro.len) { \ - asFloat[j]=0; \ - asInt[j]=0; \ - } else { \ - asFloat[j]=macro.val[j+macroDragScroll]+macroDispMin; \ - asInt[j]=macro.val[j+macroDragScroll]+macroDispMin+bitOff; \ - } \ - if (j+macroDragScroll>=macro.len || (j+macroDragScroll>macro.rel && macro.loop=macro.loop))|((macro.rel!=-1 && (j+macroDragScroll)==macro.rel)<<1); \ - } \ - } \ - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); \ - \ - if (bitfield) { \ - PlotBitfield("##IMacro_" macroName,asInt,totalFit,0,bfVal,macroHeight,ImVec2(availableWidth,(displayLoop)?(displayHeight*dpiScale):(32.0f*dpiScale))); \ - } else { \ - PlotCustom("##IMacro_" macroName,asFloat,totalFit,macroDragScroll,NULL,macroDispMin+macroMin,macroHeight+macroDispMin,ImVec2(availableWidth,(displayLoop)?(displayHeight*dpiScale):(32.0f*dpiScale)),sizeof(float),macroColor,macro.len-macroDragScroll,hoverFunc,blockMode); \ - } \ - if (displayLoop && ImGui::IsItemClicked(ImGuiMouseButton_Left)) { \ - macroDragStart=ImGui::GetItemRectMin(); \ - macroDragAreaSize=ImVec2(availableWidth,displayHeight*dpiScale); \ - macroDragMin=macroMin; \ - macroDragMax=macroHeight; \ - macroDragBitOff=bitOff; \ - macroDragBitMode=bitfield; \ - macroDragInitialValueSet=false; \ - macroDragInitialValue=false; \ - macroDragLen=totalFit; \ - macroDragActive=true; \ - macroDragTarget=macro.val; \ - macroDragChar=false; \ - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); \ - } \ - if (displayLoop) { \ - if (drawSlider) { \ - ImGui::SameLine(); \ - CWVSliderInt("##IArpMacroPos",ImVec2(20.0f*dpiScale,displayHeight*dpiScale),sliderVal,sliderLow,70); \ - } \ - PlotCustom("##IMacroLoop_" macroName,loopIndicator,totalFit,macroDragScroll,NULL,0,2,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),macroColor,macro.len-macroDragScroll,¯oHoverLoop); \ - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { \ - macroLoopDragStart=ImGui::GetItemRectMin(); \ - macroLoopDragAreaSize=ImVec2(availableWidth,12.0f*dpiScale); \ - macroLoopDragLen=totalFit; \ - if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { \ - macroLoopDragTarget=¯o.rel; \ - } else { \ - macroLoopDragTarget=¯o.loop; \ - } \ - macroLoopDragActive=true; \ - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); \ - } \ - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { \ - if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { \ - macro.rel=-1; \ - } else { \ - macro.loop=-1; \ - } \ - } \ - ImGui::SetNextItemWidth(availableWidth); \ - if (ImGui::InputText("##IMacroMML_" macroName,&mmlStr)) { \ - decodeMMLStr(mmlStr,macro.val,macro.len,macro.loop,macroAMin,(bitfield)?((1<<(bitfield?macroAMax:0))-1):macroAMax,macro.rel); \ - } \ - if (!ImGui::IsItemActive()) { \ - encodeMMLStr(mmlStr,macro.val,macro.len,macro.loop,macro.rel); \ - } \ - } \ - ImGui::PopStyleVar(); +void FurnaceGUI::drawMacros(std::vector& macros) { + float asFloat[256]; + int asInt[256]; + float loopIndicator[256]; + int index=0; -#define OP_MACRO(macro,macroHeight,op,macroName,displayName,displayHeight,displayLoop,bitfield,bfVal,macroMode,macroModeMax,displayModeName,mmlStr) \ - ImGui::TableNextRow(); \ - ImGui::TableNextColumn(); \ - ImGui::Text("%s",displayName); \ - ImGui::SameLine(); \ - if (ImGui::SmallButton(displayLoop?(ICON_FA_CHEVRON_UP "##IOPMacroOpen_" macroName):(ICON_FA_CHEVRON_DOWN "##IOPMacroOpen_" macroName))) { \ - displayLoop=!displayLoop; \ - } \ - if (displayLoop) { \ - ImGui::SetNextItemWidth(lenAvail); \ - if (ImGui::InputScalar("##IOPMacroLen_" #op macroName,ImGuiDataType_U8,¯o.len,&_ONE,&_THREE)) { MARK_MODIFIED \ - if (macro.len>127) macro.len=127; \ - } \ - if (macroMode) { \ - String modeName; \ - if (macro.mode>macroModeMax) { \ - modeName="none selected"; \ - } else { \ - modeName=displayModeName[macro.mode]; \ - } \ - if (ImGui::BeginCombo("TODO: Improve##IOPMacroMode_" macroName,modeName.c_str())) { \ - String id; \ - for (unsigned int i=0; i<=macroModeMax; i++) { \ - id=fmt::sprintf("%d: %s",i,displayModeName[i]); \ - if (ImGui::Selectable(id.c_str(),macro.mode==i)) { PARAMETER \ - macro.mode=i; \ - } \ - } \ - ImGui::EndCombo(); \ - } \ - } \ - } \ - ImGui::TableNextColumn(); \ - for (int j=0; j<256; j++) { \ - if (j+macroDragScroll>=macro.len) { \ - asFloat[j]=0; \ - asInt[j]=0; \ - } else { \ - asFloat[j]=macro.val[j+macroDragScroll]; \ - asInt[j]=macro.val[j+macroDragScroll]; \ - } \ - if (j+macroDragScroll>=macro.len || (j+macroDragScroll>macro.rel && macro.loop=macro.loop))|((macro.rel!=-1 && (j+macroDragScroll)==macro.rel)<<1); \ - } \ - } \ - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); \ - \ - if (bitfield) { \ - PlotBitfield("##IOPMacro_" #op macroName,asInt,totalFit,0,bfVal,macroHeight,ImVec2(availableWidth,displayLoop?(displayHeight*dpiScale):(24*dpiScale))); \ - } else { \ - PlotCustom("##IOPMacro_" #op macroName,asFloat,totalFit,macroDragScroll,NULL,0,macroHeight,ImVec2(availableWidth,displayLoop?(displayHeight*dpiScale):(24*dpiScale)),sizeof(float),uiColors[GUI_COLOR_MACRO_OTHER],macro.len-macroDragScroll); \ - } \ - if (displayLoop && ImGui::IsItemClicked(ImGuiMouseButton_Left)) { \ - macroDragStart=ImGui::GetItemRectMin(); \ - macroDragAreaSize=ImVec2(availableWidth,displayHeight*dpiScale); \ - macroDragMin=0; \ - macroDragMax=macroHeight; \ - macroDragBitOff=0; \ - macroDragBitMode=bitfield; \ - macroDragInitialValueSet=false; \ - macroDragInitialValue=false; \ - macroDragLen=totalFit; \ - macroDragActive=true; \ - macroDragTarget=macro.val; \ - macroDragChar=false; \ - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); \ - } \ - if (displayLoop) { \ - PlotCustom("##IOPMacroLoop_" #op macroName,loopIndicator,totalFit,macroDragScroll,NULL,0,2,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),uiColors[GUI_COLOR_MACRO_OTHER],macro.len-macroDragScroll,¯oHoverLoop); \ - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { \ - macroLoopDragStart=ImGui::GetItemRectMin(); \ - macroLoopDragAreaSize=ImVec2(availableWidth,8.0f*dpiScale); \ - macroLoopDragLen=totalFit; \ - if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { \ - macroLoopDragTarget=¯o.rel; \ - } else { \ - macroLoopDragTarget=¯o.loop; \ - } \ - macroLoopDragActive=true; \ - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); \ - } \ - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { \ - if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { \ - macro.rel=-1; \ - } else { \ - macro.loop=-1; \ - } \ - } \ - ImGui::SetNextItemWidth(availableWidth); \ - if (ImGui::InputText("##IOPMacroMML_" macroName,&mmlStr)) { \ - decodeMMLStr(mmlStr,macro.val,macro.len,macro.loop,0,bitfield?((1<<(bitfield?macroHeight:0))-1):(macroHeight),macro.rel); \ - } \ - if (!ImGui::IsItemActive()) { \ - encodeMMLStr(mmlStr,macro.val,macro.len,macro.loop,macro.rel); \ - } \ - } \ - ImGui::PopStyleVar(); + float reservedSpace=ImGui::GetContentRegionAvail().x-28.0f*dpiScale; -#define MACRO_BEGIN(reservedSpace) \ -if (ImGui::BeginTable("MacroSpace",2)) { \ - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); \ - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); \ - ImGui::TableNextRow(); \ - ImGui::TableNextColumn(); \ - float lenAvail=ImGui::GetContentRegionAvail().x; \ - ImGui::Dummy(ImVec2(120.0f*dpiScale,dpiScale)); \ - ImGui::TableNextColumn(); \ - float availableWidth=ImGui::GetContentRegionAvail().x-reservedSpace; \ - int totalFit=MIN(127,availableWidth/(16*dpiScale)); \ - if (macroDragScroll>127-totalFit) { \ - macroDragScroll=127-totalFit; \ - } \ - ImGui::SetNextItemWidth(availableWidth); \ - if (CWSliderInt("##MacroScroll",¯oDragScroll,0,127-totalFit,"")) { \ - if (macroDragScroll<0) macroDragScroll=0; \ - if (macroDragScroll>127-totalFit) macroDragScroll=127-totalFit; \ + if (ImGui::BeginTable("MacroSpace",2)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + float lenAvail=ImGui::GetContentRegionAvail().x; + ImGui::Dummy(ImVec2(120.0f*dpiScale,dpiScale)); + ImGui::TableNextColumn(); + float availableWidth=ImGui::GetContentRegionAvail().x-reservedSpace; + int totalFit=MIN(128,availableWidth/MAX(1,macroPointSize*dpiScale)); + if (macroDragScroll>128-totalFit) { + macroDragScroll=128-totalFit; + } + ImGui::SetNextItemWidth(availableWidth); + if (CWSliderInt("##MacroScroll",¯oDragScroll,0,128-totalFit,"")) { + if (macroDragScroll<0) macroDragScroll=0; + if (macroDragScroll>128-totalFit) macroDragScroll=128-totalFit; + } + + // draw macros + for (FurnaceGUIMacroDesc& i: macros) { + ImGui::PushID(index); + ImGui::TableNextRow(); + + // description + ImGui::TableNextColumn(); + ImGui::Text("%s",i.displayName); + ImGui::SameLine(); + if (ImGui::SmallButton(i.macro->open?(ICON_FA_CHEVRON_UP "##IMacroOpen"):(ICON_FA_CHEVRON_DOWN "##IMacroOpen"))) { + i.macro->open=!i.macro->open; + } + if (i.macro->open) { + ImGui::SetNextItemWidth(lenAvail); + if (ImGui::InputScalar("##IMacroLen",ImGuiDataType_U8,&i.macro->len,&_ONE,&_THREE)) { MARK_MODIFIED + if (i.macro->len>128) i.macro->len=128; + } + if (i.modeName!=NULL) { + bool modeVal=i.macro->mode; + String modeName=fmt::sprintf("%s##IMacroMode",i.modeName); + if (ImGui::Checkbox(modeName.c_str(),&modeVal)) { + i.macro->mode=modeVal; + } + } + } + + // macro area + ImGui::TableNextColumn(); + for (int j=0; j<256; j++) { + if (j+macroDragScroll>=i.macro->len) { + asFloat[j]=0; + asInt[j]=0; + } else { + asFloat[j]=i.macro->val[j+macroDragScroll]; + asInt[j]=i.macro->val[j+macroDragScroll]+i.bitOffset; + } + if (j+macroDragScroll>=i.macro->len || (j+macroDragScroll>i.macro->rel && i.macro->looprel)) { + loopIndicator[j]=0; + } else { + loopIndicator[j]=((i.macro->loop!=-1 && (j+macroDragScroll)>=i.macro->loop))|((i.macro->rel!=-1 && (j+macroDragScroll)==i.macro->rel)<<1); + } + } + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); + + if (i.isBitfield) { + PlotBitfield("##IMacro",asInt,totalFit,0,i.bitfieldBits,i.max,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale))); + } else { + PlotCustom("##IMacro",asFloat,totalFit,macroDragScroll,NULL,i.min,i.max,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),i.color,i.macro->len-macroDragScroll,i.hoverFunc,i.blockMode); + } + if (i.macro->open && (ImGui::IsItemClicked(ImGuiMouseButton_Left) || ImGui::IsItemClicked(ImGuiMouseButton_Right))) { + macroDragStart=ImGui::GetItemRectMin(); + macroDragAreaSize=ImVec2(availableWidth,i.height*dpiScale); + macroDragMin=i.min; + macroDragMax=i.max; + macroDragBitOff=i.bitOffset; + macroDragBitMode=i.isBitfield; + macroDragInitialValueSet=false; + macroDragInitialValue=false; + macroDragLen=totalFit; + macroDragActive=true; + macroDragTarget=i.macro->val; + macroDragChar=false; + macroDragLineMode=(i.isBitfield)?false:ImGui::IsItemClicked(ImGuiMouseButton_Right); + macroDragLineInitial=ImVec2(0,0); + processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); + } + if (i.macro->open) { + if (ImGui::IsItemHovered() && ctrlWheeling) { + macroPointSize+=wheelY; + if (macroPointSize<1) macroPointSize=1; + if (macroPointSize>256) macroPointSize=256; + } + /*if (drawSlider) { + ImGui::SameLine(); + CWVSliderInt("##IMacroPos",ImVec2(20.0f*dpiScale,i.height*dpiScale),sliderVal,sliderLow,sliderHigh); + }*/ + PlotCustom("##IMacroLoop",loopIndicator,totalFit,macroDragScroll,NULL,0,2,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),i.color,i.macro->len-macroDragScroll,¯oHoverLoop); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + macroLoopDragStart=ImGui::GetItemRectMin(); + macroLoopDragAreaSize=ImVec2(availableWidth,12.0f*dpiScale); + macroLoopDragLen=totalFit; + if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { + macroLoopDragTarget=&i.macro->rel; + } else { + macroLoopDragTarget=&i.macro->loop; + } + macroLoopDragActive=true; + processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { + i.macro->rel=-1; + } else { + i.macro->loop=-1; + } + } + ImGui::SetNextItemWidth(availableWidth); + String& mmlStr=mmlString[index]; + if (ImGui::InputText("##IMacroMML",&mmlStr)) { + decodeMMLStr(mmlStr,i.macro->val,i.macro->len,i.macro->loop,i.min,(i.isBitfield)?((1<<(i.isBitfield?i.max:0))-1):i.max,i.macro->rel); + } + if (!ImGui::IsItemActive()) { + encodeMMLStr(mmlStr,i.macro->val,i.macro->len,i.macro->loop,i.macro->rel); + } + } + ImGui::PopStyleVar(); + ImGui::PopID(); + index++; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(availableWidth); + if (CWSliderInt("##MacroScroll",¯oDragScroll,0,128-totalFit,"")) { + if (macroDragScroll<0) macroDragScroll=0; + if (macroDragScroll>128-totalFit) macroDragScroll=128-totalFit; + } + ImGui::EndTable(); } - -#define MACRO_END \ - ImGui::TableNextRow(); \ - ImGui::TableNextColumn(); \ - ImGui::TableNextColumn(); \ - ImGui::SetNextItemWidth(availableWidth); \ - if (CWSliderInt("##MacroScroll",¯oDragScroll,0,127-totalFit,"")) { \ - if (macroDragScroll<0) macroDragScroll=0; \ - if (macroDragScroll>127-totalFit) macroDragScroll=127-totalFit; \ - } \ - ImGui::EndTable(); \ } #define DRUM_FREQ(name,db,df,prop) \ @@ -1266,6 +1269,9 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("no instrument selected"); } else { DivInstrument* ins=e->song.ins[curIns]; + if (settings.insEditColorize) { + pushAccentColors(uiColors[GUI_COLOR_INSTR_STD+ins->type],uiColors[GUI_COLOR_INSTR_STD+ins->type],uiColors[GUI_COLOR_INSTR_STD+ins->type],ImVec4(0.0f,0.0f,0.0f,0.0f)); + } if (ImGui::BeginTable("InsProp",3)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); @@ -1273,6 +1279,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); String insIndex=fmt::sprintf("%.2X",curIns); + ImGui::SetNextItemWidth(72.0f*dpiScale); if (ImGui::BeginCombo("##InsSelect",insIndex.c_str())) { String name; for (size_t i=0; isong.ins.size(); i++) { @@ -1298,7 +1305,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); // TODO: load replace if (ImGui::Button(ICON_FA_FOLDER_OPEN "##IELoad")) { - doAction(GUI_ACTION_INS_LIST_OPEN); + doAction(GUI_ACTION_INS_LIST_OPEN_REPLACE); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FLOPPY_O "##IESave")) { @@ -1312,20 +1319,28 @@ void FurnaceGUI::drawInsEdit() { if (ins->type>=DIV_INS_MAX) ins->type=DIV_INS_FM; int insType=ins->type; ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + /* if (ImGui::Combo("##Type",&insType,insTypes,DIV_INS_MAX,DIV_INS_MAX)) { ins->type=(DivInstrumentType)insType; } + */ + if (ImGui::BeginCombo("##Type",insTypes[insType])) { + for (DivInstrumentType i: e->getPossibleInsTypes()) { + if (ImGui::Selectable(insTypes[i],insType==i)) { + ins->type=i; + } + } + ImGui::EndCombo(); + } ImGui::EndTable(); } if (ImGui::BeginTabBar("insEditTab")) { + std::vector macroList; if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPZ) { char label[32]; - float asFloat[256]; - int asInt[256]; - float loopIndicator[256]; int opCount=4; if (ins->type==DIV_INS_OPLL) opCount=2; if (ins->type==DIV_INS_OPL) opCount=(ins->fm.ops==4)?4:2; @@ -1588,6 +1603,35 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); + // push colors + if (settings.separateFMColors) { + bool mod=true; + if (opCount==4) { + if (ins->type==DIV_INS_OPL) { + if (opIsOutputOPL[ins->fm.alg&3][i]) mod=false; + } else { + if (opIsOutput[ins->fm.alg&7][i]) mod=false; + } + } else { + if (i==1 || (ins->type==DIV_INS_OPL && (ins->fm.alg&1))) mod=false; + } + if (mod) { + pushAccentColors( + uiColors[GUI_COLOR_FM_PRIMARY_MOD], + uiColors[GUI_COLOR_FM_SECONDARY_MOD], + uiColors[GUI_COLOR_FM_BORDER_MOD], + uiColors[GUI_COLOR_FM_BORDER_SHADOW_MOD] + ); + } else { + pushAccentColors( + uiColors[GUI_COLOR_FM_PRIMARY_CAR], + uiColors[GUI_COLOR_FM_SECONDARY_CAR], + uiColors[GUI_COLOR_FM_BORDER_CAR], + uiColors[GUI_COLOR_FM_BORDER_SHADOW_CAR] + ); + } + } + if (i==0) sliderHeight=(ImGui::GetContentRegionAvail().y/opCount)-ImGui::GetStyle().ItemSpacing.y; ImGui::PushID(fmt::sprintf("op%d",i).c_str()); @@ -1796,7 +1840,7 @@ void FurnaceGUI::drawInsEdit() { drawWaveform(op.ws&7,ins->type==DIV_INS_OPZ,ImVec2(ImGui::GetContentRegionAvail().x,sliderHeight-ImGui::GetFrameHeightWithSpacing())); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(CWSliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,(ins->type==DIV_INS_OPZ)?opzWaveforms[op.ws&7]:oplWaveforms[op.ws&7])); rightClickable + P(CWSliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,(ins->type==DIV_INS_OPZ)?opzWaveforms[op.ws&7]:(settings.oplStandardWaveNames?oplWaveformsStandard[op.ws&7]:oplWaveforms[op.ws&7]))); rightClickable if (ins->type==DIV_INS_OPL && ImGui::IsItemHovered()) { ImGui::SetTooltip("OPL2/3 only (last 4 waveforms are OPL3 only)"); } @@ -1806,7 +1850,11 @@ void FurnaceGUI::drawInsEdit() { } ImGui::TableNextColumn(); - drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,sliderHeight)); + drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,op.sus,op.ssgEnv&8,ins->fm.alg,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,sliderHeight),ins->type); + + if (settings.separateFMColors) { + popAccentColors(); + } ImGui::PopID(); } @@ -1833,6 +1881,36 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::Separator(); ImGui::PushID(fmt::sprintf("op%d",i).c_str()); + + // push colors + if (settings.separateFMColors) { + bool mod=true; + if (opCount==4) { + if (ins->type==DIV_INS_OPL) { + if (opIsOutputOPL[ins->fm.alg&3][i]) mod=false; + } else { + if (opIsOutput[ins->fm.alg&7][i]) mod=false; + } + } else { + if (i==1 || (ins->type==DIV_INS_OPL && (ins->fm.alg&1))) mod=false; + } + if (mod) { + pushAccentColors( + uiColors[GUI_COLOR_FM_PRIMARY_MOD], + uiColors[GUI_COLOR_FM_SECONDARY_MOD], + uiColors[GUI_COLOR_FM_BORDER_MOD], + uiColors[GUI_COLOR_FM_BORDER_SHADOW_MOD] + ); + } else { + pushAccentColors( + uiColors[GUI_COLOR_FM_PRIMARY_CAR], + uiColors[GUI_COLOR_FM_SECONDARY_CAR], + uiColors[GUI_COLOR_FM_BORDER_CAR], + uiColors[GUI_COLOR_FM_BORDER_SHADOW_CAR] + ); + } + } + ImGui::Dummy(ImVec2(dpiScale,dpiScale)); if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) { if (i==1) { @@ -1889,7 +1967,7 @@ void FurnaceGUI::drawInsEdit() { } //52.0 controls vert scaling; default 96 - drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,52.0*dpiScale)); + drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,op.sus,op.ssgEnv&8,ins->fm.alg,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,52.0*dpiScale),ins->type); //P(CWSliderScalar(FM_NAME(FM_AR),ImGuiDataType_U8,&op.ar,&_ZERO,&_THIRTY_ONE)); rightClickable if (ImGui::BeginTable("opParams",2,ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.0); \ @@ -2016,7 +2094,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(CWSliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,(ins->type==DIV_INS_OPZ)?opzWaveforms[op.ws&7]:oplWaveforms[op.ws&7])); rightClickable + P(CWSliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,(ins->type==DIV_INS_OPZ)?opzWaveforms[op.ws&7]:(settings.oplStandardWaveNames?oplWaveformsStandard[op.ws&7]:oplWaveforms[op.ws&7]))); rightClickable if (ins->type==DIV_INS_OPL && ImGui::IsItemHovered()) { ImGui::SetTooltip("OPL2/3 only (last 4 waveforms are OPL3 only)"); } @@ -2037,6 +2115,10 @@ void FurnaceGUI::drawInsEdit() { } } + if (settings.separateFMColors) { + popAccentColors(); + } + ImGui::PopID(); } ImGui::EndTable(); @@ -2046,42 +2128,40 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndTabItem(); } if (ImGui::BeginTabItem("FM Macros")) { - MACRO_BEGIN(0); if (ins->type==DIV_INS_OPLL) { - NORMAL_MACRO(ins->std.algMacro,0,1,"alg",FM_NAME(FM_SUS),32,ins->std.algMacro.open,true,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[0],0,1,NULL,false); - NORMAL_MACRO(ins->std.fbMacro,0,7,"fb",FM_NAME(FM_FB),96,ins->std.fbMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[1],0,7,NULL,false); - NORMAL_MACRO(ins->std.fmsMacro,0,1,"fms",FM_NAME(FM_DC),32,ins->std.fmsMacro.open,true,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,1,NULL,false); - NORMAL_MACRO(ins->std.amsMacro,0,1,"ams",FM_NAME(FM_DM),32,ins->std.amsMacro.open,true,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,1,NULL,false); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_SUS),&ins->std.algMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_FB),&ins->std.fbMacro,0,7,96,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_DC),&ins->std.fmsMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_DM),&ins->std.amsMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); } else { - NORMAL_MACRO(ins->std.algMacro,0,7,"alg",FM_NAME(FM_ALG),96,ins->std.algMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[0],0,7,NULL,false); - NORMAL_MACRO(ins->std.fbMacro,0,7,"fb",FM_NAME(FM_FB),96,ins->std.fbMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[1],0,7,NULL,false); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_ALG),&ins->std.algMacro,0,7,96,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_FB),&ins->std.fbMacro,0,7,96,uiColors[GUI_COLOR_MACRO_OTHER])); if (ins->type!=DIV_INS_OPL) { if (ins->type==DIV_INS_OPZ) { // TODO: FMS2/AMS2 macros - NORMAL_MACRO(ins->std.fmsMacro,0,7,"fms",FM_NAME(FM_FMS),96,ins->std.fmsMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,7,NULL,false); - NORMAL_MACRO(ins->std.amsMacro,0,3,"ams",FM_NAME(FM_AMS),48,ins->std.amsMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,3,NULL,false); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_FMS),&ins->std.fmsMacro,0,7,96,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_AMS),&ins->std.amsMacro,0,3,48,uiColors[GUI_COLOR_MACRO_OTHER])); } else { - NORMAL_MACRO(ins->std.fmsMacro,0,7,"fms",FM_NAME(FM_FMS),96,ins->std.fmsMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,7,NULL,false); - NORMAL_MACRO(ins->std.amsMacro,0,3,"ams",FM_NAME(FM_AMS),48,ins->std.amsMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,3,NULL,false); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_FMS),&ins->std.fmsMacro,0,7,96,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_AMS),&ins->std.amsMacro,0,3,48,uiColors[GUI_COLOR_MACRO_OTHER])); } } } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { - NORMAL_MACRO(ins->std.ex1Macro,0,127,"ex1","AM Depth",128,ins->std.ex1Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,127,NULL,false); - NORMAL_MACRO(ins->std.ex2Macro,0,127,"ex2","PM Depth",128,ins->std.ex2Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,127,NULL,false); - NORMAL_MACRO(ins->std.ex3Macro,0,255,"ex3","LFO Speed",128,ins->std.ex3Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,255,NULL,false); - NORMAL_MACRO(ins->std.waveMacro,0,3,"wave","LFO Shape",48,ins->std.waveMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[9],0,3,¯oLFOWaves,false); - NORMAL_MACRO(ins->std.ex4Macro,0,4,"ex4","OpMask",128,ins->std.ex4Macro.open,true,fmOperatorBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[10],0,4,NULL,false); + macroList.push_back(FurnaceGUIMacroDesc("AM Depth",&ins->std.ex1Macro,0,127,128,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("PM Depth",&ins->std.ex2Macro,0,127,128,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("LFO Speed",&ins->std.ex3Macro,0,255,128,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("LFO Shape",&ins->std.waveMacro,0,3,48,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,macroLFOWaves)); + macroList.push_back(FurnaceGUIMacroDesc("OpMask",&ins->std.ex4Macro,0,4,128,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,fmOperatorBits)); } - MACRO_END; + drawMacros(macroList); ImGui::EndTabItem(); } for (int i=0; itype==DIV_INS_OPLL) { @@ -2097,47 +2177,50 @@ void FurnaceGUI::drawInsEdit() { int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ)?31:15; if (ins->type==DIV_INS_OPL) { - OP_MACRO(ins->std.opMacros[ordi].tlMacro,maxTl,ordi,"tl",FM_NAME(FM_TL),128,ins->std.opMacros[ordi].tlMacro.open,false,NULL,false,0,macroDummyMode,mmlString[0]); - OP_MACRO(ins->std.opMacros[ordi].arMacro,maxArDr,ordi,"ar",FM_NAME(FM_AR),64,ins->std.opMacros[ordi].arMacro.open,false,NULL,false,0,macroDummyMode,mmlString[1]); - OP_MACRO(ins->std.opMacros[ordi].drMacro,maxArDr,ordi,"dr",FM_NAME(FM_DR),64,ins->std.opMacros[ordi].drMacro.open,false,NULL,false,0,macroDummyMode,mmlString[2]); - OP_MACRO(ins->std.opMacros[ordi].slMacro,15,ordi,"sl",FM_NAME(FM_SL),64,ins->std.opMacros[ordi].slMacro.open,false,NULL,false,0,macroDummyMode,mmlString[5]); - OP_MACRO(ins->std.opMacros[ordi].rrMacro,15,ordi,"rr",FM_NAME(FM_RR),64,ins->std.opMacros[ordi].rrMacro.open,false,NULL,false,0,macroDummyMode,mmlString[4]); - OP_MACRO(ins->std.opMacros[ordi].kslMacro,3,ordi,"ksl",FM_NAME(FM_KSL),32,ins->std.opMacros[ordi].kslMacro.open,false,NULL,false,0,macroDummyMode,mmlString[6]); - OP_MACRO(ins->std.opMacros[ordi].multMacro,15,ordi,"mult",FM_NAME(FM_MULT),64,ins->std.opMacros[ordi].multMacro.open,false,NULL,false,0,macroDummyMode,mmlString[7]); - OP_MACRO(ins->std.opMacros[ordi].wsMacro,7,ordi,"ws",FM_NAME(FM_WS),64,ins->std.opMacros[ordi].wsMacro.open,false,NULL,false,0,macroDummyMode,mmlString[8]); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_TL),&ins->std.opMacros[ordi].tlMacro,0,maxTl,128,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_AR),&ins->std.opMacros[ordi].arMacro,0,maxArDr,64,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_DR),&ins->std.opMacros[ordi].drMacro,0,maxArDr,64,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_SL),&ins->std.opMacros[ordi].slMacro,0,15,64,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_RR),&ins->std.opMacros[ordi].rrMacro,0,15,64,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_KSL),&ins->std.opMacros[ordi].kslMacro,0,3,32,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_MULT),&ins->std.opMacros[ordi].multMacro,0,15,64,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_WS),&ins->std.opMacros[ordi].wsMacro,0,7,64,uiColors[GUI_COLOR_MACRO_OTHER])); - OP_MACRO(ins->std.opMacros[ordi].amMacro,1,ordi,"am",FM_NAME(FM_AM),32,ins->std.opMacros[ordi].amMacro.open,true,NULL,false,0,macroDummyMode,mmlString[9]); - OP_MACRO(ins->std.opMacros[ordi].vibMacro,1,ordi,"vib",FM_NAME(FM_VIB),32,ins->std.opMacros[ordi].vibMacro.open,true,NULL,false,0,macroDummyMode,mmlString[10]); - OP_MACRO(ins->std.opMacros[ordi].ksrMacro,1,ordi,"ksr",FM_NAME(FM_KSR),32,ins->std.opMacros[ordi].ksrMacro.open,true,NULL,false,0,macroDummyMode,mmlString[11]); - OP_MACRO(ins->std.opMacros[ordi].susMacro,1,ordi,"sus",FM_NAME(FM_SUS),32,ins->std.opMacros[ordi].susMacro.open,true,NULL,false,0,macroDummyMode,mmlString[12]); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_AM),&ins->std.opMacros[ordi].amMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_VIB),&ins->std.opMacros[ordi].vibMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_KSR),&ins->std.opMacros[ordi].ksrMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_SUS),&ins->std.opMacros[ordi].susMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); } else if (ins->type==DIV_INS_OPLL) { - OP_MACRO(ins->std.opMacros[ordi].tlMacro,maxTl,ordi,"tl",FM_NAME(FM_TL),128,ins->std.opMacros[ordi].tlMacro.open,false,NULL,false,0,macroDummyMode,mmlString[0]); - OP_MACRO(ins->std.opMacros[ordi].arMacro,maxArDr,ordi,"ar",FM_NAME(FM_AR),64,ins->std.opMacros[ordi].arMacro.open,false,NULL,false,0,macroDummyMode,mmlString[1]); - OP_MACRO(ins->std.opMacros[ordi].drMacro,maxArDr,ordi,"dr",FM_NAME(FM_DR),64,ins->std.opMacros[ordi].drMacro.open,false,NULL,false,0,macroDummyMode,mmlString[2]); - OP_MACRO(ins->std.opMacros[ordi].slMacro,15,ordi,"sl",FM_NAME(FM_SL),64,ins->std.opMacros[ordi].slMacro.open,false,NULL,false,0,macroDummyMode,mmlString[5]); - OP_MACRO(ins->std.opMacros[ordi].rrMacro,15,ordi,"rr",FM_NAME(FM_RR),64,ins->std.opMacros[ordi].rrMacro.open,false,NULL,false,0,macroDummyMode,mmlString[4]); - OP_MACRO(ins->std.opMacros[ordi].kslMacro,3,ordi,"ksl",FM_NAME(FM_KSL),32,ins->std.opMacros[ordi].kslMacro.open,false,NULL,false,0,macroDummyMode,mmlString[6]); - OP_MACRO(ins->std.opMacros[ordi].multMacro,15,ordi,"mult",FM_NAME(FM_MULT),64,ins->std.opMacros[ordi].multMacro.open,false,NULL,false,0,macroDummyMode,mmlString[7]); - - OP_MACRO(ins->std.opMacros[ordi].amMacro,1,ordi,"am",FM_NAME(FM_AM),32,ins->std.opMacros[ordi].amMacro.open,true,NULL,false,0,macroDummyMode,mmlString[8]); - OP_MACRO(ins->std.opMacros[ordi].vibMacro,1,ordi,"vib",FM_NAME(FM_VIB),32,ins->std.opMacros[ordi].vibMacro.open,true,NULL,false,0,macroDummyMode,mmlString[9]); - OP_MACRO(ins->std.opMacros[ordi].ksrMacro,1,ordi,"ksr",FM_NAME(FM_KSR),32,ins->std.opMacros[ordi].ksrMacro.open,true,NULL,false,0,macroDummyMode,mmlString[10]); - OP_MACRO(ins->std.opMacros[ordi].egtMacro,1,ordi,"egt",FM_NAME(FM_EGS),32,ins->std.opMacros[ordi].egtMacro.open,true,NULL,false,0,macroDummyMode,mmlString[11]); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_TL),&ins->std.opMacros[ordi].tlMacro,0,maxTl,128,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_AR),&ins->std.opMacros[ordi].arMacro,0,maxArDr,64,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_DR),&ins->std.opMacros[ordi].drMacro,0,maxArDr,64,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_SL),&ins->std.opMacros[ordi].slMacro,0,15,64,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_RR),&ins->std.opMacros[ordi].rrMacro,0,15,64,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_KSL),&ins->std.opMacros[ordi].kslMacro,0,3,32,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_MULT),&ins->std.opMacros[ordi].multMacro,0,15,64,uiColors[GUI_COLOR_MACRO_OTHER])); + + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_AM),&ins->std.opMacros[ordi].amMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_VIB),&ins->std.opMacros[ordi].vibMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_KSR),&ins->std.opMacros[ordi].ksrMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_EGS),&ins->std.opMacros[ordi].egtMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); } else { - OP_MACRO(ins->std.opMacros[ordi].tlMacro,maxTl,ordi,"tl",FM_NAME(FM_TL),128,ins->std.opMacros[ordi].tlMacro.open,false,NULL,false,0,macroDummyMode,mmlString[0]); - OP_MACRO(ins->std.opMacros[ordi].arMacro,maxArDr,ordi,"ar",FM_NAME(FM_AR),64,ins->std.opMacros[ordi].arMacro.open,false,NULL,false,0,macroDummyMode,mmlString[1]); - OP_MACRO(ins->std.opMacros[ordi].drMacro,maxArDr,ordi,"dr",FM_NAME(FM_DR),64,ins->std.opMacros[ordi].drMacro.open,false,NULL,false,0,macroDummyMode,mmlString[2]); - OP_MACRO(ins->std.opMacros[ordi].d2rMacro,31,ordi,"d2r",FM_NAME(FM_D2R),64,ins->std.opMacros[ordi].d2rMacro.open,false,NULL,false,0,macroDummyMode,mmlString[3]); - OP_MACRO(ins->std.opMacros[ordi].rrMacro,15,ordi,"rr",FM_NAME(FM_RR),64,ins->std.opMacros[ordi].rrMacro.open,false,NULL,false,0,macroDummyMode,mmlString[4]); - OP_MACRO(ins->std.opMacros[ordi].slMacro,15,ordi,"sl",FM_NAME(FM_SL),64,ins->std.opMacros[ordi].slMacro.open,false,NULL,false,0,macroDummyMode,mmlString[5]); - OP_MACRO(ins->std.opMacros[ordi].rsMacro,3,ordi,"rs",FM_NAME(FM_RS),32,ins->std.opMacros[ordi].rsMacro.open,false,NULL,false,0,macroDummyMode,mmlString[6]); - OP_MACRO(ins->std.opMacros[ordi].multMacro,15,ordi,"mult",FM_NAME(FM_MULT),64,ins->std.opMacros[ordi].multMacro.open,false,NULL,false,0,macroDummyMode,mmlString[7]); - OP_MACRO(ins->std.opMacros[ordi].dtMacro,7,ordi,"dt",FM_NAME(FM_DT),64,ins->std.opMacros[ordi].dtMacro.open,false,NULL,false,0,macroDummyMode,mmlString[8]); - OP_MACRO(ins->std.opMacros[ordi].dt2Macro,3,ordi,"dt2",FM_NAME(FM_DT2),32,ins->std.opMacros[ordi].dt2Macro.open,false,NULL,false,0,macroDummyMode,mmlString[9]); - OP_MACRO(ins->std.opMacros[ordi].amMacro,1,ordi,"am",FM_NAME(FM_AM),32,ins->std.opMacros[ordi].amMacro.open,true,NULL,false,0,macroDummyMode,mmlString[10]); - OP_MACRO(ins->std.opMacros[ordi].ssgMacro,4,ordi,"ssg",FM_NAME(FM_SSG),64,ins->std.opMacros[ordi].ssgMacro.open,true,ssgEnvBits,false,0,macroDummyMode,mmlString[11]); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_TL),&ins->std.opMacros[ordi].tlMacro,0,maxTl,128,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_AR),&ins->std.opMacros[ordi].arMacro,0,maxArDr,64,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_DR),&ins->std.opMacros[ordi].drMacro,0,maxArDr,64,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_D2R),&ins->std.opMacros[ordi].d2rMacro,0,31,64,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_RR),&ins->std.opMacros[ordi].rrMacro,0,15,64,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_SL),&ins->std.opMacros[ordi].slMacro,0,15,64,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_RS),&ins->std.opMacros[ordi].rsMacro,0,3,32,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_MULT),&ins->std.opMacros[ordi].multMacro,0,15,64,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_DT),&ins->std.opMacros[ordi].dtMacro,0,7,64,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_DT2),&ins->std.opMacros[ordi].dt2Macro,0,3,32,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_AM),&ins->std.opMacros[ordi].amMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + + if (ins->type==DIV_INS_FM) { + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_SSG),&ins->std.opMacros[ordi].ssgMacro,0,4,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,ssgEnvBits)); + } } - MACRO_END; + drawMacros(macroList); ImGui::PopID(); ImGui::EndTabItem(); } @@ -2160,6 +2243,8 @@ void FurnaceGUI::drawInsEdit() { goesUp=false; ins->gb.envDir=goesUp; } + + drawGBEnv(ins->gb.envVol,ins->gb.envLen,ins->gb.soundLen,ins->gb.envDir,ImVec2(ImGui::GetContentRegionAvail().x,100.0f*dpiScale)); ImGui::EndTabItem(); } if (ins->type==DIV_INS_C64) if (ImGui::BeginTabItem("C64")) { @@ -2189,10 +2274,47 @@ void FurnaceGUI::drawInsEdit() { } ImGui::PopStyleColor(); - P(CWSliderScalar("Attack",ImGuiDataType_U8,&ins->c64.a,&_ZERO,&_FIFTEEN)); rightClickable - P(CWSliderScalar("Decay",ImGuiDataType_U8,&ins->c64.d,&_ZERO,&_FIFTEEN)); rightClickable - P(CWSliderScalar("Sustain",ImGuiDataType_U8,&ins->c64.s,&_ZERO,&_FIFTEEN)); rightClickable - P(CWSliderScalar("Release",ImGuiDataType_U8,&ins->c64.r,&_ZERO,&_FIFTEEN)); rightClickable + ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale); + + if (ImGui::BeginTable("C64EnvParams",5,ImGuiTableFlags_NoHostExtendX)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + CENTER_TEXT("A"); + ImGui::TextUnformatted("A"); + ImGui::TableNextColumn(); + CENTER_TEXT("D"); + ImGui::TextUnformatted("D"); + ImGui::TableNextColumn(); + CENTER_TEXT("S"); + ImGui::TextUnformatted("S"); + ImGui::TableNextColumn(); + CENTER_TEXT("R"); + ImGui::TextUnformatted("R"); + ImGui::TableNextColumn(); + CENTER_TEXT("Envelope"); + ImGui::TextUnformatted("Envelope"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + P(CWVSliderScalar("##Attack",sliderSize,ImGuiDataType_U8,&ins->c64.a,&_ZERO,&_FIFTEEN)); + ImGui::TableNextColumn(); + P(CWVSliderScalar("##Decay",sliderSize,ImGuiDataType_U8,&ins->c64.d,&_ZERO,&_FIFTEEN)); + ImGui::TableNextColumn(); + P(CWVSliderScalar("##Sustain",sliderSize,ImGuiDataType_U8,&ins->c64.s,&_ZERO,&_FIFTEEN)); + ImGui::TableNextColumn(); + P(CWVSliderScalar("##Release",sliderSize,ImGuiDataType_U8,&ins->c64.r,&_ZERO,&_FIFTEEN)); + ImGui::TableNextColumn(); + drawFMEnv(0,16-ins->c64.a,16-ins->c64.d,15-ins->c64.r,15-ins->c64.r,15-ins->c64.s,0,0,0,15,16,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); + + ImGui::EndTable(); + } + P(CWSliderScalar("Duty",ImGuiDataType_U16,&ins->c64.duty,&_ZERO,&_FOUR_THOUSAND_NINETY_FIVE)); rightClickable bool ringMod=ins->c64.ringMod; @@ -2239,9 +2361,10 @@ void FurnaceGUI::drawInsEdit() { P(ImGui::Checkbox("Volume Macro is Cutoff Macro",&ins->c64.volIsCutoff)); P(ImGui::Checkbox("Absolute Cutoff Macro",&ins->c64.filterIsAbs)); P(ImGui::Checkbox("Absolute Duty Macro",&ins->c64.dutyIsAbs)); + P(ImGui::Checkbox("Don't test/gate before new note",&ins->c64.noTest)); ImGui::EndTabItem(); } - if (ins->type==DIV_INS_AMIGA) if (ImGui::BeginTabItem("Amiga/Sample")) { + if (ins->type==DIV_INS_AMIGA) if (ImGui::BeginTabItem("Sample")) { String sName; if (ins->amiga.initSample<0 || ins->amiga.initSample>=e->song.sampleLen) { sName="none selected"; @@ -2258,6 +2381,17 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndCombo(); } + P(ImGui::Checkbox("Use wavetable (Amiga only)",&ins->amiga.useWave)); + if (ins->amiga.useWave) { + int len=ins->amiga.waveLen+1; + if (ImGui::InputInt("Width",&len,2,16)) { + if (len<2) len=2; + if (len>256) len=256; + ins->amiga.waveLen=(len&(~1))-1; + PARAMETER + } + } + ImGui::BeginDisabled(ins->amiga.useWave); P(ImGui::Checkbox("Use sample map (does not work yet!)",&ins->amiga.useNoteMap)); if (ins->amiga.useNoteMap) { if (ImGui::BeginTable("NoteMap",3,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { @@ -2311,6 +2445,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndTable(); } } + ImGui::EndDisabled(); ImGui::EndTabItem(); } if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) { @@ -2372,12 +2507,116 @@ void FurnaceGUI::drawInsEdit() { macroDragActive=true; macroDragCTarget=(unsigned char*)ins->fds.modTable; macroDragChar=true; - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); \ + macroDragLineMode=false; + macroDragLineInitial=ImVec2(0,0); + processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); } ImGui::EndTabItem(); } + if (ins->type==DIV_INS_MULTIPCM) { + if (ImGui::BeginTabItem("MultiPCM")) { + String sName; + if (ins->amiga.initSample<0 || ins->amiga.initSample>=e->song.sampleLen) { + sName="none selected"; + } else { + sName=e->song.sample[ins->amiga.initSample]->name; + } + if (ImGui::BeginCombo("Initial Sample",sName.c_str())) { + String id; + for (int i=0; isong.sampleLen; i++) { + id=fmt::sprintf("%d: %s",i,e->song.sample[i]->name); + if (ImGui::Selectable(id.c_str(),ins->amiga.initSample==i)) { + ins->amiga.initSample=i; + PARAMETER + } + } + ImGui::EndCombo(); + } + ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale); + if (ImGui::BeginTable("MultiPCMADSRParams",7,ImGuiTableFlags_NoHostExtendX)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + ImGui::TableSetupColumn("c6",ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + CENTER_TEXT("AR"); + ImGui::TextUnformatted("AR"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Attack Rate"); + } + ImGui::TableNextColumn(); + CENTER_TEXT("D1R"); + ImGui::TextUnformatted("D1R"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Decay 1 Rate"); + } + ImGui::TableNextColumn(); + CENTER_TEXT("DL"); + ImGui::TextUnformatted("DL"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Decay Level"); + } + ImGui::TableNextColumn(); + CENTER_TEXT("D2R"); + ImGui::TextUnformatted("D2R"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Decay 2 Rate"); + } + ImGui::TableNextColumn(); + CENTER_TEXT("RR"); + ImGui::TextUnformatted("RR"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Release Rate"); + } + ImGui::TableNextColumn(); + CENTER_TEXT("RC"); + ImGui::TextUnformatted("RC"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Rate Correction"); + } + ImGui::TableNextColumn(); + CENTER_TEXT("Envelope"); + ImGui::TextUnformatted("Envelope"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + P(CWVSliderScalar("##Attack Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.ar,&_ZERO,&_FIFTEEN)); + ImGui::TableNextColumn(); + P(CWVSliderScalar("##Decay 1 Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.d1r,&_ZERO,&_FIFTEEN)); + ImGui::TableNextColumn(); + P(CWVSliderScalar("##Decay Level",sliderSize,ImGuiDataType_U8,&ins->multipcm.dl,&_ZERO,&_FIFTEEN)); + ImGui::TableNextColumn(); + P(CWVSliderScalar("##Decay 2 Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.d2r,&_ZERO,&_FIFTEEN)); + ImGui::TableNextColumn(); + P(CWVSliderScalar("##Release Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.rr,&_ZERO,&_FIFTEEN)); + ImGui::TableNextColumn(); + P(CWVSliderScalar("##Rate Correction",sliderSize,ImGuiDataType_U8,&ins->multipcm.rc,&_ZERO,&_FIFTEEN)); + ImGui::TableNextColumn(); + drawFMEnv(0,ins->multipcm.ar,ins->multipcm.d1r,ins->multipcm.d2r,ins->multipcm.rr,ins->multipcm.dl,0,0,0,127,15,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); + ImGui::EndTable(); + } + if (ImGui::BeginTable("MultiPCMLFOParams",3,ImGuiTableFlags_SizingStretchSame)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.0); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.0); + ImGui::TableNextColumn(); + P(CWSliderScalar("LFO Rate",ImGuiDataType_U8,&ins->multipcm.lfo,&_ZERO,&_SEVEN)); rightClickable + ImGui::TableNextColumn(); + P(CWSliderScalar("PM Depth",ImGuiDataType_U8,&ins->multipcm.vib,&_ZERO,&_SEVEN)); rightClickable + ImGui::TableNextColumn(); + P(CWSliderScalar("AM Depth",ImGuiDataType_U8,&ins->multipcm.am,&_ZERO,&_SEVEN)); rightClickable + ImGui::EndTable(); + } + ImGui::EndTabItem(); + } + } if (ins->type==DIV_INS_GB || - ins->type==DIV_INS_AMIGA || + (ins->type==DIV_INS_AMIGA && ins->amiga.useWave) || ins->type==DIV_INS_X1_010 || ins->type==DIV_INS_N163 || ins->type==DIV_INS_FDS || @@ -2480,9 +2719,6 @@ void FurnaceGUI::drawInsEdit() { } } if (ImGui::BeginTabItem("Macros")) { - float asFloat[256]; - int asInt[256]; - float loopIndicator[256]; const char* volumeLabel="Volume"; int volMax=15; @@ -2493,8 +2729,8 @@ void FurnaceGUI::drawInsEdit() { if (ins->c64.filterIsAbs) { volMax=2047; } else { - volMin=-18; - volMax=18; + volMin=-64; + volMax=64; } } } @@ -2507,7 +2743,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_AMIGA) { volMax=64; } - if (ins->type==DIV_INS_FM || ins->type == DIV_INS_MIKEY) { + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU) { volMax=127; } if (ins->type==DIV_INS_GB) { @@ -2520,16 +2756,16 @@ void FurnaceGUI::drawInsEdit() { volMax=32; } - bool arpMode=ins->std.arpMacro.mode; - const char* dutyLabel="Duty/Noise"; + int dutyMin=0; int dutyMax=3; if (ins->type==DIV_INS_C64) { dutyLabel="Duty"; if (ins->c64.dutyIsAbs) { dutyMax=4095; } else { - dutyMax=24; + dutyMin=-96; + dutyMax=96; } } if (ins->type==DIV_INS_FM) { @@ -2558,7 +2794,7 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Noise"; dutyMax=8; } - if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_VRC6_SAW || ins->type==DIV_INS_FDS) { + if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_VRC6_SAW || ins->type==DIV_INS_FDS || ins->type==DIV_INS_MULTIPCM) { dutyMax=0; } if (ins->type==DIV_INS_VERA) { @@ -2573,7 +2809,9 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Duty"; dutyMax=7; } - bool dutyIsRel=(ins->type==DIV_INS_C64 && !ins->c64.dutyIsAbs); + if (ins->type==DIV_INS_SU) { + dutyMax=127; + } const char* waveLabel="Waveform"; int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VERA)?3:63; @@ -2587,6 +2825,8 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_SAA1099) waveMax=2; if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) waveMax=0; if (ins->type==DIV_INS_MIKEY) waveMax=0; + if (ins->type==DIV_INS_MULTIPCM) waveMax=0; + if (ins->type==DIV_INS_SU) waveMax=7; if (ins->type==DIV_INS_PET) { waveMax=8; bitMode=true; @@ -2622,329 +2862,153 @@ void FurnaceGUI::drawInsEdit() { ex1Max=63; ex2Max=4095; } + if (ins->type==DIV_INS_SU) { + ex1Max=16383; + ex2Max=255; + } if (ins->type==DIV_INS_SAA1099) ex1Max=8; - if (settings.macroView==0) { // modern view - MACRO_BEGIN(28*dpiScale); - if (volMax>0) { - NORMAL_MACRO(ins->std.volMacro,volMin,volMax,"vol",volumeLabel,160,ins->std.volMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_VOLUME],mmlString[0],volMin,volMax,NULL,false); - } - NORMAL_MACRO(ins->std.arpMacro,arpMacroScroll,arpMacroScroll+24,"arp","Arpeggio",160,ins->std.arpMacro.open,false,NULL,true,&arpMacroScroll,(arpMode?-60:-80),0,0,true,1,macroAbsoluteMode,uiColors[GUI_COLOR_MACRO_PITCH],mmlString[1],-92,94,(ins->std.arpMacro.mode?(¯oHoverNote):NULL),true); - if (dutyMax>0) { - if (ins->type==DIV_INS_MIKEY) { - NORMAL_MACRO(ins->std.dutyMacro,0,dutyMax,"duty",dutyLabel,160,ins->std.dutyMacro.open,true,mikeyFeedbackBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,dutyMax,NULL,false); - } else if (ins->type==DIV_INS_C64) { - NORMAL_MACRO(ins->std.dutyMacro,0,dutyMax,"duty",dutyLabel,160,ins->std.dutyMacro.open,false,NULL,false,NULL,0,0,0,true,1,macroAbsoluteMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,dutyMax,NULL,false); - } else { - NORMAL_MACRO(ins->std.dutyMacro,0,dutyMax,"duty",dutyLabel,160,ins->std.dutyMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,dutyMax,NULL,false); - } - } - if (waveMax>0) { - NORMAL_MACRO(ins->std.waveMacro,0,waveMax,"wave",waveLabel,(bitMode && ins->type!=DIV_INS_PET)?64:160,ins->std.waveMacro.open,bitMode,waveNames,false,NULL,0,0,((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?1:0),false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[3],0,waveMax,NULL,false); - } - if (ex1Max>0) { - if (ins->type==DIV_INS_C64) { - NORMAL_MACRO(ins->std.ex1Macro,0,ex1Max,"ex1","Filter Mode",64,ins->std.ex1Macro.open,true,filtModeBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); - } else if (ins->type==DIV_INS_SAA1099) { - NORMAL_MACRO(ins->std.ex1Macro,0,ex1Max,"ex1","Envelope",160,ins->std.ex1Macro.open,true,saaEnvBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); - } else if (ins->type==DIV_INS_X1_010) { - NORMAL_MACRO(ins->std.ex1Macro,0,ex1Max,"ex1","Envelope Mode",160,ins->std.ex1Macro.open,true,x1_010EnvBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); - } else if (ins->type==DIV_INS_N163) { - NORMAL_MACRO(ins->std.ex1Macro,0,ex1Max,"ex1","Waveform len.",160,ins->std.ex1Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); - } else if (ins->type==DIV_INS_FDS) { - NORMAL_MACRO(ins->std.ex1Macro,0,ex1Max,"ex1","Mod Depth",160,ins->std.ex1Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); - } else { - NORMAL_MACRO(ins->std.ex1Macro,0,ex1Max,"ex1","Duty",160,ins->std.ex1Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); - } - } - if (ex2Max>0) { - if (ins->type==DIV_INS_C64) { - NORMAL_MACRO(ins->std.ex2Macro,0,ex2Max,"ex2","Resonance",64,ins->std.ex2Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); - } else if (ins->type==DIV_INS_N163) { - NORMAL_MACRO(ins->std.ex2Macro,0,ex2Max,"ex2","Waveform update",64,ins->std.ex2Macro.open,true,n163UpdateBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); - } else if (ins->type==DIV_INS_FDS) { - NORMAL_MACRO(ins->std.ex2Macro,0,ex2Max,"ex2","Mod Speed",160,ins->std.ex2Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); - } else { - NORMAL_MACRO(ins->std.ex2Macro,0,ex2Max,"ex2","Envelope",ex2Bit?64:160,ins->std.ex2Macro.open,ex2Bit,ayEnvBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); - } - } - if (ins->type==DIV_INS_C64) { - NORMAL_MACRO(ins->std.ex3Macro,0,2,"ex3","Special",32,ins->std.ex3Macro.open,true,c64SpecialBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,2,NULL,false); - } - if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_X1_010) { - NORMAL_MACRO(ins->std.ex3Macro,0,15,"ex3","AutoEnv Num",96,ins->std.ex3Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,15,NULL,false); - NORMAL_MACRO(ins->std.algMacro,0,15,"alg","AutoEnv Den",96,ins->std.algMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,15,NULL,false); - } - if (ins->type==DIV_INS_AY8930) { - // oh my i am running out of macros - NORMAL_MACRO(ins->std.fbMacro,0,8,"fb","Noise AND Mask",96,ins->std.fbMacro.open,true,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,8,NULL,false); - NORMAL_MACRO(ins->std.fmsMacro,0,8,"fms","Noise OR Mask",96,ins->std.fmsMacro.open,true,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[9],0,8,NULL,false); - } - if (ins->type==DIV_INS_N163) { - NORMAL_MACRO(ins->std.ex3Macro,0,255,"ex3","Waveform to Load",160,ins->std.ex3Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,255,NULL,false); - NORMAL_MACRO(ins->std.algMacro,0,255,"alg","Wave pos. to Load",160,ins->std.algMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,255,NULL,false); - NORMAL_MACRO(ins->std.fbMacro,0,252,"fb","Wave len. to Load",160,ins->std.fbMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,252,NULL,false); - NORMAL_MACRO(ins->std.fmsMacro,0,2,"fms","Waveform load",64,ins->std.fmsMacro.open,true,n163UpdateBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[9],0,2,NULL,false); - } - if (ins->type==DIV_INS_FDS) { - NORMAL_MACRO(ins->std.ex3Macro,0,127,"ex3","Mod Position",160,ins->std.ex3Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,2,NULL,false); - } + int panMin=0; + int panMax=0; + bool panSingle=false; + bool panSingleNoBit=false; + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_GB || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_VERA) { + panMax=1; + panSingle=true; + } + if (ins->type==DIV_INS_AMIGA) { + panMax=127; + } + if (ins->type==DIV_INS_X1_010 || ins->type==DIV_INS_PCE || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_SAA1099) { + panMax=15; + } + if (ins->type==DIV_INS_AMIGA && ins->std.panLMacro.mode) { + panMin=-16; + panMax=16; + } + if (ins->type==DIV_INS_MULTIPCM) { + panMin=-7; + panMax=7; + panSingleNoBit=true; + } + if (ins->type==DIV_INS_SU) { + panMin=-127; + panMax=127; + panSingleNoBit=true; + } - MACRO_END; - } else { // classic view - // volume macro - ImGui::Separator(); - if (ins->type==DIV_INS_C64 && ins->c64.volIsCutoff) { - if (ins->c64.filterIsAbs) { - ImGui::Text("Cutoff Macro"); - } else { - ImGui::Text("Relative Cutoff Macro"); - } + if (volMax>0) { + macroList.push_back(FurnaceGUIMacroDesc(volumeLabel,&ins->std.volMacro,volMin,volMax,160,uiColors[GUI_COLOR_MACRO_VOLUME])); + } + macroList.push_back(FurnaceGUIMacroDesc("Arpeggio",&ins->std.arpMacro,-120,120,160,uiColors[GUI_COLOR_MACRO_PITCH],false,macroAbsoluteMode,ins->std.arpMacro.mode?(¯oHoverNote):NULL)); + if (dutyMax>0) { + if (ins->type==DIV_INS_MIKEY) { + macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,mikeyFeedbackBits)); } else { - ImGui::Text("Volume Macro"); + macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,dutyMin,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER])); } - for (int i=0; istd.volMacro.len; i++) { - if (ins->type==DIV_INS_C64 && ins->c64.volIsCutoff && !ins->c64.filterIsAbs) { - asFloat[i]=ins->std.volMacro.val[i]-18; + } + if (waveMax>0) { + macroList.push_back(FurnaceGUIMacroDesc(waveLabel,&ins->std.waveMacro,0,waveMax,(bitMode && ins->type!=DIV_INS_PET)?64:160,uiColors[GUI_COLOR_MACRO_WAVE],false,NULL,NULL,bitMode,waveNames,((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?1:0))); + } + if (panMax>0) { + if (panSingle) { + macroList.push_back(FurnaceGUIMacroDesc("Panning",&ins->std.panLMacro,0,2,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,panBits)); + } else { + if (panSingleNoBit || (ins->type==DIV_INS_AMIGA && ins->std.panLMacro.mode)) { + macroList.push_back(FurnaceGUIMacroDesc("Panning",&ins->std.panLMacro,panMin,panMax,(31+panMax-panMin),uiColors[GUI_COLOR_MACRO_OTHER],false,(ins->type==DIV_INS_AMIGA)?macroQSoundMode:NULL)); } else { - asFloat[i]=ins->std.volMacro.val[i]; + macroList.push_back(FurnaceGUIMacroDesc("Panning (left)",&ins->std.panLMacro,panMin,panMax,(31+panMax-panMin),uiColors[GUI_COLOR_MACRO_OTHER],false,(ins->type==DIV_INS_AMIGA)?macroQSoundMode:NULL)); } - loopIndicator[i]=(ins->std.volMacro.loop!=-1 && i>=ins->std.volMacro.loop); - } - macroDragScroll=0; - if (volMax>0) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); - ImGui::PlotHistogram("##IVolMacro",asFloat,ins->std.volMacro.len,0,NULL,volMin,volMax,ImVec2(400.0f*dpiScale,200.0f*dpiScale)); - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - macroDragStart=ImGui::GetItemRectMin(); - macroDragAreaSize=ImVec2(400.0f*dpiScale,200.0f*dpiScale); - macroDragMin=volMin; - macroDragMax=volMax; - macroDragLen=ins->std.volMacro.len; - macroDragActive=true; - macroDragTarget=ins->std.volMacro.val; - macroDragChar=false; - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); - } - ImGui::PlotHistogram("##IVolMacro.loop",loopIndicator,ins->std.volMacro.len,0,NULL,0,1,ImVec2(400.0f*dpiScale,16.0f*dpiScale)); - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - macroLoopDragStart=ImGui::GetItemRectMin(); - macroLoopDragAreaSize=ImVec2(400.0f*dpiScale,16.0f*dpiScale); - macroLoopDragLen=ins->std.volMacro.len; - macroLoopDragTarget=&ins->std.volMacro.loop; - macroLoopDragActive=true; - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); - } - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - ins->std.volMacro.loop=-1; - } - ImGui::PopStyleVar(); - if (ImGui::InputScalar("Length##IVolMacroL",ImGuiDataType_U8,&ins->std.volMacro.len,&_ONE,&_THREE)) { - if (ins->std.volMacro.len>127) ins->std.volMacro.len=127; - } - } - - // arp macro - ImGui::Separator(); - ImGui::Text("Arpeggio Macro"); - for (int i=0; istd.arpMacro.len; i++) { - asFloat[i]=ins->std.arpMacro.val[i]; - loopIndicator[i]=(ins->std.arpMacro.loop!=-1 && i>=ins->std.arpMacro.loop); - } - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); - ImGui::PlotHistogram("##IArpMacro",asFloat,ins->std.arpMacro.len,0,NULL,arpMode?arpMacroScroll:(arpMacroScroll-12),arpMacroScroll+(arpMode?24:12),ImVec2(400.0f*dpiScale,200.0f*dpiScale)); - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - macroDragStart=ImGui::GetItemRectMin(); - macroDragAreaSize=ImVec2(400.0f*dpiScale,200.0f*dpiScale); - macroDragMin=arpMacroScroll; - macroDragMax=arpMacroScroll+24; - macroDragLen=ins->std.arpMacro.len; - macroDragActive=true; - macroDragTarget=ins->std.arpMacro.val; - macroDragChar=false; - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); - } - ImGui::SameLine(); - CWVSliderInt("##IArpMacroPos",ImVec2(20.0f*dpiScale,200.0f*dpiScale),&arpMacroScroll,arpMode?0:-80,70); - ImGui::PlotHistogram("##IArpMacro.loop",loopIndicator,ins->std.arpMacro.len,0,NULL,0,1,ImVec2(400.0f*dpiScale,16.0f*dpiScale)); - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - macroLoopDragStart=ImGui::GetItemRectMin(); - macroLoopDragAreaSize=ImVec2(400.0f*dpiScale,16.0f*dpiScale); - macroLoopDragLen=ins->std.arpMacro.len; - macroLoopDragTarget=&ins->std.arpMacro.loop; - macroLoopDragActive=true; - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); - } - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - ins->std.arpMacro.loop=-1; - } - ImGui::PopStyleVar(); - if (ImGui::InputScalar("Length##IArpMacroL",ImGuiDataType_U8,&ins->std.arpMacro.len,&_ONE,&_THREE)) { - if (ins->std.arpMacro.len>127) ins->std.arpMacro.len=127; - } - if (ImGui::Checkbox("Fixed",&arpMode)) { - ins->std.arpMacro.mode=arpMode; - if (arpMode) { - if (arpMacroScroll<0) arpMacroScroll=0; - } - } - - // duty macro - if (dutyMax>0) { - ImGui::Separator(); - if (ins->type==DIV_INS_C64) { - if (ins->c64.dutyIsAbs) { - ImGui::Text("Duty Macro"); + if (!panSingleNoBit) { + if (ins->type==DIV_INS_AMIGA && ins->std.panLMacro.mode) { + macroList.push_back(FurnaceGUIMacroDesc("Surround",&ins->std.panRMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); } else { - ImGui::Text("Relative Duty Macro"); + macroList.push_back(FurnaceGUIMacroDesc("Panning (right)",&ins->std.panRMacro,panMin,panMax,(31+panMax-panMin),uiColors[GUI_COLOR_MACRO_OTHER])); } - } else { - if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) { - ImGui::Text("Noise Frequency Macro"); - } else { - ImGui::Text("Duty/Noise Mode Macro"); - } - } - for (int i=0; istd.dutyMacro.len; i++) { - asFloat[i]=ins->std.dutyMacro.val[i]-(dutyIsRel?12:0); - loopIndicator[i]=(ins->std.dutyMacro.loop!=-1 && i>=ins->std.dutyMacro.loop); - } - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); - - ImGui::PlotHistogram("##IDutyMacro",asFloat,ins->std.dutyMacro.len,0,NULL,dutyIsRel?-12:0,dutyMax-(dutyIsRel?12:0),ImVec2(400.0f*dpiScale,200.0f*dpiScale)); - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - macroDragStart=ImGui::GetItemRectMin(); - macroDragAreaSize=ImVec2(400.0f*dpiScale,200.0f*dpiScale); - macroDragMin=0; - macroDragMax=dutyMax; - macroDragLen=ins->std.dutyMacro.len; - macroDragActive=true; - macroDragTarget=ins->std.dutyMacro.val; - macroDragChar=false; - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); - } - ImGui::PlotHistogram("##IDutyMacro.loop",loopIndicator,ins->std.dutyMacro.len,0,NULL,0,1,ImVec2(400.0f*dpiScale,16.0f*dpiScale)); - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - macroLoopDragStart=ImGui::GetItemRectMin(); - macroLoopDragAreaSize=ImVec2(400.0f*dpiScale,16.0f*dpiScale); - macroLoopDragLen=ins->std.dutyMacro.len; - macroLoopDragTarget=&ins->std.dutyMacro.loop; - macroLoopDragActive=true; - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); - } - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - ins->std.dutyMacro.loop=-1; - } - ImGui::PopStyleVar(); - if (ImGui::InputScalar("Length##IDutyMacroL",ImGuiDataType_U8,&ins->std.dutyMacro.len,&_ONE,&_THREE)) { - if (ins->std.dutyMacro.len>127) ins->std.dutyMacro.len=127; - } - } - - // wave macro - if (waveMax>0) { - ImGui::Separator(); - ImGui::Text("Waveform Macro"); - for (int i=0; istd.waveMacro.len; i++) { - asFloat[i]=ins->std.waveMacro.val[i]; - if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930) { - asInt[i]=ins->std.waveMacro.val[i]+1; - } else { - asInt[i]=ins->std.waveMacro.val[i]; - } - loopIndicator[i]=(ins->std.waveMacro.loop!=-1 && i>=ins->std.waveMacro.loop); - } - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); - - ImVec2 areaSize=ImVec2(400.0f*dpiScale,200.0f*dpiScale); - if (ins->type==DIV_INS_C64 || ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) { - areaSize=ImVec2(400.0f*dpiScale,waveMax*32.0f*dpiScale); - PlotBitfield("##IWaveMacro",asInt,ins->std.waveMacro.len,0,(ins->type==DIV_INS_C64)?c64ShapeBits:ayShapeBits,waveMax,areaSize); - bitMode=true; - } else { - ImGui::PlotHistogram("##IWaveMacro",asFloat,ins->std.waveMacro.len,0,NULL,0,waveMax,areaSize); - } - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - macroDragStart=ImGui::GetItemRectMin(); - macroDragAreaSize=areaSize; - macroDragMin=0; - macroDragMax=waveMax; - macroDragBitOff=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?1:0; - macroDragBitMode=bitMode; - macroDragInitialValueSet=false; - macroDragInitialValue=false; - macroDragLen=ins->std.waveMacro.len; - macroDragActive=true; - macroDragTarget=ins->std.waveMacro.val; - macroDragChar=false; - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); - } - ImGui::PlotHistogram("##IWaveMacro.loop",loopIndicator,ins->std.waveMacro.len,0,NULL,0,1,ImVec2(400.0f*dpiScale,16.0f*dpiScale)); - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - macroLoopDragStart=ImGui::GetItemRectMin(); - macroLoopDragAreaSize=ImVec2(400.0f*dpiScale,16.0f*dpiScale); - macroLoopDragLen=ins->std.waveMacro.len; - macroLoopDragTarget=&ins->std.waveMacro.loop; - macroLoopDragActive=true; - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); - } - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - ins->std.waveMacro.loop=-1; - } - ImGui::PopStyleVar(); - if (ImGui::InputScalar("Length##IWaveMacroL",ImGuiDataType_U8,&ins->std.waveMacro.len,&_ONE,&_THREE)) { - if (ins->std.waveMacro.len>127) ins->std.waveMacro.len=127; - } - } - - // extra 1 macro - if (ex1Max>0) { - ImGui::Separator(); - if (ins->type==DIV_INS_AY8930) { - ImGui::Text("Duty Macro"); - } else { - ImGui::Text("Extra 1 Macro"); - } - for (int i=0; istd.ex1Macro.len; i++) { - asFloat[i]=ins->std.ex1Macro.val[i]; - loopIndicator[i]=(ins->std.ex1Macro.loop!=-1 && i>=ins->std.ex1Macro.loop); - } - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); - - ImGui::PlotHistogram("##IEx1Macro",asFloat,ins->std.ex1Macro.len,0,NULL,0,ex1Max,ImVec2(400.0f*dpiScale,200.0f*dpiScale)); - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - macroDragStart=ImGui::GetItemRectMin(); - macroDragAreaSize=ImVec2(400.0f*dpiScale,200.0f*dpiScale); - macroDragMin=0; - macroDragMax=ex1Max; - macroDragLen=ins->std.ex1Macro.len; - macroDragActive=true; - macroDragTarget=ins->std.ex1Macro.val; - macroDragChar=false; - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); - } - ImGui::PlotHistogram("##IEx1Macro.loop",loopIndicator,ins->std.ex1Macro.len,0,NULL,0,1,ImVec2(400.0f*dpiScale,16.0f*dpiScale)); - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - macroLoopDragStart=ImGui::GetItemRectMin(); - macroLoopDragAreaSize=ImVec2(400.0f*dpiScale,16.0f*dpiScale); - macroLoopDragLen=ins->std.ex1Macro.len; - macroLoopDragTarget=&ins->std.ex1Macro.loop; - macroLoopDragActive=true; - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); - } - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - ins->std.ex1Macro.loop=-1; - } - ImGui::PopStyleVar(); - if (ImGui::InputScalar("Length##IEx1MacroL",ImGuiDataType_U8,&ins->std.ex1Macro.len,&_ONE,&_THREE)) { - if (ins->std.ex1Macro.len>127) ins->std.ex1Macro.len=127; } } } + macroList.push_back(FurnaceGUIMacroDesc("Pitch",&ins->std.pitchMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,macroRelativeMode)); + if (ins->type==DIV_INS_FM || + ins->type==DIV_INS_STD || + ins->type==DIV_INS_OPL || + ins->type==DIV_INS_OPZ || + ins->type==DIV_INS_PCE || + ins->type==DIV_INS_GB || + ins->type==DIV_INS_AMIGA || + ins->type==DIV_INS_OPLL || + ins->type==DIV_INS_AY || + ins->type==DIV_INS_AY8930 || + ins->type==DIV_INS_SWAN || + ins->type==DIV_INS_MULTIPCM || + ins->type==DIV_INS_SU) { + macroList.push_back(FurnaceGUIMacroDesc("Phase Reset",&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + } + if (ex1Max>0) { + if (ins->type==DIV_INS_C64) { + macroList.push_back(FurnaceGUIMacroDesc("Filter Mode",&ins->std.ex1Macro,0,ex1Max,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,filtModeBits)); + } else if (ins->type==DIV_INS_SAA1099) { + macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,saaEnvBits)); + } else if (ins->type==DIV_INS_X1_010) { + macroList.push_back(FurnaceGUIMacroDesc("Envelope Mode",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,x1_010EnvBits)); + } else if (ins->type==DIV_INS_N163) { + macroList.push_back(FurnaceGUIMacroDesc("Wave Length",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); + } else if (ins->type==DIV_INS_FDS) { + macroList.push_back(FurnaceGUIMacroDesc("Mod Depth",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); + } else if (ins->type==DIV_INS_SU) { + macroList.push_back(FurnaceGUIMacroDesc("Cutoff",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); + } else { + macroList.push_back(FurnaceGUIMacroDesc("Duty",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); + } + } + if (ex2Max>0) { + if (ins->type==DIV_INS_C64) { + macroList.push_back(FurnaceGUIMacroDesc("Resonance",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_OTHER])); + } else if (ins->type==DIV_INS_N163) { + macroList.push_back(FurnaceGUIMacroDesc("Wave Update",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,n163UpdateBits)); + } else if (ins->type==DIV_INS_FDS) { + macroList.push_back(FurnaceGUIMacroDesc("Mod Speed",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); + } else if (ins->type==DIV_INS_SU) { + macroList.push_back(FurnaceGUIMacroDesc("Resonance",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); + } else { + macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex2Macro,0,ex2Max,ex2Bit?64:160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,ex2Bit,ayEnvBits)); + } + } + if (ins->type==DIV_INS_C64) { + macroList.push_back(FurnaceGUIMacroDesc("Special",&ins->std.ex3Macro,0,2,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,c64SpecialBits)); + macroList.push_back(FurnaceGUIMacroDesc("Test/Gate",&ins->std.ex4Macro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + } + if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_X1_010) { + macroList.push_back(FurnaceGUIMacroDesc("AutoEnv Num",&ins->std.ex3Macro,0,15,160,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("AutoEnv Den",&ins->std.algMacro,0,15,160,uiColors[GUI_COLOR_MACRO_OTHER])); + } + if (ins->type==DIV_INS_AY8930) { + // oh my i am running out of macros + macroList.push_back(FurnaceGUIMacroDesc("Noise AND Mask",&ins->std.fbMacro,0,8,96,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + macroList.push_back(FurnaceGUIMacroDesc("Noise OR Mask",&ins->std.fmsMacro,0,8,96,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + } + if (ins->type==DIV_INS_N163) { + macroList.push_back(FurnaceGUIMacroDesc("WaveLoad Wave",&ins->std.ex3Macro,0,255,160,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("WaveLoad Pos",&ins->std.algMacro,0,255,160,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("WaveLoad Len",&ins->std.fbMacro,0,252,160,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("WaveLoad Trigger",&ins->std.fmsMacro,0,2,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,n163UpdateBits)); + } + if (ins->type==DIV_INS_FDS) { + macroList.push_back(FurnaceGUIMacroDesc("Mod Position",&ins->std.ex3Macro,0,127,160,uiColors[GUI_COLOR_MACRO_OTHER])); + } + if (ins->type==DIV_INS_SU) { + macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.ex3Macro,0,4,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,suControlBits)); + } + + drawMacros(macroList); ImGui::EndTabItem(); } ImGui::EndTabBar(); } + if (settings.insEditColorize) { + popAccentColors(); + } } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_EDIT; diff --git a/src/gui/log.cpp b/src/gui/log.cpp index c6c4e2a86..b5f4d3556 100644 --- a/src/gui/log.cpp +++ b/src/gui/log.cpp @@ -1,3 +1,22 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + #include "gui.h" #include "../ta-log.h" #include @@ -73,5 +92,6 @@ void FurnaceGUI::drawLog() { ImGui::EndTable(); } } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_LOG; ImGui::End(); } diff --git a/src/gui/newSong.cpp b/src/gui/newSong.cpp index d6188b3e5..2bf11fb5c 100644 --- a/src/gui/newSong.cpp +++ b/src/gui/newSong.cpp @@ -66,6 +66,22 @@ void FurnaceGUI::drawNewSong() { ImGui::EndTable(); } + if (ImGui::Button("I'm feeling lucky")) { + if (sysCategories.size()==0) { + ImGui::CloseCurrentPopup(); + } else { + FurnaceGUISysCategory* newSystemCat=&sysCategories[rand()%sysCategories.size()]; + if (newSystemCat->systems.size()==0) { + ImGui::CloseCurrentPopup(); + } else { + nextDesc=newSystemCat->systems[rand()%newSystemCat->systems.size()].definition.data(); + accepted=true; + } + } + } + + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { ImGui::CloseCurrentPopup(); } @@ -87,4 +103,4 @@ void FurnaceGUI::drawNewSong() { updateWindowTitle(); ImGui::CloseCurrentPopup(); } -} \ No newline at end of file +} diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index 945f551d7..2311a3980 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -47,10 +47,9 @@ void FurnaceGUI::drawOrders() { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,prevSpacing); ImGui::TableSetupScrollFreeze(1,1); float lineHeight=(ImGui::GetTextLineHeight()+4*dpiScale); - int curOrder=e->getOrder(); if (e->isPlaying()) { if (followOrders) { - ImGui::SetScrollY((curOrder+1)*lineHeight-(ImGui::GetContentRegionAvail().y/2)); + ImGui::SetScrollY((e->getOrder()+1)*lineHeight-(ImGui::GetContentRegionAvail().y/2)); } } ImGui::TableNextRow(0,lineHeight); @@ -75,7 +74,7 @@ void FurnaceGUI::drawOrders() { snprintf(selID,4096,"%d##O_S%.2x",i,i); } if (ImGui::Selectable(selID)) { - e->setOrder(i); + setOrder(i); curNibble=false; orderCursor=-1; @@ -115,7 +114,7 @@ void FurnaceGUI::drawOrders() { curNibble=false; } } else { - e->setOrder(i); + setOrder(i); e->walkSong(loopOrder,loopRow,loopEnd); if (orderEditMode!=0) { orderCursor=j; @@ -151,7 +150,7 @@ void FurnaceGUI::drawOrders() { curNibble=false; } } else { - e->setOrder(i); + setOrder(i); e->walkSong(loopOrder,loopRow,loopEnd); if (orderEditMode!=0) { orderCursor=j; diff --git a/src/gui/osc.cpp b/src/gui/osc.cpp index c0ce978cf..030a84e9c 100644 --- a/src/gui/osc.cpp +++ b/src/gui/osc.cpp @@ -19,14 +19,18 @@ #include "gui.h" #include "imgui_internal.h" -#include -#include +// TODO: +// - potentially move oscilloscope seek position to the end, and read the last samples +// - this allows for setting up the window size void FurnaceGUI::readOsc() { int writePos=e->oscWritePos; int readPos=e->oscReadPos; int avail=0; int total=0; + if (firstFrame) { + readPos=writePos; + } if (writePos>=readPos) { avail=writePos-readPos; } else { @@ -45,12 +49,19 @@ void FurnaceGUI::readOsc() { for (int i=0; i<512; i++) { int pos=(readPos+(i*total/512))&0x7fff; oscValues[i]=(e->oscBuf[0][pos]+e->oscBuf[1][pos])*0.5f; + if (oscValues[i]>0.001f || oscValues[i]<-0.001f) { + WAKE_UP; + } } float peakDecay=0.05f*60.0f*ImGui::GetIO().DeltaTime; for (int i=0; i<2; i++) { peak[i]*=1.0-peakDecay; - if (peak[i]<0.0001) peak[i]=0.0; + if (peak[i]<0.0001) { + peak[i]=0.0; + } else { + WAKE_UP; + } float newPeak=peak[i]; for (int j=0; jAddLine( ImLerp(rect.Min,rect.Max,ImVec2(0.0f,0.5f)), - ImLerp(rect.Min,rect.Max,ImVec2(0.0f,0.5f)), + ImLerp(rect.Min,rect.Max,ImVec2(1.0f,0.5f)), refColor, dpiScale ); + dl->AddLine( + ImLerp(rect.Min,rect.Max,ImVec2(0.48f,0.125f)), + ImLerp(rect.Min,rect.Max,ImVec2(0.52f,0.125f)), + guideColor, + dpiScale + ); + + dl->AddLine( + ImLerp(rect.Min,rect.Max,ImVec2(0.47f,0.25f)), + ImLerp(rect.Min,rect.Max,ImVec2(0.53f,0.25f)), + guideColor, + dpiScale + ); + + dl->AddLine( + ImLerp(rect.Min,rect.Max,ImVec2(0.45f,0.375f)), + ImLerp(rect.Min,rect.Max,ImVec2(0.55f,0.375f)), + guideColor, + dpiScale + ); + + dl->AddLine( + ImLerp(rect.Min,rect.Max,ImVec2(0.45f,0.625f)), + ImLerp(rect.Min,rect.Max,ImVec2(0.55f,0.625f)), + guideColor, + dpiScale + ); + + dl->AddLine( + ImLerp(rect.Min,rect.Max,ImVec2(0.47f,0.75f)), + ImLerp(rect.Min,rect.Max,ImVec2(0.53f,0.75f)), + guideColor, + dpiScale + ); + + dl->AddLine( + ImLerp(rect.Min,rect.Max,ImVec2(0.48f,0.875f)), + ImLerp(rect.Min,rect.Max,ImVec2(0.52f,0.875f)), + guideColor, + dpiScale + ); + + dl->AddLine( + ImLerp(rect.Min,rect.Max,ImVec2(0.5f,0.08f)), + ImLerp(rect.Min,rect.Max,ImVec2(0.5f,0.92f)), + guideColor, + dpiScale + ); + for (size_t i=0; i<512; i++) { float x=(float)i/512.0f; float y=oscValues[i]*oscZoom; if (y<-0.5f) y=-0.5f; if (y>0.5f) y=0.5f; - waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5f-y)); + waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y)); } dl->AddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale); if (settings.oscBorder) { diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 0decef970..d284b08fc 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include #define _USE_MATH_DEFINES #include "gui.h" #include "../ta-log.h" @@ -26,68 +27,14 @@ #include "guiConst.h" #include -const FurnaceGUIColors fxColors[16]={ - GUI_COLOR_PATTERN_EFFECT_MISC, // 00 - GUI_COLOR_PATTERN_EFFECT_PITCH, // 01 - GUI_COLOR_PATTERN_EFFECT_PITCH, // 02 - GUI_COLOR_PATTERN_EFFECT_PITCH, // 03 - GUI_COLOR_PATTERN_EFFECT_PITCH, // 04 - GUI_COLOR_PATTERN_EFFECT_VOLUME, // 05 - GUI_COLOR_PATTERN_EFFECT_VOLUME, // 06 - GUI_COLOR_PATTERN_EFFECT_VOLUME, // 07 - GUI_COLOR_PATTERN_EFFECT_PANNING, // 08 - GUI_COLOR_PATTERN_EFFECT_SPEED, // 09 - GUI_COLOR_PATTERN_EFFECT_VOLUME, // 0A - GUI_COLOR_PATTERN_EFFECT_SONG, // 0B - GUI_COLOR_PATTERN_EFFECT_TIME, // 0C - GUI_COLOR_PATTERN_EFFECT_SONG, // 0D - GUI_COLOR_PATTERN_EFFECT_INVALID, // 0E - GUI_COLOR_PATTERN_EFFECT_SPEED, // 0F -}; - -const FurnaceGUIColors extFxColors[32]={ - GUI_COLOR_PATTERN_EFFECT_MISC, // E0 - GUI_COLOR_PATTERN_EFFECT_PITCH, // E1 - GUI_COLOR_PATTERN_EFFECT_PITCH, // E2 - GUI_COLOR_PATTERN_EFFECT_MISC, // E3 - GUI_COLOR_PATTERN_EFFECT_MISC, // E4 - GUI_COLOR_PATTERN_EFFECT_PITCH, // E5 - GUI_COLOR_PATTERN_EFFECT_INVALID, // E6 - GUI_COLOR_PATTERN_EFFECT_INVALID, // E7 - GUI_COLOR_PATTERN_EFFECT_INVALID, // E8 - GUI_COLOR_PATTERN_EFFECT_INVALID, // E9 - GUI_COLOR_PATTERN_EFFECT_MISC, // EA - GUI_COLOR_PATTERN_EFFECT_MISC, // EB - GUI_COLOR_PATTERN_EFFECT_TIME, // EC - GUI_COLOR_PATTERN_EFFECT_TIME, // ED - GUI_COLOR_PATTERN_EFFECT_SONG, // EE - GUI_COLOR_PATTERN_EFFECT_SONG, // EF - GUI_COLOR_PATTERN_EFFECT_SPEED, // F0 - GUI_COLOR_PATTERN_EFFECT_PITCH, // F1 - GUI_COLOR_PATTERN_EFFECT_PITCH, // F2 - GUI_COLOR_PATTERN_EFFECT_VOLUME, // F3 - GUI_COLOR_PATTERN_EFFECT_VOLUME, // F4 - GUI_COLOR_PATTERN_EFFECT_INVALID, // F5 - GUI_COLOR_PATTERN_EFFECT_INVALID, // F6 - GUI_COLOR_PATTERN_EFFECT_INVALID, // F7 - GUI_COLOR_PATTERN_EFFECT_VOLUME, // F8 - GUI_COLOR_PATTERN_EFFECT_VOLUME, // F9 - GUI_COLOR_PATTERN_EFFECT_VOLUME, // FA - GUI_COLOR_PATTERN_EFFECT_INVALID, // FB - GUI_COLOR_PATTERN_EFFECT_INVALID, // FC - GUI_COLOR_PATTERN_EFFECT_INVALID, // FD - GUI_COLOR_PATTERN_EFFECT_INVALID, // FE - GUI_COLOR_PATTERN_EFFECT_SONG, // FF -}; - inline float randRange(float min, float max) { return min+((float)rand()/(float)RAND_MAX)*(max-min); } // draw a pattern row -inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache) { +inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache, bool inhibitSel) { static char id[32]; - bool selectedRow=(i>=sel1.y && i<=sel2.y); + bool selectedRow=(i>=sel1.y && i<=sel2.y && !inhibitSel); ImGui::TableNextRow(0,lineHeight); ImGui::TableNextColumn(); float cursorPosY=ImGui::GetCursorPos().y-ImGui::GetScrollY(); @@ -117,9 +64,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int } // check overflow highlight if (settings.overflowHighlight) { - if (edit && cursor.y==i) { + if (edit && cursor.y==i && curWindowLast==GUI_WINDOW_PATTERN) { ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING])); - } else if (isPlaying && oldRow==i) { + } else if (isPlaying && oldRow==i && ord==e->getOrder()) { ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PLAY_HEAD])); } else if (e->song.hilightB>0 && !(i%e->song.hilightB)) { ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_2])); @@ -128,9 +75,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int } } else { isPushing=true; - if (edit && cursor.y==i) { + if (edit && cursor.y==i && curWindowLast==GUI_WINDOW_PATTERN) { ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING])); - } else if (isPlaying && oldRow==i) { + } else if (isPlaying && oldRow==i && ord==e->getOrder()) { ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PLAY_HEAD])); } else if (e->song.hilightB>0 && !(i%e->song.hilightB)) { ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_2])); @@ -166,9 +113,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int bool selectedNote=selectedRow && (j32>=sel1XSum && j32<=sel2XSum); bool selectedIns=selectedRow && (j32+1>=sel1XSum && j32+1<=sel2XSum); bool selectedVol=selectedRow && (j32+2>=sel1XSum && j32+2<=sel2XSum); - bool cursorNote=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==0); - bool cursorIns=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==1); - bool cursorVol=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==2); + bool cursorNote=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==0 && curWindowLast==GUI_WINDOW_PATTERN); + bool cursorIns=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==1 && curWindowLast==GUI_WINDOW_PATTERN); + bool cursorVol=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==2 && curWindowLast==GUI_WINDOW_PATTERN); // note sprintf(id,"%s##PN_%d_%d",noteName(pat->data[i][0],pat->data[i][1]),i,j); @@ -198,7 +145,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int ImGui::PopStyleColor(); // the following is only visible when the channel is not collapsed - if (!e->song.chanCollapse[j]) { + if (e->song.chanCollapse[j]<3) { // instrument if (pat->data[i][2]==-1) { ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor); @@ -236,7 +183,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int updateSelection(j,1,i); } ImGui::PopStyleColor(); + } + if (e->song.chanCollapse[j]<2) { // volume if (pat->data[i][3]==-1) { sprintf(id,"..##PV_%d_%d",i,j); @@ -268,14 +217,16 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int updateSelection(j,2,i); } ImGui::PopStyleColor(); + } + if (e->song.chanCollapse[j]<1) { // effects - for (int k=0; ksong.pat[j].effectRows; k++) { + for (int k=0; ksong.pat[j].effectCols; k++) { int index=4+(k<<1); bool selectedEffect=selectedRow && (j32+index-1>=sel1XSum && j32+index-1<=sel2XSum); bool selectedEffectVal=selectedRow && (j32+index>=sel1XSum && j32+index<=sel2XSum); - bool cursorEffect=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==index-1); - bool cursorEffectVal=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==index); + bool cursorEffect=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==index-1 && curWindowLast==GUI_WINDOW_PATTERN); + bool cursorEffectVal=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==index && curWindowLast==GUI_WINDOW_PATTERN); // effect if (pat->data[i][index]==-1) { @@ -288,27 +239,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int } else { const unsigned char data=pat->data[i][index]; sprintf(id,"%.2X##PE%d_%d_%d",data,k,i,j); - if (data<0x10) { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[fxColors[data]]); - } else if (data<0x20) { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY]); - } else if (data<0x30) { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY]); - } else if (data<0x48) { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY]); - } else if (data<0x90) { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]); - } else if (data<0xa0) { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_MISC]); - } else if (data<0xc0) { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]); - } else if (data<0xd0) { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SPEED]); - } else if (data<0xe0) { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]); - } else { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[extFxColors[data-0xe0]]); - } + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[fxColors[data]]); } } ImGui::SameLine(0.0f,0.0f); @@ -379,7 +310,7 @@ void FurnaceGUI::drawPattern() { bool inhibitMenu=false; float scrollX=0; - if (e->isPlaying() && followPattern) cursor.y=oldRow; + if (e->isPlaying() && followPattern && (!e->isStepping() || pendingStepUpdate)) cursor.y=oldRow+((pendingStepUpdate)?1:0); demandX=0; sel1=selStart; sel2=selEnd; @@ -408,8 +339,11 @@ void FurnaceGUI::drawPattern() { patWindowSize=ImGui::GetWindowSize(); //char id[32]; ImGui::PushFont(patFont); - int ord=e->isPlaying()?oldOrder:e->getOrder(); - oldOrder=e->getOrder(); + int ord=oldOrder; + if (followPattern) { + curOrder=e->getOrder(); + } + oldOrder=curOrder; int chans=e->getTotalChannelCount(); int displayChans=0; const DivPattern* patCache[DIV_MAX_CHANS]; @@ -425,7 +359,8 @@ void FurnaceGUI::drawPattern() { char chanID[2048]; float lineHeight=(ImGui::GetTextLineHeight()+2*dpiScale); int curRow=e->getRow(); - if (e->isPlaying() && followPattern) updateScroll(curRow); + if (e->isPlaying() && followPattern && (!e->isStepping() || pendingStepUpdate)) updateScroll(curRow); + pendingStepUpdate=false; if (nextScroll>-0.5f) { ImGui::SetScrollY(nextScroll); nextScroll=-1.0f; @@ -476,7 +411,7 @@ void FurnaceGUI::drawPattern() { displayTooltip=true; } else { const char* chName=e->getChannelName(i); - size_t chNameLimit=6+4*e->song.pat[i].effectRows; + size_t chNameLimit=6+4*e->song.pat[i].effectCols; if (strlen(chName)>chNameLimit) { String shortChName=chName; shortChName.resize(chNameLimit-3); @@ -493,6 +428,12 @@ void FurnaceGUI::drawPattern() { ImVec4 chanHeadHover=chanHead; if (e->keyHit[i]) { keyHit[i]=0.2; + if (!muted) { + int note=e->getChanState(i)->note+60; + if (note>=0 && note<180) { + pianoKeyHit[note]=1.0; + } + } e->keyHit[i]=false; } if (settings.guiColorsBase) { @@ -562,23 +503,27 @@ void FurnaceGUI::drawPattern() { snprintf(chanID,2048,"%c##_HCH%d",e->song.chanCollapse[i]?'+':'-',i); ImGui::SetCursorPosX(ImGui::GetCursorPosX()+4.0f*dpiScale); if (ImGui::SmallButton(chanID)) { - e->song.chanCollapse[i]=!e->song.chanCollapse[i]; + if (e->song.chanCollapse[i]==0) { + e->song.chanCollapse[i]=3; + } else if (e->song.chanCollapse[i]>0) { + e->song.chanCollapse[i]--; + } } if (!e->song.chanCollapse[i]) { ImGui::SameLine(); snprintf(chanID,2048,"<##_LCH%d",i); - ImGui::BeginDisabled(e->song.pat[i].effectRows<=1); + ImGui::BeginDisabled(e->song.pat[i].effectCols<=1); if (ImGui::SmallButton(chanID)) { - e->song.pat[i].effectRows--; - if (e->song.pat[i].effectRows<1) e->song.pat[i].effectRows=1; + e->song.pat[i].effectCols--; + if (e->song.pat[i].effectCols<1) e->song.pat[i].effectCols=1; } ImGui::EndDisabled(); ImGui::SameLine(); - ImGui::BeginDisabled(e->song.pat[i].effectRows>=8); + ImGui::BeginDisabled(e->song.pat[i].effectCols>=8); snprintf(chanID,2048,">##_RCH%d",i); if (ImGui::SmallButton(chanID)) { - e->song.pat[i].effectRows++; - if (e->song.pat[i].effectRows>8) e->song.pat[i].effectRows=8; + e->song.pat[i].effectCols++; + if (e->song.pat[i].effectCols>8) e->song.pat[i].effectCols=8; } ImGui::EndDisabled(); } @@ -602,7 +547,7 @@ void FurnaceGUI::drawPattern() { patCache[i]=e->song.pat[i].getPattern(e->song.orders.ord[i][ord-1],true); } for (int i=0; isong.patLen+i-dummyRows+1,e->isPlaying(),lineHeight,chans,ord-1,patCache); + patternRow(e->song.patLen+i-dummyRows+1,e->isPlaying(),lineHeight,chans,ord-1,patCache,true); } } else { for (int i=0; isong.pat[i].getPattern(e->song.orders.ord[i][ord],true); } for (int i=0; isong.patLen; i++) { - patternRow(i,e->isPlaying(),lineHeight,chans,ord,patCache); + patternRow(i,e->isPlaying(),lineHeight,chans,ord,patCache,false); } // next pattern ImGui::BeginDisabled(); @@ -625,7 +570,7 @@ void FurnaceGUI::drawPattern() { patCache[i]=e->song.pat[i].getPattern(e->song.orders.ord[i][ord+1],true); } for (int i=0; i<=dummyRows; i++) { - patternRow(i,e->isPlaying(),lineHeight,chans,ord+1,patCache); + patternRow(i,e->isPlaying(),lineHeight,chans,ord+1,patCache,true); } } else { for (int i=0; i<=dummyRows; i++) { @@ -645,6 +590,44 @@ void FurnaceGUI::drawPattern() { demandScrollX=false; } scrollX=ImGui::GetScrollX(); + + // overflow changes order + // TODO: this is very unreliable and sometimes it can warp you out of the song + if (settings.scrollChangesOrder && !e->isPlaying()) { + if (wheelY!=0) { + if (wheelY>0) { + if (ImGui::GetScrollY()<=0) { + if (haveHitBounds) { + if (curOrder>0) { + setOrder(curOrder-1); + ImGui::SetScrollY(ImGui::GetScrollMaxY()); + updateScroll(e->song.patLen); + } + haveHitBounds=false; + } else { + haveHitBounds=true; + } + } else { + haveHitBounds=false; + } + } else { + if (ImGui::GetScrollY()>=ImGui::GetScrollMaxY()) { + if (haveHitBounds) { + if (curOrder<(e->song.ordersLen-1)) { + setOrder(curOrder+1); + ImGui::SetScrollY(0); + updateScroll(0); + } + haveHitBounds=false; + } else { + haveHitBounds=true; + } + } else { + haveHitBounds=false; + } + } + } + } ImGui::EndTable(); } @@ -726,17 +709,14 @@ void FurnaceGUI::drawPattern() { break; } case DIV_CMD_PANNING: { - if (i.value==0) { - num=0; - break; - } - float ratio=float(((i.value>>4)&15)-(i.value&15))/MAX(((i.value>>4)&15),(i.value&15)); + float ratio=(float)(128-e->convertPanSplitToLinearLR(i.value,i.value2,256))/128.0f; + logV("ratio %f",ratio); speedX=-22.0f*sin(ratio*M_PI*0.5); speedY=-22.0f*cos(ratio*M_PI*0.5); spread=5.0f+fabs(sin(ratio*M_PI*0.5))*7.0f; grav=0.0f; frict=0.96f; - if (((i.value>>4)&15)==(i.value&15)) { + if (i.value==i.value2) { partIcon=ICON_FA_ARROWS_H; } else if (ratio>0) { partIcon=ICON_FA_ARROW_LEFT; @@ -841,6 +821,7 @@ void FurnaceGUI::drawPattern() { // particle simulation ImDrawList* fdl=ImGui::GetForegroundDrawList(); + if (!particles.empty()) WAKE_UP; for (size_t i=0; iDC.CursorPos; + ImVec2 maxArea=ImVec2( + minArea.x+size.x, + minArea.y+size.y + ); + ImRect rect=ImRect(minArea,maxArea); + + // render piano + //ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID("pianoDisplay"))) { + int bottomNotes=7*pianoOctaves; + for (int i=0; i=180) continue; + float pkh=pianoKeyHit[note]*0.5; + ImVec4 color=ImVec4(1.0f-pkh,1.0f-pkh,1.0f-pkh,1.0f); + ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/bottomNotes,0.0f)); + ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/bottomNotes,1.0f)); + p1.x-=dpiScale; + dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color)); + } + + for (int i=0; i=180) continue; + float pkh=pianoKeyHit[note]*0.5; + ImVec4 color=ImVec4(pkh,pkh,pkh,1.0f); + ImVec2 p0=ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f)); + ImVec2 p1=ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f)); + dl->AddRectFilled(p0,p1,0xff000000); + p0.x+=dpiScale; + p1.x-=dpiScale; + p1.y-=dpiScale; + dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color)); + } + } + + const float reduction=ImGui::GetIO().DeltaTime*60.0f*0.12; + for (int i=0; i<180; i++) { + pianoKeyHit[i]-=reduction; + if (pianoKeyHit[i]<0) pianoKeyHit[i]=0; + } + } + + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + pianoOptions=!pianoOptions; + } + + ImGui::EndTable(); + } + /* for (int i=0; igetTotalChannelCount(); i++) { DivChannelState* cs=e->getChanState(i); if (cs->keyOn) { @@ -39,7 +128,7 @@ void FurnaceGUI::drawPiano() { } ImGui::Text("%d: %s",i,noteName); } - } + }*/ } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_PIANO; ImGui::End(); diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index e7b08081c..3dd24e0af 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -88,6 +88,12 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2414", { + DIV_SYSTEM_OPZ, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Yamaha YM3526", { DIV_SYSTEM_OPL, 64, 0, 0, @@ -360,29 +366,29 @@ void FurnaceGUI::initSystemPresets() { )); cat.systems.push_back(FurnaceGUISysDef( "Commodore 64 (6581 SID + Sound Expander)", { - DIV_SYSTEM_OPL, 64, 0, 0, DIV_SYSTEM_C64_6581, 64, 0, 1, + DIV_SYSTEM_OPL, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( "Commodore 64 (6581 SID + Sound Expander with drums mode)", { - DIV_SYSTEM_OPL_DRUMS, 64, 0, 0, DIV_SYSTEM_C64_6581, 64, 0, 1, + DIV_SYSTEM_OPL_DRUMS, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( "Commodore 64 (8580 SID + Sound Expander)", { - DIV_SYSTEM_OPL, 64, 0, 0, DIV_SYSTEM_C64_8580, 64, 0, 1, + DIV_SYSTEM_OPL, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( "Commodore 64 (8580 SID + Sound Expander with drums mode)", { - DIV_SYSTEM_OPL_DRUMS, 64, 0, 0, DIV_SYSTEM_C64_8580, 64, 0, 1, + DIV_SYSTEM_OPL_DRUMS, 64, 0, 0, 0 } )); @@ -400,22 +406,22 @@ void FurnaceGUI::initSystemPresets() { )); cat.systems.push_back(FurnaceGUISysDef( "MSX + SFG-01", { - DIV_SYSTEM_YM2151, 64, 0, 0, DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_YM2151, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( "MSX + MSX-MUSIC", { - DIV_SYSTEM_OPLL, 64, 0, 0, DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_OPLL, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( "MSX + MSX-MUSIC (drums mode)", { - DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, 0 } )); @@ -578,8 +584,8 @@ void FurnaceGUI::initSystemPresets() { ));*/ cat.systems.push_back(FurnaceGUISysDef( "Commander X16", { - DIV_SYSTEM_YM2151, 64, 0, 0, DIV_SYSTEM_VERA, 64, 0, 0, + DIV_SYSTEM_YM2151, 64, 0, 0, 0 } )); @@ -645,8 +651,8 @@ void FurnaceGUI::initSystemPresets() { )); cat.systems.push_back(FurnaceGUISysDef( "Seta 1 + FM addon", { - DIV_SYSTEM_YM2612, 64, 0, 2, // Discrete YM3438 DIV_SYSTEM_X1_010, 64, 0, 0, + DIV_SYSTEM_YM2612, 64, 0, 2, // Discrete YM3438 0 } )); @@ -720,6 +726,13 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Famicom Disk System", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_FDS, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Commodore 64 (6581 SID)", { DIV_SYSTEM_C64_6581, 64, 0, 1, diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 04af4cab3..38a737bdb 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -46,520 +46,1052 @@ void FurnaceGUI::drawSampleEdit() { sampleType=sampleDepths[sample->depth]; } } - ImGui::Text("Name"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputText("##SampleName",&sample->name)) { - MARK_MODIFIED; - } - - if (ImGui::BeginTable("SampleProps",4,ImGuiTableFlags_SizingStretchSame)) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Type"); + if (!settings.sampleLayout) { + ImGui::Text("Name"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("##SampleType",sampleType.c_str())) { - for (int i=0; i<17; i++) { - if (sampleDepths[i]==NULL) continue; - if (ImGui::Selectable(sampleDepths[i])) { - sample->prepareUndo(true); - sample->depth=i; - e->renderSamplesP(); - updateSampleTex=true; - MARK_MODIFIED; - } - } - ImGui::EndCombo(); + if (ImGui::InputText("##SampleName",&sample->name)) { + MARK_MODIFIED; } - ImGui::TableNextColumn(); - ImGui::Text("Rate (Hz)"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##SampleRate",&sample->rate,10,200)) { MARK_MODIFIED - if (sample->rate<100) sample->rate=100; - if (sample->rate>96000) sample->rate=96000; - } - - ImGui::TableNextColumn(); - ImGui::Text("C-4 (Hz)"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##SampleCenter",&sample->centerRate,10,200)) { MARK_MODIFIED - if (sample->centerRate<100) sample->centerRate=100; - if (sample->centerRate>65535) sample->centerRate=65535; - } - - ImGui::TableNextColumn(); - bool doLoop=(sample->loopStart>=0); - if (ImGui::Checkbox("Loop",&doLoop)) { MARK_MODIFIED - if (doLoop) { - sample->loopStart=0; - } else { - sample->loopStart=-1; - } - updateSampleTex=true; - } - if (doLoop) { + if (ImGui::BeginTable("SampleProps",4,ImGuiTableFlags_SizingStretchSame)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Type"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { MARK_MODIFIED - if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { + if (ImGui::BeginCombo("##SampleType",sampleType.c_str())) { + for (int i=0; i<17; i++) { + if (sampleDepths[i]==NULL) continue; + if (ImGui::Selectable(sampleDepths[i])) { + sample->prepareUndo(true); + sample->depth=i; + e->renderSamplesP(); + updateSampleTex=true; + MARK_MODIFIED; + } + } + ImGui::EndCombo(); + } + + ImGui::TableNextColumn(); + ImGui::Text("C-4 (Hz)"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##SampleCenter",&sample->centerRate,10,200)) { MARK_MODIFIED + if (sample->centerRate<100) sample->centerRate=100; + if (sample->centerRate>65535) sample->centerRate=65535; + } + + ImGui::TableNextColumn(); + ImGui::Text("Compat Rate"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##SampleRate",&sample->rate,10,200)) { MARK_MODIFIED + if (sample->rate<100) sample->rate=100; + if (sample->rate>96000) sample->rate=96000; + } + + ImGui::TableNextColumn(); + bool doLoop=(sample->loopStart>=0); + if (ImGui::Checkbox("Loop",&doLoop)) { MARK_MODIFIED + if (doLoop) { sample->loopStart=0; + } else { + sample->loopStart=-1; } updateSampleTex=true; } - } - ImGui::EndTable(); - } - - /* - if (ImGui::Button("Apply")) { - e->renderSamplesP(); - } - ImGui::SameLine(); - */ - ImGui::Separator(); - - ImGui::BeginDisabled(sample->depth!=8 && sample->depth!=16); - - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode)); - if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { - sampleDragMode=false; - } - ImGui::PopStyleColor(); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Edit mode: Select"); - } - ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(sampleDragMode)); - if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) { - sampleDragMode=true; - } - ImGui::PopStyleColor(); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Edit mode: Draw"); - } - ImGui::SameLine(); - ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); - ImGui::SameLine(); - ImGui::Button(ICON_FA_ARROWS_H "##SResize"); - if (ImGui::IsItemClicked()) { - resizeSize=sample->samples; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Resize"); - } - if (openSampleResizeOpt) { - openSampleResizeOpt=false; - ImGui::OpenPopup("SResizeOpt"); - } - if (ImGui::BeginPopupContextItem("SResizeOpt",ImGuiPopupFlags_MouseButtonLeft)) { - if (ImGui::InputInt("Samples",&resizeSize,1,64)) { - if (resizeSize<0) resizeSize=0; - if (resizeSize>16777215) resizeSize=16777215; - } - if (ImGui::Button("Resize")) { - sample->prepareUndo(true); - e->lockEngine([this,sample]() { - if (!sample->resize(resizeSize)) { - showError("couldn't resize! make sure your sample is 8 or 16-bit."); + if (doLoop) { + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { MARK_MODIFIED + if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { + sample->loopStart=0; + } + updateSampleTex=true; } - e->renderSamples(); - }); - updateSampleTex=true; - sampleSelStart=-1; - sampleSelEnd=-1; - MARK_MODIFIED; - ImGui::CloseCurrentPopup(); + } + ImGui::EndTable(); } - ImGui::EndPopup(); - } else { - resizeSize=sample->samples; - } - ImGui::SameLine(); - ImGui::Button(ICON_FA_EXPAND "##SResample"); - if (ImGui::IsItemClicked()) { - resampleTarget=sample->rate; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Resample"); - } - if (openSampleResampleOpt) { - openSampleResampleOpt=false; - ImGui::OpenPopup("SResampleOpt"); - } - if (ImGui::BeginPopupContextItem("SResampleOpt",ImGuiPopupFlags_MouseButtonLeft)) { - ImGui::Text("Rate"); - if (ImGui::InputDouble("##SRRate",&resampleTarget,1.0,50.0,"%g")) { - if (resampleTarget<0) resampleTarget=0; - if (resampleTarget>96000) resampleTarget=96000; + + /* + if (ImGui::Button("Apply")) { + e->renderSamplesP(); } ImGui::SameLine(); - if (ImGui::Button("0.5x")) { - resampleTarget*=0.5; + */ + ImGui::Separator(); + + ImGui::BeginDisabled(sample->depth!=8 && sample->depth!=16); + + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode)); + if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { + sampleDragMode=false; + } + ImGui::PopStyleColor(); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Edit mode: Select"); } ImGui::SameLine(); - if (ImGui::Button("==")) { + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(sampleDragMode)); + if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) { + sampleDragMode=true; + } + ImGui::PopStyleColor(); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Edit mode: Draw"); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); + ImGui::SameLine(); + ImGui::Button(ICON_FA_ARROWS_H "##SResize"); + if (ImGui::IsItemClicked()) { + resizeSize=sample->samples; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Resize"); + } + if (openSampleResizeOpt) { + openSampleResizeOpt=false; + ImGui::OpenPopup("SResizeOpt"); + } + if (ImGui::BeginPopupContextItem("SResizeOpt",ImGuiPopupFlags_MouseButtonLeft)) { + if (ImGui::InputInt("Samples",&resizeSize,1,64)) { + if (resizeSize<0) resizeSize=0; + if (resizeSize>16777215) resizeSize=16777215; + } + if (ImGui::Button("Resize")) { + sample->prepareUndo(true); + e->lockEngine([this,sample]() { + if (!sample->resize(resizeSize)) { + showError("couldn't resize! make sure your sample is 8 or 16-bit."); + } + e->renderSamples(); + }); + updateSampleTex=true; + sampleSelStart=-1; + sampleSelEnd=-1; + MARK_MODIFIED; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } else { + resizeSize=sample->samples; + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_EXPAND "##SResample"); + if (ImGui::IsItemClicked()) { + resampleTarget=sample->rate; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Resample"); + } + if (openSampleResampleOpt) { + openSampleResampleOpt=false; + ImGui::OpenPopup("SResampleOpt"); + } + if (ImGui::BeginPopupContextItem("SResampleOpt",ImGuiPopupFlags_MouseButtonLeft)) { + ImGui::Text("Rate"); + if (ImGui::InputDouble("##SRRate",&resampleTarget,1.0,50.0,"%g")) { + if (resampleTarget<0) resampleTarget=0; + if (resampleTarget>96000) resampleTarget=96000; + } + ImGui::SameLine(); + if (ImGui::Button("0.5x")) { + resampleTarget*=0.5; + } + ImGui::SameLine(); + if (ImGui::Button("==")) { + resampleTarget=sample->rate; + } + ImGui::SameLine(); + if (ImGui::Button("2.0x")) { + resampleTarget*=2.0; + } + double factor=resampleTarget/(double)sample->rate; + if (ImGui::InputDouble("Factor",&factor,0.125,0.5,"%g")) { + resampleTarget=(double)sample->rate*factor; + if (resampleTarget<0) resampleTarget=0; + if (resampleTarget>96000) resampleTarget=96000; + } + ImGui::Combo("Filter",&resampleStrat,resampleStrats,6); + if (ImGui::Button("Resample")) { + sample->prepareUndo(true); + e->lockEngine([this,sample]() { + if (!sample->resample(resampleTarget,resampleStrat)) { + showError("couldn't resample! make sure your sample is 8 or 16-bit."); + } + e->renderSamples(); + }); + updateSampleTex=true; + sampleSelStart=-1; + sampleSelEnd=-1; + MARK_MODIFIED; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } else { resampleTarget=sample->rate; } ImGui::SameLine(); - if (ImGui::Button("2.0x")) { - resampleTarget*=2.0; + ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##SUndo")) { + doUndoSample(); } - double factor=resampleTarget/(double)sample->rate; - if (ImGui::InputDouble("Factor",&factor,0.125,0.5,"%g")) { - resampleTarget=(double)sample->rate*factor; - if (resampleTarget<0) resampleTarget=0; - if (resampleTarget>96000) resampleTarget=96000; - } - ImGui::Combo("Filter",&resampleStrat,resampleStrats,6); - if (ImGui::Button("Resample")) { - sample->prepareUndo(true); - e->lockEngine([this,sample]() { - if (!sample->resample(resampleTarget,resampleStrat)) { - showError("couldn't resample! make sure your sample is 8 or 16-bit."); - } - e->renderSamples(); - }); - updateSampleTex=true; - sampleSelStart=-1; - sampleSelEnd=-1; - MARK_MODIFIED; - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); - } else { - resampleTarget=sample->rate; - } - ImGui::SameLine(); - ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_UNDO "##SUndo")) { - doUndoSample(); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Undo"); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_REPEAT "##SRedo")) { - doRedoSample(); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Redo"); - } - ImGui::SameLine(); - ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); - ImGui::SameLine(); - ImGui::Button(ICON_FA_VOLUME_UP "##SAmplify"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Amplify"); - } - if (openSampleAmplifyOpt) { - openSampleAmplifyOpt=false; - ImGui::OpenPopup("SAmplifyOpt"); - } - if (ImGui::BeginPopupContextItem("SAmplifyOpt",ImGuiPopupFlags_MouseButtonLeft)) { - ImGui::Text("Volume"); - if (ImGui::InputFloat("##SRVolume",&lifyVol,10.0,50.0,"%g%%")) { - if (amplifyVol<0) amplifyVol=0; - if (amplifyVol>10000) amplifyVol=10000; + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Undo"); } ImGui::SameLine(); - ImGui::Text("(%.1fdB)",20.0*log10(amplifyVol/100.0f)); - if (ImGui::Button("Apply")) { - sample->prepareUndo(true); - e->lockEngine([this,sample]() { - SAMPLE_OP_BEGIN; - float vol=amplifyVol/100.0f; - - if (sample->depth==16) { - for (unsigned int i=start; idata16[i]*vol; - if (val<-32768) val=-32768; - if (val>32767) val=32767; - sample->data16[i]=val; - } - } else if (sample->depth==8) { - for (unsigned int i=start; idata8[i]*vol; - if (val<-128) val=-128; - if (val>127) val=127; - sample->data8[i]=val; - } - } - - updateSampleTex=true; - - e->renderSamples(); - }); - MARK_MODIFIED; - ImGui::CloseCurrentPopup(); + if (ImGui::Button(ICON_FA_REPEAT "##SRedo")) { + doRedoSample(); } - ImGui::EndPopup(); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_ARROWS_V "##SNormalize")) { - doAction(GUI_ACTION_SAMPLE_NORMALIZE); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Normalize"); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_ARROW_UP "##SFadeIn")) { - doAction(GUI_ACTION_SAMPLE_FADE_IN); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Fade in"); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_ARROW_DOWN "##SFadeOut")) { - doAction(GUI_ACTION_SAMPLE_FADE_OUT); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Fade out"); - } - ImGui::SameLine(); - ImGui::Button(ICON_FA_ADJUST "##SInsertSilence"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Insert silence"); - } - if (openSampleSilenceOpt) { - openSampleSilenceOpt=false; - ImGui::OpenPopup("SSilenceOpt"); - } - if (ImGui::BeginPopupContextItem("SSilenceOpt",ImGuiPopupFlags_MouseButtonLeft)) { - if (ImGui::InputInt("Samples",&silenceSize,1,64)) { - if (silenceSize<0) silenceSize=0; - if (silenceSize>16777215) silenceSize=16777215; - } - if (ImGui::Button("Resize")) { - int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?sample->samples:sampleSelStart; - sample->prepareUndo(true); - e->lockEngine([this,sample,pos]() { - if (!sample->insert(pos,silenceSize)) { - showError("couldn't insert! make sure your sample is 8 or 16-bit."); - } - e->renderSamples(); - }); - updateSampleTex=true; - sampleSelStart=pos; - sampleSelEnd=pos+silenceSize; - MARK_MODIFIED; - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_ERASER "##SSilence")) { - doAction(GUI_ACTION_SAMPLE_SILENCE); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Apply silence"); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_TIMES "##SDelete")) { - doAction(GUI_ACTION_SAMPLE_DELETE); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Delete"); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_CROP "##STrim")) { - doAction(GUI_ACTION_SAMPLE_TRIM); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Trim"); - } - ImGui::SameLine(); - ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_BACKWARD "##SReverse")) { - doAction(GUI_ACTION_SAMPLE_REVERSE); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Reverse"); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_SORT_AMOUNT_ASC "##SInvert")) { - doAction(GUI_ACTION_SAMPLE_INVERT); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Invert"); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_LEVEL_DOWN "##SSign")) { - doAction(GUI_ACTION_SAMPLE_SIGN); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Signed/unsigned exchange"); - } - ImGui::SameLine(); - ImGui::Button(ICON_FA_INDUSTRY "##SFilter"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Apply filter"); - } - if (openSampleFilterOpt) { - openSampleFilterOpt=false; - ImGui::OpenPopup("SFilterOpt"); - } - if (ImGui::BeginPopupContextItem("SFilterOpt",ImGuiPopupFlags_MouseButtonLeft)) { - float lowP=sampleFilterL*100.0f; - float bandP=sampleFilterB*100.0f; - float highP=sampleFilterH*100.0f; - float resP=sampleFilterRes*100.0f; - ImGui::Text("Cutoff:"); - if (ImGui::SliderFloat("From",&sampleFilterCutStart,0.0f,sample->rate*0.5,"%.0fHz")) { - if (sampleFilterCutStart<0.0) sampleFilterCutStart=0.0; - if (sampleFilterCutStart>sample->rate*0.5) sampleFilterCutStart=sample->rate*0.5; - } - if (ImGui::SliderFloat("To",&sampleFilterCutEnd,0.0f,sample->rate*0.5,"%.0fHz")) { - if (sampleFilterCutEnd<0.0) sampleFilterCutEnd=0.0; - if (sampleFilterCutEnd>sample->rate*0.5) sampleFilterCutEnd=sample->rate*0.5; - } - ImGui::Separator(); - if (ImGui::SliderFloat("Resonance",&resP,0.0f,99.0f,"%.1f%%")) { - sampleFilterRes=resP/100.0f; - if (sampleFilterRes<0.0f) sampleFilterRes=0.0f; - if (sampleFilterRes>0.99f) sampleFilterRes=0.99f; - } - ImGui::Text("Power"); - ImGui::SameLine(); - if (ImGui::RadioButton("1x",sampleFilterPower==1)) { - sampleFilterPower=1; + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Redo"); } ImGui::SameLine(); - if (ImGui::RadioButton("2x",sampleFilterPower==2)) { - sampleFilterPower=2; - } + ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); ImGui::SameLine(); - if (ImGui::RadioButton("3x",sampleFilterPower==3)) { - sampleFilterPower=3; + ImGui::Button(ICON_FA_VOLUME_UP "##SAmplify"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Amplify"); } - ImGui::Separator(); - if (ImGui::SliderFloat("Low-pass",&lowP,0.0f,100.0f,"%.1f%%")) { - sampleFilterL=lowP/100.0f; - if (sampleFilterL<0.0f) sampleFilterL=0.0f; - if (sampleFilterL>1.0f) sampleFilterL=1.0f; - } - if (ImGui::SliderFloat("Band-pass",&bandP,0.0f,100.0f,"%.1f%%")) { - sampleFilterB=bandP/100.0f; - if (sampleFilterB<0.0f) sampleFilterB=0.0f; - if (sampleFilterB>1.0f) sampleFilterB=1.0f; - } - if (ImGui::SliderFloat("High-pass",&highP,0.0f,100.0f,"%.1f%%")) { - sampleFilterH=highP/100.0f; - if (sampleFilterH<0.0f) sampleFilterH=0.0f; - if (sampleFilterH>1.0f) sampleFilterH=1.0f; + if (openSampleAmplifyOpt) { + openSampleAmplifyOpt=false; + ImGui::OpenPopup("SAmplifyOpt"); } + if (ImGui::BeginPopupContextItem("SAmplifyOpt",ImGuiPopupFlags_MouseButtonLeft)) { + ImGui::Text("Volume"); + if (ImGui::InputFloat("##SRVolume",&lifyVol,10.0,50.0,"%g%%")) { + if (amplifyVol<0) amplifyVol=0; + if (amplifyVol>10000) amplifyVol=10000; + } + ImGui::SameLine(); + ImGui::Text("(%.1fdB)",20.0*log10(amplifyVol/100.0f)); + if (ImGui::Button("Apply")) { + sample->prepareUndo(true); + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + float vol=amplifyVol/100.0f; - if (ImGui::Button("Apply")) { - sample->prepareUndo(true); - e->lockEngine([this,sample]() { - SAMPLE_OP_BEGIN; - float res=1.0-pow(sampleFilterRes,0.5f); - float low=0; - float band=0; - float high=0; - - double power=(sampleFilterCutStart>sampleFilterCutEnd)?0.5:2.0; - - if (sample->depth==16) { - for (unsigned int i=start; irate))*M_PI); - - for (int j=0; jdata16[i])-low-(res*band); - band=cut*high+band; + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]*vol; + if (val<-32768) val=-32768; + if (val>32767) val=32767; + sample->data16[i]=val; } - - float val=low*sampleFilterL+band*sampleFilterB+high*sampleFilterH; - if (val<-32768) val=-32768; - if (val>32767) val=32767; - sample->data16[i]=val; - } - } else if (sample->depth==8) { - for (unsigned int i=start; irate))*M_PI); - - for (int j=0; jdata8[i])-low-(res*band); - band=cut*high+band; + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]*vol; + if (val<-128) val=-128; + if (val>127) val=127; + sample->data8[i]=val; } - - float val=low*sampleFilterL+band*sampleFilterB+high*sampleFilterH; - if (val<-128) val=-128; - if (val>127) val=127; - sample->data8[i]=val; } - } - updateSampleTex=true; + updateSampleTex=true; - e->renderSamples(); - }); - MARK_MODIFIED; - ImGui::CloseCurrentPopup(); + e->renderSamples(); + }); + MARK_MODIFIED; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); } - ImGui::EndPopup(); - } - ImGui::SameLine(); - ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_VOLUME_UP "##PreviewSample")) { - e->previewSample(curSample); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Preview sample"); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_VOLUME_OFF "##StopSample")) { - e->stopSamplePreview(); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Stop sample preview"); - } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROWS_V "##SNormalize")) { + doAction(GUI_ACTION_SAMPLE_NORMALIZE); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Normalize"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROW_UP "##SFadeIn")) { + doAction(GUI_ACTION_SAMPLE_FADE_IN); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Fade in"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROW_DOWN "##SFadeOut")) { + doAction(GUI_ACTION_SAMPLE_FADE_OUT); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Fade out"); + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_ADJUST "##SInsertSilence"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Insert silence"); + } + if (openSampleSilenceOpt) { + openSampleSilenceOpt=false; + ImGui::OpenPopup("SSilenceOpt"); + } + if (ImGui::BeginPopupContextItem("SSilenceOpt",ImGuiPopupFlags_MouseButtonLeft)) { + if (ImGui::InputInt("Samples",&silenceSize,1,64)) { + if (silenceSize<0) silenceSize=0; + if (silenceSize>16777215) silenceSize=16777215; + } + if (ImGui::Button("Resize")) { + int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?sample->samples:sampleSelStart; + sample->prepareUndo(true); + e->lockEngine([this,sample,pos]() { + if (!sample->insert(pos,silenceSize)) { + showError("couldn't insert! make sure your sample is 8 or 16-bit."); + } + e->renderSamples(); + }); + updateSampleTex=true; + sampleSelStart=pos; + sampleSelEnd=pos+silenceSize; + MARK_MODIFIED; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ERASER "##SSilence")) { + doAction(GUI_ACTION_SAMPLE_SILENCE); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Apply silence"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_TIMES "##SDelete")) { + doAction(GUI_ACTION_SAMPLE_DELETE); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Delete"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_CROP "##STrim")) { + doAction(GUI_ACTION_SAMPLE_TRIM); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Trim"); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_BACKWARD "##SReverse")) { + doAction(GUI_ACTION_SAMPLE_REVERSE); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Reverse"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_SORT_AMOUNT_ASC "##SInvert")) { + doAction(GUI_ACTION_SAMPLE_INVERT); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Invert"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_LEVEL_DOWN "##SSign")) { + doAction(GUI_ACTION_SAMPLE_SIGN); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Signed/unsigned exchange"); + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_INDUSTRY "##SFilter"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Apply filter"); + } + if (openSampleFilterOpt) { + openSampleFilterOpt=false; + ImGui::OpenPopup("SFilterOpt"); + } + if (ImGui::BeginPopupContextItem("SFilterOpt",ImGuiPopupFlags_MouseButtonLeft)) { + float lowP=sampleFilterL*100.0f; + float bandP=sampleFilterB*100.0f; + float highP=sampleFilterH*100.0f; + float resP=sampleFilterRes*100.0f; + ImGui::Text("Cutoff:"); + if (ImGui::SliderFloat("From",&sampleFilterCutStart,0.0f,sample->rate*0.5,"%.0fHz")) { + if (sampleFilterCutStart<0.0) sampleFilterCutStart=0.0; + if (sampleFilterCutStart>sample->rate*0.5) sampleFilterCutStart=sample->rate*0.5; + } + if (ImGui::SliderFloat("To",&sampleFilterCutEnd,0.0f,sample->rate*0.5,"%.0fHz")) { + if (sampleFilterCutEnd<0.0) sampleFilterCutEnd=0.0; + if (sampleFilterCutEnd>sample->rate*0.5) sampleFilterCutEnd=sample->rate*0.5; + } + ImGui::Separator(); + if (ImGui::SliderFloat("Resonance",&resP,0.0f,99.0f,"%.1f%%")) { + sampleFilterRes=resP/100.0f; + if (sampleFilterRes<0.0f) sampleFilterRes=0.0f; + if (sampleFilterRes>0.99f) sampleFilterRes=0.99f; + } + ImGui::Text("Power"); + ImGui::SameLine(); + if (ImGui::RadioButton("1x",sampleFilterPower==1)) { + sampleFilterPower=1; + } + ImGui::SameLine(); + if (ImGui::RadioButton("2x",sampleFilterPower==2)) { + sampleFilterPower=2; + } + ImGui::SameLine(); + if (ImGui::RadioButton("3x",sampleFilterPower==3)) { + sampleFilterPower=3; + } + ImGui::Separator(); + if (ImGui::SliderFloat("Low-pass",&lowP,0.0f,100.0f,"%.1f%%")) { + sampleFilterL=lowP/100.0f; + if (sampleFilterL<0.0f) sampleFilterL=0.0f; + if (sampleFilterL>1.0f) sampleFilterL=1.0f; + } + if (ImGui::SliderFloat("Band-pass",&bandP,0.0f,100.0f,"%.1f%%")) { + sampleFilterB=bandP/100.0f; + if (sampleFilterB<0.0f) sampleFilterB=0.0f; + if (sampleFilterB>1.0f) sampleFilterB=1.0f; + } + if (ImGui::SliderFloat("High-pass",&highP,0.0f,100.0f,"%.1f%%")) { + sampleFilterH=highP/100.0f; + if (sampleFilterH<0.0f) sampleFilterH=0.0f; + if (sampleFilterH>1.0f) sampleFilterH=1.0f; + } - ImGui::SameLine(); - double zoomPercent=100.0/sampleZoom; - ImGui::Text("Zoom"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(150.0f*dpiScale); - if (ImGui::InputDouble("##SZoom",&zoomPercent,5.0,20.0,"%g%%")) { - if (zoomPercent>10000.0) zoomPercent=10000.0; - if (zoomPercent<1.0) zoomPercent=1.0; - sampleZoom=100.0/zoomPercent; - if (sampleZoom<0.01) sampleZoom=0.01; - sampleZoomAuto=false; - updateSampleTex=true; - } - ImGui::SameLine(); - if (sampleZoomAuto) { - if (ImGui::Button("100%")) { - sampleZoom=1.0; + if (ImGui::Button("Apply")) { + sample->prepareUndo(true); + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + float res=1.0-pow(sampleFilterRes,0.5f); + float low=0; + float band=0; + float high=0; + + double power=(sampleFilterCutStart>sampleFilterCutEnd)?0.5:2.0; + + if (sample->depth==16) { + for (unsigned int i=start; irate))*M_PI); + + for (int j=0; jdata16[i])-low-(res*band); + band=cut*high+band; + } + + float val=low*sampleFilterL+band*sampleFilterB+high*sampleFilterH; + if (val<-32768) val=-32768; + if (val>32767) val=32767; + sample->data16[i]=val; + } + } else if (sample->depth==8) { + for (unsigned int i=start; irate))*M_PI); + + for (int j=0; jdata8[i])-low-(res*band); + band=cut*high+band; + } + + float val=low*sampleFilterL+band*sampleFilterB+high*sampleFilterH; + if (val<-128) val=-128; + if (val>127) val=127; + sample->data8[i]=val; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_VOLUME_UP "##PreviewSample")) { + e->previewSample(curSample); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Preview sample"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_VOLUME_OFF "##StopSample")) { + e->stopSamplePreview(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Stop sample preview"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UPLOAD "##MakeIns")) { + doAction(GUI_ACTION_SAMPLE_MAKE_INS); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Create instrument from sample"); + } + + ImGui::SameLine(); + double zoomPercent=100.0/sampleZoom; + ImGui::Text("Zoom"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(150.0f*dpiScale); + if (ImGui::InputDouble("##SZoom",&zoomPercent,5.0,20.0,"%g%%")) { + if (zoomPercent>10000.0) zoomPercent=10000.0; + if (zoomPercent<1.0) zoomPercent=1.0; + sampleZoom=100.0/zoomPercent; + if (sampleZoom<0.01) sampleZoom=0.01; sampleZoomAuto=false; updateSampleTex=true; } + ImGui::SameLine(); + if (sampleZoomAuto) { + if (ImGui::Button("100%")) { + sampleZoom=1.0; + sampleZoomAuto=false; + updateSampleTex=true; + } + } else { + if (ImGui::Button("Auto")) { + sampleZoomAuto=true; + updateSampleTex=true; + } + } } else { - if (ImGui::Button("Auto")) { - sampleZoomAuto=true; + ImGui::Text("Name"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputText("##SampleName",&sample->name)) { + MARK_MODIFIED; + } + + if (ImGui::BeginTable("SampleProps",2,ImGuiTableFlags_SizingStretchSame)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Type"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("##SampleType",sampleType.c_str())) { + for (int i=0; i<17; i++) { + if (sampleDepths[i]==NULL) continue; + if (ImGui::Selectable(sampleDepths[i])) { + sample->prepareUndo(true); + sample->depth=i; + e->renderSamplesP(); + updateSampleTex=true; + MARK_MODIFIED; + } + } + ImGui::EndCombo(); + } + + ImGui::TableNextColumn(); + ImGui::Text("C-4 (Hz)"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##SampleCenter",&sample->centerRate,10,200)) { MARK_MODIFIED + if (sample->centerRate<100) sample->centerRate=100; + if (sample->centerRate>65535) sample->centerRate=65535; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Compat Rate"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##SampleRate",&sample->rate,10,200)) { MARK_MODIFIED + if (sample->rate<100) sample->rate=100; + if (sample->rate>96000) sample->rate=96000; + } + + ImGui::TableNextColumn(); + bool doLoop=(sample->loopStart>=0); + if (ImGui::Checkbox("Loop",&doLoop)) { MARK_MODIFIED + if (doLoop) { + sample->loopStart=0; + } else { + sample->loopStart=-1; + } + updateSampleTex=true; + } + if (doLoop) { + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { MARK_MODIFIED + if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { + sample->loopStart=0; + } + updateSampleTex=true; + } + } + ImGui::EndTable(); + } + + /* + if (ImGui::Button("Apply")) { + e->renderSamplesP(); + } + ImGui::SameLine(); + */ + ImGui::Separator(); + + ImGui::BeginDisabled(sample->depth!=8 && sample->depth!=16); + + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode)); + if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { + sampleDragMode=false; + } + ImGui::PopStyleColor(); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Edit mode: Select"); + } + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(sampleDragMode)); + if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) { + sampleDragMode=true; + } + ImGui::PopStyleColor(); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Edit mode: Draw"); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(7.0*dpiScale,dpiScale)); + ImGui::SameLine(); + ImGui::Button(ICON_FA_ARROWS_H "##SResize"); + if (ImGui::IsItemClicked()) { + resizeSize=sample->samples; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Resize"); + } + if (openSampleResizeOpt) { + openSampleResizeOpt=false; + ImGui::OpenPopup("SResizeOpt"); + } + if (ImGui::BeginPopupContextItem("SResizeOpt",ImGuiPopupFlags_MouseButtonLeft)) { + if (ImGui::InputInt("Samples",&resizeSize,1,64)) { + if (resizeSize<0) resizeSize=0; + if (resizeSize>16777215) resizeSize=16777215; + } + if (ImGui::Button("Resize")) { + sample->prepareUndo(true); + e->lockEngine([this,sample]() { + if (!sample->resize(resizeSize)) { + showError("couldn't resize! make sure your sample is 8 or 16-bit."); + } + e->renderSamples(); + }); + updateSampleTex=true; + sampleSelStart=-1; + sampleSelEnd=-1; + MARK_MODIFIED; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } else { + resizeSize=sample->samples; + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_EXPAND "##SResample"); + if (ImGui::IsItemClicked()) { + resampleTarget=sample->rate; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Resample"); + } + if (openSampleResampleOpt) { + openSampleResampleOpt=false; + ImGui::OpenPopup("SResampleOpt"); + } + if (ImGui::BeginPopupContextItem("SResampleOpt",ImGuiPopupFlags_MouseButtonLeft)) { + ImGui::Text("Rate"); + if (ImGui::InputDouble("##SRRate",&resampleTarget,1.0,50.0,"%g")) { + if (resampleTarget<0) resampleTarget=0; + if (resampleTarget>96000) resampleTarget=96000; + } + ImGui::SameLine(); + if (ImGui::Button("0.5x")) { + resampleTarget*=0.5; + } + ImGui::SameLine(); + if (ImGui::Button("==")) { + resampleTarget=sample->rate; + } + ImGui::SameLine(); + if (ImGui::Button("2.0x")) { + resampleTarget*=2.0; + } + double factor=resampleTarget/(double)sample->rate; + if (ImGui::InputDouble("Factor",&factor,0.125,0.5,"%g")) { + resampleTarget=(double)sample->rate*factor; + if (resampleTarget<0) resampleTarget=0; + if (resampleTarget>96000) resampleTarget=96000; + } + ImGui::Combo("Filter",&resampleStrat,resampleStrats,6); + if (ImGui::Button("Resample")) { + sample->prepareUndo(true); + e->lockEngine([this,sample]() { + if (!sample->resample(resampleTarget,resampleStrat)) { + showError("couldn't resample! make sure your sample is 8 or 16-bit."); + } + e->renderSamples(); + }); + updateSampleTex=true; + sampleSelStart=-1; + sampleSelEnd=-1; + MARK_MODIFIED; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } else { + resampleTarget=sample->rate; + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(0.5*dpiScale,dpiScale)); + ImGui::SameLine(); + ImGui::Button(ICON_FA_INDUSTRY "##SFilter"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Apply filter"); + } + if (openSampleFilterOpt) { + openSampleFilterOpt=false; + ImGui::OpenPopup("SFilterOpt"); + } + if (ImGui::BeginPopupContextItem("SFilterOpt",ImGuiPopupFlags_MouseButtonLeft)) { + float lowP=sampleFilterL*100.0f; + float bandP=sampleFilterB*100.0f; + float highP=sampleFilterH*100.0f; + float resP=sampleFilterRes*100.0f; + ImGui::Text("Cutoff:"); + if (ImGui::SliderFloat("From",&sampleFilterCutStart,0.0f,sample->rate*0.5,"%.0fHz")) { + if (sampleFilterCutStart<0.0) sampleFilterCutStart=0.0; + if (sampleFilterCutStart>sample->rate*0.5) sampleFilterCutStart=sample->rate*0.5; + } + if (ImGui::SliderFloat("To",&sampleFilterCutEnd,0.0f,sample->rate*0.5,"%.0fHz")) { + if (sampleFilterCutEnd<0.0) sampleFilterCutEnd=0.0; + if (sampleFilterCutEnd>sample->rate*0.5) sampleFilterCutEnd=sample->rate*0.5; + } + ImGui::Separator(); + if (ImGui::SliderFloat("Resonance",&resP,0.0f,99.0f,"%.1f%%")) { + sampleFilterRes=resP/100.0f; + if (sampleFilterRes<0.0f) sampleFilterRes=0.0f; + if (sampleFilterRes>0.99f) sampleFilterRes=0.99f; + } + ImGui::Text("Power"); + ImGui::SameLine(); + if (ImGui::RadioButton("1x",sampleFilterPower==1)) { + sampleFilterPower=1; + } + ImGui::SameLine(); + if (ImGui::RadioButton("2x",sampleFilterPower==2)) { + sampleFilterPower=2; + } + ImGui::SameLine(); + if (ImGui::RadioButton("3x",sampleFilterPower==3)) { + sampleFilterPower=3; + } + ImGui::Separator(); + if (ImGui::SliderFloat("Low-pass",&lowP,0.0f,100.0f,"%.1f%%")) { + sampleFilterL=lowP/100.0f; + if (sampleFilterL<0.0f) sampleFilterL=0.0f; + if (sampleFilterL>1.0f) sampleFilterL=1.0f; + } + if (ImGui::SliderFloat("Band-pass",&bandP,0.0f,100.0f,"%.1f%%")) { + sampleFilterB=bandP/100.0f; + if (sampleFilterB<0.0f) sampleFilterB=0.0f; + if (sampleFilterB>1.0f) sampleFilterB=1.0f; + } + if (ImGui::SliderFloat("High-pass",&highP,0.0f,100.0f,"%.1f%%")) { + sampleFilterH=highP/100.0f; + if (sampleFilterH<0.0f) sampleFilterH=0.0f; + if (sampleFilterH>1.0f) sampleFilterH=1.0f; + } + + if (ImGui::Button("Apply")) { + sample->prepareUndo(true); + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + float res=1.0-pow(sampleFilterRes,0.5f); + float low=0; + float band=0; + float high=0; + + double power=(sampleFilterCutStart>sampleFilterCutEnd)?0.5:2.0; + + if (sample->depth==16) { + for (unsigned int i=start; irate))*M_PI); + + for (int j=0; jdata16[i])-low-(res*band); + band=cut*high+band; + } + + float val=low*sampleFilterL+band*sampleFilterB+high*sampleFilterH; + if (val<-32768) val=-32768; + if (val>32767) val=32767; + sample->data16[i]=val; + } + } else if (sample->depth==8) { + for (unsigned int i=start; irate))*M_PI); + + for (int j=0; jdata8[i])-low-(res*band); + band=cut*high+band; + } + + float val=low*sampleFilterL+band*sampleFilterB+high*sampleFilterH; + if (val<-128) val=-128; + if (val>127) val=127; + sample->data8[i]=val; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(0.5*dpiScale,dpiScale)); + ImGui::SameLine(); + + + ImGui::Button(ICON_FA_VOLUME_UP "##SAmplify"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Amplify"); + } + if (openSampleAmplifyOpt) { + openSampleAmplifyOpt=false; + ImGui::OpenPopup("SAmplifyOpt"); + } + if (ImGui::BeginPopupContextItem("SAmplifyOpt",ImGuiPopupFlags_MouseButtonLeft)) { + ImGui::Text("Volume"); + if (ImGui::InputFloat("##SRVolume",&lifyVol,10.0,50.0,"%g%%")) { + if (amplifyVol<0) amplifyVol=0; + if (amplifyVol>10000) amplifyVol=10000; + } + ImGui::SameLine(); + ImGui::Text("(%.1fdB)",20.0*log10(amplifyVol/100.0f)); + if (ImGui::Button("Apply")) { + sample->prepareUndo(true); + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + float vol=amplifyVol/100.0f; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]*vol; + if (val<-32768) val=-32768; + if (val>32767) val=32767; + sample->data16[i]=val; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]*vol; + if (val<-128) val=-128; + if (val>127) val=127; + sample->data8[i]=val; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROWS_V "##SNormalize")) { + doAction(GUI_ACTION_SAMPLE_NORMALIZE); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Normalize"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROW_UP "##SFadeIn")) { + doAction(GUI_ACTION_SAMPLE_FADE_IN); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Fade in"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROW_DOWN "##SFadeOut")) { + doAction(GUI_ACTION_SAMPLE_FADE_OUT); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Fade out"); + } + + if (ImGui::Button(ICON_FA_UNDO "##SUndo")) { + doUndoSample(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Undo"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_REPEAT "##SRedo")) { + doRedoSample(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Redo"); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(7.0*dpiScale,dpiScale)); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_BACKWARD "##SReverse")) { + doAction(GUI_ACTION_SAMPLE_REVERSE); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Reverse"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_SORT_AMOUNT_ASC "##SInvert")) { + doAction(GUI_ACTION_SAMPLE_INVERT); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Invert"); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(0.5*dpiScale,dpiScale)); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_LEVEL_DOWN "##SSign")) { + doAction(GUI_ACTION_SAMPLE_SIGN); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Signed/unsigned exchange"); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(0.5*dpiScale,dpiScale)); + ImGui::SameLine(); + ImGui::Button(ICON_FA_ADJUST "##SInsertSilence"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Insert silence"); + } + if (openSampleSilenceOpt) { + openSampleSilenceOpt=false; + ImGui::OpenPopup("SSilenceOpt"); + } + if (ImGui::BeginPopupContextItem("SSilenceOpt",ImGuiPopupFlags_MouseButtonLeft)) { + if (ImGui::InputInt("Samples",&silenceSize,1,64)) { + if (silenceSize<0) silenceSize=0; + if (silenceSize>16777215) silenceSize=16777215; + } + if (ImGui::Button("Resize")) { + int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?sample->samples:sampleSelStart; + sample->prepareUndo(true); + e->lockEngine([this,sample,pos]() { + if (!sample->insert(pos,silenceSize)) { + showError("couldn't insert! make sure your sample is 8 or 16-bit."); + } + e->renderSamples(); + }); + updateSampleTex=true; + sampleSelStart=pos; + sampleSelEnd=pos+silenceSize; + MARK_MODIFIED; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ERASER "##SSilence")) { + doAction(GUI_ACTION_SAMPLE_SILENCE); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Apply silence"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_TIMES "##SDelete")) { + doAction(GUI_ACTION_SAMPLE_DELETE); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Delete"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_CROP "##STrim")) { + doAction(GUI_ACTION_SAMPLE_TRIM); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Trim"); + } + + if (ImGui::Button(ICON_FA_PLAY "##PreviewSample")) { + e->previewSample(curSample); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Preview sample"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_STOP "##StopSample")) { + e->stopSamplePreview(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Stop sample preview"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UPLOAD "##MakeIns")) { + doAction(GUI_ACTION_SAMPLE_MAKE_INS); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Create instrument from sample"); + } + ImGui::SameLine(); + double zoomPercent=100.0/sampleZoom; + ImGui::Text("Zoom"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(150.0f*dpiScale); + if (ImGui::InputDouble("##SZoom",&zoomPercent,5.0,20.0,"%g%%")) { + if (zoomPercent>10000.0) zoomPercent=10000.0; + if (zoomPercent<1.0) zoomPercent=1.0; + sampleZoom=100.0/zoomPercent; + if (sampleZoom<0.01) sampleZoom=0.01; + sampleZoomAuto=false; updateSampleTex=true; } + ImGui::SameLine(); + if (sampleZoomAuto) { + if (ImGui::Button("100%")) { + sampleZoom=1.0; + sampleZoomAuto=false; + updateSampleTex=true; + } + } else { + if (ImGui::Button("Auto")) { + sampleZoomAuto=true; + updateSampleTex=true; + } + } } ImGui::Separator(); - ImVec2 avail=ImGui::GetContentRegionAvail(); + ImVec2 avail=ImGui::GetContentRegionAvail(); // graph size determined here + if (ImGui::GetContentRegionAvail().y>(ImGui::GetContentRegionAvail().x*0.5f)) { + avail=ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetContentRegionAvail().x*0.5f); + } avail.y-=ImGui::GetFontSize()+ImGui::GetStyle().ItemSpacing.y+ImGui::GetStyle().ScrollbarSize; + if (avail.y<1.0) { // Prevents crash + avail.y=1.0; + } + int availX=avail.x; int availY=avail.y; - if (sampleZoomAuto) { samplePos=0; if (sample->samples<1 || avail.x<=0) { @@ -605,7 +1137,8 @@ void FurnaceGUI::drawSampleEdit() { ImU32 centerLineColor=ImAlphaBlendColors(bgColor,ImGui::GetColorU32(ImGuiCol_PlotLines,0.25)); for (int i=0; iloopStart>=0 && sample->loopStart<(int)sample->samples && ((j+samplePos)*sampleZoom)>sample->loopStart) { + int scaledPos=samplePos+(j*sampleZoom); + if (sample->loopStart>=0 && sample->loopStart<(int)sample->samples && scaledPos>=sample->loopStart) { data[i*availX+j]=bgColorLoop; } else { data[i*availX+j]=bgColor; @@ -670,6 +1203,7 @@ void FurnaceGUI::drawSampleEdit() { ImVec2 rectSize=ImGui::GetItemRectSize(); if (ImGui::IsItemClicked()) { + nextWindow=GUI_WINDOW_SAMPLE_EDIT; if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { sampleDragActive=false; sampleSelStart=0; @@ -734,6 +1268,36 @@ void FurnaceGUI::drawSampleEdit() { } if (ImGui::IsItemHovered()) { + if (ctrlWheeling) { + double zoomPercent=100.0/sampleZoom; + zoomPercent+=wheelY*10.0; + if (zoomPercent>10000.0) zoomPercent=10000.0; + if (zoomPercent<1.0) zoomPercent=1.0; + sampleZoom=100.0/zoomPercent; + if (sampleZoom<0.01) sampleZoom=0.01; + sampleZoomAuto=false; + int bounds=((int)sample->samples-round(rectSize.x*sampleZoom)); + if (bounds<0) bounds=0; + if (samplePos>bounds) samplePos=bounds; + updateSampleTex=true; + } else { + if (wheelY!=0) { + if (!sampleZoomAuto) { + double scrollAmount=MAX(fabs((double)wheelY*sampleZoom*60.0),1.0); + if (wheelY>0) { + samplePos+=scrollAmount; + } else { + samplePos-=scrollAmount; + } + if (samplePos<0) samplePos=0; + int bounds=((int)sample->samples-round(rectSize.x*sampleZoom)); + if (bounds<0) bounds=0; + if (samplePos>bounds) samplePos=bounds; + updateSampleTex=true; + } + } + } + int posX=-1; int posY=0; ImVec2 pos=ImGui::GetMousePos(); @@ -801,47 +1365,6 @@ void FurnaceGUI::drawSampleEdit() { ImGui::SetCursorPosY(ImGui::GetCursorPosY()+ImGui::GetStyle().ScrollbarSize); ImGui::Text("%s",statusBar.c_str()); } - - /* - bool considerations=false; - ImGui::Text("notes:"); - if (sample->loopStart>=0) { - considerations=true; - ImGui::Text("- sample won't loop on Neo Geo ADPCM-A and X1-010"); - if (sample->loopStart&1) { - ImGui::Text("- sample loop start will be aligned to the nearest even sample on Amiga"); - } - if (sample->loopStart>0) { - ImGui::Text("- sample loop start will be ignored on Neo Geo ADPCM-B"); - } - } - if (sample->samples&1) { - considerations=true; - ImGui::Text("- sample length will be aligned to the nearest even sample on Amiga"); - } - if (sample->samples&511) { - considerations=true; - ImGui::Text("- sample length will be aligned and padded to 512 sample units on Neo Geo ADPCM."); - } - if (sample->samples&4095) { - considerations=true; - ImGui::Text("- sample length will be aligned and padded to 4096 sample units on X1-010."); - } - if (sample->samples>65535) { - considerations=true; - ImGui::Text("- maximum sample length on Sega PCM and QSound is 65536 samples"); - } - if (sample->samples>131071) { - considerations=true; - ImGui::Text("- maximum sample length on X1-010 is 131072 samples"); - } - if (sample->samples>2097151) { - considerations=true; - ImGui::Text("- maximum sample length on Neo Geo ADPCM is 2097152 samples"); - } - if (!considerations) { - ImGui::Text("- none"); - }*/ } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_EDIT; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 87b798572..b98004e5e 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -23,6 +23,7 @@ #include "../fileutils.h" #include "util.h" #include "guiConst.h" +#include "intConst.h" #include "ImGuiFileDialog.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" @@ -31,6 +32,13 @@ #define DEFAULT_NOTE_KEYS "5:7;6:4;7:3;8:16;10:6;11:8;12:24;13:10;16:11;17:9;18:26;19:28;20:12;21:17;22:1;23:19;24:23;25:5;26:14;27:2;28:21;29:0;30:100;31:13;32:15;34:18;35:20;36:22;38:25;39:27;43:100;46:101;47:29;48:31;53:102;" +#if defined(_WIN32) || defined(__APPLE__) +#define POWER_SAVE_DEFAULT 1 +#else +// currently off on Linux/other due to Mesa catch-up behavior. +#define POWER_SAVE_DEFAULT 0 +#endif + const char* mainFonts[]={ "IBM Plex Sans", "Liberation Sans", @@ -76,6 +84,11 @@ const char* saaCores[]={ "SAASound" }; +const char* nesCores[]={ + "puNES", + "NSFplay" +}; + const char* valueInputStyles[]={ "Disabled/custom", "Two octaves (0 is C-4, F is D#5)", @@ -205,1221 +218,1529 @@ void FurnaceGUI::drawSettings() { nextWindow=GUI_WINDOW_NOTHING; } if (!settingsOpen) return; - if (ImGui::Begin("Settings",NULL,ImGuiWindowFlags_NoDocking)) { + if (ImGui::Begin("Settings",&settingsOpen,ImGuiWindowFlags_NoDocking)) { + if (!settingsOpen) { + settingsOpen=true; + showWarning("Do you want to save your settings?",GUI_WARN_CLOSE_SETTINGS); + } if (ImGui::BeginTabBar("settingsTab")) { if (ImGui::BeginTabItem("General")) { - ImGui::Text("Workspace layout"); - if (ImGui::Button("Import")) { - openFileDialog(GUI_FILE_IMPORT_LAYOUT); - } - ImGui::SameLine(); - if (ImGui::Button("Export")) { - openFileDialog(GUI_FILE_EXPORT_LAYOUT); - } - ImGui::SameLine(); - if (ImGui::Button("Reset")) { - showWarning("Are you sure you want to reset the workspace layout?",GUI_WARN_RESET_LAYOUT); - } - ImGui::Separator(); - ImGui::Text("Toggle channel solo on:"); - if (ImGui::RadioButton("Right-click or double-click##soloA",settings.soloAction==0)) { - settings.soloAction=0; - } - if (ImGui::RadioButton("Right-click##soloR",settings.soloAction==1)) { - settings.soloAction=1; - } - if (ImGui::RadioButton("Double-click##soloD",settings.soloAction==2)) { - settings.soloAction=2; - } - - bool pullDeleteBehaviorB=settings.pullDeleteBehavior; - if (ImGui::Checkbox("Move cursor up on backspace-delete",&pullDeleteBehaviorB)) { - settings.pullDeleteBehavior=pullDeleteBehaviorB; - } - - bool stepOnDeleteB=settings.stepOnDelete; - if (ImGui::Checkbox("Move cursor by edit step on delete",&stepOnDeleteB)) { - settings.stepOnDelete=stepOnDeleteB; - } - - bool effectDeletionAltersValueB=settings.effectDeletionAltersValue; - if (ImGui::Checkbox("Delete effect value when deleting effect",&effectDeletionAltersValueB)) { - settings.effectDeletionAltersValue=effectDeletionAltersValueB; - } - - bool stepOnInsertB=settings.stepOnInsert; - if (ImGui::Checkbox("Move cursor by edit step on insert (push)",&stepOnInsertB)) { - settings.stepOnInsert=stepOnInsertB; - } - - bool cursorPastePosB=settings.cursorPastePos; - if (ImGui::Checkbox("Move cursor to end of clipboard content when pasting",&cursorPastePosB)) { - settings.cursorPastePos=cursorPastePosB; - } - - bool allowEditDockingB=settings.allowEditDocking; - if (ImGui::Checkbox("Allow docking editors",&allowEditDockingB)) { - settings.allowEditDocking=allowEditDockingB; - } - - bool avoidRaisingPatternB=settings.avoidRaisingPattern; - if (ImGui::Checkbox("Don't raise pattern editor on click",&avoidRaisingPatternB)) { - settings.avoidRaisingPattern=avoidRaisingPatternB; - } - - bool insFocusesPatternB=settings.insFocusesPattern; - if (ImGui::Checkbox("Focus pattern editor when selecting instrument",&insFocusesPatternB)) { - settings.insFocusesPattern=insFocusesPatternB; - } - - bool restartOnFlagChangeB=settings.restartOnFlagChange; - if (ImGui::Checkbox("Restart song when changing system properties",&restartOnFlagChangeB)) { - settings.restartOnFlagChange=restartOnFlagChangeB; - } - - bool sysFileDialogB=settings.sysFileDialog; - if (ImGui::Checkbox("Use system file picker",&sysFileDialogB)) { - settings.sysFileDialog=sysFileDialogB; - } - - ImGui::Text("Wrap pattern cursor horizontally:"); - if (ImGui::RadioButton("No##wrapH0",settings.wrapHorizontal==0)) { - settings.wrapHorizontal=0; - } - if (ImGui::RadioButton("Yes##wrapH1",settings.wrapHorizontal==1)) { - settings.wrapHorizontal=1; - } - if (ImGui::RadioButton("Yes, and move to next/prev row##wrapH2",settings.wrapHorizontal==2)) { - settings.wrapHorizontal=2; - } - - ImGui::Text("Wrap pattern cursor vertically:"); - if (ImGui::RadioButton("No##wrapV0",settings.wrapVertical==0)) { - settings.wrapVertical=0; - } - if (ImGui::RadioButton("Yes##wrapV1",settings.wrapVertical==1)) { - settings.wrapVertical=1; - } - if (ImGui::RadioButton("Yes, and move to next/prev pattern##wrapV2",settings.wrapVertical==2)) { - settings.wrapVertical=2; - } - - ImGui::Text("Cursor movement keys behavior:"); - if (ImGui::RadioButton("Move by one##cmk0",settings.scrollStep==0)) { - settings.scrollStep=0; - } - if (ImGui::RadioButton("Move by Edit Step##cmk1",settings.scrollStep==1)) { - settings.scrollStep=1; - } - - ImGui::Text("Effect input cursor behavior:"); - if (ImGui::RadioButton("Move down##eicb0",settings.effectCursorDir==0)) { - settings.effectCursorDir=0; - } - if (ImGui::RadioButton("Move to effect value (otherwise move down)##eicb1",settings.effectCursorDir==1)) { - settings.effectCursorDir=1; - } - if (ImGui::RadioButton("Move to effect value/next effect and wrap around##eicb2",settings.effectCursorDir==2)) { - settings.effectCursorDir=2; - } - - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Audio/MIDI")) { - ImGui::Text("Backend"); - ImGui::SameLine(); - ImGui::Combo("##Backend",&settings.audioEngine,audioBackends,2); - - ImGui::Text("Device"); - ImGui::SameLine(); - String audioDevName=settings.audioDevice.empty()?"":settings.audioDevice; - if (ImGui::BeginCombo("##AudioDevice",audioDevName.c_str())) { - if (ImGui::Selectable("",settings.audioDevice.empty())) { - settings.audioDevice=""; - } - for (String& i: e->getAudioDevices()) { - if (ImGui::Selectable(i.c_str(),i==settings.audioDevice)) { - settings.audioDevice=i; - } - } - ImGui::EndCombo(); - } - - ImGui::Text("Sample rate"); - ImGui::SameLine(); - String sr=fmt::sprintf("%d",settings.audioRate); - if (ImGui::BeginCombo("##SampleRate",sr.c_str())) { - SAMPLE_RATE_SELECTABLE(8000); - SAMPLE_RATE_SELECTABLE(16000); - SAMPLE_RATE_SELECTABLE(22050); - SAMPLE_RATE_SELECTABLE(32000); - SAMPLE_RATE_SELECTABLE(44100); - SAMPLE_RATE_SELECTABLE(48000); - SAMPLE_RATE_SELECTABLE(88200); - SAMPLE_RATE_SELECTABLE(96000); - SAMPLE_RATE_SELECTABLE(192000); - ImGui::EndCombo(); - } - - ImGui::Text("Buffer size"); - ImGui::SameLine(); - String bs=fmt::sprintf("%d (latency: ~%.1fms)",settings.audioBufSize,2000.0*(double)settings.audioBufSize/(double)MAX(1,settings.audioRate)); - if (ImGui::BeginCombo("##BufferSize",bs.c_str())) { - BUFFER_SIZE_SELECTABLE(64); - BUFFER_SIZE_SELECTABLE(128); - BUFFER_SIZE_SELECTABLE(256); - BUFFER_SIZE_SELECTABLE(512); - BUFFER_SIZE_SELECTABLE(1024); - BUFFER_SIZE_SELECTABLE(2048); - ImGui::EndCombo(); - } - - ImGui::Text("Quality"); - ImGui::SameLine(); - ImGui::Combo("##Quality",&settings.audioQuality,audioQualities,2); - - bool forceMonoB=settings.forceMono; - if (ImGui::Checkbox("Force mono audio",&forceMonoB)) { - settings.forceMono=forceMonoB; - } - - TAAudioDesc& audioWant=e->getAudioDescWant(); - TAAudioDesc& audioGot=e->getAudioDescGot(); - - ImGui::Text("want: %d samples @ %.0fHz",audioWant.bufsize,audioWant.rate); - ImGui::Text("got: %d samples @ %.0fHz",audioGot.bufsize,audioGot.rate); - - ImGui::Separator(); - - ImGui::Text("MIDI input"); - ImGui::SameLine(); - String midiInName=settings.midiInDevice.empty()?"":settings.midiInDevice; - bool hasToReloadMidi=false; - if (ImGui::BeginCombo("##MidiInDevice",midiInName.c_str())) { - if (ImGui::Selectable("",settings.midiInDevice.empty())) { - settings.midiInDevice=""; - hasToReloadMidi=true; - } - for (String& i: e->getMidiIns()) { - if (ImGui::Selectable(i.c_str(),i==settings.midiInDevice)) { - settings.midiInDevice=i; - hasToReloadMidi=true; - } - } - ImGui::EndCombo(); - } - - if (hasToReloadMidi) { - midiMap.read(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); - midiMap.compile(); - } - - ImGui::Text("MIDI output"); - ImGui::SameLine(); - String midiOutName=settings.midiOutDevice.empty()?"":settings.midiOutDevice; - if (ImGui::BeginCombo("##MidiOutDevice",midiOutName.c_str())) { - if (ImGui::Selectable("",settings.midiOutDevice.empty())) { - settings.midiOutDevice=""; - } - for (String& i: e->getMidiIns()) { - if (ImGui::Selectable(i.c_str(),i==settings.midiOutDevice)) { - settings.midiOutDevice=i; - } - } - ImGui::EndCombo(); - } - - if (ImGui::TreeNode("MIDI input settings")) { - ImGui::Checkbox("Note input",&midiMap.noteInput); - ImGui::Checkbox("Velocity input",&midiMap.volInput); - // TODO - //ImGui::Checkbox("Use raw velocity value (don't map from linear to log)",&midiMap.rawVolume); - //ImGui::Checkbox("Polyphonic/chord input",&midiMap.polyInput); - ImGui::Checkbox("Map MIDI channels to direct channels",&midiMap.directChannel); - ImGui::Checkbox("Program change is instrument selection",&midiMap.programChange); - //ImGui::Checkbox("Listen to MIDI clock",&midiMap.midiClock); - //ImGui::Checkbox("Listen to MIDI time code",&midiMap.midiTimeCode); - ImGui::Combo("Value input style",&midiMap.valueInputStyle,valueInputStyles,7); - if (midiMap.valueInputStyle>3) { - if (midiMap.valueInputStyle==6) { - if (ImGui::InputInt("Control##valueCCS",&midiMap.valueInputControlSingle,1,16)) { - if (midiMap.valueInputControlSingle<0) midiMap.valueInputControlSingle=0; - if (midiMap.valueInputControlSingle>127) midiMap.valueInputControlSingle=127; - } - } else { - if (ImGui::InputInt((midiMap.valueInputStyle==4)?"CC of upper nibble##valueCC1":"MSB CC##valueCC1",&midiMap.valueInputControlMSB,1,16)) { - if (midiMap.valueInputControlMSB<0) midiMap.valueInputControlMSB=0; - if (midiMap.valueInputControlMSB>127) midiMap.valueInputControlMSB=127; - } - if (ImGui::InputInt((midiMap.valueInputStyle==4)?"CC of lower nibble##valueCC2":"LSB CC##valueCC2",&midiMap.valueInputControlLSB,1,16)) { - if (midiMap.valueInputControlLSB<0) midiMap.valueInputControlLSB=0; - if (midiMap.valueInputControlLSB>127) midiMap.valueInputControlLSB=127; - } - } - } - if (ImGui::TreeNode("Per-column control change")) { - for (int i=0; i<18; i++) { - ImGui::PushID(i); - ImGui::Combo(specificControls[i],&midiMap.valueInputSpecificStyle[i],valueSInputStyles,4); - if (midiMap.valueInputSpecificStyle[i]>0) { - ImGui::Indent(); - if (midiMap.valueInputSpecificStyle[i]==3) { - if (ImGui::InputInt("Control##valueCCS",&midiMap.valueInputSpecificSingle[i],1,16)) { - if (midiMap.valueInputSpecificSingle[i]<0) midiMap.valueInputSpecificSingle[i]=0; - if (midiMap.valueInputSpecificSingle[i]>127) midiMap.valueInputSpecificSingle[i]=127; - } - } else { - if (ImGui::InputInt((midiMap.valueInputSpecificStyle[i]==4)?"CC of upper nibble##valueCC1":"MSB CC##valueCC1",&midiMap.valueInputSpecificMSB[i],1,16)) { - if (midiMap.valueInputSpecificMSB[i]<0) midiMap.valueInputSpecificMSB[i]=0; - if (midiMap.valueInputSpecificMSB[i]>127) midiMap.valueInputSpecificMSB[i]=127; - } - if (ImGui::InputInt((midiMap.valueInputSpecificStyle[i]==4)?"CC of lower nibble##valueCC2":"LSB CC##valueCC2",&midiMap.valueInputSpecificLSB[i],1,16)) { - if (midiMap.valueInputSpecificLSB[i]<0) midiMap.valueInputSpecificLSB[i]=0; - if (midiMap.valueInputSpecificLSB[i]>127) midiMap.valueInputSpecificLSB[i]=127; - } - } - ImGui::Unindent(); - } - ImGui::PopID(); - } - ImGui::TreePop(); - } - if (ImGui::SliderFloat("Volume curve",&midiMap.volExp,0.01,8.0,"%.2f")) { - if (midiMap.volExp<0.01) midiMap.volExp=0.01; - if (midiMap.volExp>8.0) midiMap.volExp=8.0; - } rightClickable - float curve[128]; - for (int i=0; i<128; i++) { - curve[i]=(int)(pow((double)i/127.0,midiMap.volExp)*127.0); - } - ImGui::PlotLines("##VolCurveDisplay",curve,128,0,"Volume curve",0.0,127.0,ImVec2(200.0f*dpiScale,200.0f*dpiScale)); - - ImGui::Text("Actions:"); + ImVec2 settingsViewSize=ImGui::GetContentRegionAvail(); + settingsViewSize.y-=ImGui::GetFrameHeight()+ImGui::GetStyle().WindowPadding.y; + if (ImGui::BeginChild("SettingsView",settingsViewSize)) { + ImGui::Text("Workspace layout:"); ImGui::SameLine(); - if (ImGui::Button(ICON_FA_PLUS "##AddAction")) { - midiMap.binds.push_back(MIDIBind()); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_EXTERNAL_LINK "##AddLearnAction")) { - midiMap.binds.push_back(MIDIBind()); - learning=midiMap.binds.size()-1; - } - if (learning!=-1) { - ImGui::SameLine(); - ImGui::Text("(learning! press a button or move a slider/knob/something on your device.)"); - } - - if (ImGui::BeginTable("MIDIActions",7)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.2); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.1); - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.3); - ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.2); - ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,0.5); - ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c6",ImGuiTableColumnFlags_WidthFixed); - - ImGui::TableNextRow(ImGuiTableRowFlags_Headers); - ImGui::TableNextColumn(); - ImGui::Text("Type"); - ImGui::TableNextColumn(); - ImGui::Text("Channel"); - ImGui::TableNextColumn(); - ImGui::Text("Note/Control"); - ImGui::TableNextColumn(); - ImGui::Text("Velocity/Value"); - ImGui::TableNextColumn(); - ImGui::Text("Action"); - ImGui::TableNextColumn(); - ImGui::Text("Learn"); - ImGui::TableNextColumn(); - ImGui::Text("Remove"); - - for (size_t i=0; i3.0f) settings.dpiScale=3.0f; - } rightClickable - } - ImGui::Text("Main font"); - ImGui::SameLine(); - ImGui::Combo("##MainFont",&settings.mainFont,mainFonts,7); - if (settings.mainFont==6) { - ImGui::InputText("##MainFontPath",&settings.mainFontPath); - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FOLDER "##MainFontLoad")) { - openFileDialog(GUI_FILE_LOAD_MAIN_FONT); - } - } - if (ImGui::InputInt("Size##MainFontSize",&settings.mainFontSize)) { - if (settings.mainFontSize<3) settings.mainFontSize=3; - if (settings.mainFontSize>96) settings.mainFontSize=96; - } - ImGui::Text("Pattern font"); - ImGui::SameLine(); - ImGui::Combo("##PatFont",&settings.patFont,patFonts,7); - if (settings.patFont==6) { - ImGui::InputText("##PatFontPath",&settings.patFontPath); - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FOLDER "##PatFontLoad")) { - openFileDialog(GUI_FILE_LOAD_PAT_FONT); - } - } - if (ImGui::InputInt("Size##PatFontSize",&settings.patFontSize)) { - if (settings.patFontSize<3) settings.patFontSize=3; - if (settings.patFontSize>96) settings.patFontSize=96; - } - - bool loadJapaneseB=settings.loadJapanese; - if (ImGui::Checkbox("Display Japanese characters",&loadJapaneseB)) { - settings.loadJapanese=loadJapaneseB; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip( - "Only toggle this option if you have enough graphics memory.\n" - "This is a temporary solution until dynamic font atlas is implemented in Dear ImGui.\n\n" - "このオプションは、十分なグラフィックメモリがある場合にのみ切り替えてください。\n" - "これは、Dear ImGuiにダイナミックフォントアトラスが実装されるまでの一時的な解決策です。" - ); - } - - ImGui::Separator(); - - ImGui::Text("Orders row number format:"); - if (ImGui::RadioButton("Decimal##orbD",settings.orderRowsBase==0)) { - settings.orderRowsBase=0; - } - if (ImGui::RadioButton("Hexadecimal##orbH",settings.orderRowsBase==1)) { - settings.orderRowsBase=1; - } - - ImGui::Text("Pattern row number format:"); - if (ImGui::RadioButton("Decimal##prbD",settings.patRowsBase==0)) { - settings.patRowsBase=0; - } - if (ImGui::RadioButton("Hexadecimal##prbH",settings.patRowsBase==1)) { - settings.patRowsBase=1; - } - - ImGui::Text("FM parameter names:"); - if (ImGui::RadioButton("Friendly##fmn0",settings.fmNames==0)) { - settings.fmNames=0; - } - if (ImGui::RadioButton("Technical##fmn1",settings.fmNames==1)) { - settings.fmNames=1; - } - if (ImGui::RadioButton("Technical (alternate)##fmn2",settings.fmNames==2)) { - settings.fmNames=2; - } - - ImGui::Separator(); - - ImGui::Text("Title bar:"); - if (ImGui::RadioButton("Furnace##tbar0",settings.titleBarInfo==0)) { - settings.titleBarInfo=0; - updateWindowTitle(); - } - if (ImGui::RadioButton("Song Name - Furnace##tbar1",settings.titleBarInfo==1)) { - settings.titleBarInfo=1; - updateWindowTitle(); - } - if (ImGui::RadioButton("file_name.fur - Furnace##tbar2",settings.titleBarInfo==2)) { - settings.titleBarInfo=2; - updateWindowTitle(); - } - if (ImGui::RadioButton("/path/to/file.fur - Furnace##tbar3",settings.titleBarInfo==3)) { - settings.titleBarInfo=3; - updateWindowTitle(); - } - - bool titleBarSysB=settings.titleBarSys; - if (ImGui::Checkbox("Display system name on title bar",&titleBarSysB)) { - settings.titleBarSys=titleBarSysB; - updateWindowTitle(); - } - - ImGui::Text("Status bar:"); - if (ImGui::RadioButton("Cursor details##sbar0",settings.statusDisplay==0)) { - settings.statusDisplay=0; - } - if (ImGui::RadioButton("File path##sbar1",settings.statusDisplay==1)) { - settings.statusDisplay=1; - } - if (ImGui::RadioButton("Cursor details or file path##sbar2",settings.statusDisplay==2)) { - settings.statusDisplay=2; - } - if (ImGui::RadioButton("Nothing##sbar3",settings.statusDisplay==3)) { - settings.statusDisplay=3; - } - - ImGui::Text("Play/edit controls layout:"); - if (ImGui::RadioButton("Classic##ecl0",settings.controlLayout==0)) { - settings.controlLayout=0; - } - if (ImGui::RadioButton("Compact##ecl1",settings.controlLayout==1)) { - settings.controlLayout=1; - } - if (ImGui::RadioButton("Compact (vertical)##ecl2",settings.controlLayout==2)) { - settings.controlLayout=2; - } - if (ImGui::RadioButton("Split##ecl3",settings.controlLayout==3)) { - settings.controlLayout=3; - } - - ImGui::Text("FM parameter editor layout:"); - if (ImGui::RadioButton("Modern##fml0",settings.fmLayout==0)) { - settings.fmLayout=0; - } - if (ImGui::RadioButton("Compact (2x2, classic)##fml1",settings.fmLayout==1)) { - settings.fmLayout=1; - } - if (ImGui::RadioButton("Compact (1x4)##fml2",settings.fmLayout==2)) { - settings.fmLayout=2; - } - if (ImGui::RadioButton("Compact (4x1)##fml3",settings.fmLayout==3)) { - settings.fmLayout=3; - } - - ImGui::Text("Position of Sustain in FM editor:"); - if (ImGui::RadioButton("Between Decay and Sustain Rate##susp0",settings.susPosition==0)) { - settings.susPosition=0; - } - if (ImGui::RadioButton("After Release Rate##susp1",settings.susPosition==1)) { - settings.susPosition=1; - } - - bool macroViewB=settings.macroView; - if (ImGui::Checkbox("Classic macro view (standard macros only; deprecated!)",¯oViewB)) { - settings.macroView=macroViewB; - } - - bool unifiedDataViewB=settings.unifiedDataView; - if (ImGui::Checkbox("Unified instrument/wavetable/sample list",&unifiedDataViewB)) { - settings.unifiedDataView=unifiedDataViewB; - } - - bool chipNamesB=settings.chipNames; - if (ImGui::Checkbox("Use chip names instead of system names",&chipNamesB)) { - settings.chipNames=chipNamesB; - } - - bool overflowHighlightB=settings.overflowHighlight; - if (ImGui::Checkbox("Overflow pattern highlights",&overflowHighlightB)) { - settings.overflowHighlight=overflowHighlightB; - } - - bool viewPrevPatternB=settings.viewPrevPattern; - if (ImGui::Checkbox("Display previous/next pattern",&viewPrevPatternB)) { - settings.viewPrevPattern=viewPrevPatternB; - } - - bool germanNotationB=settings.germanNotation; - if (ImGui::Checkbox("Use German notation",&germanNotationB)) { - settings.germanNotation=germanNotationB; - } - - // sorry. temporarily disabled until ImGui has a way to add separators in tables arbitrarily. - /*bool sysSeparatorsB=settings.sysSeparators; - if (ImGui::Checkbox("Add separators between systems in Orders",&sysSeparatorsB)) { - settings.sysSeparators=sysSeparatorsB; - }*/ - - bool partyTimeB=settings.partyTime; - if (ImGui::Checkbox("About screen party time",&partyTimeB)) { - settings.partyTime=partyTimeB; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Warning: may cause epileptic seizures."); - } - - ImGui::Separator(); - - bool roundedWindowsB=settings.roundedWindows; - if (ImGui::Checkbox("Rounded window corners",&roundedWindowsB)) { - settings.roundedWindows=roundedWindowsB; - } - - bool roundedButtonsB=settings.roundedButtons; - if (ImGui::Checkbox("Rounded buttons",&roundedButtonsB)) { - settings.roundedButtons=roundedButtonsB; - } - - bool roundedMenusB=settings.roundedMenus; - if (ImGui::Checkbox("Rounded menu corners",&roundedMenusB)) { - settings.roundedMenus=roundedMenusB; - } - - bool frameBordersB=settings.frameBorders; - if (ImGui::Checkbox("Borders around widgets",&frameBordersB)) { - settings.frameBorders=frameBordersB; - } - - ImGui::Separator(); - - ImGui::Text("Oscilloscope settings:"); - - bool oscRoundedCornersB=settings.oscRoundedCorners; - if (ImGui::Checkbox("Rounded corners",&oscRoundedCornersB)) { - settings.oscRoundedCorners=oscRoundedCornersB; - } - - bool oscTakesEntireWindowB=settings.oscTakesEntireWindow; - if (ImGui::Checkbox("Fill entire window",&oscTakesEntireWindowB)) { - settings.oscTakesEntireWindow=oscTakesEntireWindowB; - } - - bool oscBorderB=settings.oscBorder; - if (ImGui::Checkbox("Border",&oscBorderB)) { - settings.oscBorder=oscBorderB; - } - - ImGui::Separator(); - - if (ImGui::TreeNode("Color scheme")) { if (ImGui::Button("Import")) { - openFileDialog(GUI_FILE_IMPORT_COLORS); + openFileDialog(GUI_FILE_IMPORT_LAYOUT); } ImGui::SameLine(); if (ImGui::Button("Export")) { - openFileDialog(GUI_FILE_EXPORT_COLORS); + openFileDialog(GUI_FILE_EXPORT_LAYOUT); } ImGui::SameLine(); - if (ImGui::Button("Reset defaults")) { - showWarning("Are you sure you want to reset the color scheme?",GUI_WARN_RESET_COLORS); + if (ImGui::Button("Reset")) { + showWarning("Are you sure you want to reset the workspace layout?",GUI_WARN_RESET_LAYOUT); } - if (ImGui::TreeNode("General")) { - ImGui::Text("Color scheme type:"); - if (ImGui::RadioButton("Dark##gcb0",settings.guiColorsBase==0)) { - settings.guiColorsBase=0; + + ImGui::Separator(); + + ImGui::Text("Initial system/chips:"); + ImGui::SameLine(); + if (ImGui::Button("Current systems")) { + settings.initialSys.clear(); + for (int i=0; isong.systemLen; i++) { + settings.initialSys.push_back(e->song.system[i]); + settings.initialSys.push_back(e->song.systemVol[i]); + settings.initialSys.push_back(e->song.systemPan[i]); + settings.initialSys.push_back(e->song.systemFlags[i]); } - if (ImGui::RadioButton("Light##gcb1",settings.guiColorsBase==1)) { - settings.guiColorsBase=1; + } + ImGui::SameLine(); + if (ImGui::Button("Randomize")) { + settings.initialSys.clear(); + int howMany=1+rand()%3; + int totalAvailSys=0; + for (totalAvailSys=0; availableSystems[totalAvailSys]; totalAvailSys++); + if (totalAvailSys>0) { + for (int i=0; i sorted; - if (ImGui::BeginTable("keysNoteInput",4)) { - for (std::map::value_type& i: noteKeys) { - std::vector::iterator j; - for (j=sorted.begin(); j!=sorted.end(); j++) { - if (j->val>i.second) { - break; - } - } - sorted.insert(j,MappedInput(i.first,i.second)); - } - - static char id[4096]; - - ImGui::TableNextRow(ImGuiTableRowFlags_Headers); - ImGui::TableNextColumn(); - ImGui::Text("Key"); - ImGui::TableNextColumn(); - ImGui::Text("Type"); - ImGui::TableNextColumn(); - ImGui::Text("Value"); - ImGui::TableNextColumn(); - ImGui::Text("Remove"); - - for (MappedInput& i: sorted) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("%s",SDL_GetScancodeName((SDL_Scancode)i.scan)); - ImGui::TableNextColumn(); - if (i.val==102) { - snprintf(id,4095,"Envelope release##SNType_%d",i.scan); - if (ImGui::Button(id)) { - noteKeys[i.scan]=0; - } - } else if (i.val==101) { - snprintf(id,4095,"Note release##SNType_%d",i.scan); - if (ImGui::Button(id)) { - noteKeys[i.scan]=102; - } - } else if (i.val==100) { - snprintf(id,4095,"Note off##SNType_%d",i.scan); - if (ImGui::Button(id)) { - noteKeys[i.scan]=101; - } - } else { - snprintf(id,4095,"Note##SNType_%d",i.scan); - if (ImGui::Button(id)) { - noteKeys[i.scan]=100; - } - } - ImGui::TableNextColumn(); - if (i.val<100) { - snprintf(id,4095,"##SNValue_%d",i.scan); - if (ImGui::InputInt(id,&i.val,1,1)) { - if (i.val<0) i.val=0; - if (i.val>96) i.val=96; - noteKeys[i.scan]=i.val; - } - } - ImGui::TableNextColumn(); - snprintf(id,4095,ICON_FA_TIMES "##SNRemove_%d",i.scan); - if (ImGui::Button(id)) { - noteKeys.erase(i.scan); - } - } - ImGui::EndTable(); - - if (ImGui::BeginCombo("##SNAddNew","Add...")) { - for (int i=0; i=8) { + settings.initialSys.erase(settings.initialSys.begin()+i,settings.initialSys.begin()+i+4); + i-=4; + } } - ImGui::TreePop(); - } - if (ImGui::TreeNode("Pattern")) { - KEYBIND_CONFIG_BEGIN("keysPattern"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_NOTE_UP); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_NOTE_DOWN); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_OCTAVE_UP); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_OCTAVE_DOWN); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECT_ALL); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CUT); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COPY); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_PASTE); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_PASTE_MIX); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_PASTE_MIX_BG); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_PASTE_FLOOD); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_PASTE_OVERFLOW); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_UP); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_DOWN); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_LEFT); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_RIGHT); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_UP_ONE); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_DOWN_ONE); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_LEFT_CHANNEL); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_RIGHT_CHANNEL); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_PREVIOUS_CHANNEL); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_NEXT_CHANNEL); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_BEGIN); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_END); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_UP_COARSE); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_DOWN_COARSE); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_UP); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_DOWN); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_LEFT); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_RIGHT); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_UP_ONE); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_DOWN_ONE); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_BEGIN); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_END); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_UP_COARSE); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_DOWN_COARSE); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_DELETE); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_PULL_DELETE); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_INSERT); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_MUTE_CURSOR); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SOLO_CURSOR); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_UNMUTE_ALL); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_NEXT_ORDER); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_PREV_ORDER); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COLLAPSE); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_INCREASE_COLUMNS); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_DECREASE_COLUMNS); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_INTERPOLATE); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_FADE); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_INVERT_VALUES); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_FLIP_SELECTION); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COLLAPSE_ROWS); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_EXPAND_ROWS); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_LATCH); + if (ImGui::Button(ICON_FA_PLUS "##InitSysAdd")) { + settings.initialSys.push_back(DIV_SYSTEM_YM2612); + settings.initialSys.push_back(64); + settings.initialSys.push_back(0); + settings.initialSys.push_back(0); + } + + ImGui::Separator(); + + ImGui::Text("Toggle channel solo on:"); + if (ImGui::RadioButton("Right-click or double-click##soloA",settings.soloAction==0)) { + settings.soloAction=0; + } + if (ImGui::RadioButton("Right-click##soloR",settings.soloAction==1)) { + settings.soloAction=1; + } + if (ImGui::RadioButton("Double-click##soloD",settings.soloAction==2)) { + settings.soloAction=2; + } + + bool pushNibbleB=settings.pushNibble; + if (ImGui::Checkbox("Push value when overwriting instead of clearing it",&pushNibbleB)) { + settings.pushNibble=pushNibbleB; + } + + bool pullDeleteBehaviorB=settings.pullDeleteBehavior; + if (ImGui::Checkbox("Move cursor up on backspace-delete",&pullDeleteBehaviorB)) { + settings.pullDeleteBehavior=pullDeleteBehaviorB; + } + + bool stepOnDeleteB=settings.stepOnDelete; + if (ImGui::Checkbox("Move cursor by edit step on delete",&stepOnDeleteB)) { + settings.stepOnDelete=stepOnDeleteB; + } + + bool absorbInsInputB=settings.absorbInsInput; + if (ImGui::Checkbox("Change current instrument when changing instrument column (absorb)",&absorbInsInputB)) { + settings.absorbInsInput=absorbInsInputB; + } + + bool effectDeletionAltersValueB=settings.effectDeletionAltersValue; + if (ImGui::Checkbox("Delete effect value when deleting effect",&effectDeletionAltersValueB)) { + settings.effectDeletionAltersValue=effectDeletionAltersValueB; + } + + bool scrollChangesOrderB=settings.scrollChangesOrder; + if (ImGui::Checkbox("Change order when scrolling outside of pattern bounds",&scrollChangesOrderB)) { + settings.scrollChangesOrder=scrollChangesOrderB; + } + + bool stepOnInsertB=settings.stepOnInsert; + if (ImGui::Checkbox("Move cursor by edit step on insert (push)",&stepOnInsertB)) { + settings.stepOnInsert=stepOnInsertB; + } + + bool cursorPastePosB=settings.cursorPastePos; + if (ImGui::Checkbox("Move cursor to end of clipboard content when pasting",&cursorPastePosB)) { + settings.cursorPastePos=cursorPastePosB; + } + + bool cursorMoveNoScrollB=settings.cursorMoveNoScroll; + if (ImGui::Checkbox("Don't scroll when moving cursor",&cursorMoveNoScrollB)) { + settings.cursorMoveNoScroll=cursorMoveNoScrollB; + } + + bool allowEditDockingB=settings.allowEditDocking; + if (ImGui::Checkbox("Allow docking editors",&allowEditDockingB)) { + settings.allowEditDocking=allowEditDockingB; + } + + bool avoidRaisingPatternB=settings.avoidRaisingPattern; + if (ImGui::Checkbox("Don't raise pattern editor on click",&avoidRaisingPatternB)) { + settings.avoidRaisingPattern=avoidRaisingPatternB; + } + + bool insFocusesPatternB=settings.insFocusesPattern; + if (ImGui::Checkbox("Focus pattern editor when selecting instrument",&insFocusesPatternB)) { + settings.insFocusesPattern=insFocusesPatternB; + } + + bool restartOnFlagChangeB=settings.restartOnFlagChange; + if (ImGui::Checkbox("Restart song when changing system properties",&restartOnFlagChangeB)) { + settings.restartOnFlagChange=restartOnFlagChangeB; + } + + bool insLoadAlwaysReplaceB=settings.insLoadAlwaysReplace; + if (ImGui::Checkbox("Always replace currently selected instrument when loading from instrument list",&insLoadAlwaysReplaceB)) { + settings.insLoadAlwaysReplace=insLoadAlwaysReplaceB; + } + + bool sysFileDialogB=settings.sysFileDialog; + if (ImGui::Checkbox("Use system file picker",&sysFileDialogB)) { + settings.sysFileDialog=sysFileDialogB; + } + + bool moveWindowTitleB=settings.moveWindowTitle; + if (ImGui::Checkbox("Only allow window movement when clicking on title bar",&moveWindowTitleB)) { + settings.moveWindowTitle=moveWindowTitleB; + applyUISettings(false); + } + + bool eventDelayB=settings.eventDelay; + if (ImGui::Checkbox("Enable event delay",&eventDelayB)) { + settings.eventDelay=eventDelayB; + applyUISettings(false); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("may cause issues with high-polling-rate mice when previewing notes."); + } + + bool powerSaveB=settings.powerSave; + if (ImGui::Checkbox("Power-saving mode",&powerSaveB)) { + settings.powerSave=powerSaveB; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("saves power by lowering the frame rate to 2fps when idle.\nmay cause issues under Mesa drivers!"); + } + + ImGui::Text("Note preview behavior:"); + if (ImGui::RadioButton("Never##npb0",settings.notePreviewBehavior==0)) { + settings.notePreviewBehavior=0; + } + if (ImGui::RadioButton("When cursor is in Note column##npb1",settings.notePreviewBehavior==1)) { + settings.notePreviewBehavior=1; + } + if (ImGui::RadioButton("When cursor is in Note column or not in edit mode##npb2",settings.notePreviewBehavior==2)) { + settings.notePreviewBehavior=2; + } + if (ImGui::RadioButton("Always##npb3",settings.notePreviewBehavior==3)) { + settings.notePreviewBehavior=3; + } + + ImGui::Text("Wrap pattern cursor horizontally:"); + if (ImGui::RadioButton("No##wrapH0",settings.wrapHorizontal==0)) { + settings.wrapHorizontal=0; + } + if (ImGui::RadioButton("Yes##wrapH1",settings.wrapHorizontal==1)) { + settings.wrapHorizontal=1; + } + if (ImGui::RadioButton("Yes, and move to next/prev row##wrapH2",settings.wrapHorizontal==2)) { + settings.wrapHorizontal=2; + } + + ImGui::Text("Wrap pattern cursor vertically:"); + if (ImGui::RadioButton("No##wrapV0",settings.wrapVertical==0)) { + settings.wrapVertical=0; + } + if (ImGui::RadioButton("Yes##wrapV1",settings.wrapVertical==1)) { + settings.wrapVertical=1; + } + if (ImGui::RadioButton("Yes, and move to next/prev pattern##wrapV2",settings.wrapVertical==2)) { + settings.wrapVertical=2; + } + + ImGui::Text("Cursor movement keys behavior:"); + if (ImGui::RadioButton("Move by one##cmk0",settings.scrollStep==0)) { + settings.scrollStep=0; + } + if (ImGui::RadioButton("Move by Edit Step##cmk1",settings.scrollStep==1)) { + settings.scrollStep=1; + } + + ImGui::Text("Effect input cursor behavior:"); + if (ImGui::RadioButton("Move down##eicb0",settings.effectCursorDir==0)) { + settings.effectCursorDir=0; + } + if (ImGui::RadioButton("Move to effect value (otherwise move down)##eicb1",settings.effectCursorDir==1)) { + settings.effectCursorDir=1; + } + if (ImGui::RadioButton("Move to effect value/next effect and wrap around##eicb2",settings.effectCursorDir==2)) { + settings.effectCursorDir=2; + } + } + ImGui::EndChild(); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Audio/MIDI")) { + ImVec2 settingsViewSize=ImGui::GetContentRegionAvail(); + settingsViewSize.y-=ImGui::GetFrameHeight()+ImGui::GetStyle().WindowPadding.y; + if (ImGui::BeginChild("SettingsView",settingsViewSize)) { + ImGui::Text("Backend"); + ImGui::SameLine(); + ImGui::Combo("##Backend",&settings.audioEngine,audioBackends,2); + + ImGui::Text("Device"); + ImGui::SameLine(); + String audioDevName=settings.audioDevice.empty()?"":settings.audioDevice; + if (ImGui::BeginCombo("##AudioDevice",audioDevName.c_str())) { + if (ImGui::Selectable("",settings.audioDevice.empty())) { + settings.audioDevice=""; + } + for (String& i: e->getAudioDevices()) { + if (ImGui::Selectable(i.c_str(),i==settings.audioDevice)) { + settings.audioDevice=i; + } + } + ImGui::EndCombo(); + } + + ImGui::Text("Sample rate"); + ImGui::SameLine(); + String sr=fmt::sprintf("%d",settings.audioRate); + if (ImGui::BeginCombo("##SampleRate",sr.c_str())) { + SAMPLE_RATE_SELECTABLE(8000); + SAMPLE_RATE_SELECTABLE(16000); + SAMPLE_RATE_SELECTABLE(22050); + SAMPLE_RATE_SELECTABLE(32000); + SAMPLE_RATE_SELECTABLE(44100); + SAMPLE_RATE_SELECTABLE(48000); + SAMPLE_RATE_SELECTABLE(88200); + SAMPLE_RATE_SELECTABLE(96000); + SAMPLE_RATE_SELECTABLE(192000); + ImGui::EndCombo(); + } + + ImGui::Text("Buffer size"); + ImGui::SameLine(); + String bs=fmt::sprintf("%d (latency: ~%.1fms)",settings.audioBufSize,2000.0*(double)settings.audioBufSize/(double)MAX(1,settings.audioRate)); + if (ImGui::BeginCombo("##BufferSize",bs.c_str())) { + BUFFER_SIZE_SELECTABLE(64); + BUFFER_SIZE_SELECTABLE(128); + BUFFER_SIZE_SELECTABLE(256); + BUFFER_SIZE_SELECTABLE(512); + BUFFER_SIZE_SELECTABLE(1024); + BUFFER_SIZE_SELECTABLE(2048); + ImGui::EndCombo(); + } - // TODO: collapse/expand pattern and song + ImGui::Text("Quality"); + ImGui::SameLine(); + ImGui::Combo("##Quality",&settings.audioQuality,audioQualities,2); - KEYBIND_CONFIG_END; - ImGui::TreePop(); + ImGui::Text("Metronome volume"); + ImGui::SameLine(); + if (ImGui::SliderInt("##MetroVol",&settings.metroVol,0,200,"%d%%")) { + if (settings.metroVol<0) settings.metroVol=0; + if (settings.metroVol>200) settings.metroVol=200; + e->setMetronomeVol(((float)settings.metroVol)/100.0f); + } + + bool lowLatencyB=settings.lowLatency; + if (ImGui::Checkbox("Low-latency mode (experimental!)",&lowLatencyB)) { + settings.lowLatency=lowLatencyB; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("reduces latency by running the engine faster than the tick rate.\nuseful for live playback/jam mode.\n\nwarning: experimental! may produce glitches.\nonly enable if your buffer size is small (10ms or less)."); + } + + bool forceMonoB=settings.forceMono; + if (ImGui::Checkbox("Force mono audio",&forceMonoB)) { + settings.forceMono=forceMonoB; + } + + TAAudioDesc& audioWant=e->getAudioDescWant(); + TAAudioDesc& audioGot=e->getAudioDescGot(); + + ImGui::Text("want: %d samples @ %.0fHz",audioWant.bufsize,audioWant.rate); + ImGui::Text("got: %d samples @ %.0fHz",audioGot.bufsize,audioGot.rate); + + ImGui::Separator(); + + ImGui::Text("MIDI input"); + ImGui::SameLine(); + String midiInName=settings.midiInDevice.empty()?"":settings.midiInDevice; + bool hasToReloadMidi=false; + if (ImGui::BeginCombo("##MidiInDevice",midiInName.c_str())) { + if (ImGui::Selectable("",settings.midiInDevice.empty())) { + settings.midiInDevice=""; + hasToReloadMidi=true; + } + for (String& i: e->getMidiIns()) { + if (ImGui::Selectable(i.c_str(),i==settings.midiInDevice)) { + settings.midiInDevice=i; + hasToReloadMidi=true; + } + } + ImGui::EndCombo(); + } + + if (hasToReloadMidi) { + midiMap.read(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); + midiMap.compile(); + } + + ImGui::Text("MIDI output"); + ImGui::SameLine(); + String midiOutName=settings.midiOutDevice.empty()?"":settings.midiOutDevice; + if (ImGui::BeginCombo("##MidiOutDevice",midiOutName.c_str())) { + if (ImGui::Selectable("",settings.midiOutDevice.empty())) { + settings.midiOutDevice=""; + } + for (String& i: e->getMidiIns()) { + if (ImGui::Selectable(i.c_str(),i==settings.midiOutDevice)) { + settings.midiOutDevice=i; + } + } + ImGui::EndCombo(); + } + + if (ImGui::TreeNode("MIDI input settings")) { + ImGui::Checkbox("Note input",&midiMap.noteInput); + ImGui::Checkbox("Velocity input",&midiMap.volInput); + // TODO + //ImGui::Checkbox("Use raw velocity value (don't map from linear to log)",&midiMap.rawVolume); + //ImGui::Checkbox("Polyphonic/chord input",&midiMap.polyInput); + ImGui::Checkbox("Map MIDI channels to direct channels",&midiMap.directChannel); + ImGui::Checkbox("Program change is instrument selection",&midiMap.programChange); + //ImGui::Checkbox("Listen to MIDI clock",&midiMap.midiClock); + //ImGui::Checkbox("Listen to MIDI time code",&midiMap.midiTimeCode); + ImGui::Combo("Value input style",&midiMap.valueInputStyle,valueInputStyles,7); + if (midiMap.valueInputStyle>3) { + if (midiMap.valueInputStyle==6) { + if (ImGui::InputInt("Control##valueCCS",&midiMap.valueInputControlSingle,1,16)) { + if (midiMap.valueInputControlSingle<0) midiMap.valueInputControlSingle=0; + if (midiMap.valueInputControlSingle>127) midiMap.valueInputControlSingle=127; + } + } else { + if (ImGui::InputInt((midiMap.valueInputStyle==4)?"CC of upper nibble##valueCC1":"MSB CC##valueCC1",&midiMap.valueInputControlMSB,1,16)) { + if (midiMap.valueInputControlMSB<0) midiMap.valueInputControlMSB=0; + if (midiMap.valueInputControlMSB>127) midiMap.valueInputControlMSB=127; + } + if (ImGui::InputInt((midiMap.valueInputStyle==4)?"CC of lower nibble##valueCC2":"LSB CC##valueCC2",&midiMap.valueInputControlLSB,1,16)) { + if (midiMap.valueInputControlLSB<0) midiMap.valueInputControlLSB=0; + if (midiMap.valueInputControlLSB>127) midiMap.valueInputControlLSB=127; + } + } + } + if (ImGui::TreeNode("Per-column control change")) { + for (int i=0; i<18; i++) { + ImGui::PushID(i); + ImGui::Combo(specificControls[i],&midiMap.valueInputSpecificStyle[i],valueSInputStyles,4); + if (midiMap.valueInputSpecificStyle[i]>0) { + ImGui::Indent(); + if (midiMap.valueInputSpecificStyle[i]==3) { + if (ImGui::InputInt("Control##valueCCS",&midiMap.valueInputSpecificSingle[i],1,16)) { + if (midiMap.valueInputSpecificSingle[i]<0) midiMap.valueInputSpecificSingle[i]=0; + if (midiMap.valueInputSpecificSingle[i]>127) midiMap.valueInputSpecificSingle[i]=127; + } + } else { + if (ImGui::InputInt((midiMap.valueInputSpecificStyle[i]==4)?"CC of upper nibble##valueCC1":"MSB CC##valueCC1",&midiMap.valueInputSpecificMSB[i],1,16)) { + if (midiMap.valueInputSpecificMSB[i]<0) midiMap.valueInputSpecificMSB[i]=0; + if (midiMap.valueInputSpecificMSB[i]>127) midiMap.valueInputSpecificMSB[i]=127; + } + if (ImGui::InputInt((midiMap.valueInputSpecificStyle[i]==4)?"CC of lower nibble##valueCC2":"LSB CC##valueCC2",&midiMap.valueInputSpecificLSB[i],1,16)) { + if (midiMap.valueInputSpecificLSB[i]<0) midiMap.valueInputSpecificLSB[i]=0; + if (midiMap.valueInputSpecificLSB[i]>127) midiMap.valueInputSpecificLSB[i]=127; + } + } + ImGui::Unindent(); + } + ImGui::PopID(); + } + ImGui::TreePop(); + } + if (ImGui::SliderFloat("Volume curve",&midiMap.volExp,0.01,8.0,"%.2f")) { + if (midiMap.volExp<0.01) midiMap.volExp=0.01; + if (midiMap.volExp>8.0) midiMap.volExp=8.0; + } rightClickable + float curve[128]; + for (int i=0; i<128; i++) { + curve[i]=(int)(pow((double)i/127.0,midiMap.volExp)*127.0); + } + ImGui::PlotLines("##VolCurveDisplay",curve,128,0,"Volume curve",0.0,127.0,ImVec2(200.0f*dpiScale,200.0f*dpiScale)); + + ImGui::Text("Actions:"); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_PLUS "##AddAction")) { + midiMap.binds.push_back(MIDIBind()); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_EXTERNAL_LINK "##AddLearnAction")) { + midiMap.binds.push_back(MIDIBind()); + learning=midiMap.binds.size()-1; + } + if (learning!=-1) { + ImGui::SameLine(); + ImGui::Text("(learning! press a button or move a slider/knob/something on your device.)"); + } + + if (ImGui::BeginTable("MIDIActions",7)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.2); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.1); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.3); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.2); + ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,0.5); + ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c6",ImGuiTableColumnFlags_WidthFixed); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Type"); + ImGui::TableNextColumn(); + ImGui::Text("Channel"); + ImGui::TableNextColumn(); + ImGui::Text("Note/Control"); + ImGui::TableNextColumn(); + ImGui::Text("Velocity/Value"); + ImGui::TableNextColumn(); + ImGui::Text("Action"); + ImGui::TableNextColumn(); + ImGui::Text("Learn"); + ImGui::TableNextColumn(); + ImGui::Text("Remove"); + + for (size_t i=0; i0 && (bind.data1+60)<180) { + nName=noteNames[bind.data1+60]; + } + snprintf(bindID,1024,"%d (0x%.2X, %s)",bind.data1,bind.data1,nName); + } + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("##BValue1",bindID)) { + if (ImGui::Selectable("Any",bind.data1==128)) { + bind.data1=128; + } + for (int j=0; j<128; j++) { + const char* nName="???"; + if ((j+60)>0 && (j+60)<180) { + nName=noteNames[j+60]; + } + snprintf(bindID,1024,"%d (0x%.2X, %s)##BV1_%d",j,j,nName,j); + if (ImGui::Selectable(bindID,bind.data1==j)) { + bind.data1=j; + } + } + ImGui::EndCombo(); + } + + ImGui::TableNextColumn(); + if (bind.data2==128) { + snprintf(bindID,1024,"Any"); + } else { + snprintf(bindID,1024,"%d (0x%.2X)",bind.data2,bind.data2); + } + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("##BValue2",bindID)) { + if (ImGui::Selectable("Any",bind.data2==128)) { + bind.data2=128; + } + for (int j=0; j<128; j++) { + snprintf(bindID,1024,"%d (0x%.2X)##BV2_%d",j,j,j); + if (ImGui::Selectable(bindID,bind.data2==j)) { + bind.data2=j; + } + } + ImGui::EndCombo(); + } + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("##BAction",(bind.action==0)?"--none--":guiActions[bind.action].friendlyName)) { + if (ImGui::Selectable("--none--",bind.action==0)) { + bind.action=0; + } + for (int j=0; j3.0f) settings.dpiScale=3.0f; + } rightClickable + } + ImGui::Text("Main font"); + ImGui::SameLine(); + ImGui::Combo("##MainFont",&settings.mainFont,mainFonts,7); + if (settings.mainFont==6) { + ImGui::InputText("##MainFontPath",&settings.mainFontPath); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FOLDER "##MainFontLoad")) { + openFileDialog(GUI_FILE_LOAD_MAIN_FONT); + } + } + if (ImGui::InputInt("Size##MainFontSize",&settings.mainFontSize)) { + if (settings.mainFontSize<3) settings.mainFontSize=3; + if (settings.mainFontSize>96) settings.mainFontSize=96; + } + ImGui::Text("Pattern font"); + ImGui::SameLine(); + ImGui::Combo("##PatFont",&settings.patFont,patFonts,7); + if (settings.patFont==6) { + ImGui::InputText("##PatFontPath",&settings.patFontPath); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FOLDER "##PatFontLoad")) { + openFileDialog(GUI_FILE_LOAD_PAT_FONT); + } + } + if (ImGui::InputInt("Size##PatFontSize",&settings.patFontSize)) { + if (settings.patFontSize<3) settings.patFontSize=3; + if (settings.patFontSize>96) settings.patFontSize=96; + } - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_ADD); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_DUPLICATE); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_OPEN); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_SAVE); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_MOVE_UP); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_MOVE_DOWN); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_DELETE); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_EDIT); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_UP); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_DOWN); + bool loadJapaneseB=settings.loadJapanese; + if (ImGui::Checkbox("Display Japanese characters",&loadJapaneseB)) { + settings.loadJapanese=loadJapaneseB; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "Only toggle this option if you have enough graphics memory.\n" + "This is a temporary solution until dynamic font atlas is implemented in Dear ImGui.\n\n" + "このオプションは、十分なグラフィックメモリがある場合にのみ切り替えてください。\n" + "これは、Dear ImGuiにダイナミックフォントアトラスが実装されるまでの一時的な解決策です。" + ); + } - KEYBIND_CONFIG_END; - ImGui::TreePop(); + ImGui::Separator(); + + ImGui::Text("Orders row number format:"); + if (ImGui::RadioButton("Decimal##orbD",settings.orderRowsBase==0)) { + settings.orderRowsBase=0; + } + if (ImGui::RadioButton("Hexadecimal##orbH",settings.orderRowsBase==1)) { + settings.orderRowsBase=1; + } + + ImGui::Text("Pattern row number format:"); + if (ImGui::RadioButton("Decimal##prbD",settings.patRowsBase==0)) { + settings.patRowsBase=0; + } + if (ImGui::RadioButton("Hexadecimal##prbH",settings.patRowsBase==1)) { + settings.patRowsBase=1; + } + + ImGui::Text("FM parameter names:"); + if (ImGui::RadioButton("Friendly##fmn0",settings.fmNames==0)) { + settings.fmNames=0; + } + if (ImGui::RadioButton("Technical##fmn1",settings.fmNames==1)) { + settings.fmNames=1; + } + if (ImGui::RadioButton("Technical (alternate)##fmn2",settings.fmNames==2)) { + settings.fmNames=2; + } + + ImGui::Separator(); + + ImGui::Text("Title bar:"); + if (ImGui::RadioButton("Furnace##tbar0",settings.titleBarInfo==0)) { + settings.titleBarInfo=0; + updateWindowTitle(); + } + if (ImGui::RadioButton("Song Name - Furnace##tbar1",settings.titleBarInfo==1)) { + settings.titleBarInfo=1; + updateWindowTitle(); + } + if (ImGui::RadioButton("file_name.fur - Furnace##tbar2",settings.titleBarInfo==2)) { + settings.titleBarInfo=2; + updateWindowTitle(); + } + if (ImGui::RadioButton("/path/to/file.fur - Furnace##tbar3",settings.titleBarInfo==3)) { + settings.titleBarInfo=3; + updateWindowTitle(); + } + + bool titleBarSysB=settings.titleBarSys; + if (ImGui::Checkbox("Display system name on title bar",&titleBarSysB)) { + settings.titleBarSys=titleBarSysB; + updateWindowTitle(); + } + + bool noMultiSystemB=settings.noMultiSystem; + if (ImGui::Checkbox("Display chip names instead of \"multi-system\" in title bar",&noMultiSystemB)) { + settings.noMultiSystem=noMultiSystemB; + updateWindowTitle(); + } + + ImGui::Text("Status bar:"); + if (ImGui::RadioButton("Cursor details##sbar0",settings.statusDisplay==0)) { + settings.statusDisplay=0; + } + if (ImGui::RadioButton("File path##sbar1",settings.statusDisplay==1)) { + settings.statusDisplay=1; + } + if (ImGui::RadioButton("Cursor details or file path##sbar2",settings.statusDisplay==2)) { + settings.statusDisplay=2; + } + if (ImGui::RadioButton("Nothing##sbar3",settings.statusDisplay==3)) { + settings.statusDisplay=3; + } + + ImGui::Text("Play/edit controls layout:"); + if (ImGui::RadioButton("Classic##ecl0",settings.controlLayout==0)) { + settings.controlLayout=0; + } + if (ImGui::RadioButton("Compact##ecl1",settings.controlLayout==1)) { + settings.controlLayout=1; + } + if (ImGui::RadioButton("Compact (vertical)##ecl2",settings.controlLayout==2)) { + settings.controlLayout=2; + } + if (ImGui::RadioButton("Split##ecl3",settings.controlLayout==3)) { + settings.controlLayout=3; + } + + ImGui::Text("FM parameter editor layout:"); + if (ImGui::RadioButton("Modern##fml0",settings.fmLayout==0)) { + settings.fmLayout=0; + } + if (ImGui::RadioButton("Compact (2x2, classic)##fml1",settings.fmLayout==1)) { + settings.fmLayout=1; + } + if (ImGui::RadioButton("Compact (1x4)##fml2",settings.fmLayout==2)) { + settings.fmLayout=2; + } + if (ImGui::RadioButton("Compact (4x1)##fml3",settings.fmLayout==3)) { + settings.fmLayout=3; + } + + ImGui::Text("Position of Sustain in FM editor:"); + if (ImGui::RadioButton("Between Decay and Sustain Rate##susp0",settings.susPosition==0)) { + settings.susPosition=0; + } + if (ImGui::RadioButton("After Release Rate##susp1",settings.susPosition==1)) { + settings.susPosition=1; + } + + ImGui::Separator(); + + bool insEditColorizeB=settings.insEditColorize; + if (ImGui::Checkbox("Colorize instrument editor using instrument type",&insEditColorizeB)) { + settings.insEditColorize=insEditColorizeB; + } + + bool separateFMColorsB=settings.separateFMColors; + if (ImGui::Checkbox("Use separate colors for carriers/modulators in FM editor",&separateFMColorsB)) { + settings.separateFMColors=separateFMColorsB; + } + + bool unifiedDataViewB=settings.unifiedDataView; + if (ImGui::Checkbox("Unified instrument/wavetable/sample list",&unifiedDataViewB)) { + settings.unifiedDataView=unifiedDataViewB; + } + if (settings.unifiedDataView) { + settings.horizontalDataView=0; + } + + ImGui::BeginDisabled(settings.unifiedDataView); + bool horizontalDataViewB=settings.horizontalDataView; + if (ImGui::Checkbox("Horizontal instrument list",&horizontalDataViewB)) { + settings.horizontalDataView=horizontalDataViewB; + } + ImGui::EndDisabled(); + + bool chipNamesB=settings.chipNames; + if (ImGui::Checkbox("Use chip names instead of system names",&chipNamesB)) { + settings.chipNames=chipNamesB; + } + + bool oplStandardWaveNamesB=settings.oplStandardWaveNames; + if (ImGui::Checkbox("Use standard OPL waveform names",&oplStandardWaveNamesB)) { + settings.oplStandardWaveNames=oplStandardWaveNamesB; + } + + if (nonLatchNibble) { + bool hiddenSystemsB=settings.hiddenSystems; + if (ImGui::Checkbox(":smile: :star_struck: :sunglasses: :ok_hand:",&hiddenSystemsB)) { + settings.hiddenSystems=hiddenSystemsB; + } + } + + bool overflowHighlightB=settings.overflowHighlight; + if (ImGui::Checkbox("Overflow pattern highlights",&overflowHighlightB)) { + settings.overflowHighlight=overflowHighlightB; + } + + bool viewPrevPatternB=settings.viewPrevPattern; + if (ImGui::Checkbox("Display previous/next pattern",&viewPrevPatternB)) { + settings.viewPrevPattern=viewPrevPatternB; + } + + bool germanNotationB=settings.germanNotation; + if (ImGui::Checkbox("Use German notation",&germanNotationB)) { + settings.germanNotation=germanNotationB; + } + + // sorry. temporarily disabled until ImGui has a way to add separators in tables arbitrarily. + /*bool sysSeparatorsB=settings.sysSeparators; + if (ImGui::Checkbox("Add separators between systems in Orders",&sysSeparatorsB)) { + settings.sysSeparators=sysSeparatorsB; + }*/ + + bool partyTimeB=settings.partyTime; + if (ImGui::Checkbox("About screen party time",&partyTimeB)) { + settings.partyTime=partyTimeB; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Warning: may cause epileptic seizures."); + } + + ImGui::Separator(); + + bool waveLayoutB=settings.waveLayout; + if (ImGui::Checkbox("Use compact wave editor",&waveLayoutB)) { + settings.waveLayout=waveLayoutB; + } + + bool sampleLayoutB=settings.sampleLayout; + if (ImGui::Checkbox("Use compact sample editor",&sampleLayoutB)) { + settings.sampleLayout=sampleLayoutB; + } + + bool roundedWindowsB=settings.roundedWindows; + if (ImGui::Checkbox("Rounded window corners",&roundedWindowsB)) { + settings.roundedWindows=roundedWindowsB; + } + + bool roundedButtonsB=settings.roundedButtons; + if (ImGui::Checkbox("Rounded buttons",&roundedButtonsB)) { + settings.roundedButtons=roundedButtonsB; + } + + bool roundedMenusB=settings.roundedMenus; + if (ImGui::Checkbox("Rounded menu corners",&roundedMenusB)) { + settings.roundedMenus=roundedMenusB; + } + + bool frameBordersB=settings.frameBorders; + if (ImGui::Checkbox("Borders around widgets",&frameBordersB)) { + settings.frameBorders=frameBordersB; + } + + ImGui::Separator(); + + ImGui::Text("Oscilloscope settings:"); + + bool oscRoundedCornersB=settings.oscRoundedCorners; + if (ImGui::Checkbox("Rounded corners",&oscRoundedCornersB)) { + settings.oscRoundedCorners=oscRoundedCornersB; + } + + bool oscTakesEntireWindowB=settings.oscTakesEntireWindow; + if (ImGui::Checkbox("Fill entire window",&oscTakesEntireWindowB)) { + settings.oscTakesEntireWindow=oscTakesEntireWindowB; + } + + bool oscBorderB=settings.oscBorder; + if (ImGui::Checkbox("Border",&oscBorderB)) { + settings.oscBorder=oscBorderB; + } + + ImGui::Separator(); + + if (ImGui::TreeNode("Color scheme")) { + if (ImGui::Button("Import")) { + openFileDialog(GUI_FILE_IMPORT_COLORS); + } + ImGui::SameLine(); + if (ImGui::Button("Export")) { + openFileDialog(GUI_FILE_EXPORT_COLORS); + } + ImGui::SameLine(); + if (ImGui::Button("Reset defaults")) { + showWarning("Are you sure you want to reset the color scheme?",GUI_WARN_RESET_COLORS); + } + if (ImGui::TreeNode("General")) { + ImGui::Text("Color scheme type:"); + if (ImGui::RadioButton("Dark##gcb0",settings.guiColorsBase==0)) { + settings.guiColorsBase=0; + } + if (ImGui::RadioButton("Light##gcb1",settings.guiColorsBase==1)) { + settings.guiColorsBase=1; + } + UI_COLOR_CONFIG(GUI_COLOR_BACKGROUND,"Background"); + UI_COLOR_CONFIG(GUI_COLOR_FRAME_BACKGROUND,"Window background"); + UI_COLOR_CONFIG(GUI_COLOR_MODAL_BACKDROP,"Modal backdrop"); + UI_COLOR_CONFIG(GUI_COLOR_HEADER,"Header"); + UI_COLOR_CONFIG(GUI_COLOR_TEXT,"Text"); + UI_COLOR_CONFIG(GUI_COLOR_ACCENT_PRIMARY,"Primary"); + UI_COLOR_CONFIG(GUI_COLOR_ACCENT_SECONDARY,"Secondary"); + UI_COLOR_CONFIG(GUI_COLOR_BORDER,"Border"); + UI_COLOR_CONFIG(GUI_COLOR_BORDER_SHADOW,"Border shadow"); + UI_COLOR_CONFIG(GUI_COLOR_TOGGLE_ON,"Toggle on"); + UI_COLOR_CONFIG(GUI_COLOR_TOGGLE_OFF,"Toggle off"); + UI_COLOR_CONFIG(GUI_COLOR_EDITING,"Editing"); + UI_COLOR_CONFIG(GUI_COLOR_SONG_LOOP,"Song loop"); + UI_COLOR_CONFIG(GUI_COLOR_PLAYBACK_STAT,"Playback status"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("File Picker (built-in)")) { + UI_COLOR_CONFIG(GUI_COLOR_FILE_DIR,"Directory"); + UI_COLOR_CONFIG(GUI_COLOR_FILE_SONG_NATIVE,"Song (native)"); + UI_COLOR_CONFIG(GUI_COLOR_FILE_SONG_IMPORT,"Song (import)"); + UI_COLOR_CONFIG(GUI_COLOR_FILE_INSTR,"Instrument"); + UI_COLOR_CONFIG(GUI_COLOR_FILE_AUDIO,"Audio"); + UI_COLOR_CONFIG(GUI_COLOR_FILE_WAVE,"Wavetable"); + UI_COLOR_CONFIG(GUI_COLOR_FILE_VGM,"VGM"); + UI_COLOR_CONFIG(GUI_COLOR_FILE_FONT,"Font"); + UI_COLOR_CONFIG(GUI_COLOR_FILE_OTHER,"Other"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Oscilloscope")) { + UI_COLOR_CONFIG(GUI_COLOR_OSC_BORDER,"Border"); + UI_COLOR_CONFIG(GUI_COLOR_OSC_BG1,"Background (top-left)"); + UI_COLOR_CONFIG(GUI_COLOR_OSC_BG2,"Background (top-right)"); + UI_COLOR_CONFIG(GUI_COLOR_OSC_BG3,"Background (bottom-left)"); + UI_COLOR_CONFIG(GUI_COLOR_OSC_BG4,"Background (bottom-right)"); + UI_COLOR_CONFIG(GUI_COLOR_OSC_WAVE,"Waveform"); + UI_COLOR_CONFIG(GUI_COLOR_OSC_WAVE_PEAK,"Waveform (clip)"); + UI_COLOR_CONFIG(GUI_COLOR_OSC_REF,"Reference"); + UI_COLOR_CONFIG(GUI_COLOR_OSC_GUIDE,"Guide"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Volume Meter")) { + UI_COLOR_CONFIG(GUI_COLOR_VOLMETER_LOW,"Low"); + UI_COLOR_CONFIG(GUI_COLOR_VOLMETER_HIGH,"High"); + UI_COLOR_CONFIG(GUI_COLOR_VOLMETER_PEAK,"Clip"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Orders")) { + UI_COLOR_CONFIG(GUI_COLOR_ORDER_ROW_INDEX,"Order number"); + UI_COLOR_CONFIG(GUI_COLOR_ORDER_ACTIVE,"Current order background"); + UI_COLOR_CONFIG(GUI_COLOR_ORDER_SIMILAR,"Similar patterns"); + UI_COLOR_CONFIG(GUI_COLOR_ORDER_INACTIVE,"Inactive patterns"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Envelope View")) { + UI_COLOR_CONFIG(GUI_COLOR_FM_ENVELOPE,"Envelope"); + UI_COLOR_CONFIG(GUI_COLOR_FM_ENVELOPE_SUS_GUIDE,"Sustain guide"); + UI_COLOR_CONFIG(GUI_COLOR_FM_ENVELOPE_RELEASE,"Release"); + + ImGui::TreePop(); + } + if (ImGui::TreeNode("FM Editor")) { + UI_COLOR_CONFIG(GUI_COLOR_FM_ALG_BG,"Algorithm background"); + UI_COLOR_CONFIG(GUI_COLOR_FM_ALG_LINE,"Algorithm lines"); + UI_COLOR_CONFIG(GUI_COLOR_FM_MOD,"Modulator"); + UI_COLOR_CONFIG(GUI_COLOR_FM_CAR,"Carrier"); + + UI_COLOR_CONFIG(GUI_COLOR_FM_SSG,"SSG-EG"); + UI_COLOR_CONFIG(GUI_COLOR_FM_WAVE,"Waveform"); + + ImGui::TextWrapped("(the following colors only apply when \"Use separate colors for carriers/modulators in FM editor\" is on!)"); + + UI_COLOR_CONFIG(GUI_COLOR_FM_PRIMARY_MOD,"Mod. accent (primary)"); + UI_COLOR_CONFIG(GUI_COLOR_FM_SECONDARY_MOD,"Mod. accent (secondary)"); + UI_COLOR_CONFIG(GUI_COLOR_FM_BORDER_MOD,"Mod. border"); + UI_COLOR_CONFIG(GUI_COLOR_FM_BORDER_SHADOW_MOD,"Mod. border shadow"); + + UI_COLOR_CONFIG(GUI_COLOR_FM_PRIMARY_CAR,"Car. accent (primary"); + UI_COLOR_CONFIG(GUI_COLOR_FM_SECONDARY_CAR,"Car. accent (secondary)"); + UI_COLOR_CONFIG(GUI_COLOR_FM_BORDER_CAR,"Car. border"); + UI_COLOR_CONFIG(GUI_COLOR_FM_BORDER_SHADOW_CAR,"Car. border shadow"); + + ImGui::TreePop(); + } + if (ImGui::TreeNode("Macro Editor")) { + UI_COLOR_CONFIG(GUI_COLOR_MACRO_VOLUME,"Volume"); + UI_COLOR_CONFIG(GUI_COLOR_MACRO_PITCH,"Pitch"); + UI_COLOR_CONFIG(GUI_COLOR_MACRO_WAVE,"Wave"); + UI_COLOR_CONFIG(GUI_COLOR_MACRO_OTHER,"Other"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Instrument Types")) { + UI_COLOR_CONFIG(GUI_COLOR_INSTR_FM,"FM (4-operator)"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_STD,"Standard"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_GB,"Game Boy"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_C64,"C64"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_AMIGA,"Amiga/Sample"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_PCE,"PC Engine"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_AY,"AY-3-8910/SSG"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_AY8930,"AY8930"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_TIA,"TIA"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_SAA1099,"SAA1099"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_VIC,"VIC"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_PET,"PET"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_VRC6,"VRC6"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_VRC6_SAW,"VRC6 (saw)"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_OPLL,"FM (OPLL)"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_OPL,"FM (OPL)"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_FDS,"FDS"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_VBOY,"Virtual Boy"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_N163,"Namco 163"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_SCC,"Konami SCC"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_OPZ,"FM (OPZ)"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_POKEY,"POKEY"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_BEEPER,"PC Beeper"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_SWAN,"WonderSwan"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_MIKEY,"Lynx"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_VERA,"VERA"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_X1_010,"X1-010"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_ES5506,"ES5506"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_MULTIPCM,"MultiPCM"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_SNES,"SNES"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_SU,"Sound Unit"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Channel")) { + UI_COLOR_CONFIG(GUI_COLOR_CHANNEL_FM,"FM"); + UI_COLOR_CONFIG(GUI_COLOR_CHANNEL_PULSE,"Pulse"); + UI_COLOR_CONFIG(GUI_COLOR_CHANNEL_NOISE,"Noise"); + UI_COLOR_CONFIG(GUI_COLOR_CHANNEL_PCM,"PCM"); + UI_COLOR_CONFIG(GUI_COLOR_CHANNEL_WAVE,"Wave"); + UI_COLOR_CONFIG(GUI_COLOR_CHANNEL_OP,"FM operator"); + UI_COLOR_CONFIG(GUI_COLOR_CHANNEL_MUTED,"Muted"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Pattern")) { + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_PLAY_HEAD,"Playhead"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_CURSOR,"Cursor"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_CURSOR_HOVER,"Cursor (hovered)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_CURSOR_ACTIVE,"Cursor (clicked)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_SELECTION,"Selection"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_SELECTION_HOVER,"Selection (hovered)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_SELECTION_ACTIVE,"Selection (clicked)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_HI_1,"Highlight 1"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_HI_2,"Highlight 2"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_ROW_INDEX,"Row number"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_ROW_INDEX_HI1,"Row number (highlight 1)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_ROW_INDEX_HI2,"Row number (highlight 2)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_ACTIVE,"Note"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_ACTIVE_HI1,"Note (highlight 1)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_ACTIVE_HI2,"Note (highlight 2)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_INACTIVE,"Blank"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_INACTIVE_HI1,"Blank (highlight 1)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_INACTIVE_HI2,"Blank (highlight 2)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_INS,"Instrument"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_INS_WARN,"Instrument (invalid type)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_INS_ERROR,"Instrument (out of range)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_VOLUME_MIN,"Volume (0%)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_VOLUME_HALF,"Volume (50%)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_VOLUME_MAX,"Volume (100%)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_INVALID,"Invalid effect"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_PITCH,"Pitch effect"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_VOLUME,"Volume effect"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_PANNING,"Panning effect"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SONG,"Song effect"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_TIME,"Time effect"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SPEED,"Speed effect"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,"Primary system effect"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY,"Secondary system effect"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_MISC,"Miscellaneous"); + UI_COLOR_CONFIG(GUI_COLOR_EE_VALUE,"External command output"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Log Viewer")) { + UI_COLOR_CONFIG(GUI_COLOR_LOGLEVEL_ERROR,"Log level: Error"); + UI_COLOR_CONFIG(GUI_COLOR_LOGLEVEL_WARNING,"Log level: Warning"); + UI_COLOR_CONFIG(GUI_COLOR_LOGLEVEL_INFO,"Log level: Info"); + UI_COLOR_CONFIG(GUI_COLOR_LOGLEVEL_DEBUG,"Log level: Debug"); + UI_COLOR_CONFIG(GUI_COLOR_LOGLEVEL_TRACE,"Log level: Trace/Verbose"); + ImGui::TreePop(); + } + ImGui::TreePop(); + } } - if (ImGui::TreeNode("Sample list")) { - KEYBIND_CONFIG_BEGIN("keysSampleList"); + ImGui::EndChild(); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Keyboard")) { + ImVec2 settingsViewSize=ImGui::GetContentRegionAvail(); + settingsViewSize.y-=ImGui::GetFrameHeight()+ImGui::GetStyle().WindowPadding.y; + if (ImGui::BeginChild("SettingsView",settingsViewSize)) { + if (ImGui::Button("Import")) { + openFileDialog(GUI_FILE_IMPORT_KEYBINDS); + } + ImGui::SameLine(); + if (ImGui::Button("Export")) { + openFileDialog(GUI_FILE_EXPORT_KEYBINDS); + } + ImGui::SameLine(); + if (ImGui::Button("Reset defaults")) { + showWarning("Are you sure you want to reset the keyboard settings?",GUI_WARN_RESET_KEYBINDS); + } + if (ImGui::TreeNode("Global hotkeys")) { + KEYBIND_CONFIG_BEGIN("keysGlobal"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_ADD); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_DUPLICATE); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_OPEN); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_SAVE); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_MOVE_UP); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_MOVE_DOWN); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_DELETE); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_EDIT); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_UP); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_DOWN); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_PREVIEW); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW); + UI_KEYBIND_CONFIG(GUI_ACTION_OPEN); + UI_KEYBIND_CONFIG(GUI_ACTION_OPEN_BACKUP); + UI_KEYBIND_CONFIG(GUI_ACTION_SAVE); + UI_KEYBIND_CONFIG(GUI_ACTION_SAVE_AS); + UI_KEYBIND_CONFIG(GUI_ACTION_UNDO); + UI_KEYBIND_CONFIG(GUI_ACTION_REDO); + UI_KEYBIND_CONFIG(GUI_ACTION_PLAY_TOGGLE); + UI_KEYBIND_CONFIG(GUI_ACTION_PLAY); + UI_KEYBIND_CONFIG(GUI_ACTION_STOP); + UI_KEYBIND_CONFIG(GUI_ACTION_PLAY_REPEAT); + UI_KEYBIND_CONFIG(GUI_ACTION_PLAY_CURSOR); + UI_KEYBIND_CONFIG(GUI_ACTION_STEP_ONE); + UI_KEYBIND_CONFIG(GUI_ACTION_OCTAVE_UP); + UI_KEYBIND_CONFIG(GUI_ACTION_OCTAVE_DOWN); + UI_KEYBIND_CONFIG(GUI_ACTION_INS_UP); + UI_KEYBIND_CONFIG(GUI_ACTION_INS_DOWN); + UI_KEYBIND_CONFIG(GUI_ACTION_STEP_UP); + UI_KEYBIND_CONFIG(GUI_ACTION_STEP_DOWN); + UI_KEYBIND_CONFIG(GUI_ACTION_TOGGLE_EDIT); + UI_KEYBIND_CONFIG(GUI_ACTION_METRONOME); + UI_KEYBIND_CONFIG(GUI_ACTION_REPEAT_PATTERN); + UI_KEYBIND_CONFIG(GUI_ACTION_FOLLOW_ORDERS); + UI_KEYBIND_CONFIG(GUI_ACTION_FOLLOW_PATTERN); + UI_KEYBIND_CONFIG(GUI_ACTION_FULLSCREEN); + UI_KEYBIND_CONFIG(GUI_ACTION_PANIC); - KEYBIND_CONFIG_END; - ImGui::TreePop(); - } - if (ImGui::TreeNode("Orders")) { - KEYBIND_CONFIG_BEGIN("keysOrders"); - - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_UP); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_DOWN); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_LEFT); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_RIGHT); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_INCREASE); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_DECREASE); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_EDIT_MODE); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_LINK); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_ADD); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_DUPLICATE); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_DEEP_CLONE); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_DUPLICATE_END); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_DEEP_CLONE_END); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_REMOVE); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_MOVE_UP); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_MOVE_DOWN); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_REPLAY); - - KEYBIND_CONFIG_END; - ImGui::TreePop(); - } - if (ImGui::TreeNode("Sample editor")) { - KEYBIND_CONFIG_BEGIN("keysSampleEdit"); - - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SELECT); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_DRAW); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_CUT); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_COPY); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PASTE); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PASTE_REPLACE); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PASTE_MIX); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SELECT_ALL); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_RESIZE); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_RESAMPLE); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_AMPLIFY); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_NORMALIZE); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FADE_IN); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FADE_OUT); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_INSERT); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SILENCE); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_DELETE); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_TRIM); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_REVERSE); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_INVERT); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SIGN); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FILTER); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PREVIEW); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_STOP_PREVIEW); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_ZOOM_IN); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_ZOOM_OUT); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_ZOOM_AUTO); - - KEYBIND_CONFIG_END; - ImGui::TreePop(); + KEYBIND_CONFIG_END; + ImGui::TreePop(); + } + if (ImGui::TreeNode("Window activation")) { + KEYBIND_CONFIG_BEGIN("keysWindow"); + + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_EDIT_CONTROLS); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_ORDERS); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_INS_LIST); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_INS_EDIT); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_SONG_INFO); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_PATTERN); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_WAVE_LIST); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_WAVE_EDIT); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_SAMPLE_LIST); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_SAMPLE_EDIT); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_ABOUT); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_SETTINGS); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_MIXER); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_DEBUG); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_OSCILLOSCOPE); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_CHAN_OSC); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_VOL_METER); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_STATS); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_COMPAT_FLAGS); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_PIANO); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_NOTES); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_CHANNELS); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_REGISTER_VIEW); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_LOG); + + UI_KEYBIND_CONFIG(GUI_ACTION_COLLAPSE_WINDOW); + UI_KEYBIND_CONFIG(GUI_ACTION_CLOSE_WINDOW); + + KEYBIND_CONFIG_END; + ImGui::TreePop(); + } + if (ImGui::TreeNode("Note input")) { + std::vector sorted; + if (ImGui::BeginTable("keysNoteInput",4)) { + for (std::map::value_type& i: noteKeys) { + std::vector::iterator j; + for (j=sorted.begin(); j!=sorted.end(); j++) { + if (j->val>i.second) { + break; + } + } + sorted.insert(j,MappedInput(i.first,i.second)); + } + + static char id[4096]; + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Key"); + ImGui::TableNextColumn(); + ImGui::Text("Type"); + ImGui::TableNextColumn(); + ImGui::Text("Value"); + ImGui::TableNextColumn(); + ImGui::Text("Remove"); + + for (MappedInput& i: sorted) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s",SDL_GetScancodeName((SDL_Scancode)i.scan)); + ImGui::TableNextColumn(); + if (i.val==102) { + snprintf(id,4095,"Envelope release##SNType_%d",i.scan); + if (ImGui::Button(id)) { + noteKeys[i.scan]=0; + } + } else if (i.val==101) { + snprintf(id,4095,"Note release##SNType_%d",i.scan); + if (ImGui::Button(id)) { + noteKeys[i.scan]=102; + } + } else if (i.val==100) { + snprintf(id,4095,"Note off##SNType_%d",i.scan); + if (ImGui::Button(id)) { + noteKeys[i.scan]=101; + } + } else { + snprintf(id,4095,"Note##SNType_%d",i.scan); + if (ImGui::Button(id)) { + noteKeys[i.scan]=100; + } + } + ImGui::TableNextColumn(); + if (i.val<100) { + snprintf(id,4095,"##SNValue_%d",i.scan); + if (ImGui::InputInt(id,&i.val,1,1)) { + if (i.val<0) i.val=0; + if (i.val>96) i.val=96; + noteKeys[i.scan]=i.val; + } + } + ImGui::TableNextColumn(); + snprintf(id,4095,ICON_FA_TIMES "##SNRemove_%d",i.scan); + if (ImGui::Button(id)) { + noteKeys.erase(i.scan); + } + } + ImGui::EndTable(); + + if (ImGui::BeginCombo("##SNAddNew","Add...")) { + for (int i=0; igetConfInt("arcadeCore",0); settings.ym2612Core=e->getConfInt("ym2612Core",0); settings.saaCore=e->getConfInt("saaCore",1); + settings.nesCore=e->getConfInt("nesCore",0); + settings.fdsCore=e->getConfInt("fdsCore",0); settings.mainFont=e->getConfInt("mainFont",0); settings.patFont=e->getConfInt("patFont",0); settings.mainFontPath=e->getConfString("mainFontPath",""); @@ -1498,6 +1821,8 @@ void FurnaceGUI::syncSettings() { settings.roundedMenus=e->getConfInt("roundedMenus",0); settings.loadJapanese=e->getConfInt("loadJapanese",0); settings.fmLayout=e->getConfInt("fmLayout",0); + settings.sampleLayout=e->getConfInt("sampleLayout",0); + settings.waveLayout=e->getConfInt("waveLayout",0); settings.susPosition=e->getConfInt("susPosition",0); settings.effectCursorDir=e->getConfInt("effectCursorDir",1); settings.cursorPastePos=e->getConfInt("cursorPastePos",1); @@ -1508,6 +1833,23 @@ void FurnaceGUI::syncSettings() { settings.oscRoundedCorners=e->getConfInt("oscRoundedCorners",1); settings.oscTakesEntireWindow=e->getConfInt("oscTakesEntireWindow",0); settings.oscBorder=e->getConfInt("oscBorder",1); + settings.separateFMColors=e->getConfInt("separateFMColors",0); + settings.insEditColorize=e->getConfInt("insEditColorize",0); + settings.metroVol=e->getConfInt("metroVol",100); + settings.pushNibble=e->getConfInt("pushNibble",0); + settings.scrollChangesOrder=e->getConfInt("scrollChangesOrder",0); + settings.oplStandardWaveNames=e->getConfInt("oplStandardWaveNames",0); + settings.cursorMoveNoScroll=e->getConfInt("cursorMoveNoScroll",0); + settings.lowLatency=e->getConfInt("lowLatency",0); + settings.notePreviewBehavior=e->getConfInt("notePreviewBehavior",1); + settings.powerSave=e->getConfInt("powerSave",POWER_SAVE_DEFAULT); + settings.absorbInsInput=e->getConfInt("absorbInsInput",0); + settings.eventDelay=e->getConfInt("eventDelay",0); + settings.moveWindowTitle=e->getConfInt("moveWindowTitle",0); + settings.hiddenSystems=e->getConfInt("hiddenSystems",0); + settings.insLoadAlwaysReplace=e->getConfInt("insLoadAlwaysReplace",1); + settings.horizontalDataView=e->getConfInt("horizontalDataView",0); + settings.noMultiSystem=e->getConfInt("noMultiSystem",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -1519,6 +1861,8 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.arcadeCore,0,1); clampSetting(settings.ym2612Core,0,1); clampSetting(settings.saaCore,0,1); + clampSetting(settings.nesCore,0,1); + clampSetting(settings.fdsCore,0,1); clampSetting(settings.mainFont,0,6); clampSetting(settings.patFont,0,6); clampSetting(settings.patRowsBase,0,1); @@ -1560,6 +1904,38 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.titleBarSys,0,1); clampSetting(settings.frameBorders,0,1); clampSetting(settings.effectDeletionAltersValue,0,1); + clampSetting(settings.sampleLayout,0,1); + clampSetting(settings.waveLayout,0,1); + clampSetting(settings.separateFMColors,0,1); + clampSetting(settings.insEditColorize,0,1); + clampSetting(settings.metroVol,0,200); + clampSetting(settings.pushNibble,0,1); + clampSetting(settings.scrollChangesOrder,0,1); + clampSetting(settings.oplStandardWaveNames,0,1); + clampSetting(settings.cursorMoveNoScroll,0,1); + clampSetting(settings.lowLatency,0,1); + clampSetting(settings.notePreviewBehavior,0,3); + clampSetting(settings.powerSave,0,1); + clampSetting(settings.absorbInsInput,0,1); + clampSetting(settings.eventDelay,0,1); + clampSetting(settings.moveWindowTitle,0,1); + clampSetting(settings.hiddenSystems,0,1); + clampSetting(settings.insLoadAlwaysReplace,0,1); + clampSetting(settings.horizontalDataView,0,1); + clampSetting(settings.noMultiSystem,0,1) + + settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys","")); + if (settings.initialSys.size()<4) { + settings.initialSys.clear(); + settings.initialSys.push_back(DIV_SYSTEM_YM2612); + settings.initialSys.push_back(64); + settings.initialSys.push_back(0); + settings.initialSys.push_back(0); + settings.initialSys.push_back(DIV_SYSTEM_SMS); + settings.initialSys.push_back(32); + settings.initialSys.push_back(0); + settings.initialSys.push_back(0); + } // keybinds for (int i=0; isetMidiDirect(midiMap.directChannel); + e->setMetronomeVol(((float)settings.metroVol)/100.0f); } void FurnaceGUI::commitSettings() { @@ -1591,6 +1968,8 @@ void FurnaceGUI::commitSettings() { e->setConf("arcadeCore",settings.arcadeCore); e->setConf("ym2612Core",settings.ym2612Core); e->setConf("saaCore",settings.saaCore); + e->setConf("nesCore",settings.nesCore); + e->setConf("fdsCore",settings.fdsCore); e->setConf("mainFont",settings.mainFont); e->setConf("patFont",settings.patFont); e->setConf("mainFontPath",settings.mainFontPath); @@ -1628,6 +2007,8 @@ void FurnaceGUI::commitSettings() { e->setConf("roundedMenus",settings.roundedMenus); e->setConf("loadJapanese",settings.loadJapanese); e->setConf("fmLayout",settings.fmLayout); + e->setConf("sampleLayout",settings.sampleLayout); + e->setConf("waveLayout",settings.waveLayout); e->setConf("susPosition",settings.susPosition); e->setConf("effectCursorDir",settings.effectCursorDir); e->setConf("cursorPastePos",settings.cursorPastePos); @@ -1638,6 +2019,24 @@ void FurnaceGUI::commitSettings() { e->setConf("oscRoundedCorners",settings.oscRoundedCorners); e->setConf("oscTakesEntireWindow",settings.oscTakesEntireWindow); e->setConf("oscBorder",settings.oscBorder); + e->setConf("separateFMColors",settings.separateFMColors); + e->setConf("insEditColorize",settings.insEditColorize); + e->setConf("metroVol",settings.metroVol); + e->setConf("pushNibble",settings.pushNibble); + e->setConf("scrollChangesOrder",settings.scrollChangesOrder); + e->setConf("oplStandardWaveNames",settings.oplStandardWaveNames); + e->setConf("cursorMoveNoScroll",settings.cursorMoveNoScroll); + e->setConf("lowLatency",settings.lowLatency); + e->setConf("notePreviewBehavior",settings.notePreviewBehavior); + e->setConf("powerSave",settings.powerSave); + e->setConf("absorbInsInput",settings.absorbInsInput); + e->setConf("eventDelay",settings.eventDelay); + e->setConf("moveWindowTitle",settings.moveWindowTitle); + e->setConf("hiddenSystems",settings.hiddenSystems); + e->setConf("initialSys",e->encodeSysDesc(settings.initialSys)); + e->setConf("insLoadAlwaysReplace",settings.insLoadAlwaysReplace); + e->setConf("horizontalDataView",settings.horizontalDataView); + e->setConf("noMultiSystem",settings.noMultiSystem); // colors for (int i=0; iAddFontFromMemoryCompressedTTF(iconFont_compressed_data,iconFont_compressed_size,e->getConfInt("iconSize",16)*dpiScale,&fc,fontRangeIcon))==NULL) { logE("could not load icon font!"); } + if (settings.mainFontSize==settings.patFontSize && settings.patFont<5 && builtinFontM[settings.patFont]==builtinFont[settings.mainFont]) { logD("using main font for pat font."); patFont=mainFont; @@ -2222,8 +2688,9 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { logE("could not load pattern font!"); patFont=ImGui::GetIO().Fonts->AddFontDefault(); } + } } - } + if ((bigFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_plexSans_compressed_data,font_plexSans_compressed_size,40*dpiScale))==NULL) { logE("could not load big UI font!"); } @@ -2242,19 +2709,27 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmp",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmw",uiColors[GUI_COLOR_FILE_WAVE],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".wav",uiColors[GUI_COLOR_FILE_AUDIO],ICON_FA_FILE_AUDIO_O); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmc",uiColors[GUI_COLOR_FILE_AUDIO],ICON_FA_FILE_AUDIO_O); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgm",uiColors[GUI_COLOR_FILE_VGM],ICON_FA_FILE_AUDIO_O); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttf",uiColors[GUI_COLOR_FILE_FONT],ICON_FA_FONT); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".otf",uiColors[GUI_COLOR_FILE_FONT],ICON_FA_FONT); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttc",uiColors[GUI_COLOR_FILE_FONT],ICON_FA_FONT); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".mod",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ftm",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".tfi",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgi",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".s3i",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".sbi",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".opli",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".opni",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".y12",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".bnk",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fti",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".bti",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ff",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".opm",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); if (updateFonts) { if (fileDialog!=NULL) delete fileDialog; diff --git a/src/gui/songInfo.cpp b/src/gui/songInfo.cpp index a2f788c35..552f1c5d3 100644 --- a/src/gui/songInfo.cpp +++ b/src/gui/songInfo.cpp @@ -41,6 +41,17 @@ void FurnaceGUI::drawSongInfo() { if (ImGui::InputText("##Name",&e->song.name)) { MARK_MODIFIED updateWindowTitle(); } + if (e->song.name.size()==27) { + unsigned int checker=0x11111111; + unsigned int checker1=0; + for (int i=0; i<27; i++) { + checker^=e->song.name[i]<song.name[i]; + checker=(checker>>1|(((checker)^(checker>>2)^(checker>>3)^(checker>>5))&1)<<31); + checker1<<=1; + } + if (checker==0x94ffb4f7 && checker1==0x801c68a6) nonLatchNibble=true; + } ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("Author"); @@ -124,8 +135,8 @@ void FurnaceGUI::drawSongInfo() { if (ordLen<1) ordLen=1; if (ordLen>256) ordLen=256; e->song.ordersLen=ordLen; - if (e->getOrder()>=ordLen) { - e->setOrder(ordLen-1); + if (curOrder>=ordLen) { + setOrder(ordLen-1); } } @@ -173,4 +184,4 @@ void FurnaceGUI::drawSongInfo() { } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SONG_INFO; ImGui::End(); -} \ No newline at end of file +} diff --git a/src/gui/stats.cpp b/src/gui/stats.cpp index 274be2eda..9e1790c9e 100644 --- a/src/gui/stats.cpp +++ b/src/gui/stats.cpp @@ -19,6 +19,7 @@ #include "gui.h" #include +#include void FurnaceGUI::drawStats() { if (nextWindow==GUI_WINDOW_STATS) { @@ -28,22 +29,24 @@ void FurnaceGUI::drawStats() { } if (!statsOpen) return; if (ImGui::Begin("Statistics",&statsOpen)) { - String adpcmAUsage=fmt::sprintf("%d/16384KB",e->adpcmAMemLen/1024); - String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024); - String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024); - String x1_010Usage=fmt::sprintf("%d/1024KB",e->x1_010MemLen/1024); - ImGui::Text("ADPCM-A"); + size_t lastProcTime=e->processTime; + double maxGot=1000000000.0*(double)e->getAudioDescGot().bufsize/(double)e->getAudioDescGot().rate; + String procStr=fmt::sprintf("%.1f%%",100.0*((double)lastProcTime/(double)maxGot)); + ImGui::Text("Audio load"); ImGui::SameLine(); - ImGui::ProgressBar(((float)e->adpcmAMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmAUsage.c_str()); - ImGui::Text("ADPCM-B"); - ImGui::SameLine(); - ImGui::ProgressBar(((float)e->adpcmBMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmBUsage.c_str()); - ImGui::Text("QSound"); - ImGui::SameLine(); - ImGui::ProgressBar(((float)e->qsoundMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),qsoundUsage.c_str()); - ImGui::Text("X1-010"); - ImGui::SameLine(); - ImGui::ProgressBar(((float)e->x1_010MemLen)/1048576.0f,ImVec2(-FLT_MIN,0),x1_010Usage.c_str()); + ImGui::ProgressBar((double)lastProcTime/maxGot,ImVec2(-FLT_MIN,0),procStr.c_str()); + ImGui::Separator(); + for (int i=0; isong.systemLen; i++) { + DivDispatch* dispatch=e->getDispatch(i); + for (int j=0; dispatch!=NULL && dispatch->getSampleMemCapacity(j)>0; j++) { + size_t capacity=dispatch->getSampleMemCapacity(j); + size_t usage=dispatch->getSampleMemUsage(j); + String usageStr=fmt::sprintf("%d/%dKB",usage/1024,capacity/1024); + ImGui::Text("%s [%d]", e->getSystemName(e->song.system[i]), j); + ImGui::SameLine(); + ImGui::ProgressBar(((float)usage)/((float)capacity),ImVec2(-FLT_MIN,0),usageStr.c_str()); + } + } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_STATS; ImGui::End(); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 40cc67a35..8005ee0d3 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -19,75 +19,70 @@ #include "gui.h" -void FurnaceGUI::drawSysConf(int i) { - unsigned int flags=e->song.systemFlags[i]; - bool restart=settings.restartOnFlagChange; +void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool modifyOnChange) { + bool restart=settings.restartOnFlagChange && modifyOnChange; bool sysPal=flags&1; - switch (e->song.system[i]) { + unsigned int copyOfFlags=flags; + switch (type) { case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612_EXT: { if (ImGui::RadioButton("NTSC (7.67MHz)",(flags&3)==0)) { - e->setSysFlags(i,(flags&0x80000000)|0,restart); - updateWindowTitle(); + copyOfFlags=(flags&0x80000000)|0; } if (ImGui::RadioButton("PAL (7.61MHz)",(flags&3)==1)) { - e->setSysFlags(i,(flags&0x80000000)|1,restart); - updateWindowTitle(); + copyOfFlags=(flags&0x80000000)|1; } if (ImGui::RadioButton("FM Towns (8MHz)",(flags&3)==2)) { - e->setSysFlags(i,(flags&0x80000000)|2,restart); - updateWindowTitle(); + copyOfFlags=(flags&0x80000000)|2; } if (ImGui::RadioButton("AtGames Genesis (6.13MHz)",(flags&3)==3)) { - e->setSysFlags(i,(flags&0x80000000)|3,restart); - updateWindowTitle(); + copyOfFlags=(flags&0x80000000)|3; } bool ladder=flags&0x80000000; if (ImGui::Checkbox("Enable DAC distortion",&ladder)) { - e->setSysFlags(i,(flags&(~0x80000000))|(ladder?0x80000000:0),restart); - updateWindowTitle(); + copyOfFlags=(flags&(~0x80000000))|(ladder?0x80000000:0); } break; } case DIV_SYSTEM_SMS: { ImGui::Text("Clock rate:"); if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&3)==0)) { - e->setSysFlags(i,(flags&(~3))|0,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~3))|0; + } if (ImGui::RadioButton("PAL (3.55MHz)",(flags&3)==1)) { - e->setSysFlags(i,(flags&(~3))|1,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~3))|1; + } if (ImGui::RadioButton("BBC Micro (4MHz)",(flags&3)==2)) { - e->setSysFlags(i,(flags&(~3))|2,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~3))|2; + } if (ImGui::RadioButton("Half NTSC (1.79MHz)",(flags&3)==3)) { - e->setSysFlags(i,(flags&(~3))|3,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~3))|3; + } ImGui::Text("Chip type:"); if (ImGui::RadioButton("Sega VDP/Master System",((flags>>2)&3)==0)) { - e->setSysFlags(i,(flags&(~12))|0,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~12))|0; + } if (ImGui::RadioButton("TI SN76489",((flags>>2)&3)==1)) { - e->setSysFlags(i,(flags&(~12))|4,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~12))|4; + } if (ImGui::RadioButton("TI SN76489 with Atari-like short noise",((flags>>2)&3)==2)) { - e->setSysFlags(i,(flags&(~12))|8,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~12))|8; + } /*if (ImGui::RadioButton("Game Gear",(flags>>2)==3)) { - e->setSysFlags(i,(flags&3)|12); + copyOfFlags=(flags&3)|12); }*/ bool noPhaseReset=flags&16; if (ImGui::Checkbox("Disable noise period change phase reset",&noPhaseReset)) { - e->setSysFlags(i,(flags&(~16))|(noPhaseReset<<4),restart); - updateWindowTitle(); + copyOfFlags=(flags&(~16))|(noPhaseReset<<4); + } break; } @@ -96,54 +91,54 @@ void FurnaceGUI::drawSysConf(int i) { case DIV_SYSTEM_VRC7: { ImGui::Text("Clock rate:"); if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&15)==0)) { - e->setSysFlags(i,(flags&(~15))|0,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|0; + } if (ImGui::RadioButton("PAL (3.55MHz)",(flags&15)==1)) { - e->setSysFlags(i,(flags&(~15))|1,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|1; + } if (ImGui::RadioButton("BBC Micro (4MHz)",(flags&15)==2)) { - e->setSysFlags(i,(flags&(~15))|2,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|2; + } if (ImGui::RadioButton("Half NTSC (1.79MHz)",(flags&15)==3)) { - e->setSysFlags(i,(flags&(~15))|3,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|3; + } - if (e->song.system[i]!=DIV_SYSTEM_VRC7) { + if (type!=DIV_SYSTEM_VRC7) { ImGui::Text("Patch set:"); if (ImGui::RadioButton("Yamaha YM2413",((flags>>4)&15)==0)) { - e->setSysFlags(i,(flags&(~0xf0))|0,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~0xf0))|0; + } if (ImGui::RadioButton("Yamaha YMF281",((flags>>4)&15)==1)) { - e->setSysFlags(i,(flags&(~0xf0))|0x10,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~0xf0))|0x10; + } if (ImGui::RadioButton("Yamaha YM2423",((flags>>4)&15)==2)) { - e->setSysFlags(i,(flags&(~0xf0))|0x20,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~0xf0))|0x20; + } if (ImGui::RadioButton("Konami VRC7",((flags>>4)&15)==3)) { - e->setSysFlags(i,(flags&(~0xf0))|0x30,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~0xf0))|0x30; + } } break; } case DIV_SYSTEM_YM2151: if (ImGui::RadioButton("NTSC/X16 (3.58MHz)",flags==0)) { - e->setSysFlags(i,0,restart); - updateWindowTitle(); + copyOfFlags=0; + } if (ImGui::RadioButton("PAL (3.55MHz)",flags==1)) { - e->setSysFlags(i,1,restart); - updateWindowTitle(); + copyOfFlags=1; + } if (ImGui::RadioButton("X1/X68000 (4MHz)",flags==2)) { - e->setSysFlags(i,2,restart); - updateWindowTitle(); + copyOfFlags=2; + } break; case DIV_SYSTEM_NES: @@ -151,120 +146,117 @@ void FurnaceGUI::drawSysConf(int i) { case DIV_SYSTEM_FDS: case DIV_SYSTEM_MMC5: if (ImGui::RadioButton("NTSC (1.79MHz)",flags==0)) { - e->setSysFlags(i,0,restart); - updateWindowTitle(); + copyOfFlags=0; + } if (ImGui::RadioButton("PAL (1.67MHz)",flags==1)) { - e->setSysFlags(i,1,restart); - updateWindowTitle(); + copyOfFlags=1; + } if (ImGui::RadioButton("Dendy (1.77MHz)",flags==2)) { - e->setSysFlags(i,2,restart); - updateWindowTitle(); + copyOfFlags=2; + } break; case DIV_SYSTEM_C64_8580: case DIV_SYSTEM_C64_6581: if (ImGui::RadioButton("NTSC (1.02MHz)",flags==0)) { - e->setSysFlags(i,0,restart); - updateWindowTitle(); + copyOfFlags=0; + } if (ImGui::RadioButton("PAL (0.99MHz)",flags==1)) { - e->setSysFlags(i,1,restart); - updateWindowTitle(); + copyOfFlags=1; + } if (ImGui::RadioButton("SSI 2001 (0.89MHz)",flags==2)) { - e->setSysFlags(i,2,restart); - updateWindowTitle(); + copyOfFlags=2; + } break; case DIV_SYSTEM_AY8910: case DIV_SYSTEM_AY8930: { ImGui::Text("Clock rate:"); if (ImGui::RadioButton("1.79MHz (ZX Spectrum NTSC/MSX)",(flags&15)==0)) { - e->setSysFlags(i,(flags&(~15))|0,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|0; + } if (ImGui::RadioButton("1.77MHz (ZX Spectrum)",(flags&15)==1)) { - e->setSysFlags(i,(flags&(~15))|1,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|1; + } if (ImGui::RadioButton("1.75MHz (ZX Spectrum)",(flags&15)==2)) { - e->setSysFlags(i,(flags&(~15))|2,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|2; + } if (ImGui::RadioButton("2MHz (Atari ST/Sharp X1)",(flags&15)==3)) { - e->setSysFlags(i,(flags&(~15))|3,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|3; + } if (ImGui::RadioButton("1.5MHz (Vectrex)",(flags&15)==4)) { - e->setSysFlags(i,(flags&(~15))|4,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|4; + } if (ImGui::RadioButton("1MHz (Amstrad CPC)",(flags&15)==5)) { - e->setSysFlags(i,(flags&(~15))|5,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|5; + } if (ImGui::RadioButton("0.89MHz (Sunsoft 5B)",(flags&15)==6)) { - e->setSysFlags(i,(flags&(~15))|6,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|6; + } if (ImGui::RadioButton("1.67MHz (?)",(flags&15)==7)) { - e->setSysFlags(i,(flags&(~15))|7,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|7; + } if (ImGui::RadioButton("0.83MHz (Sunsoft 5B on PAL)",(flags&15)==8)) { - e->setSysFlags(i,(flags&(~15))|8,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|8; + } if (ImGui::RadioButton("1.10MHz (Gamate/VIC-20 PAL)",(flags&15)==9)) { - e->setSysFlags(i,(flags&(~15))|9,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|9; + } if (ImGui::RadioButton("2^21Hz (Game Boy)",(flags&15)==10)) { - e->setSysFlags(i,(flags&(~15))|10,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|10; + } - if (e->song.system[i]==DIV_SYSTEM_AY8910) { + if (type==DIV_SYSTEM_AY8910) { ImGui::Text("Chip type:"); if (ImGui::RadioButton("AY-3-8910",(flags&0x30)==0)) { - e->setSysFlags(i,(flags&(~0x30))|0,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~0x30))|0; + } if (ImGui::RadioButton("YM2149(F)",(flags&0x30)==16)) { - e->setSysFlags(i,(flags&(~0x30))|16,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~0x30))|16; + } if (ImGui::RadioButton("Sunsoft 5B",(flags&0x30)==32)) { - e->setSysFlags(i,(flags&(~0x30))|32,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~0x30))|32; + } if (ImGui::RadioButton("AY-3-8914",(flags&0x30)==48)) { - e->setSysFlags(i,(flags&(~0x30))|48,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~0x30))|48; + } } bool stereo=flags&0x40; ImGui::BeginDisabled((flags&0x30)==32); if (ImGui::Checkbox("Stereo##_AY_STEREO",&stereo)) { - e->setSysFlags(i,(flags&(~0x40))|(stereo?0x40:0),restart); - updateWindowTitle(); + copyOfFlags=(flags&(~0x40))|(stereo?0x40:0); + } ImGui::EndDisabled(); break; } case DIV_SYSTEM_SAA1099: if (ImGui::RadioButton("SAM Coupé (8MHz)",flags==0)) { - e->setSysFlags(i,0,restart); - updateWindowTitle(); + copyOfFlags=0; } if (ImGui::RadioButton("NTSC (7.15MHz)",flags==1)) { - e->setSysFlags(i,1,restart); - updateWindowTitle(); + copyOfFlags=1; } if (ImGui::RadioButton("PAL (7.09MHz)",flags==2)) { - e->setSysFlags(i,2,restart); - updateWindowTitle(); + copyOfFlags=2; } break; case DIV_SYSTEM_AMIGA: { @@ -273,44 +265,37 @@ void FurnaceGUI::drawSysConf(int i) { if (CWSliderInt("##StereoSep",&stereoSep,0,127)) { if (stereoSep<0) stereoSep=0; if (stereoSep>127) stereoSep=127; - e->setSysFlags(i,(flags&(~0x7f00))|((stereoSep&127)<<8),restart); - updateWindowTitle(); + copyOfFlags=(flags&(~0x7f00))|((stereoSep&127)<<8); } rightClickable if (ImGui::RadioButton("Amiga 500 (OCS)",(flags&2)==0)) { - e->setSysFlags(i,flags&(~2),restart); + copyOfFlags=flags&(~2); } if (ImGui::RadioButton("Amiga 1200 (AGA)",(flags&2)==2)) { - e->setSysFlags(i,(flags&(~2))|2,restart); + copyOfFlags=(flags&(~2))|2; } sysPal=flags&1; if (ImGui::Checkbox("PAL",&sysPal)) { - e->setSysFlags(i,(flags&(~1))|(unsigned int)sysPal,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~1))|(unsigned int)sysPal; } bool bypassLimits=flags&4; if (ImGui::Checkbox("Bypass frequency limits",&bypassLimits)) { - e->setSysFlags(i,(flags&(~4))|(bypassLimits<<2),restart); - updateWindowTitle(); + copyOfFlags=(flags&(~4))|(bypassLimits<<2); } break; } case DIV_SYSTEM_PCSPKR: { ImGui::Text("Speaker type:"); if (ImGui::RadioButton("Unfiltered",(flags&3)==0)) { - e->setSysFlags(i,(flags&(~3))|0,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~3))|0; } if (ImGui::RadioButton("Cone",(flags&3)==1)) { - e->setSysFlags(i,(flags&(~3))|1,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~3))|1; } if (ImGui::RadioButton("Piezo",(flags&3)==2)) { - e->setSysFlags(i,(flags&(~3))|2,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~3))|2; } if (ImGui::RadioButton("Use system beeper (Linux only!)",(flags&3)==3)) { - e->setSysFlags(i,(flags&(~3))|3,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~3))|3; } break; } @@ -320,62 +305,53 @@ void FurnaceGUI::drawSysConf(int i) { if (CWSliderInt("##EchoBufSize",&echoBufSize,0,2725)) { if (echoBufSize<0) echoBufSize=0; if (echoBufSize>2725) echoBufSize=2725; - e->setSysFlags(i,(flags & ~4095) | ((2725 - echoBufSize) & 4095),restart); - updateWindowTitle(); + copyOfFlags=(flags & ~4095) | ((2725 - echoBufSize) & 4095); } rightClickable ImGui::Text("Echo feedback:"); int echoFeedback=(flags>>12)&255; if (CWSliderInt("##EchoFeedback",&echoFeedback,0,255)) { if (echoFeedback<0) echoFeedback=0; if (echoFeedback>255) echoFeedback=255; - e->setSysFlags(i,(flags & ~(255 << 12)) | ((echoFeedback & 255) << 12),restart); - updateWindowTitle(); + copyOfFlags=(flags & ~(255 << 12)) | ((echoFeedback & 255) << 12); } rightClickable break; } case DIV_SYSTEM_X1_010: { ImGui::Text("Clock rate:"); if (ImGui::RadioButton("16MHz (Seta 1)",(flags&15)==0)) { - e->setSysFlags(i,(flags&(~15))|0,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|0; } if (ImGui::RadioButton("16.67MHz (Seta 2)",(flags&15)==1)) { - e->setSysFlags(i,(flags&(~15))|1,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|1; } bool x1_010Stereo=flags&16; if (ImGui::Checkbox("Stereo",&x1_010Stereo)) { - e->setSysFlags(i,(flags&(~16))|(x1_010Stereo<<4),restart); - updateWindowTitle(); + copyOfFlags=(flags&(~16))|(x1_010Stereo<<4); } break; } case DIV_SYSTEM_N163: { ImGui::Text("Clock rate:"); if (ImGui::RadioButton("NTSC (1.79MHz)",(flags&15)==0)) { - e->setSysFlags(i,(flags&(~15))|0,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|0; } if (ImGui::RadioButton("PAL (1.67MHz)",(flags&15)==1)) { - e->setSysFlags(i,(flags&(~15))|1,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|1; } if (ImGui::RadioButton("Dendy (1.77MHz)",(flags&15)==2)) { - e->setSysFlags(i,(flags&(~15))|2,restart); - updateWindowTitle(); + copyOfFlags=(flags&(~15))|2; } ImGui::Text("Initial channel limit:"); int initialChannelLimit=((flags>>4)&7)+1; if (CWSliderInt("##InitialChannelLimit",&initialChannelLimit,1,8)) { if (initialChannelLimit<1) initialChannelLimit=1; if (initialChannelLimit>8) initialChannelLimit=8; - e->setSysFlags(i,(flags & ~(7 << 4)) | (((initialChannelLimit-1) & 7) << 4),restart); - updateWindowTitle(); + copyOfFlags=(flags & ~(7 << 4)) | (((initialChannelLimit-1) & 7) << 4); + } rightClickable bool n163Multiplex=flags&128; if (ImGui::Checkbox("Disable hissing",&n163Multiplex)) { - e->setSysFlags(i,(flags&(~128))|(n163Multiplex<<7),restart); - updateWindowTitle(); + copyOfFlags=(flags&(~128))|(n163Multiplex<<7); } break; } @@ -395,9 +371,17 @@ void FurnaceGUI::drawSysConf(int i) { break; default: if (ImGui::Checkbox("PAL",&sysPal)) { - e->setSysFlags(i,sysPal,restart); - updateWindowTitle(); + copyOfFlags=sysPal; } break; } + + if (copyOfFlags!=flags) { + if (chan>=0) { + e->setSysFlags(chan,copyOfFlags,restart); + updateWindowTitle(); + } else { + flags=copyOfFlags; + } + } } diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index a9efe512e..5c812283e 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -31,7 +31,7 @@ void FurnaceGUI::drawWaveEdit() { } if (!waveEditOpen) return; float wavePreview[256]; - ImGui::SetNextWindowSizeConstraints(ImVec2(450.0f*dpiScale,300.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + ImGui::SetNextWindowSizeConstraints(ImVec2(300.0f*dpiScale,300.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); if (ImGui::Begin("Wavetable Editor",&waveEditOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { if (curWave<0 || curWave>=(int)e->song.wave.size()) { ImGui::Text("no wavetable selected"); @@ -53,31 +53,34 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SameLine(); DivWavetable* wave=e->song.wave[curWave]; - ImGui::Text("Width"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine and WonderSwan\n- 64 on FDS\n- 128 on X1-010\nany other widths will be scaled during playback."); - } - ImGui::SameLine(); - ImGui::SetNextItemWidth(128.0f*dpiScale); - if (ImGui::InputInt("##_WTW",&wave->len,1,2)) { - if (wave->len>256) wave->len=256; - if (wave->len<1) wave->len=1; - e->notifyWaveChange(curWave); - if (wavePreviewOn) e->previewWave(curWave,wavePreviewNote); - MARK_MODIFIED; - } - ImGui::SameLine(); - ImGui::Text("Height"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, X1-010 Envelope shape and N163\n- 31 for PC Engine\n- 63 for FDS\n- 255 for X1-010\nany other heights will be scaled during playback."); - } - ImGui::SameLine(); - ImGui::SetNextItemWidth(128.0f*dpiScale); - if (ImGui::InputInt("##_WTH",&wave->max,1,2)) { - if (wave->max>255) wave->max=255; - if (wave->max<1) wave->max=1; - e->notifyWaveChange(curWave); - MARK_MODIFIED; + + if (!settings.waveLayout){ + ImGui::Text("Width"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine and WonderSwan\n- 64 on FDS\n- 128 on X1-010\nany other widths will be scaled during playback."); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(128.0f*dpiScale); + if (ImGui::InputInt("##_WTW",&wave->len,1,2)) { + if (wave->len>256) wave->len=256; + if (wave->len<1) wave->len=1; + e->notifyWaveChange(curWave); + if (wavePreviewOn) e->previewWave(curWave,wavePreviewNote); + MARK_MODIFIED; + } + ImGui::SameLine(); + ImGui::Text("Height"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, X1-010 Envelope shape and N163\n- 31 for PC Engine\n- 63 for FDS\n- 255 for X1-010\nany other heights will be scaled during playback."); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(128.0f*dpiScale); + if (ImGui::InputInt("##_WTH",&wave->max,1,2)) { + if (wave->max>255) wave->max=255; + if (wave->max<1) wave->max=1; + e->notifyWaveChange(curWave); + MARK_MODIFIED; + } } ImGui::SameLine(); if (ImGui::RadioButton("Dec",!waveHex)) { @@ -87,6 +90,42 @@ void FurnaceGUI::drawWaveEdit() { if (ImGui::RadioButton("Hex",waveHex)) { waveHex=true; } + + if (settings.waveLayout){ + if (ImGui::BeginTable("WaveProps",2,ImGuiTableFlags_SizingStretchSame)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Width"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine and WonderSwan\n- 64 on FDS\n- 128 on X1-010\nany other widths will be scaled during playback."); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##_WTW",&wave->len,1,2)) { + if (wave->len>256) wave->len=256; + if (wave->len<1) wave->len=1; + e->notifyWaveChange(curWave); + if (wavePreviewOn) e->previewWave(curWave,wavePreviewNote); + MARK_MODIFIED; + } + ImGui::TableNextColumn(); + ImGui::SameLine(); + ImGui::Text("Height"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, X1-010 Envelope shape and N163\n- 31 for PC Engine\n- 63 for FDS\n- 255 for X1-010\nany other heights will be scaled during playback."); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##_WTH",&wave->max,1,2)) { + if (wave->max>255) wave->max=255; + if (wave->max<1) wave->max=1; + e->notifyWaveChange(curWave); + MARK_MODIFIED; + } + ImGui::EndTable(); + } + } + for (int i=0; ilen; i++) { if (wave->data[i]>wave->max) wave->data[i]=wave->max; wavePreview[i]=wave->data[i]; diff --git a/src/log.cpp b/src/log.cpp index 7f7fae8a9..89602b422 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -27,69 +27,7 @@ LogEntry logEntries[TA_LOG_SIZE]; static constexpr unsigned int TA_LOG_MASK=TA_LOG_SIZE-1; -int logV(const char* format, ...) { - va_list va; - int ret; - if (logLevel params; -bool pHelp(String) { +TAParamResult pHelp(String) { printf("usage: furnace [params] [filename]\n" "you may specify the following parameters:\n"); for (auto& i: params) { @@ -70,13 +70,13 @@ bool pHelp(String) { printf(" -%s: %s\n",i.name.c_str(),i.desc.c_str()); } } - return false; + return TA_PARAM_QUIT; } -bool pAudio(String val) { +TAParamResult pAudio(String val) { if (outName!="") { logE("can't use -audio and -output at the same time."); - return false; + return TA_PARAM_ERROR; } if (val=="jack") { e.setAudio(DIV_AUDIO_JACK); @@ -84,12 +84,12 @@ bool pAudio(String val) { e.setAudio(DIV_AUDIO_SDL); } else { logE("invalid value for audio engine! valid values are: jack, sdl."); - return false; + return TA_PARAM_ERROR; } - return true; + return TA_PARAM_SUCCESS; } -bool pView(String val) { +TAParamResult pView(String val) { if (val=="pattern") { e.setView(DIV_STATUS_PATTERN); } else if (val=="commands") { @@ -98,17 +98,17 @@ bool pView(String val) { e.setView(DIV_STATUS_NOTHING); } else { logE("invalid value for view type! valid values are: pattern, commands, nothing."); - return false; + return TA_PARAM_ERROR; } - return true; + return TA_PARAM_SUCCESS; } -bool pConsole(String val) { +TAParamResult pConsole(String val) { consoleMode=true; - return true; + return TA_PARAM_SUCCESS; } -bool pLogLevel(String val) { +TAParamResult pLogLevel(String val) { if (val=="trace") { logLevel=LOGLEVEL_TRACE; } else if (val=="debug") { @@ -121,12 +121,12 @@ bool pLogLevel(String val) { logLevel=LOGLEVEL_ERROR; } else { logE("invalid value for loglevel! valid values are: trace, debug, info, warning, error."); - return false; + return TA_PARAM_ERROR; } - return true; + return TA_PARAM_SUCCESS; } -bool pVersion(String) { +TAParamResult pVersion(String) { printf("Furnace version " DIV_VERSION ".\n\n"); printf("copyright (C) 2021-2022 tildearrow and contributors.\n"); printf("licensed under the GNU General Public License version 2 or later\n"); @@ -152,10 +152,10 @@ bool pVersion(String) { printf("- puNES by FHorse (GPLv2)\n"); printf("- reSID by Dag Lem (GPLv2)\n"); printf("- Stella by Stella Team (GPLv2)\n"); - return false; + return TA_PARAM_QUIT; } -bool pWarranty(String) { +TAParamResult pWarranty(String) { printf("This program is free software; you can redistribute it and/or\n" "modify it under the terms of the GNU General Public License\n" "as published by the Free Software Foundation; either version 2\n" @@ -169,10 +169,10 @@ bool pWarranty(String) { "You should have received a copy of the GNU General Public License\n" "along with this program; if not, write to the Free Software\n" "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n"); - return false; + return TA_PARAM_QUIT; } -bool pLoops(String val) { +TAParamResult pLoops(String val) { try { int count=std::stoi(val); if (count<0) { @@ -182,12 +182,12 @@ bool pLoops(String val) { } } catch (std::exception& e) { logE("loop count shall be a number."); - return false; + return TA_PARAM_ERROR; } - return true; + return TA_PARAM_SUCCESS; } -bool pOutMode(String val) { +TAParamResult pOutMode(String val) { if (val=="one") { outMode=DIV_EXPORT_MODE_ONE; } else if (val=="persys") { @@ -196,21 +196,21 @@ bool pOutMode(String val) { outMode=DIV_EXPORT_MODE_MANY_CHAN; } else { logE("invalid value for outmode! valid values are: one, persys and perchan."); - return false; + return TA_PARAM_ERROR; } - return true; + return TA_PARAM_SUCCESS; } -bool pOutput(String val) { +TAParamResult pOutput(String val) { outName=val; e.setAudio(DIV_AUDIO_DUMMY); - return true; + return TA_PARAM_SUCCESS; } -bool pVGMOut(String val) { +TAParamResult pVGMOut(String val) { vgmOutName=val; e.setAudio(DIV_AUDIO_DUMMY); - return true; + return TA_PARAM_SUCCESS; } bool needsValue(String param) { @@ -239,6 +239,8 @@ void initParams() { params.push_back(TAParam("W","warranty",false,pWarranty,"","view warranty disclaimer.")); } +// TODO: CoInitializeEx on Windows? +// TODO: add crash log int main(int argc, char** argv) { initLog(); #if !(defined(__APPLE__) || defined(_WIN32)) @@ -281,7 +283,16 @@ int main(int argc, char** argv) { } for (size_t j=0; j int logV(const char* msg, const T&... args) { - fmt::printf_args a=fmt::make_printf_args(args...); - return writeLog(LOGLEVEL_TRACE,msg,a); + return writeLog(LOGLEVEL_TRACE,msg,fmt::make_printf_args(args...)); } template int logD(const char* msg, const T&... args) { - fmt::printf_args a=fmt::make_printf_args(args...); - return writeLog(LOGLEVEL_DEBUG,msg,a); + return writeLog(LOGLEVEL_DEBUG,msg,fmt::make_printf_args(args...)); } template int logI(const char* msg, const T&... args) { - fmt::printf_args a=fmt::make_printf_args(args...); - return writeLog(LOGLEVEL_INFO,msg,a); + return writeLog(LOGLEVEL_INFO,msg,fmt::make_printf_args(args...)); } template int logW(const char* msg, const T&... args) { - fmt::printf_args a=fmt::make_printf_args(args...); - return writeLog(LOGLEVEL_WARN,msg,a); + return writeLog(LOGLEVEL_WARN,msg,fmt::make_printf_args(args...)); } template int logE(const char* msg, const T&... args) { - fmt::printf_args a=fmt::make_printf_args(args...); - return writeLog(LOGLEVEL_ERROR,msg,a); + return writeLog(LOGLEVEL_ERROR,msg,fmt::make_printf_args(args...)); } void initLog(); diff --git a/src/ta-utils.h b/src/ta-utils.h index 98b184f31..3a619248b 100644 --- a/src/ta-utils.h +++ b/src/ta-utils.h @@ -43,14 +43,20 @@ typedef std::string String; typedef std::wstring WString; +enum TAParamResult { + TA_PARAM_ERROR=0, + TA_PARAM_SUCCESS, + TA_PARAM_QUIT +}; + struct TAParam { String shortName; String name; String valName; String desc; bool value; - bool (*func)(String); - TAParam(String sn, String n, bool v, bool (*f)(String), String vn, String d): + TAParamResult (*func)(String); + TAParam(const String& sn, const String& n, bool v, TAParamResult (*f)(String), const String& vn, const String& d): shortName(sn), name(n), valName(vn),