diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8fd08833b..7e060924f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -243,7 +243,7 @@ jobs: cp -v ../LICENSE LICENSE.txt cp -v ../README.md README.txt - cp -vr ../{papers,demos} ../${binPath}/furnace.exe ./ + cp -vr ../{papers,demos,instruments} ../${binPath}/furnace.exe ./ popd @@ -278,6 +278,7 @@ jobs: popd - name: Upload artifact + if: ${{ github.repository == 'tildearrow/furnace' && github.ref_name == 'master' }} uses: actions/upload-artifact@v3 with: name: ${{ steps.package-identify.outputs.id }} diff --git a/.gitignore b/.gitignore index 06576ee52..566a3c3fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .vscode/ build/ +clangbuild/ nosdl/ release/ t/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 212c1a32f..ad76c141e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -252,7 +252,7 @@ src/audio/midi.cpp ) if (USE_SDL2) - list(APPEND AUDIO_SOURCES src/audio/sdl.cpp) + list(APPEND AUDIO_SOURCES src/audio/sdlAudio.cpp) endif() if (WITH_JACK) @@ -502,6 +502,8 @@ src/gui/doAction.cpp src/gui/editing.cpp src/gui/editControls.cpp src/gui/effectList.cpp +src/gui/findReplace.cpp +src/gui/gradient.cpp src/gui/insEdit.cpp src/gui/log.cpp src/gui/mixer.cpp @@ -517,6 +519,7 @@ src/gui/sampleEdit.cpp src/gui/settings.cpp src/gui/songInfo.cpp src/gui/songNotes.cpp +src/gui/spoiler.cpp src/gui/stats.cpp src/gui/subSongs.cpp src/gui/sysConf.cpp @@ -527,12 +530,39 @@ src/gui/volMeter.cpp src/gui/gui.cpp ) +if (WIN32 OR APPLE) + list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_common.cpp) +endif() + +if (WIN32) + list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_win.cpp) +endif() + if (APPLE) list(APPEND GUI_SOURCES src/gui/macstuff.m) + list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_cocoa.mm) endif() if (NOT WIN32 AND NOT APPLE) list(APPEND GUI_SOURCES src/gui/icon.c) + + include(CheckIncludeFile) + + CHECK_INCLUDE_FILE(sys/io.h SYS_IO_FOUND) + CHECK_INCLUDE_FILE(linux/input.h LINUX_INPUT_FOUND) + CHECK_INCLUDE_FILE(linux/kd.h LINUX_KD_FOUND) + if (SYS_IO_FOUND) + list(APPEND DEPENDENCIES_DEFINES HAVE_SYS_IO) + message(STATUS "PC speaker output: outb()") + endif() + if (LINUX_INPUT_FOUND) + list(APPEND DEPENDENCIES_DEFINES HAVE_LINUX_INPUT) + message(STATUS "PC speaker output: evdev") + endif() + if (LINUX_KD_FOUND) + list(APPEND DEPENDENCIES_DEFINES HAVE_LINUX_KD) + message(STATUS "PC speaker output: KIOCSOUND") + endif() endif() set(USED_SOURCES ${ENGINE_SOURCES} ${AUDIO_SOURCES} src/main.cpp) @@ -556,6 +586,11 @@ if (BUILD_GUI) extern/IconFontCppHeaders extern/igfd ) + if (WIN32 OR APPLE) + list(APPEND DEPENDENCIES_INCLUDE_DIRS + extern/nfd-modified/src/include + ) + endif() list(APPEND DEPENDENCIES_DEFINES HAVE_GUI) message(STATUS "Building GUI") else() @@ -577,7 +612,11 @@ endif() if (NOT MSVC) set(WARNING_FLAGS -Wall -Wextra -Wno-unused-parameter) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - list(APPEND WARNING_FLAGS -Wno-cast-function-type) + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8.0.0) + # nothing + else() + list(APPEND WARNING_FLAGS -Wno-cast-function-type) + endif() endif() if (WARNINGS_ARE_ERRORS) list(APPEND WARNING_FLAGS -Werror) @@ -640,6 +679,7 @@ if (NOT ANDROID OR TERMUX) install(DIRECTORY papers DESTINATION ${CMAKE_INSTALL_DOCDIR}) install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/furnace) install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace) + install(DIRECTORY instruments DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace) foreach(num 16 32 64 128 256 512) set(res ${num}x${num}) install(FILES res/icon.iconset/icon_${res}.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${res}/apps) diff --git a/TODO.md b/TODO.md index 2e58aa492..14016e825 100644 --- a/TODO.md +++ b/TODO.md @@ -6,14 +6,14 @@ # to-do for 0.6pre1 -- rewrite the system name detection function anyway -- add another FM editor layout -- if macros have release, note off should release them -- add ability to move selection by dragging -- find and replace - implement Defle slide bug when using E1xy/E2xy and repeating origin note (requires format change) # to-do for 0.6pre2 (as this requires new data structures) +- rewrite the system name detection function anyway + - this involves the addition of a new "system" field in the song (which solves the problem) + - songs made in older versions will go through old system name detection for compatibility - Game Boy envelope macro/sequence - volume commands should work on Game Boy +- ability to customize `OFF`, `===` and `REL` +- stereo separation control for AY diff --git a/demos/AY-3-8910_Jam.fur b/demos/AY-3-8910_Jam.fur index b249c5bfd..dad4f190e 100644 Binary files a/demos/AY-3-8910_Jam.fur and b/demos/AY-3-8910_Jam.fur differ diff --git a/demos/E1M4OPL2.fur b/demos/E1M4OPL2.fur new file mode 100644 index 000000000..19f8ce54f Binary files /dev/null and b/demos/E1M4OPL2.fur differ diff --git a/demos/FDS TEST.fur b/demos/FDS TEST.fur new file mode 100644 index 000000000..54bdd36cd Binary files /dev/null and b/demos/FDS TEST.fur differ diff --git a/demos/Fake Gameboy.fur b/demos/Fake Gameboy.fur new file mode 100644 index 000000000..50d7e36d9 Binary files /dev/null and b/demos/Fake Gameboy.fur differ diff --git a/demos/Funky_Bubbles_OPL3.fur b/demos/Funky_Bubbles_OPL3.fur new file mode 100644 index 000000000..5f2ffd244 Binary files /dev/null and b/demos/Funky_Bubbles_OPL3.fur differ diff --git a/demos/Fusion.fur b/demos/Fusion.fur new file mode 100644 index 000000000..a69f26e81 Binary files /dev/null and b/demos/Fusion.fur differ diff --git a/demos/Melody_of_Certain_Feelings.fur b/demos/Melody_of_Certain_Feelings.fur new file mode 100644 index 000000000..6ccf4e13c Binary files /dev/null and b/demos/Melody_of_Certain_Feelings.fur differ diff --git a/demos/The_only_thing_the_Metallix_fear_is_you_Forte....fur b/demos/The_only_thing_the_Metallix_fear_is_you_Forte....fur new file mode 100644 index 000000000..123381d8d Binary files /dev/null and b/demos/The_only_thing_the_Metallix_fear_is_you_Forte....fur differ diff --git a/demos/UNATCOPCM.fur b/demos/UNATCOPCM.fur index ca8a0a9f0..0a5560782 100644 Binary files a/demos/UNATCOPCM.fur and b/demos/UNATCOPCM.fur differ diff --git a/demos/atmosphere.fur b/demos/atmosphere.fur new file mode 100644 index 000000000..7600b42c3 Binary files /dev/null and b/demos/atmosphere.fur differ diff --git a/demos/bruno_time.fur b/demos/bruno_time.fur index 6d5756d71..0ad2d841e 100644 Binary files a/demos/bruno_time.fur and b/demos/bruno_time.fur differ diff --git a/demos/c64 ring test.fur b/demos/c64 ring test.fur new file mode 100644 index 000000000..72304532a Binary files /dev/null and b/demos/c64 ring test.fur differ diff --git a/demos/ecolove.fur b/demos/ecolove.fur index 9f287ec61..744b0c9f6 100644 Binary files a/demos/ecolove.fur and b/demos/ecolove.fur differ diff --git a/demos/game boy thing.fur b/demos/game boy thing.fur new file mode 100644 index 000000000..297ca9f9e Binary files /dev/null and b/demos/game boy thing.fur differ diff --git a/demos/going_up_a_step_at_time.fur b/demos/going_up_a_step_at_time.fur new file mode 100644 index 000000000..7d52d155a Binary files /dev/null and b/demos/going_up_a_step_at_time.fur differ diff --git a/demos/neon_night_riders_TFMX.fur b/demos/neon_night_riders_TFMX.fur new file mode 100644 index 000000000..a15f44892 Binary files /dev/null and b/demos/neon_night_riders_TFMX.fur differ diff --git a/demos/su_memory.fur b/demos/su_memory.fur index cb80b8b17..ff030dbe7 100644 Binary files a/demos/su_memory.fur and b/demos/su_memory.fur differ diff --git a/demos/the_erfngjt.fur b/demos/the_erfngjt.fur new file mode 100644 index 000000000..5d70dca8e Binary files /dev/null and b/demos/the_erfngjt.fur differ diff --git a/demos/thick bass test.fur b/demos/thick bass test.fur new file mode 100644 index 000000000..13268e48b Binary files /dev/null and b/demos/thick bass test.fur differ diff --git a/demos/wolf3d.fur b/demos/wolf3d.fur new file mode 100644 index 000000000..a99731ff1 Binary files /dev/null and b/demos/wolf3d.fur differ diff --git a/extern/Nuked-PSG/ympsg.c b/extern/Nuked-PSG/ympsg.c index 3df4f8e39..935b43ea9 100644 --- a/extern/Nuked-PSG/ympsg.c +++ b/extern/Nuked-PSG/ympsg.c @@ -130,7 +130,7 @@ static void YMPSG_ClockInternal1(ympsg_t *chip) else if (noise_of && !chip->noise_of) { noise_bit1 = (chip->noise >> chip->noise_tap2) & 1; - noise_bit2 = (chip->noise >> 12) & 1; + noise_bit2 = (chip->noise >> chip->noise_tap1) & 1; noise_bit1 ^= noise_bit2; noise_next = ((noise_bit1 && ((chip->noise_data >> 2) & 1)) || ((chip->noise & chip->noise_size) == 0)); chip->noise <<= 1; @@ -257,13 +257,14 @@ uint16_t YMPSG_Read(ympsg_t *chip) return data; } -void YMPSG_Init(ympsg_t *chip, uint8_t real_sn) +void YMPSG_Init(ympsg_t *chip, uint8_t real_sn, uint8_t noise_tap1, uint8_t noise_tap2, uint32_t noise_size) { uint32_t i; memset(chip, 0, sizeof(ympsg_t)); YMPSG_SetIC(chip, 1); - chip->noise_tap2 = real_sn ? 13 : 15; - chip->noise_size = real_sn ? 16383 : 32767; + chip->noise_tap1 = noise_tap1; + chip->noise_tap2 = noise_tap2; + chip->noise_size = noise_size; for (i = 0; i < 17; i++) { chip->vol_table[i]=(real_sn?tipsg_vol[i]:ympsg_vol[i]) * 8192.0f; diff --git a/extern/Nuked-PSG/ympsg.h b/extern/Nuked-PSG/ympsg.h index c00b3d720..97e039e3c 100644 --- a/extern/Nuked-PSG/ympsg.h +++ b/extern/Nuked-PSG/ympsg.h @@ -46,8 +46,9 @@ typedef struct { uint8_t sign_l; uint8_t noise_sign_l; uint16_t noise; + uint8_t noise_tap1; uint8_t noise_tap2; - uint16_t noise_size; + uint32_t noise_size; uint8_t test; uint8_t volume_out[4]; @@ -68,7 +69,7 @@ typedef struct { void YMPSG_Write(ympsg_t *chip, uint8_t data); uint16_t YMPSG_Read(ympsg_t *chip); -void YMPSG_Init(ympsg_t *chip, uint8_t real_sn); +void YMPSG_Init(ympsg_t *chip, uint8_t real_sn, uint8_t noise_tap1, uint8_t noise_tap2, uint32_t noise_size); void YMPSG_SetIC(ympsg_t *chip, uint32_t ic); void YMPSG_Clock(ympsg_t *chip); int YMPSG_GetOutput(ympsg_t *chip); diff --git a/extern/igfd/ImGuiFileDialog.cpp b/extern/igfd/ImGuiFileDialog.cpp index f7a2be5b1..776ad3738 100644 --- a/extern/igfd/ImGuiFileDialog.cpp +++ b/extern/igfd/ImGuiFileDialog.cpp @@ -1287,8 +1287,6 @@ namespace IGFD std::sort(prFileList.begin(), prFileList.end(), [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool { - if (a==NULL || b==NULL) - return false; if (!a.use_count() || !b.use_count()) return false; @@ -1760,7 +1758,7 @@ namespace IGFD struct stat statInfos = {}; char timebuf[100]; int result = stat(fpn.c_str(), &statInfos); - if (!result) + if (result!=-1) { if (vInfos->fileType != 'd') { @@ -1781,7 +1779,11 @@ namespace IGFD { vInfos->fileModifDate = std::string(timebuf, len); } - } + } else { + vInfos->fileSize=0; + vInfos->formatedFileSize = prFormatFileSize(vInfos->fileSize); + vInfos->fileModifDate="???"; + } } } diff --git a/extern/imgui_patched/imgui_widgets.cpp b/extern/imgui_patched/imgui_widgets.cpp index c6fec68bc..93dfd5343 100644 --- a/extern/imgui_patched/imgui_widgets.cpp +++ b/extern/imgui_patched/imgui_widgets.cpp @@ -4545,27 +4545,52 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size // of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used // without any storage on user's side. - IM_ASSERT(apply_new_text_length >= 0); - if (is_resizable) - { - ImGuiInputTextCallbackData callback_data; - callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize; - callback_data.Flags = flags; - callback_data.Buf = buf; - callback_data.BufTextLen = apply_new_text_length; - callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1); - callback_data.UserData = callback_user_data; - callback(&callback_data); - buf = callback_data.Buf; - buf_size = callback_data.BufSize; - apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1); - IM_ASSERT(apply_new_text_length <= buf_size); - } - //IMGUI_DEBUG_LOG("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length); - // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size. - ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size)); - value_changed = true; + // don't assert because maybe the user passes an invalid UTF-8 string... + if (apply_new_text_length >= 0) { + if (is_resizable) + { + ImGuiInputTextCallbackData callback_data; + callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize; + callback_data.Flags = flags; + callback_data.Buf = buf; + callback_data.BufTextLen = apply_new_text_length; + callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1); + callback_data.UserData = callback_user_data; + callback(&callback_data); + buf = callback_data.Buf; + buf_size = callback_data.BufSize; + apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1); + IM_ASSERT(apply_new_text_length <= buf_size); + } + //IMGUI_DEBUG_LOG("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length); + + // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size. + ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size)); + value_changed = true; + } else { + printf("invalid buffer!\n"); + if (is_resizable) + { + ImGuiInputTextCallbackData callback_data; + callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize; + callback_data.Flags = flags; + callback_data.Buf = buf; + callback_data.BufTextLen = 0; + callback_data.BufSize = ImMax(buf_size, 1); + callback_data.UserData = callback_user_data; + callback(&callback_data); + buf = callback_data.Buf; + buf_size = callback_data.BufSize; + apply_new_text_length = 0; + IM_ASSERT(apply_new_text_length <= buf_size); + } + //IMGUI_DEBUG_LOG("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length); + + // clear the buffer + ImStrncpy(buf, "", 1); + value_changed = true; + } } // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value) diff --git a/extern/nfd-modified/.gitignore b/extern/nfd-modified/.gitignore new file mode 100644 index 000000000..5f144cbee --- /dev/null +++ b/extern/nfd-modified/.gitignore @@ -0,0 +1,181 @@ +.sconsign.dblite +# Object files +*.o +*.ko +*.obj +*.elf +# Precompiled Headers +*.gch +*.pch +# Libraries +*.lib +*.a +*.la +*.lo +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates +# User-specific folders +*.sln.ide/ +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +# Roslyn cache directories +*.ide/ +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* +#NUNIT +*.VisualState.xml +TestResult.xml +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc +# Chutzpah Test files +_Chutzpah* +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile +# Visual Studio profiler +*.psess +*.vsp +*.vspx +# TFS 2012 Local Workspace +$tf/ +# Guidance Automation Toolkit +*.gpState +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user +# JustCode is a .NET coding addin-in +.JustCode +# TeamCity is a build add-in +_TeamCity* +# DotCover is a Code Coverage Tool +*.dotCover +# NCrunch +_NCrunch_* +.*crunch*.local.xml +# MightyMoose +*.mm.* +AutoTest.Net/ +# Web workbench (sass) +.sass-cache/ +# Installshield output folder +[Ee]xpress/ +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html +# Click-Once directory +publish/ +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# If using the old MSBuild-Integrated Package Restore, uncomment this: +#!**/packages/repositories.config +# Windows Azure Build Output +csx/ +*.build.csdef +# Windows Store app package directory +AppPackages/ +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +bower_components/ +# RIA/Silverlight projects +Generated_Code/ +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +# SQL Server files +*.mdf +*.ldf +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +# Microsoft Fakes +FakesAssemblies/ diff --git a/extern/nfd-modified/LICENSE b/extern/nfd-modified/LICENSE new file mode 100644 index 000000000..3ab103c55 --- /dev/null +++ b/extern/nfd-modified/LICENSE @@ -0,0 +1,16 @@ +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + diff --git a/extern/nfd-modified/MODIFIED.md b/extern/nfd-modified/MODIFIED.md new file mode 100644 index 000000000..bb542f8bb --- /dev/null +++ b/extern/nfd-modified/MODIFIED.md @@ -0,0 +1,7 @@ +# MODIFIED + +this is a modified, altered, edited and revised version of the Native File Dialog library used to display native-feeling, original and operating-system specific file archive dialog picker selectors, which is done by the Native File Dialog library. + +it should not and shall NOT be mistaken for the original, authentic or actual version and revision of the library. + +this is a version tailored for Furnace. diff --git a/extern/nfd-modified/README.md b/extern/nfd-modified/README.md new file mode 100644 index 000000000..fd7a97832 --- /dev/null +++ b/extern/nfd-modified/README.md @@ -0,0 +1,182 @@ +# Native File Dialog Modified Version!!! # + +A tiny, neat C library that portably invokes native file open, folder select and save dialogs. Write dialog code once and have it pop up native dialogs on all supported platforms. Avoid linking large dependencies like wxWidgets and qt. + +This is a modified version of Native File Dialog, tailored for Furnace. + +Features: + + - Lean C API, static library -- no ObjC, no C++, no STL. + - Zlib licensed. + - Consistent UTF-8 support on all platforms. + - Simple universal file filter syntax. + - Paid support available. + - Multiple file selection support. + - 64-bit and 32-bit friendly. + - GCC, Clang, Xcode, Mingw and Visual Studio supported. + - No third party dependencies for building or linking. + - Support for Vista's modern `IFileDialog` on Windows. + - Support for non-deprecated Cocoa APIs on OS X. + - GTK3 dialog on Linux. + - Optional Zenity support on Linux to avoid linking GTK. + - Tested, works alongside [http://www.libsdl.org](SDL2) on all platforms, for the game developers out there. + +# Example Usage # + +```C +#include +#include +#include + +int main( void ) +{ + nfdchar_t *outPath = NULL; + nfdresult_t result = NFD_OpenDialog( NULL, NULL, &outPath ); + + if ( result == NFD_OKAY ) { + puts("Success!"); + puts(outPath); + free(outPath); + } + else if ( result == NFD_CANCEL ) { + puts("User pressed cancel."); + } + else { + printf("Error: %s\n", NFD_GetError() ); + } + + return 0; +} +``` + +See self-documenting API [NFD.h](src/include/nfd.h) for more options. + +# Screenshots # + +![Windows rendering a dialog](screens/open_win.png?raw=true) +![GTK3 on Linux rendering a dialog](screens/open_gtk3.png?raw=true) +![Cocoa on MacOS rendering a dialog](screens/open_cocoa.png?raw=true) + +## Changelog ## + + - **Major** version increments denote API or ABI departure. + - **Minor** version increments denote build or trivial departures. + - **Micro** version increments just recompile and drop-in. + +release | what's new | date +--------|-----------------------------|--------- +1.0.0 | initial | oct 2014 +1.1.0 | premake5; scons deprecated | aug 2016 +1.1.1 | mingw support, build fixes | aug 2016 +1.1.2 | test_pickfolder() added | aug 2016 +1.1.3 | zenity linux backend added | nov 2017 + | fix char type in decls | nov 2017 +1.1.4 | fix win32 memleaks | dec 2018 + | improve win32 errorhandling | dec 2018 + | macos fix focus bug | dec 2018 +1.1.5 | win32 fix com reinitialize | aug 2019 +1.1.6 | fix osx filter bug | aug 2019 + | remove deprecated scons | aug 2019 + | fix mingw compilation | aug 2019 + | -Wextra warning cleanup | aug 2019 + +## Building ## + +NFD uses [Premake5](https://premake.github.io/download.html) generated Makefiles and IDE project files. The generated project files are checked in under `build/` so you don't have to download and use Premake in most cases. + +If you need to run Premake5 directly, further [build documentation](docs/build.md) is available. + +Previously, NFD used SCons to build. As of 1.1.6, SCons support has been removed entirely. + +`nfd.a` will be built for release builds, and `nfd_d.a` will be built for debug builds. + +### Makefiles ### + +The makefile offers up to four options, with `release_x64` as the default. + + make config=release_x86 + make config=release_x64 + make config=debug_x86 + make config=debug_x64 + +### Compiling Your Programs ### + + 1. Add `src/include` to your include search path. + 2. Add `nfd.lib` or `nfd_d.lib` to the list of list of static libraries to link against (for release or debug, respectively). + 3. Add `build//` to the library search path. + +#### Linux GTK #### + +`apt-get libgtk-3-dev` installs the gtk dependency for library compilation. + +On Linux, you have the option of compiling and linking against GTK. If you use it, the recommended way to compile is to include the arguments of `pkg-config --cflags --libs gtk+-3.0`. + +#### Linux Zenity #### + +Alternatively, you can use the Zenity backend by running the Makefile in `build/gmake_linux_zenity`. Zenity runs the dialog in its own address space, but requires the user to have Zenity correctly installed and configured on their system. + +#### MacOS #### + +On Mac OS, add `AppKit` to the list of frameworks. + +#### Windows #### + +On Windows, ensure you are linking against `comctl32.lib`. + +## Usage ## + +See `NFD.h` for API calls. See `tests/*.c` for example code. + +After compiling, `build/bin` contains compiled test programs. The appropriate subdirectory under `build/lib` contains the built library. + +## File Filter Syntax ## + +There is a form of file filtering in every file dialog API, but no consistent means of supporting it. NFD provides support for filtering files by groups of extensions, providing its own descriptions (where applicable) for the extensions. + +A wildcard filter is always added to every dialog. + +### Separators ### + + - `;` Begin a new filter. + - `,` Add a separate type to the filter. + +#### Examples #### + +`txt` The default filter is for text files. There is a wildcard option in a dropdown. + +`png,jpg;psd` The default filter is for png and jpg files. A second filter is available for psd files. There is a wildcard option in a dropdown. + +`NULL` Wildcard only. + +## Iterating Over PathSets ## + +See [test_opendialogmultiple.c](test/test_opendialogmultiple.c). + +# Known Limitations # + +I accept quality code patches, or will resolve these and other matters through support. See [contributing](docs/contributing.md) for details. + + - No support for Windows XP's legacy dialogs such as `GetOpenFileName`. + - No support for file filter names -- ex: "Image Files" (*.png, *.jpg). Nameless filters are supported, however. + - GTK Zenity implementation's process exec error handling does not gracefully handle numerous error cases, choosing to abort rather than cleanup and return. + - GTK 3 spams one warning per dialog created. + +# Copyright and Credit # + +Copyright © 2014-2019 [Frogtoss Games](http://www.frogtoss.com), Inc. +File [LICENSE](LICENSE) covers all files in this repo. + +Native File Dialog by Michael Labbe + + +Tomasz Konojacki for [microutf8](http://puszcza.gnu.org.ua/software/microutf8/) + +[Denis Kolodin](https://github.com/DenisKolodin) for mingw support. + +[Tom Mason](https://github.com/wheybags) for Zenity support. + +## Support ## + +Directed support for this work is available from the original author under a paid agreement. + +[Contact Frogtoss Games](http://www.frogtoss.com/pages/contact.html). diff --git a/extern/nfd-modified/docs/build.md b/extern/nfd-modified/docs/build.md new file mode 100644 index 000000000..a1f732719 --- /dev/null +++ b/extern/nfd-modified/docs/build.md @@ -0,0 +1,39 @@ +# Building NFD # + +Most of the building instructions are included in [README.md](/README.md). This file just contains apocrypha. + +## Running Premake5 Directly ## + +*You shouldn't have to run Premake5 directly to build Native File Dialog. This is for package maintainers or people with exotic demands only!* + +1. [Clone premake-core](https://github.com/premake/premake-core) +2. [Follow instructions on how to build premake](https://github.com/premake/premake-core/wiki/Building-Premake) +3. `cd` to `build` +4. Type `premake5 `, where is the build you want to create. + +### Package Maintainer Only ### + +I support a custom Premake action: `premake5 dist`, which generates all of the checked in project types in subdirectories. It is useful to run this command if you are submitting a pull request to test all of the supported premake configurations. Do not check in the built projects; I will do so while accepting your pull request. + +## SCons build (deprecated) ## + +As of 1.1.6, the deprecated and unmaintained SCons support is removed. + +## Compiling with Mingw ## + +Use the Makefile in `build/gmake_windows` to build Native File Dialog with mingw. Mingw has many distributions and not all of them are reliable. Here is what worked for me, the primary author of Native File Dialog: + +1. Use mingw64, not mingw32. Downloaded from [sourceforge.net](https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/installer/mingw-w64-install.exe/download). +2. When prompted in the intsaller, install the basic compiler and g++. +3. Add the installed bin dir to command prompt path. +4. Run `set CC=g++` to enforce `g++` instead of the default, `cc` for compiling and linking. +5. In `build/gmake_windows`, run `mingw32-make config=release_x64 clean`. Running clean ensures no Visual Studio build products conflict which can cause link errors. +6. Now run `mingw32-make config=release_x64`. + +The author has not attempted to build or even install an x86 toolchain for mingw. + +If you report an issue, be sure to run make with `verbose=1` so commands are visible. + +## Adding NFD source directly to your project ## + +Lots of developers add NFD source directly to their projects instead of using the included build scripts. As of 1.1.6, this is an acknowledged approach to building. Of course, everyone has a slightly different toolchain with various warnings and linters enabled. If you run a linter or catch a warning, please consider submitting a pull request to help NFD build cleanly for everyone. diff --git a/extern/nfd-modified/docs/contributing.md b/extern/nfd-modified/docs/contributing.md new file mode 100644 index 000000000..f6c3b54cc --- /dev/null +++ b/extern/nfd-modified/docs/contributing.md @@ -0,0 +1,25 @@ +# Pull Requests # + +I have had to turn away a number of pull requests due to avoidable circumstances. Please read this file before submitting a pull request. Also look at existing, rejected pull requests which state the reason for rejection. + +Here are the rules: + +- **Submit pull requests to the devel branch**. The library must be tested on every compiler and OS, so there is no way I am going to just put your change in the master before it has been sync'd and tested on a number of machines. Master branch is depended upon by hundreds of projects. + +- **Test your changes on all platforms and compilers that you can.** Also, state which platforms you have tested your code on. 32-bit or 64-bit. Clang or GCC. Visual Studio or Mingw. I have to test all these to accept pull requests, so I prioritize changes that respect my time. + +- **Submit Premake build changes only**. As of 1.1, SCons is deprecated. Also, do not submit altered generated projects. I will re-run Premake to re-generate them to ensure that I can still generate the project prior to admitting your pull request. + +- **Do not alter existing behavior to support your desired behavior**. For instance, rewriting file open dialogs to behave differently, while trading off functionality for compatibility, will get you rejected. Consider creating an additional code path. Instead of altering `nfd_win.cpp` to support Windows XP, create `nfd_win_legacy.cpp`, which exists alongside the newer file dialog. + +- **Do not submit anything I can't verify or maintain**. If you add support for a compiler, include from-scratch install instructions that you have tested yourself. Accepting a pull request means I am now the maintainer of your code, so I must understand what it does and how to test it. + +- **Do not change the externally facing API**. NFD needs to maintain ABI compatibility. + +## Submitting Cloud Autobuild systems ## + +I have received a few pull requests for Travis and AppVeyor-based autobuilding which I have not accepted. NativeFileDialog is officially covered by my private BuildBot network which supports all three target OSes, both CPU architectures and four compilers. I take the view that having a redundant, lesser autobuild system does not improve coverage: it gives a false positive when partial building succeeds. Please do not invest time into cloud-based building with the hope of a pull request being accepted. + +## Contact Me ## + +Despite all of the "do nots" above, I am happy to recieve new pull requests! If you have any questions about style, or what I would need to accept your specific request, please contact me ahead of submitting the pull request by opening an issue on Github with your question. I will do my best to answer you. diff --git a/extern/nfd-modified/src/common.h b/extern/nfd-modified/src/common.h new file mode 100644 index 000000000..7745d323b --- /dev/null +++ b/extern/nfd-modified/src/common.h @@ -0,0 +1,21 @@ +/* + Native File Dialog + + Internal, common across platforms + + http://www.frogtoss.com/labs + */ + + +#ifndef _NFD_COMMON_H +#define _NFD_COMMON_H + +#define NFD_MAX_STRLEN 256 +#define _NFD_UNUSED(x) ((void)x) + +void *NFDi_Malloc( size_t bytes ); +void NFDi_Free( void *ptr ); +void NFDi_SetError( const char *msg ); +void NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy ); + +#endif diff --git a/extern/nfd-modified/src/include/nfd.h b/extern/nfd-modified/src/include/nfd.h new file mode 100644 index 000000000..7f630167d --- /dev/null +++ b/extern/nfd-modified/src/include/nfd.h @@ -0,0 +1,73 @@ +/* + Native File Dialog + + User API + + http://www.frogtoss.com/labs + */ + + +#ifndef _NFD_H +#define _NFD_H + +#include +#include +#include +#include + +/* denotes UTF-8 char */ +typedef char nfdchar_t; + +typedef std::function nfdselcallback_t; + +/* opaque data structure -- see NFD_PathSet_* */ +typedef struct { + nfdchar_t *buf; + size_t *indices; /* byte offsets into buf */ + size_t count; /* number of indices into buf */ +}nfdpathset_t; + +typedef enum { + NFD_ERROR, /* programmatic error */ + NFD_OKAY, /* user pressed okay, or successful return */ + NFD_CANCEL /* user pressed cancel */ +}nfdresult_t; + + +/* nfd_.c */ + +/* single file open dialog */ +nfdresult_t NFD_OpenDialog( const std::vector& filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath, + nfdselcallback_t selCallback = NULL ); + +/* multiple file open dialog */ +nfdresult_t NFD_OpenDialogMultiple( const std::vector& filterList, + const nfdchar_t *defaultPath, + nfdpathset_t *outPaths, + nfdselcallback_t selCallback = NULL ); + +/* save dialog */ +nfdresult_t NFD_SaveDialog( const std::vector& filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath, + nfdselcallback_t selCallback = NULL ); + + +/* select folder dialog */ +nfdresult_t NFD_PickFolder( const nfdchar_t *defaultPath, + nfdchar_t **outPath); + +/* nfd_common.c */ + +/* get last error -- set when nfdresult_t returns NFD_ERROR */ +const char *NFD_GetError( void ); +/* get the number of entries stored in pathSet */ +size_t NFD_PathSet_GetCount( const nfdpathset_t *pathSet ); +/* Get the UTF-8 path at offset index */ +nfdchar_t *NFD_PathSet_GetPath( const nfdpathset_t *pathSet, size_t index ); +/* Free the pathSet */ +void NFD_PathSet_Free( nfdpathset_t *pathSet ); + +#endif diff --git a/extern/nfd-modified/src/nfd_cocoa.mm b/extern/nfd-modified/src/nfd_cocoa.mm new file mode 100644 index 000000000..4e0f9e0bf --- /dev/null +++ b/extern/nfd-modified/src/nfd_cocoa.mm @@ -0,0 +1,296 @@ +/* + Native File Dialog + + http://www.frogtoss.com/labs + */ + +#include +#include "nfd.h" +#include "nfd_common.h" + +// this language is a mess! +// who thought it was a good idea to combine Objective-C and C++ together +// when you could just have used C++ and call it a day!!! +// +// might as well make Objective-Ruswift++... + +static NSArray *BuildAllowedFileTypes( const std::vector& filterList ) +{ + // Commas and semicolons are the same thing on this platform + + // like what about THIS INSTEAD! + // NSMutableArray *buildFilterList = NSMutableArray::alloc()->init(); + NSMutableArray *buildFilterList = [[NSMutableArray alloc] init]; + + std::string typebuf; + for (const std::string& i: filterList) { + typebuf=""; + for (const char& j: i) { + if (j==' ' || j==',' || j ==';') { + // or this: NSString::stringWithUTF8String(typebuf); + // buildFilterList->addObject(thisType); + // really? did you have to make this mess?! + const char* typebufC=typebuf.c_str(); + NSString *thisType = [NSString stringWithUTF8String:typebufC]; + [buildFilterList addObject:thisType]; + typebuf=""; + } else if (j!='.' && j!='*') { + typebuf+=j; + } + } + if (!typebuf.empty()) { + // I don't think this will work, but come on... + const char* typebufC=typebuf.c_str(); + NSString *thisType = [NSString stringWithUTF8String:typebufC]; + [buildFilterList addObject:thisType]; + } + } + + NSArray *returnArray = [NSArray arrayWithArray:buildFilterList]; + + [buildFilterList release]; + return returnArray; +} + +static void AddFilterListToDialog( NSSavePanel *dialog, const std::vector& filterList ) +{ + if ( filterList.size()&1 ) + return; + + NSArray *allowedFileTypes = BuildAllowedFileTypes( filterList ); + if ( [allowedFileTypes count] != 0 ) + { + [dialog setAllowedFileTypes:allowedFileTypes]; + } +} + +static void SetDefaultPath( NSSavePanel *dialog, const nfdchar_t *defaultPath ) +{ + if ( !defaultPath || strlen(defaultPath) == 0 ) + return; + + NSString *defaultPathString = [NSString stringWithUTF8String: defaultPath]; + NSURL *url = [NSURL fileURLWithPath:defaultPathString isDirectory:YES]; + [dialog setDirectoryURL:url]; +} + + +/* fixme: pathset should be pathSet */ +static nfdresult_t AllocPathSet( NSArray *urls, nfdpathset_t *pathset ) +{ + assert(pathset); + assert([urls count]); + + pathset->count = (size_t)[urls count]; + pathset->indices = (size_t*)NFDi_Malloc( sizeof(size_t)*pathset->count ); + if ( !pathset->indices ) + { + return NFD_ERROR; + } + + // count the total space needed for buf + size_t bufsize = 0; + for ( NSURL *url in urls ) + { + NSString *path = [url path]; + bufsize += [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1; + } + + pathset->buf = (nfdchar_t*)NFDi_Malloc( sizeof(nfdchar_t) * bufsize ); + if ( !pathset->buf ) + { + return NFD_ERROR; + } + + // fill buf + nfdchar_t *p_buf = pathset->buf; + size_t count = 0; + for ( NSURL *url in urls ) + { + NSString *path = [url path]; + const nfdchar_t *utf8Path = [path UTF8String]; + size_t byteLen = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1; + memcpy( p_buf, utf8Path, byteLen ); + + ptrdiff_t index = p_buf - pathset->buf; + assert( index >= 0 ); + pathset->indices[count] = (size_t)index; + + p_buf += byteLen; + ++count; + } + + return NFD_OKAY; +} + +/* public */ + + +nfdresult_t NFD_OpenDialog( const std::vector& filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath, + nfdselcallback_t selCallback ) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; + NSOpenPanel *dialog = [NSOpenPanel openPanel]; + [dialog setAllowsMultipleSelection:NO]; + + // Build the filter list + AddFilterListToDialog(dialog, filterList); + + // Set the starting directory + SetDefaultPath(dialog, defaultPath); + + nfdresult_t nfdResult = NFD_CANCEL; + if ( [dialog runModal] == NSModalResponseOK ) + { + NSURL *url = [dialog URL]; + const char *utf8Path = [[url path] UTF8String]; + + // byte count, not char count + size_t len = strlen(utf8Path);//NFDi_UTF8_Strlen(utf8Path); + + *outPath = (nfdchar_t*)NFDi_Malloc( len+1 ); + if ( !*outPath ) + { + [pool release]; + [keyWindow makeKeyAndOrderFront:nil]; + return NFD_ERROR; + } + memcpy( *outPath, utf8Path, len+1 ); /* copy null term */ + nfdResult = NFD_OKAY; + } + [pool release]; + + [keyWindow makeKeyAndOrderFront:nil]; + return nfdResult; +} + + +nfdresult_t NFD_OpenDialogMultiple( const std::vector& filterList, + const nfdchar_t *defaultPath, + nfdpathset_t *outPaths, + nfdselcallback_t selCallback ) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; + + NSOpenPanel *dialog = [NSOpenPanel openPanel]; + [dialog setAllowsMultipleSelection:YES]; + + // Build the fiter list. + AddFilterListToDialog(dialog, filterList); + + // Set the starting directory + SetDefaultPath(dialog, defaultPath); + + nfdresult_t nfdResult = NFD_CANCEL; + if ( [dialog runModal] == NSModalResponseOK ) + { + NSArray *urls = [dialog URLs]; + + if ( [urls count] == 0 ) + { + [pool release]; + [keyWindow makeKeyAndOrderFront:nil]; + return NFD_CANCEL; + } + + if ( AllocPathSet( urls, outPaths ) == NFD_ERROR ) + { + [pool release]; + [keyWindow makeKeyAndOrderFront:nil]; + return NFD_ERROR; + } + + nfdResult = NFD_OKAY; + } + [pool release]; + + [keyWindow makeKeyAndOrderFront:nil]; + return nfdResult; +} + + +nfdresult_t NFD_SaveDialog( const std::vector& filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath, + nfdselcallback_t selCallback ) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; + + NSSavePanel *dialog = [NSSavePanel savePanel]; + [dialog setExtensionHidden:NO]; + + // Build the filter list. + AddFilterListToDialog(dialog, filterList); + + // Set the starting directory + SetDefaultPath(dialog, defaultPath); + + nfdresult_t nfdResult = NFD_CANCEL; + if ( [dialog runModal] == NSModalResponseOK ) + { + NSURL *url = [dialog URL]; + const char *utf8Path = [[url path] UTF8String]; + + size_t byteLen = [url.path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1; + + *outPath = (nfdchar_t*)NFDi_Malloc( byteLen ); + if ( !*outPath ) + { + [pool release]; + [keyWindow makeKeyAndOrderFront:nil]; + return NFD_ERROR; + } + memcpy( *outPath, utf8Path, byteLen ); + nfdResult = NFD_OKAY; + } + + [pool release]; + [keyWindow makeKeyAndOrderFront:nil]; + return nfdResult; +} + +nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath, + nfdchar_t **outPath) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; + NSOpenPanel *dialog = [NSOpenPanel openPanel]; + [dialog setAllowsMultipleSelection:NO]; + [dialog setCanChooseDirectories:YES]; + [dialog setCanCreateDirectories:YES]; + [dialog setCanChooseFiles:NO]; + + // Set the starting directory + SetDefaultPath(dialog, defaultPath); + + nfdresult_t nfdResult = NFD_CANCEL; + if ( [dialog runModal] == NSModalResponseOK ) + { + NSURL *url = [dialog URL]; + const char *utf8Path = [[url path] UTF8String]; + + // byte count, not char count + size_t len = strlen(utf8Path);//NFDi_UTF8_Strlen(utf8Path); + + *outPath = (nfdchar_t*)NFDi_Malloc( len+1 ); + if ( !*outPath ) + { + [pool release]; + [keyWindow makeKeyAndOrderFront:nil]; + return NFD_ERROR; + } + memcpy( *outPath, utf8Path, len+1 ); /* copy null term */ + nfdResult = NFD_OKAY; + } + [pool release]; + + [keyWindow makeKeyAndOrderFront:nil]; + return nfdResult; +} diff --git a/extern/nfd-modified/src/nfd_common.cpp b/extern/nfd-modified/src/nfd_common.cpp new file mode 100644 index 000000000..55517f5dd --- /dev/null +++ b/extern/nfd-modified/src/nfd_common.cpp @@ -0,0 +1,142 @@ +/* + Native File Dialog + + http://www.frogtoss.com/labs + */ + +#include +#include +#include +#include "nfd_common.h" + +static char g_errorstr[NFD_MAX_STRLEN] = {0}; + +/* public routines */ + +const char *NFD_GetError( void ) +{ + return g_errorstr; +} + +size_t NFD_PathSet_GetCount( const nfdpathset_t *pathset ) +{ + assert(pathset); + return pathset->count; +} + +nfdchar_t *NFD_PathSet_GetPath( const nfdpathset_t *pathset, size_t num ) +{ + assert(pathset); + assert(num < pathset->count); + + return pathset->buf + pathset->indices[num]; +} + +void NFD_PathSet_Free( nfdpathset_t *pathset ) +{ + assert(pathset); + NFDi_Free( pathset->indices ); + NFDi_Free( pathset->buf ); +} + +/* internal routines */ + +void *NFDi_Malloc( size_t bytes ) +{ + void *ptr = malloc(bytes); + if ( !ptr ) + NFDi_SetError("NFDi_Malloc failed."); + + return ptr; +} + +void NFDi_Free( void *ptr ) +{ + assert(ptr); + free(ptr); +} + +void NFDi_SetError( const char *msg ) +{ + int bTruncate = NFDi_SafeStrncpy( g_errorstr, msg, NFD_MAX_STRLEN ); + assert( !bTruncate ); _NFD_UNUSED(bTruncate); +} + + +int NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy ) +{ + size_t n = maxCopy; + char *d = dst; + + assert( src ); + assert( dst ); + + while ( n > 0 && *src != '\0' ) + { + *d++ = *src++; + --n; + } + + /* Truncation case - + terminate string and return true */ + if ( n == 0 ) + { + dst[maxCopy-1] = '\0'; + return 1; + } + + /* No truncation. Append a single NULL and return. */ + *d = '\0'; + return 0; +} + + +/* adapted from microutf8 */ +int32_t NFDi_UTF8_Strlen( const nfdchar_t *str ) +{ + /* This function doesn't properly check validity of UTF-8 character + sequence, it is supposed to use only with valid UTF-8 strings. */ + + int32_t character_count = 0; + int32_t i = 0; /* Counter used to iterate over string. */ + nfdchar_t maybe_bom[4]; + + /* If there is UTF-8 BOM ignore it. */ + if (strlen(str) > 2) + { + strncpy(maybe_bom, str, 3); + maybe_bom[3] = 0; + if (strcmp(maybe_bom, (nfdchar_t*)NFD_UTF8_BOM) == 0) + i += 3; + } + + while(str[i]) + { + if (str[i] >> 7 == 0) + { + /* If bit pattern begins with 0 we have ascii character. */ + ++character_count; + } + else if (str[i] >> 6 == 3) + { + /* If bit pattern begins with 11 it is beginning of UTF-8 byte sequence. */ + ++character_count; + } + else if (str[i] >> 6 == 2) + ; /* If bit pattern begins with 10 it is middle of utf-8 byte sequence. */ + else + { + /* In any other case this is not valid UTF-8. */ + return -1; + } + ++i; + } + + return character_count; +} + +int NFDi_IsFilterSegmentChar( char ch ) +{ + return (ch==','||ch==';'||ch=='\0'); +} + diff --git a/extern/nfd-modified/src/nfd_common.h b/extern/nfd-modified/src/nfd_common.h new file mode 100644 index 000000000..a1dd74b51 --- /dev/null +++ b/extern/nfd-modified/src/nfd_common.h @@ -0,0 +1,31 @@ +/* + Native File Dialog + + Internal, common across platforms + + http://www.frogtoss.com/labs + */ + + +#ifndef _NFD_COMMON_H +#define _NFD_COMMON_H + +#include "nfd.h" + +#include + +#define NFD_MAX_STRLEN 256 +#define _NFD_UNUSED(x) ((void)x) + +#define NFD_UTF8_BOM "\xEF\xBB\xBF" + + +void *NFDi_Malloc( size_t bytes ); +void NFDi_Free( void *ptr ); +void NFDi_SetError( const char *msg ); +int NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy ); +int32_t NFDi_UTF8_Strlen( const nfdchar_t *str ); +int NFDi_IsFilterSegmentChar( char ch ); + + +#endif diff --git a/extern/nfd-modified/src/nfd_gtk.cpp b/extern/nfd-modified/src/nfd_gtk.cpp new file mode 100644 index 000000000..7a9958ed1 --- /dev/null +++ b/extern/nfd-modified/src/nfd_gtk.cpp @@ -0,0 +1,379 @@ +/* + Native File Dialog + + http://www.frogtoss.com/labs +*/ + +#include +#include +#include +#include +#include "nfd.h" +#include "nfd_common.h" + + +const char INIT_FAIL_MSG[] = "gtk_init_check failed to initilaize GTK+"; + + +static void AddTypeToFilterName( const char *typebuf, char *filterName, size_t bufsize ) +{ + const char SEP[] = ", "; + + size_t len = strlen(filterName); + if ( len != 0 ) + { + strncat( filterName, SEP, bufsize - len - 1 ); + len += strlen(SEP); + } + + strncat( filterName, typebuf, bufsize - len - 1 ); +} + +static void AddFiltersToDialog( GtkWidget *dialog, const char *filterList ) +{ + GtkFileFilter *filter; + char typebuf[NFD_MAX_STRLEN] = {0}; + const char *p_filterList = filterList; + char *p_typebuf = typebuf; + char filterName[NFD_MAX_STRLEN] = {0}; + + if ( !filterList || strlen(filterList) == 0 ) + return; + + filter = gtk_file_filter_new(); + while ( 1 ) + { + + if ( NFDi_IsFilterSegmentChar(*p_filterList) ) + { + char typebufWildcard[NFD_MAX_STRLEN]; + /* add another type to the filter */ + assert( strlen(typebuf) > 0 ); + assert( strlen(typebuf) < NFD_MAX_STRLEN-1 ); + + snprintf( typebufWildcard, NFD_MAX_STRLEN, "*.%s", typebuf ); + AddTypeToFilterName( typebuf, filterName, NFD_MAX_STRLEN ); + + gtk_file_filter_add_pattern( filter, typebufWildcard ); + + p_typebuf = typebuf; + memset( typebuf, 0, sizeof(char) * NFD_MAX_STRLEN ); + } + + if ( *p_filterList == ';' || *p_filterList == '\0' ) + { + /* end of filter -- add it to the dialog */ + + gtk_file_filter_set_name( filter, filterName ); + gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter ); + + filterName[0] = '\0'; + + if ( *p_filterList == '\0' ) + break; + + filter = gtk_file_filter_new(); + } + + if ( !NFDi_IsFilterSegmentChar( *p_filterList ) ) + { + *p_typebuf = *p_filterList; + p_typebuf++; + } + + p_filterList++; + } + + /* always append a wildcard option to the end*/ + + filter = gtk_file_filter_new(); + gtk_file_filter_set_name( filter, "*.*" ); + gtk_file_filter_add_pattern( filter, "*" ); + gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter ); +} + +static void SetDefaultPath( GtkWidget *dialog, const char *defaultPath ) +{ + if ( !defaultPath || strlen(defaultPath) == 0 ) + return; + + /* GTK+ manual recommends not specifically setting the default path. + We do it anyway in order to be consistent across platforms. + + If consistency with the native OS is preferred, this is the line + to comment out. -ml */ + gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), defaultPath ); +} + +static nfdresult_t AllocPathSet( GSList *fileList, nfdpathset_t *pathSet ) +{ + size_t bufSize = 0; + GSList *node; + nfdchar_t *p_buf; + size_t count = 0; + + assert(fileList); + assert(pathSet); + + pathSet->count = (size_t)g_slist_length( fileList ); + assert( pathSet->count > 0 ); + + pathSet->indices = NFDi_Malloc( sizeof(size_t)*pathSet->count ); + if ( !pathSet->indices ) + { + return NFD_ERROR; + } + + /* count the total space needed for buf */ + for ( node = fileList; node; node = node->next ) + { + assert(node->data); + bufSize += strlen( (const gchar*)node->data ) + 1; + } + + pathSet->buf = NFDi_Malloc( sizeof(nfdchar_t) * bufSize ); + + /* fill buf */ + p_buf = pathSet->buf; + for ( node = fileList; node; node = node->next ) + { + nfdchar_t *path = (nfdchar_t*)(node->data); + size_t byteLen = strlen(path)+1; + ptrdiff_t index; + + memcpy( p_buf, path, byteLen ); + g_free(node->data); + + index = p_buf - pathSet->buf; + assert( index >= 0 ); + pathSet->indices[count] = (size_t)index; + + p_buf += byteLen; + ++count; + } + + g_slist_free( fileList ); + + return NFD_OKAY; +} + +static void WaitForCleanup(void) +{ + while (gtk_events_pending()) + gtk_main_iteration(); +} + +/* public */ + +nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath ) +{ + GtkWidget *dialog; + nfdresult_t result; + + if ( !gtk_init_check( NULL, NULL ) ) + { + NFDi_SetError(INIT_FAIL_MSG); + return NFD_ERROR; + } + + dialog = gtk_file_chooser_dialog_new( "Open File", + NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Open", GTK_RESPONSE_ACCEPT, + NULL ); + + /* Build the filter list */ + AddFiltersToDialog(dialog, filterList); + + /* Set the default path */ + SetDefaultPath(dialog, defaultPath); + + result = NFD_CANCEL; + if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) + { + char *filename; + + filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) ); + + { + size_t len = strlen(filename); + *outPath = NFDi_Malloc( len + 1 ); + memcpy( *outPath, filename, len + 1 ); + if ( !*outPath ) + { + g_free( filename ); + gtk_widget_destroy(dialog); + return NFD_ERROR; + } + } + g_free( filename ); + + result = NFD_OKAY; + } + + WaitForCleanup(); + gtk_widget_destroy(dialog); + WaitForCleanup(); + + return result; +} + + +nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList, + const nfdchar_t *defaultPath, + nfdpathset_t *outPaths ) +{ + GtkWidget *dialog; + nfdresult_t result; + + if ( !gtk_init_check( NULL, NULL ) ) + { + NFDi_SetError(INIT_FAIL_MSG); + return NFD_ERROR; + } + + dialog = gtk_file_chooser_dialog_new( "Open Files", + NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Open", GTK_RESPONSE_ACCEPT, + NULL ); + gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(dialog), TRUE ); + + /* Build the filter list */ + AddFiltersToDialog(dialog, filterList); + + /* Set the default path */ + SetDefaultPath(dialog, defaultPath); + + result = NFD_CANCEL; + if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) + { + GSList *fileList = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER(dialog) ); + if ( AllocPathSet( fileList, outPaths ) == NFD_ERROR ) + { + gtk_widget_destroy(dialog); + return NFD_ERROR; + } + + result = NFD_OKAY; + } + + WaitForCleanup(); + gtk_widget_destroy(dialog); + WaitForCleanup(); + + return result; +} + +nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath ) +{ + GtkWidget *dialog; + nfdresult_t result; + + if ( !gtk_init_check( NULL, NULL ) ) + { + NFDi_SetError(INIT_FAIL_MSG); + return NFD_ERROR; + } + + dialog = gtk_file_chooser_dialog_new( "Save File", + NULL, + GTK_FILE_CHOOSER_ACTION_SAVE, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Save", GTK_RESPONSE_ACCEPT, + NULL ); + gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE ); + + /* Build the filter list */ + AddFiltersToDialog(dialog, filterList); + + /* Set the default path */ + SetDefaultPath(dialog, defaultPath); + + result = NFD_CANCEL; + if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) + { + char *filename; + filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) ); + + { + size_t len = strlen(filename); + *outPath = NFDi_Malloc( len + 1 ); + memcpy( *outPath, filename, len + 1 ); + if ( !*outPath ) + { + g_free( filename ); + gtk_widget_destroy(dialog); + return NFD_ERROR; + } + } + g_free(filename); + + result = NFD_OKAY; + } + + WaitForCleanup(); + gtk_widget_destroy(dialog); + WaitForCleanup(); + + return result; +} + +nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath, + nfdchar_t **outPath) +{ + GtkWidget *dialog; + nfdresult_t result; + + if (!gtk_init_check(NULL, NULL)) + { + NFDi_SetError(INIT_FAIL_MSG); + return NFD_ERROR; + } + + dialog = gtk_file_chooser_dialog_new( "Select folder", + NULL, + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Select", GTK_RESPONSE_ACCEPT, + NULL ); + gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE ); + + + /* Set the default path */ + SetDefaultPath(dialog, defaultPath); + + result = NFD_CANCEL; + if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) + { + char *filename; + filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) ); + + { + size_t len = strlen(filename); + *outPath = NFDi_Malloc( len + 1 ); + memcpy( *outPath, filename, len + 1 ); + if ( !*outPath ) + { + g_free( filename ); + gtk_widget_destroy(dialog); + return NFD_ERROR; + } + } + g_free(filename); + + result = NFD_OKAY; + } + + WaitForCleanup(); + gtk_widget_destroy(dialog); + WaitForCleanup(); + + return result; +} diff --git a/extern/nfd-modified/src/nfd_win.cpp b/extern/nfd-modified/src/nfd_win.cpp new file mode 100644 index 000000000..2e7f4000b --- /dev/null +++ b/extern/nfd-modified/src/nfd_win.cpp @@ -0,0 +1,797 @@ +/* + Native File Dialog + + http://www.frogtoss.com/labs + */ + + +#ifdef __MINGW32__ +// Explicitly setting NTDDI version, this is necessary for the MinGW compiler +#define NTDDI_VERSION NTDDI_VISTA +#define _WIN32_WINNT _WIN32_WINNT_VISTA +#endif + +#define _CRTDBG_MAP_ALLOC +#include +#include + +/* only locally define UNICODE in this compilation unit */ +#ifndef UNICODE +#define UNICODE +#endif + +#include +#include +#include +#include +#include +#include "nfd_common.h" + +// hack I know +#include "../../../src/utfutils.h" + +class NFDWinEvents: public IFileDialogEvents { + nfdselcallback_t selCallback; + size_t refCount; + + virtual ~NFDWinEvents() { + } + public: + IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv) { + printf("QueryInterface called DAMN IT\n"); + *ppv=NULL; + return E_NOTIMPL; + } + + IFACEMETHODIMP_(ULONG) AddRef() { + printf("AddRef() called\n"); + return InterlockedIncrement(&refCount); + } + + IFACEMETHODIMP_(ULONG) Release() { + printf("Release() called\n"); + LONG ret=InterlockedDecrement(&refCount); + if (ret==0) { + printf("Destroying the final object.\n"); + delete this; + } + return ret; + } + + IFACEMETHODIMP OnFileOk(IFileDialog*) { return E_NOTIMPL; } + IFACEMETHODIMP OnFolderChange(IFileDialog*) { return E_NOTIMPL; } + IFACEMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) { return E_NOTIMPL; } + IFACEMETHODIMP OnOverwrite(IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE*) { return E_NOTIMPL; } + IFACEMETHODIMP OnShareViolation(IFileDialog*, IShellItem*, FDE_SHAREVIOLATION_RESPONSE*) { return E_NOTIMPL; } + IFACEMETHODIMP OnTypeChange(IFileDialog*) { return E_NOTIMPL; } + + IFACEMETHODIMP OnSelectionChange(IFileDialog* dialog) { + // Get the file name + ::IShellItem *shellItem(NULL); + HRESULT result = dialog->GetCurrentSelection(&shellItem); + if ( !SUCCEEDED(result) ) + { + printf("failure!\n"); + return S_OK; + } + wchar_t *filePath(NULL); + result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath); + if ( !SUCCEEDED(result) ) + { + printf("GDN failure!\n"); + shellItem->Release(); + return S_OK; + } + std::string utf8FilePath=utf16To8(filePath); + if (selCallback!=NULL) selCallback(utf8FilePath.c_str()); + printf("I got you for a value of %s\n",utf8FilePath.c_str()); + shellItem->Release(); + return S_OK; + } + NFDWinEvents(nfdselcallback_t callback): + selCallback(callback), + refCount(1) { + } +}; + +#define COM_INITFLAGS ::COINIT_APARTMENTTHREADED | ::COINIT_DISABLE_OLE1DDE + +static BOOL COMIsInitialized(HRESULT coResult) +{ + if (coResult == RPC_E_CHANGED_MODE) + { + // If COM was previously initialized with different init flags, + // NFD still needs to operate. Eat this warning. + return TRUE; + } + + return SUCCEEDED(coResult); +} + +static HRESULT COMInit(void) +{ + return ::CoInitializeEx(NULL, COM_INITFLAGS); +} + +static void COMUninit(HRESULT coResult) +{ + // do not uninitialize if RPC_E_CHANGED_MODE occurred -- this + // case does not refcount COM. + if (SUCCEEDED(coResult)) + ::CoUninitialize(); +} + +// allocs the space in outPath -- call free() +static void CopyWCharToNFDChar( const wchar_t *inStr, nfdchar_t **outStr ) +{ + int inStrCharacterCount = static_cast(wcslen(inStr)); + int bytesNeeded = WideCharToMultiByte( CP_UTF8, 0, + inStr, inStrCharacterCount, + NULL, 0, NULL, NULL ); + assert( bytesNeeded ); + bytesNeeded += 1; + + *outStr = (nfdchar_t*)NFDi_Malloc( bytesNeeded ); + if ( !*outStr ) + return; + + int bytesWritten = WideCharToMultiByte( CP_UTF8, 0, + inStr, -1, + *outStr, bytesNeeded, + NULL, NULL ); + assert( bytesWritten ); _NFD_UNUSED( bytesWritten ); +} + +/* includes NULL terminator byte in return */ +static size_t GetUTF8ByteCountForWChar( const wchar_t *str ) +{ + size_t bytesNeeded = WideCharToMultiByte( CP_UTF8, 0, + str, -1, + NULL, 0, NULL, NULL ); + assert( bytesNeeded ); + return bytesNeeded+1; +} + +// write to outPtr -- no free() necessary. +static int CopyWCharToExistingNFDCharBuffer( const wchar_t *inStr, nfdchar_t *outPtr ) +{ + int bytesNeeded = static_cast(GetUTF8ByteCountForWChar( inStr )); + + /* invocation copies null term */ + int bytesWritten = WideCharToMultiByte( CP_UTF8, 0, + inStr, -1, + outPtr, bytesNeeded, + NULL, 0 ); + assert( bytesWritten ); + + return bytesWritten; + +} + + +// allocs the space in outStr -- call free() +static void CopyNFDCharToWChar( const nfdchar_t *inStr, wchar_t **outStr ) +{ + int inStrByteCount = static_cast(strlen(inStr)); + int charsNeeded = MultiByteToWideChar(CP_UTF8, 0, + inStr, inStrByteCount, + NULL, 0 ); + assert( charsNeeded ); + assert( !*outStr ); + charsNeeded += 1; // terminator + + *outStr = (wchar_t*)NFDi_Malloc( charsNeeded * sizeof(wchar_t) ); + if ( !*outStr ) + return; + + int ret = MultiByteToWideChar(CP_UTF8, 0, + inStr, inStrByteCount, + *outStr, charsNeeded); + (*outStr)[charsNeeded-1] = '\0'; + +#ifdef _DEBUG + int inStrCharacterCount = static_cast(NFDi_UTF8_Strlen(inStr)); + assert( ret == inStrCharacterCount ); +#else + _NFD_UNUSED(ret); +#endif +} + +static nfdresult_t AddFiltersToDialog( ::IFileDialog *fileOpenDialog, const std::vector& filterList ) +{ + const wchar_t WILDCARD[] = L"*.*"; + + if (filterList.empty()) + return NFD_OKAY; + + // list size has to be an even number (name/filter) + if (filterList.size()&1) + return NFD_ERROR; + + // Count rows to alloc + UINT filterCount = filterList.size()>>1; /* guaranteed to have one filter on a correct, non-empty parse */ + + assert(filterCount); + if ( !filterCount ) + { + NFDi_SetError("Error parsing filters."); + return NFD_ERROR; + } + + /* filterCount plus 1 because we hardcode the *.* wildcard after the while loop */ + COMDLG_FILTERSPEC *specList = (COMDLG_FILTERSPEC*)NFDi_Malloc( sizeof(COMDLG_FILTERSPEC) * ((size_t)filterCount + 1) ); + if ( !specList ) + { + return NFD_ERROR; + } + for (UINT i = 0; i < filterCount+1; ++i ) + { + specList[i].pszName = NULL; + specList[i].pszSpec = NULL; + } + + size_t specIdx = 0; + + for (size_t i=0; iSetFileTypes( filterCount+1, specList ); + + /* free speclist */ + for ( size_t i = 0; i < filterCount; ++i ) + { + NFDi_Free( (void*)specList[i].pszSpec ); + } + NFDi_Free( specList ); + + return NFD_OKAY; +} + +static nfdresult_t AllocPathSet( IShellItemArray *shellItems, nfdpathset_t *pathSet ) +{ + const char ERRORMSG[] = "Error allocating pathset."; + + assert(shellItems); + assert(pathSet); + + // How many items in shellItems? + DWORD numShellItems; + HRESULT result = shellItems->GetCount(&numShellItems); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError(ERRORMSG); + return NFD_ERROR; + } + + pathSet->count = static_cast(numShellItems); + assert( pathSet->count > 0 ); + + pathSet->indices = (size_t*)NFDi_Malloc( sizeof(size_t)*pathSet->count ); + if ( !pathSet->indices ) + { + return NFD_ERROR; + } + + /* count the total bytes needed for buf */ + size_t bufSize = 0; + for ( DWORD i = 0; i < numShellItems; ++i ) + { + ::IShellItem *shellItem; + result = shellItems->GetItemAt(i, &shellItem); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError(ERRORMSG); + return NFD_ERROR; + } + + // Confirm SFGAO_FILESYSTEM is true for this shellitem, or ignore it. + SFGAOF attribs; + result = shellItem->GetAttributes( SFGAO_FILESYSTEM, &attribs ); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError(ERRORMSG); + return NFD_ERROR; + } + if ( !(attribs & SFGAO_FILESYSTEM) ) + continue; + + LPWSTR name; + shellItem->GetDisplayName(SIGDN_FILESYSPATH, &name); + + // Calculate length of name with UTF-8 encoding + bufSize += GetUTF8ByteCountForWChar( name ); + + CoTaskMemFree(name); + } + + assert(bufSize); + + pathSet->buf = (nfdchar_t*)NFDi_Malloc( sizeof(nfdchar_t) * bufSize ); + memset( pathSet->buf, 0, sizeof(nfdchar_t) * bufSize ); + + /* fill buf */ + nfdchar_t *p_buf = pathSet->buf; + for (DWORD i = 0; i < numShellItems; ++i ) + { + ::IShellItem *shellItem; + result = shellItems->GetItemAt(i, &shellItem); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError(ERRORMSG); + return NFD_ERROR; + } + + // Confirm SFGAO_FILESYSTEM is true for this shellitem, or ignore it. + SFGAOF attribs; + result = shellItem->GetAttributes( SFGAO_FILESYSTEM, &attribs ); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError(ERRORMSG); + return NFD_ERROR; + } + if ( !(attribs & SFGAO_FILESYSTEM) ) + continue; + + LPWSTR name; + shellItem->GetDisplayName(SIGDN_FILESYSPATH, &name); + + int bytesWritten = CopyWCharToExistingNFDCharBuffer(name, p_buf); + CoTaskMemFree(name); + + ptrdiff_t index = p_buf - pathSet->buf; + assert( index >= 0 ); + pathSet->indices[i] = static_cast(index); + + p_buf += bytesWritten; + } + + return NFD_OKAY; +} + + +static nfdresult_t SetDefaultPath( IFileDialog *dialog, const char *defaultPath ) +{ + if ( !defaultPath || strlen(defaultPath) == 0 ) + return NFD_OKAY; + + wchar_t *defaultPathW = {0}; + CopyNFDCharToWChar( defaultPath, &defaultPathW ); + + IShellItem *folder; + HRESULT result = SHCreateItemFromParsingName( defaultPathW, NULL, IID_PPV_ARGS(&folder) ); + + // Valid non results. + if ( result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || result == HRESULT_FROM_WIN32(ERROR_INVALID_DRIVE) ) + { + NFDi_Free( defaultPathW ); + return NFD_OKAY; + } + + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Error creating ShellItem"); + NFDi_Free( defaultPathW ); + return NFD_ERROR; + } + + // Could also call SetDefaultFolder(), but this guarantees defaultPath -- more consistency across API. + dialog->SetFolder( folder ); + + NFDi_Free( defaultPathW ); + folder->Release(); + + return NFD_OKAY; +} + +/* public */ + + +nfdresult_t NFD_OpenDialog( const std::vector& filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath, + nfdselcallback_t selCallback ) +{ + nfdresult_t nfdResult = NFD_ERROR; + NFDWinEvents* winEvents; + bool hasEvents=true; + DWORD eventID=0; + + HRESULT coResult = COMInit(); + if (!COMIsInitialized(coResult)) + { + NFDi_SetError("Could not initialize COM."); + return nfdResult; + } + + // Create dialog + ::IFileOpenDialog *fileOpenDialog(NULL); + HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL, + CLSCTX_ALL, ::IID_IFileOpenDialog, + reinterpret_cast(&fileOpenDialog) ); + + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Could not create dialog."); + goto end; + } + + // Build the filter list + if ( !AddFiltersToDialog( fileOpenDialog, filterList ) ) + { + goto end; + } + + // Set the default path + if ( !SetDefaultPath( fileOpenDialog, defaultPath ) ) + { + goto end; + } + + // Pass the callback + winEvents=new NFDWinEvents(selCallback); + if ( !SUCCEEDED(fileOpenDialog->Advise(winEvents,&eventID)) ) { + // error... ignore + hasEvents=false; + winEvents->Release(); + } else { + winEvents->Release(); + } + + // Show the dialog. + // TODO: pass the Furnace window here + result = fileOpenDialog->Show(NULL); + if ( SUCCEEDED(result) ) + { + // Get the file name + ::IShellItem *shellItem(NULL); + result = fileOpenDialog->GetResult(&shellItem); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Could not get shell item from dialog."); + goto end; + } + wchar_t *filePath(NULL); + result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Could not get file path for selected."); + shellItem->Release(); + goto end; + } + + CopyWCharToNFDChar( filePath, outPath ); + CoTaskMemFree(filePath); + if ( !*outPath ) + { + /* error is malloc-based, error message would be redundant */ + shellItem->Release(); + goto end; + } + + nfdResult = NFD_OKAY; + shellItem->Release(); + } + else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) ) + { + nfdResult = NFD_CANCEL; + } + else + { + NFDi_SetError("File dialog box show failed."); + nfdResult = NFD_ERROR; + } + +end: + if (fileOpenDialog) { + if (hasEvents) { + fileOpenDialog->Unadvise(eventID); + } + fileOpenDialog->Release(); + } + + COMUninit(coResult); + + return nfdResult; +} + +nfdresult_t NFD_OpenDialogMultiple( const std::vector& filterList, + const nfdchar_t *defaultPath, + nfdpathset_t *outPaths, + nfdselcallback_t selCallback ) +{ + nfdresult_t nfdResult = NFD_ERROR; + + + HRESULT coResult = COMInit(); + if (!COMIsInitialized(coResult)) + { + NFDi_SetError("Could not initialize COM."); + return nfdResult; + } + + // Create dialog + ::IFileOpenDialog *fileOpenDialog(NULL); + HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL, + CLSCTX_ALL, ::IID_IFileOpenDialog, + reinterpret_cast(&fileOpenDialog) ); + + if ( !SUCCEEDED(result) ) + { + fileOpenDialog = NULL; + NFDi_SetError("Could not create dialog."); + goto end; + } + + // Build the filter list + if ( !AddFiltersToDialog( fileOpenDialog, filterList ) ) + { + goto end; + } + + // Set the default path + if ( !SetDefaultPath( fileOpenDialog, defaultPath ) ) + { + goto end; + } + + // Set a flag for multiple options + DWORD dwFlags; + result = fileOpenDialog->GetOptions(&dwFlags); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Could not get options."); + goto end; + } + result = fileOpenDialog->SetOptions(dwFlags | FOS_ALLOWMULTISELECT); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Could not set options."); + goto end; + } + + // Show the dialog. + result = fileOpenDialog->Show(NULL); + if ( SUCCEEDED(result) ) + { + IShellItemArray *shellItems; + result = fileOpenDialog->GetResults( &shellItems ); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Could not get shell items."); + goto end; + } + + if ( AllocPathSet( shellItems, outPaths ) == NFD_ERROR ) + { + shellItems->Release(); + goto end; + } + + shellItems->Release(); + nfdResult = NFD_OKAY; + } + else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) ) + { + nfdResult = NFD_CANCEL; + } + else + { + NFDi_SetError("File dialog box show failed."); + nfdResult = NFD_ERROR; + } + +end: + if ( fileOpenDialog ) + fileOpenDialog->Release(); + + COMUninit(coResult); + + return nfdResult; +} + +nfdresult_t NFD_SaveDialog( const std::vector& filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath, + nfdselcallback_t selCallback ) +{ + nfdresult_t nfdResult = NFD_ERROR; + + HRESULT coResult = COMInit(); + if (!COMIsInitialized(coResult)) + { + NFDi_SetError("Could not initialize COM."); + return nfdResult; + } + + // Create dialog + ::IFileSaveDialog *fileSaveDialog(NULL); + HRESULT result = ::CoCreateInstance(::CLSID_FileSaveDialog, NULL, + CLSCTX_ALL, ::IID_IFileSaveDialog, + reinterpret_cast(&fileSaveDialog) ); + + if ( !SUCCEEDED(result) ) + { + fileSaveDialog = NULL; + NFDi_SetError("Could not create dialog."); + goto end; + } + + // Build the filter list + if ( !AddFiltersToDialog( fileSaveDialog, filterList ) ) + { + goto end; + } + + // Set the default path + if ( !SetDefaultPath( fileSaveDialog, defaultPath ) ) + { + goto end; + } + + // Show the dialog. + result = fileSaveDialog->Show(NULL); + if ( SUCCEEDED(result) ) + { + // Get the file name + ::IShellItem *shellItem; + result = fileSaveDialog->GetResult(&shellItem); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Could not get shell item from dialog."); + goto end; + } + wchar_t *filePath(NULL); + result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath); + if ( !SUCCEEDED(result) ) + { + shellItem->Release(); + NFDi_SetError("Could not get file path for selected."); + goto end; + } + + CopyWCharToNFDChar( filePath, outPath ); + CoTaskMemFree(filePath); + if ( !*outPath ) + { + /* error is malloc-based, error message would be redundant */ + shellItem->Release(); + goto end; + } + + nfdResult = NFD_OKAY; + shellItem->Release(); + } + else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) ) + { + nfdResult = NFD_CANCEL; + } + else + { + NFDi_SetError("File dialog box show failed."); + nfdResult = NFD_ERROR; + } + +end: + if ( fileSaveDialog ) + fileSaveDialog->Release(); + + COMUninit(coResult); + + return nfdResult; +} + + + +nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath, + nfdchar_t **outPath) +{ + nfdresult_t nfdResult = NFD_ERROR; + DWORD dwOptions = 0; + + HRESULT coResult = COMInit(); + if (!COMIsInitialized(coResult)) + { + NFDi_SetError("CoInitializeEx failed."); + return nfdResult; + } + + // Create dialog + ::IFileOpenDialog *fileDialog(NULL); + HRESULT result = CoCreateInstance(CLSID_FileOpenDialog, + NULL, + CLSCTX_ALL, + IID_PPV_ARGS(&fileDialog)); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("CoCreateInstance for CLSID_FileOpenDialog failed."); + goto end; + } + + // Set the default path + if (SetDefaultPath(fileDialog, defaultPath) != NFD_OKAY) + { + NFDi_SetError("SetDefaultPath failed."); + goto end; + } + + // Get the dialogs options + if (!SUCCEEDED(fileDialog->GetOptions(&dwOptions))) + { + NFDi_SetError("GetOptions for IFileDialog failed."); + goto end; + } + + // Add in FOS_PICKFOLDERS which hides files and only allows selection of folders + if (!SUCCEEDED(fileDialog->SetOptions(dwOptions | FOS_PICKFOLDERS))) + { + NFDi_SetError("SetOptions for IFileDialog failed."); + goto end; + } + + // Show the dialog to the user + result = fileDialog->Show(NULL); + if ( SUCCEEDED(result) ) + { + // Get the folder name + ::IShellItem *shellItem(NULL); + + result = fileDialog->GetResult(&shellItem); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Could not get file path for selected."); + shellItem->Release(); + goto end; + } + + wchar_t *path = NULL; + result = shellItem->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &path); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("GetDisplayName for IShellItem failed."); + shellItem->Release(); + goto end; + } + + CopyWCharToNFDChar(path, outPath); + CoTaskMemFree(path); + if ( !*outPath ) + { + shellItem->Release(); + goto end; + } + + nfdResult = NFD_OKAY; + shellItem->Release(); + } + else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) ) + { + nfdResult = NFD_CANCEL; + } + else + { + NFDi_SetError("Show for IFileDialog failed."); + nfdResult = NFD_ERROR; + } + + end: + + if (fileDialog) + fileDialog->Release(); + + COMUninit(coResult); + + return nfdResult; +} diff --git a/extern/nfd-modified/src/nfd_zenity.cpp b/extern/nfd-modified/src/nfd_zenity.cpp new file mode 100644 index 000000000..5f9313371 --- /dev/null +++ b/extern/nfd-modified/src/nfd_zenity.cpp @@ -0,0 +1,307 @@ +/* + Native File Dialog + + http://www.frogtoss.com/labs +*/ + +#include +#include +#include +#include "nfd.h" +#include "nfd_common.h" + +#define SIMPLE_EXEC_IMPLEMENTATION +#include "simple_exec.h" + + +const char NO_ZENITY_MSG[] = "zenity not installed"; + + +static void AddTypeToFilterName( const char *typebuf, char *filterName, size_t bufsize ) +{ + size_t len = strlen(filterName); + if( len > 0 ) + strncat( filterName, " *.", bufsize - len - 1 ); + else + strncat( filterName, "--file-filter=*.", bufsize - len - 1 ); + + len = strlen(filterName); + strncat( filterName, typebuf, bufsize - len - 1 ); +} + +static void AddFiltersToCommandArgs(char** commandArgs, int commandArgsLen, const char *filterList ) +{ + char typebuf[NFD_MAX_STRLEN] = {0}; + const char *p_filterList = filterList; + char *p_typebuf = typebuf; + char filterName[NFD_MAX_STRLEN] = {0}; + int i; + + if ( !filterList || strlen(filterList) == 0 ) + return; + + while ( 1 ) + { + + if ( NFDi_IsFilterSegmentChar(*p_filterList) ) + { + char typebufWildcard[NFD_MAX_STRLEN]; + /* add another type to the filter */ + assert( strlen(typebuf) > 0 ); + assert( strlen(typebuf) < NFD_MAX_STRLEN-1 ); + + snprintf( typebufWildcard, NFD_MAX_STRLEN, "*.%s", typebuf ); + + AddTypeToFilterName( typebuf, filterName, NFD_MAX_STRLEN ); + + p_typebuf = typebuf; + memset( typebuf, 0, sizeof(char) * NFD_MAX_STRLEN ); + } + + if ( *p_filterList == ';' || *p_filterList == '\0' ) + { + /* end of filter -- add it to the dialog */ + + for(i = 0; commandArgs[i] != NULL && i < commandArgsLen; i++); + + commandArgs[i] = strdup(filterName); + + filterName[0] = '\0'; + + if ( *p_filterList == '\0' ) + break; + } + + if ( !NFDi_IsFilterSegmentChar( *p_filterList ) ) + { + *p_typebuf = *p_filterList; + p_typebuf++; + } + + p_filterList++; + } + + /* always append a wildcard option to the end*/ + + for(i = 0; commandArgs[i] != NULL && i < commandArgsLen; i++); + + commandArgs[i] = strdup("--file-filter=*.*"); +} + +static nfdresult_t ZenityCommon(char** command, int commandLen, const char* defaultPath, const char* filterList, char** stdOut) +{ + if(defaultPath != NULL) + { + char* prefix = "--filename="; + int len = strlen(prefix) + strlen(defaultPath) + 1; + + char* tmp = (char*) calloc(len, 1); + strcat(tmp, prefix); + strcat(tmp, defaultPath); + + int i; + for(i = 0; command[i] != NULL && i < commandLen; i++); + + command[i] = tmp; + } + + AddFiltersToCommandArgs(command, commandLen, filterList); + + int byteCount = 0; + int exitCode = 0; + int processInvokeError = runCommandArray(stdOut, &byteCount, &exitCode, 0, command); + + for(int i = 0; command[i] != NULL && i < commandLen; i++) + free(command[i]); + + nfdresult_t result = NFD_OKAY; + + if(processInvokeError == COMMAND_NOT_FOUND) + { + NFDi_SetError(NO_ZENITY_MSG); + result = NFD_ERROR; + } + else + { + if(exitCode == 1) + result = NFD_CANCEL; + } + + return result; +} + + +static nfdresult_t AllocPathSet(char* zenityList, nfdpathset_t *pathSet ) +{ + assert(zenityList); + assert(pathSet); + + size_t len = strlen(zenityList) + 1; + pathSet->buf = NFDi_Malloc(len); + + int numEntries = 1; + + for(size_t i = 0; i < len; i++) + { + char ch = zenityList[i]; + + if(ch == '|') + { + numEntries++; + ch = '\0'; + } + + pathSet->buf[i] = ch; + } + + pathSet->count = numEntries; + assert( pathSet->count > 0 ); + + pathSet->indices = NFDi_Malloc( sizeof(size_t)*pathSet->count ); + + int entry = 0; + pathSet->indices[0] = 0; + for(size_t i = 0; i < len; i++) + { + char ch = zenityList[i]; + + if(ch == '|') + { + entry++; + pathSet->indices[entry] = i + 1; + } + } + + return NFD_OKAY; +} + +/* public */ + +nfdresult_t NFD_OpenDialog( const char *filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath ) +{ + int commandLen = 100; + char* command[commandLen]; + memset(command, 0, commandLen * sizeof(char*)); + + command[0] = strdup("zenity"); + command[1] = strdup("--file-selection"); + command[2] = strdup("--title=Open File"); + + char* stdOut = NULL; + nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut); + + if(stdOut != NULL) + { + size_t len = strlen(stdOut); + *outPath = NFDi_Malloc(len); + memcpy(*outPath, stdOut, len); + (*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator + free(stdOut); + } + else + { + *outPath = NULL; + } + + return result; +} + + +nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList, + const nfdchar_t *defaultPath, + nfdpathset_t *outPaths ) +{ + int commandLen = 100; + char* command[commandLen]; + memset(command, 0, commandLen * sizeof(char*)); + + command[0] = strdup("zenity"); + command[1] = strdup("--file-selection"); + command[2] = strdup("--title=Open Files"); + command[3] = strdup("--multiple"); + + char* stdOut = NULL; + nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut); + + if(stdOut != NULL) + { + size_t len = strlen(stdOut); + stdOut[len-1] = '\0'; // remove trailing newline + + if ( AllocPathSet( stdOut, outPaths ) == NFD_ERROR ) + result = NFD_ERROR; + + free(stdOut); + } + else + { + result = NFD_ERROR; + } + + return result; +} + +nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath ) +{ + int commandLen = 100; + char* command[commandLen]; + memset(command, 0, commandLen * sizeof(char*)); + + command[0] = strdup("zenity"); + command[1] = strdup("--file-selection"); + command[2] = strdup("--title=Save File"); + command[3] = strdup("--save"); + + char* stdOut = NULL; + nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut); + + if(stdOut != NULL) + { + size_t len = strlen(stdOut); + *outPath = NFDi_Malloc(len); + memcpy(*outPath, stdOut, len); + (*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator + free(stdOut); + } + else + { + *outPath = NULL; + } + + return result; +} + +nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath, + nfdchar_t **outPath) +{ + int commandLen = 100; + char* command[commandLen]; + memset(command, 0, commandLen * sizeof(char*)); + + command[0] = strdup("zenity"); + command[1] = strdup("--file-selection"); + command[2] = strdup("--directory"); + command[3] = strdup("--title=Select folder"); + + char* stdOut = NULL; + nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, "", &stdOut); + + if(stdOut != NULL) + { + size_t len = strlen(stdOut); + *outPath = NFDi_Malloc(len); + memcpy(*outPath, stdOut, len); + (*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator + free(stdOut); + } + else + { + *outPath = NULL; + } + + return result; +} diff --git a/extern/nfd-modified/src/simple_exec.h b/extern/nfd-modified/src/simple_exec.h new file mode 100644 index 000000000..6308dfe0f --- /dev/null +++ b/extern/nfd-modified/src/simple_exec.h @@ -0,0 +1,218 @@ +// copied from: https://github.com/wheybags/simple_exec/blob/5a74c507c4ce1b2bb166177ead4cca7cfa23cb35/simple_exec.h + +// simple_exec.h, single header library to run external programs + retrieve their status code and output (unix only for now) +// +// do this: +// #define SIMPLE_EXEC_IMPLEMENTATION +// before you include this file in *one* C or C++ file to create the implementation. +// i.e. it should look like this: +// #define SIMPLE_EXEC_IMPLEMENTATION +// #include "simple_exec.h" + +#ifndef SIMPLE_EXEC_H +#define SIMPLE_EXEC_H + +int runCommand(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* command, ...); +int runCommandArray(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* const* allArgs); + +#endif // SIMPLE_EXEC_H + +#ifdef SIMPLE_EXEC_IMPLEMENTATION + +#include +#include +#include +#include +#include +#include +#include +#include + +#define release_assert(exp) { if (!(exp)) { abort(); } } + +enum PIPE_FILE_DESCRIPTORS +{ + READ_FD = 0, + WRITE_FD = 1 +}; + +enum RUN_COMMAND_ERROR +{ + COMMAND_RAN_OK = 0, + COMMAND_NOT_FOUND = 1 +}; + +int runCommandArray(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* const* allArgs) +{ + // adapted from: https://stackoverflow.com/a/479103 + + int bufferSize = 256; + char buffer[bufferSize + 1]; + + int dataReadFromChildDefaultSize = bufferSize * 5; + int dataReadFromChildSize = dataReadFromChildDefaultSize; + int dataReadFromChildUsed = 0; + char* dataReadFromChild = (char*)malloc(dataReadFromChildSize); + + + int parentToChild[2]; + release_assert(pipe(parentToChild) == 0); + + int childToParent[2]; + release_assert(pipe(childToParent) == 0); + + int errPipe[2]; + release_assert(pipe(errPipe) == 0); + + pid_t pid; + switch( pid = fork() ) + { + case -1: + { + release_assert(0 && "Fork failed"); + break; + } + + case 0: // child + { + release_assert(dup2(parentToChild[READ_FD ], STDIN_FILENO ) != -1); + release_assert(dup2(childToParent[WRITE_FD], STDOUT_FILENO) != -1); + + if(includeStdErr) + { + release_assert(dup2(childToParent[WRITE_FD], STDERR_FILENO) != -1); + } + else + { + int devNull = open("/dev/null", O_WRONLY); + release_assert(dup2(devNull, STDERR_FILENO) != -1); + } + + // unused + release_assert(close(parentToChild[WRITE_FD]) == 0); + release_assert(close(childToParent[READ_FD ]) == 0); + release_assert(close(errPipe[READ_FD]) == 0); + + const char* command = allArgs[0]; + execvp(command, allArgs); + + char err = 1; + ssize_t result = write(errPipe[WRITE_FD], &err, 1); + release_assert(result != -1); + + close(errPipe[WRITE_FD]); + close(parentToChild[READ_FD]); + close(childToParent[WRITE_FD]); + + exit(0); + } + + + default: // parent + { + // unused + release_assert(close(parentToChild[READ_FD]) == 0); + release_assert(close(childToParent[WRITE_FD]) == 0); + release_assert(close(errPipe[WRITE_FD]) == 0); + + while(1) + { + ssize_t bytesRead = 0; + switch(bytesRead = read(childToParent[READ_FD], buffer, bufferSize)) + { + case 0: // End-of-File, or non-blocking read. + { + int status = 0; + release_assert(waitpid(pid, &status, 0) == pid); + + // done with these now + release_assert(close(parentToChild[WRITE_FD]) == 0); + release_assert(close(childToParent[READ_FD]) == 0); + + char errChar = 0; + ssize_t result = read(errPipe[READ_FD], &errChar, 1); + release_assert(result != -1); + close(errPipe[READ_FD]); + + if(errChar) + { + free(dataReadFromChild); + return COMMAND_NOT_FOUND; + } + + // free any un-needed memory with realloc + add a null terminator for convenience + dataReadFromChild = (char*)realloc(dataReadFromChild, dataReadFromChildUsed + 1); + dataReadFromChild[dataReadFromChildUsed] = '\0'; + + if(stdOut != NULL) + *stdOut = dataReadFromChild; + else + free(dataReadFromChild); + + if(stdOutByteCount != NULL) + *stdOutByteCount = dataReadFromChildUsed; + if(returnCode != NULL) + *returnCode = WEXITSTATUS(status); + + return COMMAND_RAN_OK; + } + case -1: + { + release_assert(0 && "read() failed"); + break; + } + + default: + { + if(dataReadFromChildUsed + bytesRead + 1 >= dataReadFromChildSize) + { + dataReadFromChildSize += dataReadFromChildDefaultSize; + dataReadFromChild = (char*)realloc(dataReadFromChild, dataReadFromChildSize); + } + + memcpy(dataReadFromChild + dataReadFromChildUsed, buffer, bytesRead); + dataReadFromChildUsed += bytesRead; + break; + } + } + } + } + } +} + +int runCommand(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* command, ...) +{ + va_list vl; + va_start(vl, command); + + char* currArg = NULL; + + int allArgsInitialSize = 16; + int allArgsSize = allArgsInitialSize; + char** allArgs = (char**)malloc(sizeof(char*) * allArgsSize); + allArgs[0] = command; + + int i = 1; + do + { + currArg = va_arg(vl, char*); + allArgs[i] = currArg; + + i++; + + if(i >= allArgsSize) + { + allArgsSize += allArgsInitialSize; + allArgs = (char**)realloc(allArgs, sizeof(char*) * allArgsSize); + } + + } while(currArg != NULL); + + va_end(vl); + + int retval = runCommandArray(stdOut, stdOutByteCount, returnCode, includeStdErr, allArgs); + free(allArgs); + return retval; +} + +#endif //SIMPLE_EXEC_IMPLEMENTATION diff --git a/extern/nfd-modified/test/test_opendialog.c b/extern/nfd-modified/test/test_opendialog.c new file mode 100644 index 000000000..54bf37423 --- /dev/null +++ b/extern/nfd-modified/test/test_opendialog.c @@ -0,0 +1,29 @@ +#include "nfd.h" + +#include +#include + + +/* this test should compile on all supported platforms */ + +int main( void ) +{ + nfdchar_t *outPath = NULL; + nfdresult_t result = NFD_OpenDialog( "png,jpg;pdf", NULL, &outPath ); + if ( result == NFD_OKAY ) + { + puts("Success!"); + puts(outPath); + free(outPath); + } + else if ( result == NFD_CANCEL ) + { + puts("User pressed cancel."); + } + else + { + printf("Error: %s\n", NFD_GetError() ); + } + + return 0; +} diff --git a/extern/nfd-modified/test/test_opendialogmultiple.c b/extern/nfd-modified/test/test_opendialogmultiple.c new file mode 100644 index 000000000..45db6b65e --- /dev/null +++ b/extern/nfd-modified/test/test_opendialogmultiple.c @@ -0,0 +1,32 @@ +#include "nfd.h" + +#include +#include + +/* this test should compile on all supported platforms */ + +int main( void ) +{ + nfdpathset_t pathSet; + nfdresult_t result = NFD_OpenDialogMultiple( "png,jpg;pdf", NULL, &pathSet ); + if ( result == NFD_OKAY ) + { + size_t i; + for ( i = 0; i < NFD_PathSet_GetCount(&pathSet); ++i ) + { + nfdchar_t *path = NFD_PathSet_GetPath(&pathSet, i); + printf("Path %i: %s\n", (int)i, path ); + } + NFD_PathSet_Free(&pathSet); + } + else if ( result == NFD_CANCEL ) + { + puts("User pressed cancel."); + } + else + { + printf("Error: %s\n", NFD_GetError() ); + } + + return 0; +} diff --git a/extern/nfd-modified/test/test_pickfolder.c b/extern/nfd-modified/test/test_pickfolder.c new file mode 100644 index 000000000..708fee859 --- /dev/null +++ b/extern/nfd-modified/test/test_pickfolder.c @@ -0,0 +1,29 @@ +#include "nfd.h" + +#include +#include + + +/* this test should compile on all supported platforms */ + +int main( void ) +{ + nfdchar_t *outPath = NULL; + nfdresult_t result = NFD_PickFolder( NULL, &outPath ); + if ( result == NFD_OKAY ) + { + puts("Success!"); + puts(outPath); + free(outPath); + } + else if ( result == NFD_CANCEL ) + { + puts("User pressed cancel."); + } + else + { + printf("Error: %s\n", NFD_GetError() ); + } + + return 0; +} diff --git a/extern/nfd-modified/test/test_savedialog.c b/extern/nfd-modified/test/test_savedialog.c new file mode 100644 index 000000000..7ec37f089 --- /dev/null +++ b/extern/nfd-modified/test/test_savedialog.c @@ -0,0 +1,28 @@ +#include "nfd.h" + +#include +#include + +/* this test should compile on all supported platforms */ + +int main( void ) +{ + nfdchar_t *savePath = NULL; + nfdresult_t result = NFD_SaveDialog( "png,jpg;pdf", NULL, &savePath ); + if ( result == NFD_OKAY ) + { + puts("Success!"); + puts(savePath); + free(savePath); + } + else if ( result == NFD_CANCEL ) + { + puts("User pressed cancel."); + } + else + { + printf("Error: %s\n", NFD_GetError() ); + } + + return 0; +} diff --git a/instruments/FM/FM_4OP_-_Tenor_Sax.fui b/instruments/FM/FM_4OP_-_Tenor_Sax.fui new file mode 100644 index 000000000..be9f5eeb5 Binary files /dev/null and b/instruments/FM/FM_4OP_-_Tenor_Sax.fui differ diff --git a/instruments/FM/GEN_weird.fui b/instruments/FM/GEN_weird.fui new file mode 100644 index 000000000..1e5bc1cdf Binary files /dev/null and b/instruments/FM/GEN_weird.fui differ diff --git a/instruments/FM/OPNx_BottleBlow.fui b/instruments/FM/OPNx_BottleBlow.fui new file mode 100644 index 000000000..d4923ccf2 Binary files /dev/null and b/instruments/FM/OPNx_BottleBlow.fui differ diff --git a/instruments/FM/Octave_Square.fui b/instruments/FM/Octave_Square.fui new file mode 100644 index 000000000..446ad1219 Binary files /dev/null and b/instruments/FM/Octave_Square.fui differ diff --git a/instruments/FM/Octave_Square2.fui b/instruments/FM/Octave_Square2.fui new file mode 100644 index 000000000..f6fcea32d Binary files /dev/null and b/instruments/FM/Octave_Square2.fui differ diff --git a/instruments/FM/Reverby_Live_Chords.fui b/instruments/FM/Reverby_Live_Chords.fui new file mode 100644 index 000000000..cd2f960c3 Binary files /dev/null and b/instruments/FM/Reverby_Live_Chords.fui differ diff --git a/instruments/FM/Square.fui b/instruments/FM/Square.fui new file mode 100644 index 000000000..cf5d47bfc Binary files /dev/null and b/instruments/FM/Square.fui differ diff --git a/instruments/FM/bass/4-OP_Vowel_Bass.fui b/instruments/FM/bass/4-OP_Vowel_Bass.fui new file mode 100644 index 000000000..b45a50b12 Binary files /dev/null and b/instruments/FM/bass/4-OP_Vowel_Bass.fui differ diff --git a/instruments/FM/bass/4_OP_Funky_Bass.fui b/instruments/FM/bass/4_OP_Funky_Bass.fui new file mode 100644 index 000000000..afc221557 Binary files /dev/null and b/instruments/FM/bass/4_OP_Funky_Bass.fui differ diff --git a/instruments/FM/bass/Bass.fui b/instruments/FM/bass/Bass.fui new file mode 100644 index 000000000..147b43e76 Binary files /dev/null and b/instruments/FM/bass/Bass.fui differ diff --git a/instruments/FM/bass/FM_CriminalBass.fui b/instruments/FM/bass/FM_CriminalBass.fui new file mode 100644 index 000000000..35705d22a Binary files /dev/null and b/instruments/FM/bass/FM_CriminalBass.fui differ diff --git a/instruments/FM/bass/Heavy_synth_bass.fui b/instruments/FM/bass/Heavy_synth_bass.fui new file mode 100644 index 000000000..d1ba78903 Binary files /dev/null and b/instruments/FM/bass/Heavy_synth_bass.fui differ diff --git a/instruments/FM/bass/House_Bass.fui b/instruments/FM/bass/House_Bass.fui new file mode 100644 index 000000000..51676d265 Binary files /dev/null and b/instruments/FM/bass/House_Bass.fui differ diff --git a/instruments/FM/bass/OPNx_FingerBass.fui b/instruments/FM/bass/OPNx_FingerBass.fui new file mode 100644 index 000000000..a9f712619 Binary files /dev/null and b/instruments/FM/bass/OPNx_FingerBass.fui differ diff --git a/instruments/FM/bass/OPNx_PickBass.fui b/instruments/FM/bass/OPNx_PickBass.fui new file mode 100644 index 000000000..03914c428 Binary files /dev/null and b/instruments/FM/bass/OPNx_PickBass.fui differ diff --git a/instruments/FM/bass/OPZ-Fake_Famicom_Tri.fui b/instruments/FM/bass/OPZ-Fake_Famicom_Tri.fui new file mode 100644 index 000000000..16330fd18 Binary files /dev/null and b/instruments/FM/bass/OPZ-Fake_Famicom_Tri.fui differ diff --git a/instruments/FM/bass/PickMD.fui b/instruments/FM/bass/PickMD.fui new file mode 100644 index 000000000..094c805a1 Binary files /dev/null and b/instruments/FM/bass/PickMD.fui differ diff --git a/instruments/FM/bass/bass1.fui b/instruments/FM/bass/bass1.fui new file mode 100644 index 000000000..e4b6c127c Binary files /dev/null and b/instruments/FM/bass/bass1.fui differ diff --git a/instruments/FM/bass/bass2.fui b/instruments/FM/bass/bass2.fui new file mode 100644 index 000000000..5957d801f Binary files /dev/null and b/instruments/FM/bass/bass2.fui differ diff --git a/instruments/FM/bass/bass3.fui b/instruments/FM/bass/bass3.fui new file mode 100644 index 000000000..2466f4335 Binary files /dev/null and b/instruments/FM/bass/bass3.fui differ diff --git a/instruments/FM/bass/bass4.fui b/instruments/FM/bass/bass4.fui new file mode 100644 index 000000000..ff36a2dc0 Binary files /dev/null and b/instruments/FM/bass/bass4.fui differ diff --git a/instruments/FM/bass/bass5.fui b/instruments/FM/bass/bass5.fui new file mode 100644 index 000000000..36e1641fd Binary files /dev/null and b/instruments/FM/bass/bass5.fui differ diff --git a/instruments/FM/bass/bass6.fui b/instruments/FM/bass/bass6.fui new file mode 100644 index 000000000..093af97c9 Binary files /dev/null and b/instruments/FM/bass/bass6.fui differ diff --git a/instruments/FM/bass/opm_nestri.fui b/instruments/FM/bass/opm_nestri.fui new file mode 100644 index 000000000..2b0ac7414 Binary files /dev/null and b/instruments/FM/bass/opm_nestri.fui differ diff --git a/instruments/FM/bass/tarylbass1.fui b/instruments/FM/bass/tarylbass1.fui new file mode 100644 index 000000000..f3b592131 Binary files /dev/null and b/instruments/FM/bass/tarylbass1.fui differ diff --git a/instruments/FM/default.fui b/instruments/FM/default.fui new file mode 100644 index 000000000..6b8360d4b Binary files /dev/null and b/instruments/FM/default.fui differ diff --git a/instruments/FM/drums/Drum.fui b/instruments/FM/drums/Drum.fui new file mode 100644 index 000000000..fdc6b4fdd Binary files /dev/null and b/instruments/FM/drums/Drum.fui differ diff --git a/instruments/FM/drums/fmbigkick.fui b/instruments/FM/drums/fmbigkick.fui new file mode 100644 index 000000000..1166f2b03 Binary files /dev/null and b/instruments/FM/drums/fmbigkick.fui differ diff --git a/instruments/FM/drums/fmclap.fui b/instruments/FM/drums/fmclap.fui new file mode 100644 index 000000000..be5820016 Binary files /dev/null and b/instruments/FM/drums/fmclap.fui differ diff --git a/instruments/FM/drums/fmclap2.fui b/instruments/FM/drums/fmclap2.fui new file mode 100644 index 000000000..5b399793d Binary files /dev/null and b/instruments/FM/drums/fmclap2.fui differ diff --git a/instruments/FM/drums/fmhat.fui b/instruments/FM/drums/fmhat.fui new file mode 100644 index 000000000..f26b7fe46 Binary files /dev/null and b/instruments/FM/drums/fmhat.fui differ diff --git a/instruments/FM/drums/fmopenhat.fui b/instruments/FM/drums/fmopenhat.fui new file mode 100644 index 000000000..a5198fc03 Binary files /dev/null and b/instruments/FM/drums/fmopenhat.fui differ diff --git a/instruments/FM/drums/idk_something_percussion.fui b/instruments/FM/drums/idk_something_percussion.fui new file mode 100644 index 000000000..13380460a Binary files /dev/null and b/instruments/FM/drums/idk_something_percussion.fui differ diff --git a/instruments/FM/drums/powersnare.fui b/instruments/FM/drums/powersnare.fui new file mode 100644 index 000000000..d46eb7f26 Binary files /dev/null and b/instruments/FM/drums/powersnare.fui differ diff --git a/instruments/FM/drums/snareclap_G-2.fui b/instruments/FM/drums/snareclap_G-2.fui new file mode 100644 index 000000000..8455de324 Binary files /dev/null and b/instruments/FM/drums/snareclap_G-2.fui differ diff --git a/instruments/FM/growl.fui b/instruments/FM/growl.fui new file mode 100644 index 000000000..9be412ad8 Binary files /dev/null and b/instruments/FM/growl.fui differ diff --git a/instruments/FM/guitar/OPNx_ElectricGuitar.fui b/instruments/FM/guitar/OPNx_ElectricGuitar.fui new file mode 100644 index 000000000..8916da675 Binary files /dev/null and b/instruments/FM/guitar/OPNx_ElectricGuitar.fui differ diff --git a/instruments/FM/guitar/YM2151_DistGuitar.fui b/instruments/FM/guitar/YM2151_DistGuitar.fui new file mode 100644 index 000000000..983328c62 Binary files /dev/null and b/instruments/FM/guitar/YM2151_DistGuitar.fui differ diff --git a/instruments/FM/horn/GEN_Tuba.fui b/instruments/FM/horn/GEN_Tuba.fui new file mode 100644 index 000000000..b7885277d Binary files /dev/null and b/instruments/FM/horn/GEN_Tuba.fui differ diff --git a/instruments/FM/horn/OPNx_SynthBrass.fui b/instruments/FM/horn/OPNx_SynthBrass.fui new file mode 100644 index 000000000..900660133 Binary files /dev/null and b/instruments/FM/horn/OPNx_SynthBrass.fui differ diff --git a/instruments/FM/keys/ElisOrgan.fui b/instruments/FM/keys/ElisOrgan.fui new file mode 100644 index 000000000..82bc77aa9 Binary files /dev/null and b/instruments/FM/keys/ElisOrgan.fui differ diff --git a/instruments/FM/keys/OPNx_Piano.fui b/instruments/FM/keys/OPNx_Piano.fui new file mode 100644 index 000000000..1db80df88 Binary files /dev/null and b/instruments/FM/keys/OPNx_Piano.fui differ diff --git a/instruments/FM/keys/PianoMD.fui b/instruments/FM/keys/PianoMD.fui new file mode 100644 index 000000000..3c2c9e922 Binary files /dev/null and b/instruments/FM/keys/PianoMD.fui differ diff --git a/instruments/FM/keys/YM2151_E-PIANO.fui b/instruments/FM/keys/YM2151_E-PIANO.fui new file mode 100644 index 000000000..5db164fdd Binary files /dev/null and b/instruments/FM/keys/YM2151_E-PIANO.fui differ diff --git a/instruments/FM/keys/fmpiano.fui b/instruments/FM/keys/fmpiano.fui new file mode 100644 index 000000000..5de7ead0f Binary files /dev/null and b/instruments/FM/keys/fmpiano.fui differ diff --git a/instruments/FM/percussion/BellMD.fui b/instruments/FM/percussion/BellMD.fui new file mode 100644 index 000000000..8d94d85ef Binary files /dev/null and b/instruments/FM/percussion/BellMD.fui differ diff --git a/instruments/FM/percussion/ChurchBell.fui b/instruments/FM/percussion/ChurchBell.fui new file mode 100644 index 000000000..85a187b18 Binary files /dev/null and b/instruments/FM/percussion/ChurchBell.fui differ diff --git a/instruments/FM/percussion/Marimba.fui b/instruments/FM/percussion/Marimba.fui new file mode 100644 index 000000000..faedbf8c6 Binary files /dev/null and b/instruments/FM/percussion/Marimba.fui differ diff --git a/instruments/FM/percussion/MusicBox.fui b/instruments/FM/percussion/MusicBox.fui new file mode 100644 index 000000000..53512b99a Binary files /dev/null and b/instruments/FM/percussion/MusicBox.fui differ diff --git a/instruments/FM/percussion/OPN_-_Music_Box.fui b/instruments/FM/percussion/OPN_-_Music_Box.fui new file mode 100644 index 000000000..7bcda34fb Binary files /dev/null and b/instruments/FM/percussion/OPN_-_Music_Box.fui differ diff --git a/instruments/FM/percussion/Rainbow_Bell.fui b/instruments/FM/percussion/Rainbow_Bell.fui new file mode 100644 index 000000000..7c2d7aa3e Binary files /dev/null and b/instruments/FM/percussion/Rainbow_Bell.fui differ diff --git a/instruments/FM/percussion/octave_8_bell.fui b/instruments/FM/percussion/octave_8_bell.fui new file mode 100644 index 000000000..4dc80b630 Binary files /dev/null and b/instruments/FM/percussion/octave_8_bell.fui differ diff --git a/instruments/FM/powerchord.fui b/instruments/FM/powerchord.fui new file mode 100644 index 000000000..40e8ba34c Binary files /dev/null and b/instruments/FM/powerchord.fui differ diff --git a/instruments/FM/strings/OPNx_5thsPad.fui b/instruments/FM/strings/OPNx_5thsPad.fui new file mode 100644 index 000000000..ccc1afbe2 Binary files /dev/null and b/instruments/FM/strings/OPNx_5thsPad.fui differ diff --git a/instruments/FM/strings/OPNx_String.fui b/instruments/FM/strings/OPNx_String.fui new file mode 100644 index 000000000..63a140968 Binary files /dev/null and b/instruments/FM/strings/OPNx_String.fui differ diff --git a/instruments/FM/strings/Violin.fui b/instruments/FM/strings/Violin.fui new file mode 100644 index 000000000..2a560518c Binary files /dev/null and b/instruments/FM/strings/Violin.fui differ diff --git a/instruments/FM/strings/ViolinMD.fui b/instruments/FM/strings/ViolinMD.fui new file mode 100644 index 000000000..0b8eb5e6b Binary files /dev/null and b/instruments/FM/strings/ViolinMD.fui differ diff --git a/instruments/OPL/4op Bass.fui b/instruments/OPL/4op Bass.fui new file mode 100644 index 000000000..0432505d2 Binary files /dev/null and b/instruments/OPL/4op Bass.fui differ diff --git a/instruments/OPL/Chorus Organ.fui b/instruments/OPL/Chorus Organ.fui new file mode 100644 index 000000000..14a418d00 Binary files /dev/null and b/instruments/OPL/Chorus Organ.fui differ diff --git a/instruments/OPL/Closed Hi-hat.fui b/instruments/OPL/Closed Hi-hat.fui new file mode 100644 index 000000000..262678961 Binary files /dev/null and b/instruments/OPL/Closed Hi-hat.fui differ diff --git a/instruments/OPL/Crystal.fui b/instruments/OPL/Crystal.fui new file mode 100644 index 000000000..490ba4b7c Binary files /dev/null and b/instruments/OPL/Crystal.fui differ diff --git a/instruments/OPL/DX7 Electric Piano.fui b/instruments/OPL/DX7 Electric Piano.fui new file mode 100644 index 000000000..743a1f0fa Binary files /dev/null and b/instruments/OPL/DX7 Electric Piano.fui differ diff --git a/instruments/OPL/Distortion Guitar.fui b/instruments/OPL/Distortion Guitar.fui new file mode 100644 index 000000000..8bbe234a8 Binary files /dev/null and b/instruments/OPL/Distortion Guitar.fui differ diff --git a/instruments/OPL/Guitar 2.fui b/instruments/OPL/Guitar 2.fui new file mode 100644 index 000000000..78b590cca Binary files /dev/null and b/instruments/OPL/Guitar 2.fui differ diff --git a/instruments/OPL/Guitar.fui b/instruments/OPL/Guitar.fui new file mode 100644 index 000000000..09e0c4231 Binary files /dev/null and b/instruments/OPL/Guitar.fui differ diff --git a/instruments/OPL/OPL1_Ride_Closed.fui b/instruments/OPL/OPL1_Ride_Closed.fui new file mode 100644 index 000000000..3e9c139b8 Binary files /dev/null and b/instruments/OPL/OPL1_Ride_Closed.fui differ diff --git a/instruments/OPL/OPL3-FakeFalcomGuitar.fui b/instruments/OPL/OPL3-FakeFalcomGuitar.fui new file mode 100644 index 000000000..f18286ee4 Binary files /dev/null and b/instruments/OPL/OPL3-FakeFalcomGuitar.fui differ diff --git a/instruments/OPL/OPL3-MajorSquare.fui b/instruments/OPL/OPL3-MajorSquare.fui new file mode 100644 index 000000000..e5beed0fc Binary files /dev/null and b/instruments/OPL/OPL3-MajorSquare.fui differ diff --git a/instruments/OPL/OPL3_-_C64_like_sqr_snare.fui b/instruments/OPL/OPL3_-_C64_like_sqr_snare.fui new file mode 100644 index 000000000..19d85f5a9 Binary files /dev/null and b/instruments/OPL/OPL3_-_C64_like_sqr_snare.fui differ diff --git a/instruments/OPL/OPL3_-_PWM_Lead.fui b/instruments/OPL/OPL3_-_PWM_Lead.fui new file mode 100644 index 000000000..b4bdf2f2a Binary files /dev/null and b/instruments/OPL/OPL3_-_PWM_Lead.fui differ diff --git a/instruments/OPL/OPL3_C64_Kick_or_Snare_Start.fui b/instruments/OPL/OPL3_C64_Kick_or_Snare_Start.fui new file mode 100644 index 000000000..f3f771539 Binary files /dev/null and b/instruments/OPL/OPL3_C64_Kick_or_Snare_Start.fui differ diff --git a/instruments/OPL/OPL3_C64_Snare_Carry.fui b/instruments/OPL/OPL3_C64_Snare_Carry.fui new file mode 100644 index 000000000..e0c755a3f Binary files /dev/null and b/instruments/OPL/OPL3_C64_Snare_Carry.fui differ diff --git a/instruments/OPL/OPL3_Slap_Bass.fui b/instruments/OPL/OPL3_Slap_Bass.fui new file mode 100644 index 000000000..4de8aac82 Binary files /dev/null and b/instruments/OPL/OPL3_Slap_Bass.fui differ diff --git a/instruments/OPL/OPL_horn.fui b/instruments/OPL/OPL_horn.fui new file mode 100644 index 000000000..38313a62a Binary files /dev/null and b/instruments/OPL/OPL_horn.fui differ diff --git a/instruments/OPL/Open Hi-hat.fui b/instruments/OPL/Open Hi-hat.fui new file mode 100644 index 000000000..22b18f8d6 Binary files /dev/null and b/instruments/OPL/Open Hi-hat.fui differ diff --git a/instruments/OPL/Overdriven Guitar.fui b/instruments/OPL/Overdriven Guitar.fui new file mode 100644 index 000000000..20330f7a5 Binary files /dev/null and b/instruments/OPL/Overdriven Guitar.fui differ diff --git a/instruments/OPL/Snare Drum 2.fui b/instruments/OPL/Snare Drum 2.fui new file mode 100644 index 000000000..bdb3ed9a4 Binary files /dev/null and b/instruments/OPL/Snare Drum 2.fui differ diff --git a/instruments/OPL/Snare Drum.fui b/instruments/OPL/Snare Drum.fui new file mode 100644 index 000000000..470a78dfc Binary files /dev/null and b/instruments/OPL/Snare Drum.fui differ diff --git a/instruments/OPL/Synth Brass.fui b/instruments/OPL/Synth Brass.fui new file mode 100644 index 000000000..0bb751178 Binary files /dev/null and b/instruments/OPL/Synth Brass.fui differ diff --git a/instruments/OPL/Synth Guitar.fui b/instruments/OPL/Synth Guitar.fui new file mode 100644 index 000000000..0c7f7a400 Binary files /dev/null and b/instruments/OPL/Synth Guitar.fui differ diff --git a/instruments/OPL/Violin.fui b/instruments/OPL/Violin.fui new file mode 100644 index 000000000..f84bd6c47 Binary files /dev/null and b/instruments/OPL/Violin.fui differ diff --git a/instruments/OPL/key.fui b/instruments/OPL/key.fui new file mode 100644 index 000000000..976c2c523 Binary files /dev/null and b/instruments/OPL/key.fui differ diff --git a/instruments/OPL/opl3_4op_bass_1.fui b/instruments/OPL/opl3_4op_bass_1.fui new file mode 100644 index 000000000..bdc01cdf1 Binary files /dev/null and b/instruments/OPL/opl3_4op_bass_1.fui differ diff --git a/instruments/OPL/opl3_4op_bass_2.fui b/instruments/OPL/opl3_4op_bass_2.fui new file mode 100644 index 000000000..555cf369b Binary files /dev/null and b/instruments/OPL/opl3_4op_bass_2.fui differ diff --git a/instruments/OPL/opl3_4op_bass_3.fui b/instruments/OPL/opl3_4op_bass_3.fui new file mode 100644 index 000000000..2e8c8962e Binary files /dev/null and b/instruments/OPL/opl3_4op_bass_3.fui differ diff --git a/instruments/OPL/opl3_4op_drum_kick.fui b/instruments/OPL/opl3_4op_drum_kick.fui new file mode 100644 index 000000000..ed89055b4 Binary files /dev/null and b/instruments/OPL/opl3_4op_drum_kick.fui differ diff --git a/instruments/OPL/opl3_4op_drum_power_snare.fui b/instruments/OPL/opl3_4op_drum_power_snare.fui new file mode 100644 index 000000000..b351cf345 Binary files /dev/null and b/instruments/OPL/opl3_4op_drum_power_snare.fui differ diff --git a/instruments/OPL/opl3_4op_drum_snare.fui b/instruments/OPL/opl3_4op_drum_snare.fui new file mode 100644 index 000000000..7b3dac717 Binary files /dev/null and b/instruments/OPL/opl3_4op_drum_snare.fui differ diff --git a/instruments/OPL/opl3_4op_drum_tom.fui b/instruments/OPL/opl3_4op_drum_tom.fui new file mode 100644 index 000000000..437facdc1 Binary files /dev/null and b/instruments/OPL/opl3_4op_drum_tom.fui differ diff --git a/instruments/OPL/opl3_4op_growl_synth.fui b/instruments/OPL/opl3_4op_growl_synth.fui new file mode 100644 index 000000000..c91745b26 Binary files /dev/null and b/instruments/OPL/opl3_4op_growl_synth.fui differ diff --git a/instruments/OPL/opl3_4op_lead_guitar.fui b/instruments/OPL/opl3_4op_lead_guitar.fui new file mode 100644 index 000000000..fa3902769 Binary files /dev/null and b/instruments/OPL/opl3_4op_lead_guitar.fui differ diff --git a/instruments/OPL/opl3_4op_piano_bell.fui b/instruments/OPL/opl3_4op_piano_bell.fui new file mode 100644 index 000000000..5157820a9 Binary files /dev/null and b/instruments/OPL/opl3_4op_piano_bell.fui differ diff --git a/instruments/OPL/opl3_4op_rhythm_guitar_1.fui b/instruments/OPL/opl3_4op_rhythm_guitar_1.fui new file mode 100644 index 000000000..38f02327b Binary files /dev/null and b/instruments/OPL/opl3_4op_rhythm_guitar_1.fui differ diff --git a/instruments/OPL/opl3_4op_rhythm_guitar_2.fui b/instruments/OPL/opl3_4op_rhythm_guitar_2.fui new file mode 100644 index 000000000..97c232887 Binary files /dev/null and b/instruments/OPL/opl3_4op_rhythm_guitar_2.fui differ diff --git a/instruments/OPL/opl3_4op_strings.fui b/instruments/OPL/opl3_4op_strings.fui new file mode 100644 index 000000000..d4f8450fa Binary files /dev/null and b/instruments/OPL/opl3_4op_strings.fui differ diff --git a/instruments/OPLL/Weeppiko-piano_guitar_idk.fui b/instruments/OPLL/Weeppiko-piano_guitar_idk.fui new file mode 100644 index 000000000..1db0556cd Binary files /dev/null and b/instruments/OPLL/Weeppiko-piano_guitar_idk.fui differ diff --git a/instruments/README.md b/instruments/README.md new file mode 100644 index 000000000..fdfae2f3a --- /dev/null +++ b/instruments/README.md @@ -0,0 +1,18 @@ +# instruments + +a directory of ready-to-use instruments. +these are organized in the following categories: + +- **FM**: FM instruments for OPN/OPM. +- **OPL**: FM instruments for OPL. +- **OPLL**: FM instruments for OPLL. +- **other**: instruments for any other system. + +# submit instruments + +you may submit your own instruments by creating a pull request or contacting me! the only guidelines are: + +- any format Furnace is able to load is accepted. +- I (strongly) prefer original instruments. + +thanks for your contribution! diff --git a/instruments/other/C64_Snare.fui b/instruments/other/C64_Snare.fui new file mode 100644 index 000000000..28f1a2e73 Binary files /dev/null and b/instruments/other/C64_Snare.fui differ diff --git a/instruments/other/Castanets.fui b/instruments/other/Castanets.fui new file mode 100644 index 000000000..a81f93e14 Binary files /dev/null and b/instruments/other/Castanets.fui differ diff --git a/instruments/other/Closed_Hi-hat.fui b/instruments/other/Closed_Hi-hat.fui new file mode 100644 index 000000000..20cc2fced Binary files /dev/null and b/instruments/other/Closed_Hi-hat.fui differ diff --git a/instruments/other/FDS-oneofthosejapaneseguitarsidkhowtheyarecalledyeah.fui b/instruments/other/FDS-oneofthosejapaneseguitarsidkhowtheyarecalledyeah.fui new file mode 100644 index 000000000..3dc7798a1 Binary files /dev/null and b/instruments/other/FDS-oneofthosejapaneseguitarsidkhowtheyarecalledyeah.fui differ diff --git a/instruments/other/FDS-overfuckingholyshitdriven-guitar.fui b/instruments/other/FDS-overfuckingholyshitdriven-guitar.fui new file mode 100644 index 000000000..eb2f81665 Binary files /dev/null and b/instruments/other/FDS-overfuckingholyshitdriven-guitar.fui differ diff --git a/instruments/other/FDS-slapassbass.fui b/instruments/other/FDS-slapassbass.fui new file mode 100644 index 000000000..058a83265 Binary files /dev/null and b/instruments/other/FDS-slapassbass.fui differ diff --git a/instruments/other/GBkick.fui b/instruments/other/GBkick.fui new file mode 100644 index 000000000..d669277a5 Binary files /dev/null and b/instruments/other/GBkick.fui differ diff --git a/instruments/other/GBsnare.fui b/instruments/other/GBsnare.fui new file mode 100644 index 000000000..4731ea6b1 Binary files /dev/null and b/instruments/other/GBsnare.fui differ diff --git a/instruments/other/Gunshot.fui b/instruments/other/Gunshot.fui new file mode 100644 index 000000000..09cb8fb8b Binary files /dev/null and b/instruments/other/Gunshot.fui differ diff --git a/instruments/other/NES_0-2-5_arp_lead.fui b/instruments/other/NES_0-2-5_arp_lead.fui new file mode 100644 index 000000000..493a167d1 Binary files /dev/null and b/instruments/other/NES_0-2-5_arp_lead.fui differ diff --git a/instruments/other/NES_0-3-5_arp_lead.fui b/instruments/other/NES_0-3-5_arp_lead.fui new file mode 100644 index 000000000..05a43de2f Binary files /dev/null and b/instruments/other/NES_0-3-5_arp_lead.fui differ diff --git a/instruments/other/Open_Hi-hat.fui b/instruments/other/Open_Hi-hat.fui new file mode 100644 index 000000000..6054a5cab Binary files /dev/null and b/instruments/other/Open_Hi-hat.fui differ diff --git a/instruments/other/SMS_0-2-5_arp_lead.fui b/instruments/other/SMS_0-2-5_arp_lead.fui new file mode 100644 index 000000000..c21c2d234 Binary files /dev/null and b/instruments/other/SMS_0-2-5_arp_lead.fui differ diff --git a/instruments/other/SMS_0-3-5_arp_lead.fui b/instruments/other/SMS_0-3-5_arp_lead.fui new file mode 100644 index 000000000..22fe1c3e0 Binary files /dev/null and b/instruments/other/SMS_0-3-5_arp_lead.fui differ diff --git a/instruments/other/Side_Stick.fui b/instruments/other/Side_Stick.fui new file mode 100644 index 000000000..783833d44 Binary files /dev/null and b/instruments/other/Side_Stick.fui differ diff --git a/instruments/other/Supersaw.fui b/instruments/other/Supersaw.fui new file mode 100644 index 000000000..e4747a809 Binary files /dev/null and b/instruments/other/Supersaw.fui differ diff --git a/instruments/other/VERA_0-2-5_arp_lead.fui b/instruments/other/VERA_0-2-5_arp_lead.fui new file mode 100644 index 000000000..9f52221b5 Binary files /dev/null and b/instruments/other/VERA_0-2-5_arp_lead.fui differ diff --git a/instruments/other/VERA_0-3-5_arp_lead.fui b/instruments/other/VERA_0-3-5_arp_lead.fui new file mode 100644 index 000000000..5f5c61947 Binary files /dev/null and b/instruments/other/VERA_0-3-5_arp_lead.fui differ diff --git a/instruments/other/VERA_Noise_Kick.fui b/instruments/other/VERA_Noise_Kick.fui new file mode 100644 index 000000000..2d25ab5a2 Binary files /dev/null and b/instruments/other/VERA_Noise_Kick.fui differ diff --git a/instruments/other/VERA_Noise_Snare.fui b/instruments/other/VERA_Noise_Snare.fui new file mode 100644 index 000000000..015d9a89e Binary files /dev/null and b/instruments/other/VERA_Noise_Snare.fui differ diff --git a/instruments/other/VIC20_kick.fui b/instruments/other/VIC20_kick.fui new file mode 100644 index 000000000..bc49b4626 Binary files /dev/null and b/instruments/other/VIC20_kick.fui differ diff --git a/instruments/other/compatibility.fui b/instruments/other/compatibility.fui new file mode 100644 index 000000000..1682a4249 Binary files /dev/null and b/instruments/other/compatibility.fui differ diff --git a/papers/doc/5-wave/README.md b/papers/doc/5-wave/README.md index 0668c0bf2..dacc88eaf 100644 --- a/papers/doc/5-wave/README.md +++ b/papers/doc/5-wave/README.md @@ -2,4 +2,4 @@ Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy. -Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS, Bubble System and N163, and 32-level height for PCE. If larger wave will be defined for these systems, it will be squashed to fit within the constraints of the system. +Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS, Bubble System and N163, and 32-level height for PCE. If a larger wave is defined for these systems, it will be squashed to fit within the constraints of the system. diff --git a/papers/doc/7-systems/c64.md b/papers/doc/7-systems/c64.md index 1029eda90..9b3e87013 100644 --- a/papers/doc/7-systems/c64.md +++ b/papers/doc/7-systems/c64.md @@ -1,9 +1,11 @@ # Commodore 64 -a home computer with a synthesizer-grade sound chip of which people took decades to master. +a home computer with a synthesizer-grade sound chip of which people took decades to master. Three oscillators with four selectable waveforms, ring modulation, oscillator sync, multimode filter, ADSR envelope... very popular in Europe and mostly due to the demoscene, which stretched the machine's limbs to no end. +Two revisions do exist - 6581 (original chip) and 8580 (improved version with working waveform mixing and somewhat more consistent filter curves) + # effects - `10xx`: change wave. the following values are accepted: diff --git a/papers/doc/7-systems/namco.md b/papers/doc/7-systems/namco.md new file mode 100644 index 000000000..a856d8fb5 --- /dev/null +++ b/papers/doc/7-systems/namco.md @@ -0,0 +1,13 @@ +# Namco WSG | Namco C15 | Namco C30 + +a family of wavetable synth sound chips used by Namco in their arcade machines (Pacman and later). Waveforms are 4-bit, with 32-byte sample length. + +Everything starts with Namco WSG, simple 3ch wavetable with no extra frills. C15 is much more advanced sound source with 8 channels. C30 adds stereo output and noise mode. + +# effects + +-`10xx`: change waveform +-`11xx`: toggle noise mode WARNING: only on C30 + + + diff --git a/papers/doc/7-systems/oki.md b/papers/doc/7-systems/oki.md new file mode 100644 index 000000000..4b46eb21c --- /dev/null +++ b/papers/doc/7-systems/oki.md @@ -0,0 +1,9 @@ +# OKI MSM6258 | OKI MSM6295 + +MSM6258 is a single-channel ADPCM sound source developed by OKI. It allows max sample rate of 15.6 kHz... with no variable pitch. Most prominent use of this chip was Sharp X68000 computer, where it was paired with Yamaha YM2151. + +MSM6295 is an upgrade from 6258 - it provides 4 ADPCM channels, at max 32 kHz (still no variable pitch though). Between late 80s and late 90s, it was one of the most common, if not THE most common soundchip used in arcade machines (Capcom, Toaplan, Kaneko, Atari, Tecmo, the list can go on and on...) + +# effects + +- `20xx`: set chip output rate diff --git a/papers/doc/7-systems/ricoh.md b/papers/doc/7-systems/ricoh.md new file mode 100644 index 000000000..6f5405f2b --- /dev/null +++ b/papers/doc/7-systems/ricoh.md @@ -0,0 +1,8 @@ +# Ricoh RF5C68 + +YM2612's sidekick - poor man's SPC-700. 8ch PCM sample-based synthesizer used in Sega Mega CD, Fujitsu FM Towns and some of Sega's arcade machines. Supports up to 64 Kbytes of external PCM data. + +# effects + +none so far. + diff --git a/papers/doc/7-systems/sms.md b/papers/doc/7-systems/sms.md index 53560a976..882085f16 100644 --- a/papers/doc/7-systems/sms.md +++ b/papers/doc/7-systems/sms.md @@ -6,10 +6,13 @@ surely had better graphics than NES, but its sound (fairly weak, 4ch PSG with A- this console is powered by a derivative of the Texas Instruments SN76489. +the original iteration of the SN76489 used in the TI-99/4A computers was clocked much lower at 447 kHz, being able to play as low as 13.670 Hz (A -1). consequentially, pitch accuracy for higher notes is compromised. + + # effects - `20xy`: set noise mode. - - `x` controls whether to inherit frequency from hannel 3. + - `x` controls whether to inherit frequency from channel 3. - 0: use one of 3 preset frequencies (C: A-2; C#: A-3; D: A-4). - 1: use frequency of channel 3. - `y` controls whether to select noise or thin pulse. diff --git a/papers/doc/7-systems/ym2612.md b/papers/doc/7-systems/ym2612.md index e86dde9da..6b2212017 100644 --- a/papers/doc/7-systems/ym2612.md +++ b/papers/doc/7-systems/ym2612.md @@ -2,6 +2,8 @@ one of two chips that powered the Sega Genesis. It is a six-channel, four-operator FM synthesizer. Channel #6 can be turned into 8-bit PCM player. +For 0.6pre1, Furnace can now support advanced YM2612 features that [Fractal](https://gitlab.com/Natsumi/Fractal-Sound) sound driver adds: two software-mixed PCM channels (variable pitch, sample offsets, max 13.7 khz rate) and CSM - ch3 special mode feature that can be abused to produce rudimentary speech synthesis. + # effects - `10xy`: set LFO parameters. @@ -64,4 +66,4 @@ one of two chips that powered the Sega Genesis. It is a six-channel, four-operat - `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 +- `5Fxx`: set D2R/SR of operator 4. diff --git a/papers/doc/7-systems/ymz280b.md b/papers/doc/7-systems/ymz280b.md new file mode 100644 index 000000000..097fa6a4a --- /dev/null +++ b/papers/doc/7-systems/ymz280b.md @@ -0,0 +1,7 @@ +# Yamaha YMZ280B (PCMD8) + +8ch PCM/ADPCM sample-based synthesizer designed for use with arcade machines. Lived throughout mid to late 90s. It has 16-level stereo panning, up to 16-bit PCM and up to 16Mbytes of external PCM data. + +# effects + +none so far. diff --git a/papers/doc/7-systems/zxbeep.md b/papers/doc/7-systems/zxbeep.md new file mode 100644 index 000000000..1c6cd74f3 --- /dev/null +++ b/papers/doc/7-systems/zxbeep.md @@ -0,0 +1,10 @@ +# ZX Spectrum Speaker + +Rather than having a dedicated sound synthesizer, early ZX Spectrum models had one piezo beeper, controlled by Z80 CPU and ULA chip. It's capabilities should be on par with an IBM PC speaker... right? + +Not really - very soon talented programmers found out ways to output much more than one square wave channel. A lot of ZX beeper routines do exist, as of 0.6pre1 Furnace supports only one - Follin-like engine with 6 channels of narrow pulse wave and click drums. + +# effects + +- `12xx`: set pulse width +- `17xx`: trigger overlay drums. diff --git a/papers/format.md b/papers/format.md index 013f2c5d2..84239b3b9 100644 --- a/papers/format.md +++ b/papers/format.md @@ -248,7 +248,7 @@ size | description 4f | A-4 tuning 1 | limit slides (>=36) or reserved 1 | linear pitch (>=36) or reserved - | - 0: non-linaer + | - 0: non-linear | - 1: only pitch change (04xy/E5xx) linear | - 2: full linear (>=94) 1 | loop modality (>=36) or reserved diff --git a/papers/newIns.md b/papers/newIns.md new file mode 100644 index 000000000..fcc5f966f --- /dev/null +++ b/papers/newIns.md @@ -0,0 +1,49 @@ +# possible new Furnace instrument format + +the main issue with Furnace instrument files is that they are too big, even if the instrument is nothing more than the FM setup... + +the aim of this new format is to greatly reduce the size of a resulting instrument. + +``` +size | description +-----|------------------------------------ + 6 | "FURINS" format magic + 2 | format version + 1 | instrument type + ??? | feature bits + 4 | instrument length (if wave/sample bits are on) +``` + +the "feature bits" field is a variable length bitfield. bit 7 in a byte indicates "read one more byte". + +the feature bits are: + +- 0: has wavetables +- 1: has samples +- 2: has name +- 3: FM data +- 4: FM data size (1: 2-op, 0: 4-op) +- 5: FM data includes OPL/OPZ data + - if off, only read an op until ssgEnv. + - if on, read everything else. +- 6: Game Boy data +- 7: (continue in next byte) +- 8: C64 data +- 9: Amiga data +- 10: standard data (macros) +- 11: operator macros +- 12: release points +- 13: op release points +- 14: extended op macros +- 15: (continue in next byte) +- 16: OPL drums mode data +- 17: Amiga sample map data +- 18: Namco 163 data +- 19: extra macros +- 20: FDS data +- 21: OPZ data +- 22: wavetable synth data +- 23: (continue in next byte) +- 24: additional macro modes +- 25: extra C64 data +- 26: MultiPCM data diff --git a/scripts/release-win32.sh b/scripts/release-win32.sh index 2257a595e..7b3c76d7a 100755 --- a/scripts/release-win32.sh +++ b/scripts/release-win32.sh @@ -29,8 +29,9 @@ cp ../../win32build/furnace.exe . || exit 1 cp ../../README.md README.txt || exit 1 cp -r ../../papers papers || exit 1 cp -r ../../demos demos || exit 1 +cp -r ../../instruments instruments || exit 1 -zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos +zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos instruments furName=$(git describe --tags | sed "s/v0/0/") diff --git a/scripts/release-win64.sh b/scripts/release-win64.sh index db580e093..75a07f512 100755 --- a/scripts/release-win64.sh +++ b/scripts/release-win64.sh @@ -29,8 +29,9 @@ cp ../../winbuild/furnace.exe . || exit 1 cp ../../README.md README.txt || exit 1 cp -r ../../papers papers || exit 1 cp -r ../../demos demos || exit 1 +cp -r ../../instruments instruments || exit 1 -zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos +zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos instruments furName=$(git describe --tags | sed "s/v0/0/") diff --git a/src/audio/abstract.cpp b/src/audio/abstract.cpp index e44fdf7eb..942e31438 100644 --- a/src/audio/abstract.cpp +++ b/src/audio/abstract.cpp @@ -92,10 +92,12 @@ bool TAMidiOut::closeDevice() { } std::vector TAMidiIn::listDevices() { + logW("attempting to list devices of abstract TAMidiIn!"); return std::vector(); } std::vector TAMidiOut::listDevices() { + logW("attempting to list devices of abstract TAMidiOut!"); return std::vector(); } diff --git a/src/audio/jack.cpp b/src/audio/jack.cpp index 33a464d51..6e7b7bf1f 100644 --- a/src/audio/jack.cpp +++ b/src/audio/jack.cpp @@ -19,6 +19,7 @@ #include #include "jack.h" +#include "../ta-log.h" int taJACKonSampleRate(jack_nframes_t rate, void* inst) { TAAudioJACK* in=(TAAudioJACK*)inst; @@ -74,7 +75,10 @@ bool TAAudioJACK::quit() { if (running) { running=false; - if (jack_deactivate(ac)) return false; + if (jack_deactivate(ac)) { + logE("could not deactivate!"); + return false; + } } for (int i=0; i TAMidiInRtMidi::listDevices() { unsigned int count=port->getPortCount(); logD("got port count."); for (unsigned int i=0; igetPortName(i); + String name=sanitizePortName(port->getPortName(i)); if (name!="") ret.push_back(name); } } catch (RtMidiError& e) { @@ -75,8 +95,12 @@ bool TAMidiInRtMidi::openDevice(String name) { try { bool portOpen=false; unsigned int count=port->getPortCount(); + logD("finding port %s...",name); for (unsigned int i=0; igetPortName(i)==name) { + String portName=sanitizePortName(port->getPortName(i)); + logV("- %d: %s",i,portName); + if (portName==name) { + logD("opening port %d...",i); port->openPort(i); portOpen=true; break; @@ -184,8 +208,12 @@ bool TAMidiOutRtMidi::openDevice(String name) { try { bool portOpen=false; unsigned int count=port->getPortCount(); + logD("finding port %s...",name); for (unsigned int i=0; igetPortName(i)==name) { + String portName=sanitizePortName(port->getPortName(i)); + logV("- %d: %s",i,portName); + if (portName==name) { + logD("opening port %d...",i); port->openPort(i); portOpen=true; break; @@ -222,7 +250,7 @@ std::vector TAMidiOutRtMidi::listDevices() { try { unsigned int count=port->getPortCount(); for (unsigned int i=0; igetPortName(i); + String name=sanitizePortName(port->getPortName(i)); if (name!="") ret.push_back(name); } } catch (RtMidiError& e) { @@ -248,4 +276,4 @@ bool TAMidiOutRtMidi::quit() { port=NULL; } return true; -} \ No newline at end of file +} diff --git a/src/audio/sdl.cpp b/src/audio/sdlAudio.cpp similarity index 98% rename from src/audio/sdl.cpp rename to src/audio/sdlAudio.cpp index 3ef38d4fa..5c11a0342 100644 --- a/src/audio/sdl.cpp +++ b/src/audio/sdlAudio.cpp @@ -17,11 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include #include #include #include "../ta-log.h" -#include "sdl.h" +#include "sdlAudio.h" void taSDLProcess(void* inst, unsigned char* buf, int nframes) { TAAudioSDL* in=(TAAudioSDL*)inst; diff --git a/src/audio/sdl.h b/src/audio/sdlAudio.h similarity index 100% rename from src/audio/sdl.h rename to src/audio/sdlAudio.h diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 5f7c082ea..219a21414 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -313,10 +313,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do dispatch=new DivPlatformTX81Z; break; case DIV_SYSTEM_SAA1099: { - int saaCore=eng->getConfInt("saaCore",1); - if (saaCore<0 || saaCore>2) saaCore=0; dispatch=new DivPlatformSAA1099; - ((DivPlatformSAA1099*)dispatch)->setCore((DivSAACores)saaCore); break; } case DIV_SYSTEM_PCSPKR: diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 09d72c530..60e44e14a 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -24,7 +24,7 @@ #include "../ta-log.h" #include "../fileutils.h" #ifdef HAVE_SDL2 -#include "../audio/sdl.h" +#include "../audio/sdlAudio.h" #endif #include #ifndef _WIN32 @@ -193,6 +193,9 @@ bool DivEngine::isExporting() { #ifdef HAVE_SNDFILE void DivEngine::runExportThread() { + size_t fadeOutSamples=got.rate*exportFadeOut; + size_t curFadeOutSample=0; + bool isFadingOut=false; switch (exportMode) { case DIV_EXPORT_MODE_ONE: { SNDFILE* sf; @@ -220,15 +223,33 @@ void DivEngine::runExportThread() { logI("rendering to file..."); while (playing) { + size_t total=0; nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); - for (int i=0; iEXPORT_BUFSIZE) { logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); + totalProcessed=EXPORT_BUFSIZE; } - if (sf_writef_float(sf,outBuf[2],totalProcessed)!=(int)totalProcessed) { + for (int i=0; i<(int)totalProcessed; i++) { + total++; + if (isFadingOut) { + double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples)); + outBuf[2][i<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][i]))*mul; + outBuf[2][1+(i<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][i]))*mul; + if (++curFadeOutSample>=fadeOutSamples) { + playing=false; + break; + } + } else { + outBuf[2][i<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][i])); + outBuf[2][1+(i<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][i])); + if (lastLoopPos>-1 && i>=lastLoopPos && totalLoops>=exportLoopCount) { + logD("start fading out..."); + isFadingOut=true; + } + } + } + + if (sf_writef_float(sf,outBuf[2],total)!=(int)total) { logE("error: failed to write entire buffer!"); break; } @@ -286,7 +307,10 @@ void DivEngine::runExportThread() { float* outBuf[2]; outBuf[0]=new float[EXPORT_BUFSIZE]; outBuf[1]=new float[EXPORT_BUFSIZE]; - short* sysBuf=new short[EXPORT_BUFSIZE*2]; + short* sysBuf[32]; + for (int i=0; iisStereo()) { - sysBuf[j]=disCont[i].bbOut[0][j]; - } else { - sysBuf[j<<1]=disCont[i].bbOut[0][j]; - sysBuf[1+(j<<1)]=disCont[i].bbOut[1][j]; + if (totalProcessed>EXPORT_BUFSIZE) { + logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); + totalProcessed=EXPORT_BUFSIZE; + } + for (int j=0; j<(int)totalProcessed; j++) { + total++; + if (isFadingOut) { + double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples)); + for (int i=0; iisStereo()) { + sysBuf[i][j]=(double)disCont[i].bbOut[0][j]*mul; + } else { + sysBuf[i][j<<1]=(double)disCont[i].bbOut[0][j]*mul; + sysBuf[i][1+(j<<1)]=(double)disCont[i].bbOut[1][j]*mul; + } + } + if (++curFadeOutSample>=fadeOutSamples) { + playing=false; + break; + } + } else { + for (int i=0; iisStereo()) { + sysBuf[i][j]=disCont[i].bbOut[0][j]; + } else { + sysBuf[i][j<<1]=disCont[i].bbOut[0][j]; + sysBuf[i][1+(j<<1)]=disCont[i].bbOut[1][j]; + } + } + if (lastLoopPos>-1 && j>=lastLoopPos && totalLoops>=exportLoopCount) { + logD("start fading out..."); + isFadingOut=true; } } - if (totalProcessed>EXPORT_BUFSIZE) { - logE("error: total processed is bigger than export bufsize! (%d) %d>%d",i,totalProcessed,EXPORT_BUFSIZE); - } - if (sf_writef_short(sf[i],sysBuf,totalProcessed)!=(int)totalProcessed) { + } + for (int i=0; iEXPORT_BUFSIZE) { logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); + totalProcessed=EXPORT_BUFSIZE; } - if (sf_writef_float(sf,outBuf[2],totalProcessed)!=(int)totalProcessed) { + for (int j=0; j<(int)totalProcessed; j++) { + total++; + if (isFadingOut) { + double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples)); + outBuf[2][j<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][j]))*mul; + outBuf[2][1+(j<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][j]))*mul; + if (++curFadeOutSample>=fadeOutSamples) { + playing=false; + break; + } + } else { + outBuf[2][j<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][j])); + outBuf[2][1+(j<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][j])); + if (lastLoopPos>-1 && j>=lastLoopPos && totalLoops>=exportLoopCount) { + logD("start fading out..."); + isFadingOut=true; + } + } + } + if (sf_writef_float(sf,outBuf[2],total)!=(int)total) { logE("error: failed to write entire buffer!"); break; } @@ -448,13 +521,14 @@ void DivEngine::runExportThread() { } #endif -bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode) { +bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime) { #ifndef HAVE_SNDFILE logE("Furnace was not compiled with libsndfile. cannot export!"); return false; #else exportPath=path; exportMode=mode; + exportFadeOut=fadeOutTime; if (exportMode!=DIV_EXPORT_MODE_ONE) { // remove extension String lowerCase=exportPath; @@ -471,7 +545,12 @@ bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode) stop(); repeatPattern=false; setOrder(0); - remainingLoops=loops; + if (exportFadeOut<=0.01) { + remainingLoops=loops; + } else { + remainingLoops=-1; + } + exportLoopCount=loops; exportThread=new std::thread(_runExportThread,this); return true; #endif @@ -781,6 +860,8 @@ void DivEngine::changeSong(size_t songIndex) { curSubSongIndex=songIndex; curOrder=0; curRow=0; + prevOrder=0; + prevRow=0; } void DivEngine::swapChannelsP(int src, int dest) { @@ -872,6 +953,7 @@ void DivEngine::clearSubSongs() { song.clearSongData(); changeSong(0); curOrder=0; + prevOrder=0; saveLock.unlock(); BUSY_END; } @@ -1114,6 +1196,8 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { int goal=curOrder; curOrder=0; curRow=0; + prevOrder=0; + prevRow=0; stepPlay=0; int prevDrift; prevDrift=clockDrift; @@ -1127,6 +1211,8 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { totalTicks=0; totalSeconds=0; totalTicksR=0; + totalLoops=0; + lastLoopPos=-1; } speedAB=false; playing=true; @@ -1163,6 +1249,8 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { if (!preserveDrift) { ticks=1; subticks=1; + prevOrder=curOrder; + prevRow=curRow; } skipping=false; cmdStream.clear(); @@ -1289,6 +1377,7 @@ unsigned int DivEngine::convertPanLinearToSplit(int val, unsigned char bits, int void DivEngine::play() { BUSY_BEGIN_SOFT; + curOrder=prevOrder; sPreview.sample=-1; sPreview.wave=-1; sPreview.pos=0; @@ -1345,7 +1434,10 @@ void DivEngine::stop() { freelance=false; playing=false; extValuePresent=false; + endOfSong=false; // what? stepPlay=0; + curOrder=prevOrder; + curRow=prevRow; remainingLoops=-1; sPreview.sample=-1; sPreview.wave=-1; @@ -1595,11 +1687,11 @@ int DivEngine::getMaxVolumeChan(int ch) { } unsigned char DivEngine::getOrder() { - return curOrder; + return prevOrder; } int DivEngine::getRow() { - return curRow; + return prevRow; } size_t DivEngine::getCurrentSubSong() { @@ -1990,6 +2082,8 @@ int DivEngine::addSample() { sample->name=fmt::sprintf("Sample %d",sampleCount); song.sample.push_back(sample); song.sampleLen=sampleCount+1; + sPreview.sample=-1; + sPreview.pos=0; saveLock.unlock(); renderSamples(); BUSY_END; @@ -2106,6 +2200,7 @@ int DivEngine::addSampleFromFile(const char* path) { return -1; #else SF_INFO si; + memset(&si,0,sizeof(SF_INFO)); SNDFILE* f=sf_open(path,SFM_READ,&si); if (f==NULL) { BUSY_END; @@ -2123,9 +2218,29 @@ int DivEngine::addSampleFromFile(const char* path) { BUSY_END; return -1; } - short* buf=new short[si.channels*si.frames]; - if (sf_readf_short(f,buf,si.frames)!=si.frames) { - logW("sample read size mismatch!"); + void* buf=NULL; + sf_count_t sampleLen=sizeof(short); + if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) { + logD("sample is 8-bit unsigned"); + buf=new unsigned char[si.channels*si.frames]; + sampleLen=sizeof(unsigned char); + } else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) { + logD("sample is 32-bit float"); + buf=new float[si.channels*si.frames]; + sampleLen=sizeof(float); + } else { + logD("sample is 16-bit signed"); + buf=new short[si.channels*si.frames]; + sampleLen=sizeof(short); + } + if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8 || (si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) { + if (sf_read_raw(f,buf,si.frames*si.channels*sampleLen)!=(si.frames*si.channels*sampleLen)) { + logW("sample read size mismatch!"); + } + } else { + if (sf_read_short(f,(short*)buf,si.frames*si.channels)!=(si.frames*si.channels)) { + logW("sample read size mismatch!"); + } } DivSample* sample=new DivSample; int sampleCount=(int)song.sample.size(); @@ -2138,19 +2253,41 @@ int DivEngine::addSampleFromFile(const char* path) { sample->depth=DIV_SAMPLE_DEPTH_16BIT; } sample->init(si.frames); - for (int i=0; idata8[index++]=averaged; } - averaged/=si.channels; - if (((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8)) { - sample->data8[index++]=averaged>>8; - } else { + delete[] (unsigned char*)buf; + } else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) { + for (int i=0; i32767.0) averaged=32767.0; sample->data16[index++]=averaged; } + delete[] (float*)buf; + } else { + for (int i=0; idata16[index++]=averaged; + } + delete[] (short*)buf; } - delete[] buf; + sample->rate=si.samplerate; if (sample->rate<4000) sample->rate=4000; if (sample->rate>96000) sample->rate=96000; @@ -2190,6 +2327,8 @@ int DivEngine::addSampleFromFile(const char* path) { void DivEngine::delSample(int index) { BUSY_BEGIN; + sPreview.sample=-1; + sPreview.pos=0; saveLock.lock(); if (index>=0 && index<(int)song.sample.size()) { delete song.sample[index]; @@ -2404,6 +2543,8 @@ bool DivEngine::moveWaveUp(int which) { bool DivEngine::moveSampleUp(int which) { if (which<1 || which>=(int)song.sample.size()) return false; BUSY_BEGIN; + sPreview.sample=-1; + sPreview.pos=0; DivSample* prev=song.sample[which]; saveLock.lock(); song.sample[which]=song.sample[which-1]; @@ -2441,6 +2582,8 @@ bool DivEngine::moveWaveDown(int which) { bool DivEngine::moveSampleDown(int which) { if (which<0 || which>=((int)song.sample.size())-1) return false; BUSY_BEGIN; + sPreview.sample=-1; + sPreview.pos=0; DivSample* prev=song.sample[which]; saveLock.lock(); song.sample[which]=song.sample[which+1]; @@ -2578,6 +2721,7 @@ void DivEngine::setOrder(unsigned char order) { BUSY_BEGIN_SOFT; curOrder=order; if (order>=curSubSong->ordersLen) curOrder=0; + prevOrder=curOrder; if (playing && !freelance) { playSub(false); } @@ -2771,6 +2915,8 @@ void DivEngine::quitDispatch() { tempoAccum=0; curRow=0; curOrder=0; + prevRow=0; + prevOrder=0; nextSpeed=3; changeOrd=-1; changePos=0; @@ -2902,6 +3048,8 @@ bool DivEngine::initAudioBackend() { if (!output->midiIn->openDevice(inName)) { logW("could not open MIDI input device!"); } + } else { + logV("no MIDI input device selected."); } } if (output->midiOut) { @@ -2912,6 +3060,8 @@ bool DivEngine::initAudioBackend() { if (!output->midiOut->openDevice(outName)) { logW("could not open MIDI output device!"); } + } else { + logV("no MIDI output device selected."); } } diff --git a/src/engine/engine.h b/src/engine/engine.h index 8eb6df010..30e080421 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -60,8 +60,9 @@ enum DivStatusView { enum DivAudioEngines { DIV_AUDIO_JACK=0, DIV_AUDIO_SDL=1, - DIV_AUDIO_NULL=2, - DIV_AUDIO_DUMMY=3 + + DIV_AUDIO_NULL=126, + DIV_AUDIO_DUMMY=127 }; enum DivAudioExportModes { @@ -83,7 +84,7 @@ struct DivChannelState { int note, oldNote, lastIns, pitch, portaSpeed, portaNote; int volume, volSpeed, cut, rowDelay, volMax; int delayOrder, delayRow, retrigSpeed, retrigTick; - int vibratoDepth, vibratoRate, vibratoPos, vibratoDir, vibratoFine; + int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoDir, vibratoFine; int tremoloDepth, tremoloRate, tremoloPos; unsigned char arp, arpStage, arpTicks, panL, panR; bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff; @@ -112,6 +113,7 @@ struct DivChannelState { vibratoDepth(0), vibratoRate(0), vibratoPos(0), + vibratoPosGiant(0), vibratoDir(0), vibratoFine(15), tremoloDepth(0), @@ -303,7 +305,7 @@ class DivEngine { bool systemsRegistered; bool hasLoadedSomething; int softLockCount; - int subticks, ticks, curRow, curOrder, remainingLoops, nextSpeed; + int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed; size_t curSubSongIndex; double divider; int cycles; @@ -318,6 +320,7 @@ class DivEngine { DivChannelState chan[DIV_MAX_CHANS]; DivAudioEngines audioEngine; DivAudioExportModes exportMode; + double exportFadeOut; std::map conf; std::queue pendingNotes; bool isMuted[DIV_MAX_CHANS]; @@ -465,7 +468,7 @@ class DivEngine { // dump to VGM. SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171); // export to an audio file - bool saveAudio(const char* path, int loops, DivAudioExportModes mode); + bool saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime=0.0); // wait for audio export to finish void waitAudioFile(); // stop audio file export @@ -937,7 +940,12 @@ class DivEngine { ticks(0), curRow(0), curOrder(0), + prevRow(0), + prevOrder(0), remainingLoops(-1), + totalLoops(0), + lastLoopPos(0), + exportLoopCount(0), nextSpeed(3), curSubSongIndex(0), divider(60), @@ -961,6 +969,7 @@ class DivEngine { haltOn(DIV_HALT_NONE), audioEngine(DIV_AUDIO_NULL), exportMode(DIV_EXPORT_MODE_ONE), + exportFadeOut(0.0), midiBaseChan(0), midiPoly(true), midiAgeCounter(0), diff --git a/src/engine/macroInt.cpp b/src/engine/macroInt.cpp index 6f853bffa..06f04682e 100644 --- a/src/engine/macroInt.cpp +++ b/src/engine/macroInt.cpp @@ -101,6 +101,7 @@ void DivMacroInt::init(DivInstrument* which) { macroListLen=0; subTick=1; + hasRelease=false; released=false; if (ins==NULL) return; @@ -236,7 +237,12 @@ void DivMacroInt::init(DivInstrument* which) { } for (size_t i=0; iprepare(*macroSource[i],e); + if (macroSource[i]!=NULL) { + macroList[i]->prepare(*macroSource[i],e); + hasRelease=(macroSource[i]->rel>=0 && macroSource[i]->rellen); + } else { + hasRelease=false; + } } } diff --git a/src/engine/macroInt.h b/src/engine/macroInt.h index d82cb2b21..839625558 100644 --- a/src/engine/macroInt.h +++ b/src/engine/macroInt.h @@ -96,6 +96,9 @@ class DivMacroInt { ksr() {} } op[4]; + // state + bool hasRelease; + /** * trigger macro release. */ @@ -149,7 +152,8 @@ class DivMacroInt { ex5(), ex6(), ex7(), - ex8() { + ex8(), + hasRelease(false) { memset(macroList,0,128*sizeof(void*)); memset(macroSource,0,128*sizeof(void*)); } diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index d5229df2a..e884d29b0 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -427,6 +427,10 @@ bool DivPlatformAmiga::keyOffAffectsArp(int ch) { return true; } +DivMacroInt* DivPlatformAmiga::getChanMacroInt(int ch) { + return &chan[ch].std; +} + void DivPlatformAmiga::notifyInsChange(int ins) { for (int i=0; i<4; i++) { if (chan[i].ins==ins) { diff --git a/src/engine/platform/amiga.h b/src/engine/platform/amiga.h index 539f7830c..e7372e63b 100644 --- a/src/engine/platform/amiga.h +++ b/src/engine/platform/amiga.h @@ -99,6 +99,7 @@ class DivPlatformAmiga: public DivDispatch { void muteChannel(int ch, bool mute); bool isStereo(); bool keyOffAffectsArp(int ch); + DivMacroInt* getChanMacroInt(int ch); void setFlags(unsigned int flags); void notifyInsChange(int ins); void notifyWaveChange(int wave); diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 121183d11..1be61fa23 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -22,38 +22,6 @@ #include #include -#include "fmshared_OPM.h" - -static unsigned short chanOffs[8]={ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 -}; -static unsigned short opOffs[4]={ - 0x00, 0x08, 0x10, 0x18 -}; -static bool isOutput[8][4]={ - // 1 3 2 4 - {false,false,false,true}, - {false,false,false,true}, - {false,false,false,true}, - {false,false,false,true}, - {false,false,true ,true}, - {false,true ,true ,true}, - {false,true ,true ,true}, - {true ,true ,true ,true}, -}; -static unsigned char dtTable[8]={ - 7,6,5,0,1,2,3,4 -}; - -static int orderedOps[4]={ - 0,2,1,3 -}; - -#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 NOTE_LINEAR(x) (((x)<<6)+baseFreqOff+log2(parent->song.tuning/440.0)*12.0*64.0) - const char* regCheatSheetOPM[]={ "Test", "00", "NoteCtl", "08", @@ -198,7 +166,7 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si OPM_Write(&fm,1,w.val); regPool[w.addr&0xff]=w.val; //printf("write: %x = %.2x\n",w.addr,w.val); - writes.pop(); + writes.pop_front(); } else { OPM_Write(&fm,0,w.addr); w.addrOrVal=true; @@ -239,7 +207,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz fm_ymfm->write(0x0+((w.addr>>8)<<1),w.addr); fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val); regPool[w.addr&0xff]=w.val; - writes.pop(); + writes.pop_front(); delay=1; } } @@ -913,6 +881,10 @@ void* DivPlatformArcade::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformArcade::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformArcade::getOscBuffer(int ch) { return oscBuf[ch]; } @@ -934,7 +906,7 @@ void DivPlatformArcade::poke(std::vector& wlist) { } void DivPlatformArcade::reset() { - while (!writes.empty()) writes.pop(); + while (!writes.empty()) writes.pop_front(); memset(regPool,0,256); if (useYMFM) { fm_ymfm->reset(); @@ -974,15 +946,20 @@ void DivPlatformArcade::reset() { } void DivPlatformArcade::setFlags(unsigned int flags) { - if (flags==2) { - chipClock=4000000.0; - baseFreqOff=-122; - } else if (flags==1) { - chipClock=COLOR_PAL*4.0/5.0; - baseFreqOff=12; - } else { - chipClock=COLOR_NTSC; - baseFreqOff=0; + switch (flags&0xff) { + default: + case 0: + chipClock=COLOR_NTSC; + baseFreqOff=0; + break; + case 1: + chipClock=COLOR_PAL*4.0/5.0; + baseFreqOff=12; + break; + case 2: + chipClock=4000000.0; + baseFreqOff=-122; + break; } rate=chipClock/64; for (int i=0; i<8; i++) { diff --git a/src/engine/platform/arcade.h b/src/engine/platform/arcade.h index 6f68eedad..daa883620 100644 --- a/src/engine/platform/arcade.h +++ b/src/engine/platform/arcade.h @@ -19,19 +19,23 @@ #ifndef _ARCADE_H #define _ARCADE_H -#include "../dispatch.h" +#include "fmshared_OPM.h" +#include "../macroInt.h" #include "../instrument.h" #include #include "../../../extern/opm/opm.h" #include "sound/ymfm/ymfm_opm.h" -#include "../macroInt.h" class DivArcadeInterface: public ymfm::ymfm_interface { }; -class DivPlatformArcade: public DivDispatch { +class DivPlatformArcade: public DivPlatformOPM { protected: + const unsigned short chanOffs[8]={ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 + }; + struct Channel { DivInstrumentFM state; DivMacroInt std; @@ -71,31 +75,18 @@ class DivPlatformArcade: public DivDispatch { }; Channel chan[8]; DivDispatchOscBuffer* oscBuf[8]; - struct QueuedWrite { - unsigned short addr; - unsigned char val; - bool addrOrVal; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} - }; - std::queue writes; opm_t fm; - int delay, baseFreqOff; + int baseFreqOff; int pcmL, pcmR, pcmCycles; - unsigned char lastBusy; unsigned char amDepth, pmDepth; ymfm::ym2151* fm_ymfm; ymfm::ym2151::output_data out_ymfm; DivArcadeInterface iface; - unsigned char regPool[256]; - bool extMode, useYMFM; bool isMuted[8]; - - short oldWrites[256]; - short pendingWrites[256]; int octave(int freq); int toFreq(int freq); @@ -116,6 +107,7 @@ class DivPlatformArcade: public DivDispatch { void forceIns(); void tick(bool sysTick=true); void muteChannel(int ch, bool mute); + DivMacroInt* getChanMacroInt(int ch); void notifyInsChange(int ins); void setFlags(unsigned int flags); bool isStereo(); diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index a3d325ade..073776105 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -27,7 +27,7 @@ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(regRemap(a),v); if (dumpWrites) {addWrite(regRemap(a),v);} } -#define CHIP_DIVIDER ((sunsoft||clockSel)?16:8) +#define CHIP_DIVIDER (extMode?extDiv:((sunsoft||clockSel)?16:8)) const char* regCheatSheetAY[]={ "FreqL_A", "0", @@ -489,9 +489,9 @@ void DivPlatformAY8910::muteChannel(int ch, bool mute) { isMuted[ch]=mute; if (isMuted[ch]) { rWrite(0x08+ch,0); - } else if (intellivision && (chan[ch].psgMode&4)) { + } else if (intellivision && (chan[ch].psgMode&4) && chan[ch].active) { rWrite(0x08+ch,(chan[ch].vol&0xc)<<2); - } else { + } else if (chan[ch].active) { rWrite(0x08+ch,(chan[ch].outVol&15)|((chan[ch].psgMode&4)<<2)); } } @@ -509,6 +509,10 @@ void* DivPlatformAY8910::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformAY8910::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformAY8910::getOscBuffer(int ch) { return oscBuf[ch]; } @@ -561,8 +565,6 @@ void DivPlatformAY8910::reset() { delay=0; - extMode=false; - ioPortA=false; ioPortB=false; portAVal=0; @@ -591,50 +593,69 @@ void DivPlatformAY8910::poke(std::vector& wlist) { for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); } -void DivPlatformAY8910::setFlags(unsigned int flags) { - clockSel=(flags>>7)&1; - switch (flags&15) { - case 1: - chipClock=COLOR_PAL*2.0/5.0; - break; - case 2: - chipClock=1750000; - break; - case 3: - chipClock=2000000; - break; - case 4: - chipClock=1500000; - break; - case 5: - chipClock=1000000; - break; - case 6: - chipClock=COLOR_NTSC/4.0; - break; - case 7: - chipClock=COLOR_PAL*3.0/8.0; - break; - case 8: - chipClock=COLOR_PAL*3.0/16.0; - break; - case 9: - chipClock=COLOR_PAL/4.0; - break; - case 10: - chipClock=2097152; - break; - case 11: - chipClock=COLOR_NTSC; - break; - case 12: - chipClock=3600000; - break; - default: - chipClock=COLOR_NTSC/2.0; - break; +void DivPlatformAY8910::setExtClockDiv(unsigned int eclk, unsigned char ediv) { + if (extMode) { + extClock=eclk; + extDiv=ediv; + } +} + +void DivPlatformAY8910::setFlags(unsigned int flags) { + if (extMode) { + chipClock=extClock; + rate=chipClock/extDiv; + } else { + clockSel=(flags>>7)&1; + switch (flags&15) { + default: + case 0: + chipClock=COLOR_NTSC/2.0; + break; + case 1: + chipClock=COLOR_PAL*2.0/5.0; + break; + case 2: + chipClock=1750000; + break; + case 3: + chipClock=2000000; + break; + case 4: + chipClock=1500000; + break; + case 5: + chipClock=1000000; + break; + case 6: + chipClock=COLOR_NTSC/4.0; + break; + case 7: + chipClock=COLOR_PAL*3.0/8.0; + break; + case 8: + chipClock=COLOR_PAL*3.0/16.0; + break; + case 9: + chipClock=COLOR_PAL/4.0; + break; + case 10: + chipClock=2097152; + break; + case 11: + chipClock=COLOR_NTSC; + break; + case 12: + chipClock=3600000; + break; + case 13: + chipClock=20000000/16; + break; + case 14: + chipClock=1536000; + break; + } + rate=chipClock/8; } - rate=chipClock/8; for (int i=0; i<3; i++) { oscBuf[i]->rate=rate; } diff --git a/src/engine/platform/ay.h b/src/engine/platform/ay.h index f93d34bdc..f67a2ad97 100644 --- a/src/engine/platform/ay.h +++ b/src/engine/platform/ay.h @@ -70,6 +70,9 @@ class DivPlatformAY8910: public DivDispatch { int delay; bool extMode; + unsigned int extClock; + unsigned char extDiv; + bool stereo, sunsoft, intellivision, clockSel; bool ioPortA, ioPortB; unsigned char portAVal, portBVal; @@ -88,6 +91,7 @@ class DivPlatformAY8910: public DivDispatch { friend void putDispatchChan(void*,int,int); public: + void setExtClockDiv(unsigned int eclk=COLOR_NTSC, unsigned char ediv=8); void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); @@ -102,6 +106,7 @@ class DivPlatformAY8910: public DivDispatch { void setFlags(unsigned int flags); bool isStereo(); bool keyOffAffectsArp(int ch); + DivMacroInt* getChanMacroInt(int ch); bool getDCOffRequired(); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); @@ -110,5 +115,10 @@ class DivPlatformAY8910: public DivDispatch { const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); + DivPlatformAY8910(bool useExtMode=false, unsigned int eclk=COLOR_NTSC, unsigned char ediv=8): + DivDispatch(), + extMode(useExtMode), + extClock(eclk), + extDiv(ediv) {} }; #endif diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index fb498b2c7..0fad4025b 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -521,7 +521,7 @@ void DivPlatformAY8930::muteChannel(int ch, bool mute) { isMuted[ch]=mute; if (isMuted[ch]) { rWrite(0x08+ch,0); - } else { + } else if (chan[ch].active) { rWrite(0x08+ch,(chan[ch].outVol&31)|((chan[ch].psgMode&4)<<3)); } } @@ -539,6 +539,10 @@ void* DivPlatformAY8930::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformAY8930::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformAY8930::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/ay8930.h b/src/engine/platform/ay8930.h index 67420f48c..10ac7736b 100644 --- a/src/engine/platform/ay8930.h +++ b/src/engine/platform/ay8930.h @@ -98,6 +98,7 @@ class DivPlatformAY8930: public DivDispatch { void setFlags(unsigned int flags); bool isStereo(); bool keyOffAffectsArp(int ch); + DivMacroInt* getChanMacroInt(int ch); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); diff --git a/src/engine/platform/bubsyswsg.cpp b/src/engine/platform/bubsyswsg.cpp index 5793277dd..48a078803 100644 --- a/src/engine/platform/bubsyswsg.cpp +++ b/src/engine/platform/bubsyswsg.cpp @@ -281,6 +281,10 @@ void* DivPlatformBubSysWSG::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformBubSysWSG::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformBubSysWSG::getOscBuffer(int ch) { return oscBuf[ch]; } @@ -302,7 +306,7 @@ void DivPlatformBubSysWSG::reset() { for (int i=0; i<2; i++) { chan[i]=DivPlatformBubSysWSG::Channel(); chan[i].std.setEngine(parent); - chan[i].ws.setEngine(parent); + chan[i].ws.setEngine(parent,8); chan[i].ws.init(NULL,32,15,false); } if (dumpWrites) { diff --git a/src/engine/platform/bubsyswsg.h b/src/engine/platform/bubsyswsg.h index cb30d85c0..e288493c1 100644 --- a/src/engine/platform/bubsyswsg.h +++ b/src/engine/platform/bubsyswsg.h @@ -78,6 +78,7 @@ class DivPlatformBubSysWSG: public DivDispatch { void muteChannel(int ch, bool mute); bool isStereo(); bool keyOffAffectsArp(int ch); + DivMacroInt* getChanMacroInt(int ch); void setFlags(unsigned int flags); void notifyWaveChange(int wave); void notifyInsDeletion(void* ins); diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 726928871..a55b53906 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -109,14 +109,15 @@ const char* DivPlatformC64::getEffectName(unsigned char effect) { } void DivPlatformC64::acquire(short* bufL, short* bufR, size_t start, size_t len) { + int dcOff=sid.get_dc(0); 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; + oscBuf[0]->data[oscBuf[0]->needle++]=(sid.last_chan_out[0]-dcOff)>>5; + oscBuf[1]->data[oscBuf[1]->needle++]=(sid.last_chan_out[1]-dcOff)>>5; + oscBuf[2]->data[oscBuf[2]->needle++]=(sid.last_chan_out[2]-dcOff)>>5; } } } @@ -491,6 +492,10 @@ void* DivPlatformC64::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformC64::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformC64::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/c64.h b/src/engine/platform/c64.h index 495da461d..d9dc08040 100644 --- a/src/engine/platform/c64.h +++ b/src/engine/platform/c64.h @@ -97,6 +97,7 @@ class DivPlatformC64: public DivDispatch { void setFlags(unsigned int flags); void notifyInsChange(int ins); bool getDCOffRequired(); + DivMacroInt* getChanMacroInt(int ch); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp index fabeeed93..ea4b83e9d 100644 --- a/src/engine/platform/fds.cpp +++ b/src/engine/platform/fds.cpp @@ -436,6 +436,10 @@ void* DivPlatformFDS::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformFDS::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformFDS::getOscBuffer(int ch) { return oscBuf; } diff --git a/src/engine/platform/fds.h b/src/engine/platform/fds.h index c563a47f8..f655a7777 100644 --- a/src/engine/platform/fds.h +++ b/src/engine/platform/fds.h @@ -88,6 +88,7 @@ class DivPlatformFDS: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/fmshared_OPM.h b/src/engine/platform/fmshared_OPM.h index 0ab05344b..9b838c196 100644 --- a/src/engine/platform/fmshared_OPM.h +++ b/src/engine/platform/fmshared_OPM.h @@ -17,13 +17,32 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#define ADDR_MULT_DT 0x40 -#define ADDR_TL 0x60 -#define ADDR_RS_AR 0x80 -#define ADDR_AM_DR 0xa0 -#define ADDR_DT2_D2R 0xc0 -#define ADDR_SL_RR 0xe0 -#define ADDR_NOTE 0x28 -#define ADDR_KF 0x30 -#define ADDR_FMS_AMS 0x38 -#define ADDR_LR_FB_ALG 0x20 \ No newline at end of file +#ifndef _FMSHARED_OPM_H +#define _FMSHARED_OPM_H + +#include "fmsharedbase.h" + +#define NOTE_LINEAR(x) (((x)<<6)+baseFreqOff+log2(parent->song.tuning/440.0)*12.0*64.0) + +class DivPlatformOPM: public DivPlatformFMBase { + protected: + const unsigned short ADDR_MULT_DT=0x40; + const unsigned short ADDR_TL=0x60; + const unsigned short ADDR_RS_AR=0x80; + const unsigned short ADDR_AM_DR=0xa0; + const unsigned short ADDR_DT2_D2R=0xc0; + const unsigned short ADDR_SL_RR=0xe0; + const unsigned short ADDR_NOTE=0x28; + const unsigned short ADDR_KF=0x30; + const unsigned short ADDR_FMS_AMS=0x38; + const unsigned short ADDR_LR_FB_ALG=0x20; + + const unsigned short opOffs[4]={ + 0x00, 0x08, 0x10, 0x18 + }; + + DivPlatformOPM(): + DivPlatformFMBase() {} +}; + +#endif diff --git a/src/engine/platform/fmshared_OPN.h b/src/engine/platform/fmshared_OPN.h index a709aa1a3..0aaad16d4 100644 --- a/src/engine/platform/fmshared_OPN.h +++ b/src/engine/platform/fmshared_OPN.h @@ -20,17 +20,7 @@ #ifndef _FMSHARED_OPN_H #define _FMSHARED_OPN_H -#define ADDR_MULT_DT 0x30 -#define ADDR_TL 0x40 -#define ADDR_RS_AR 0x50 -#define ADDR_AM_DR 0x60 -#define ADDR_DT2_D2R 0x70 -#define ADDR_SL_RR 0x80 -#define ADDR_SSG 0x90 -#define ADDR_FREQ 0xa0 -#define ADDR_FREQH 0xa4 -#define ADDR_FB_ALG 0xb0 -#define ADDR_LRAF 0xb4 +#include "fmsharedbase.h" #define PLEASE_HELP_ME(_targetChan) \ int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); \ @@ -93,4 +83,34 @@ return 2; \ } -#endif \ No newline at end of file +class DivPlatformOPN: public DivPlatformFMBase { + protected: + const unsigned short ADDR_MULT_DT=0x30; + const unsigned short ADDR_TL=0x40; + const unsigned short ADDR_RS_AR=0x50; + const unsigned short ADDR_AM_DR=0x60; + const unsigned short ADDR_DT2_D2R=0x70; + const unsigned short ADDR_SL_RR=0x80; + const unsigned short ADDR_SSG=0x90; + const unsigned short ADDR_FREQ=0xa0; + const unsigned short ADDR_FREQH=0xa4; + const unsigned short ADDR_FB_ALG=0xb0; + const unsigned short ADDR_LRAF=0xb4; + + const unsigned short opOffs[4]={ + 0x00, 0x04, 0x08, 0x0c + }; + + double fmFreqBase; + unsigned int fmDivBase; + unsigned int ayDiv; + + DivPlatformOPN(double f=9440540.0, unsigned int d=72, unsigned int a=32): + DivPlatformFMBase(), + fmFreqBase(f), + fmDivBase(d), + ayDiv(a) {} + +}; + +#endif diff --git a/src/engine/platform/fmsharedbase.h b/src/engine/platform/fmsharedbase.h new file mode 100644 index 000000000..640997392 --- /dev/null +++ b/src/engine/platform/fmsharedbase.h @@ -0,0 +1,96 @@ +/** + * 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 _FMSHARED_BASE_H +#define _FMSHARED_BASE_H + +#include "../dispatch.h" +#include + +class DivPlatformFMBase: public DivDispatch { + protected: + const bool isOutput[8][4]={ + // 1 3 2 4 + {false,false,false,true}, + {false,false,false,true}, + {false,false,false,true}, + {false,false,false,true}, + {false,false,true ,true}, + {false,true ,true ,true}, + {false,true ,true ,true}, + {true ,true ,true ,true}, + }; + const unsigned char dtTable[8]={ + 7,6,5,0,1,2,3,4 + }; + + const int orderedOps[4]={ + 0,2,1,3 + }; + + struct QueuedWrite { + unsigned short addr; + unsigned char val; + bool addrOrVal; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + }; + std::deque writes; + + unsigned char lastBusy; + int delay; + + unsigned char regPool[512]; + short oldWrites[512]; + short pendingWrites[512]; + + inline void rWrite(unsigned short a, short v) { + if (!skipRegisterWrites) { + pendingWrites[a]=v; + } + } + inline void immWrite(unsigned short a, unsigned char v) { + if (!skipRegisterWrites) { + writes.push_back(QueuedWrite(a,v)); + if (dumpWrites) { + addWrite(a,v); + } + } + } + inline void urgentWrite(unsigned short a, unsigned char v) { + if (!skipRegisterWrites) { + if (writes.empty()) { + writes.push_back(QueuedWrite(a,v)); + } else if (writes.size()>16 || writes.front().addrOrVal) { + writes.push_back(QueuedWrite(a,v)); + } else { + writes.push_front(QueuedWrite(a,v)); + } + if (dumpWrites) { + addWrite(a,v); + } + } + } + + DivPlatformFMBase(): + DivDispatch(), + lastBusy(0), + delay(0) {} +}; + +#endif diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 40bf6a835..999d31b91 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -433,6 +433,10 @@ void* DivPlatformGB::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformGB::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformGB::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h index cce1ffb6c..fe2f6e51b 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -72,6 +72,7 @@ class DivPlatformGB: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index f9c58cb1f..32052499b 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -22,17 +22,11 @@ #include #include -#include "genesisshared.h" +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase #define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6]))) -static unsigned char konOffs[6]={ - 0, 1, 2, 4, 5, 6 -}; - -#define CHIP_DIVIDER 72 -#define CHIP_FREQBASE 9440540 - const char* DivPlatformGenesis::getEffectName(unsigned char effect) { switch (effect) { case 0x10: @@ -1152,6 +1146,10 @@ void* DivPlatformGenesis::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformGenesis::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformGenesis::getOscBuffer(int ch) { return oscBuf[ch]; } @@ -1250,12 +1248,13 @@ void DivPlatformGenesis::setSoftPCM(bool value) { } void DivPlatformGenesis::setFlags(unsigned int flags) { - switch (flags) { + switch (flags&(~0x80000000)) { + default: + case 0: chipClock=COLOR_NTSC*15.0/7.0; break; case 1: chipClock=COLOR_PAL*12.0/7.0; break; case 2: chipClock=8000000.0; break; case 3: chipClock=COLOR_NTSC*12.0/7.0; break; case 4: chipClock=COLOR_NTSC*9.0/4.0; break; - default: chipClock=COLOR_NTSC*15.0/7.0; break; } ladder=flags&0x80000000; OPN2_SetChipType(ladder?ym3438_mode_ym2612:0); diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index 24b077584..1322b3ebe 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -19,19 +19,26 @@ #ifndef _GENESIS_H #define _GENESIS_H -#include "../dispatch.h" -#include +#include "fmshared_OPN.h" +#include "../macroInt.h" #include "../../../extern/Nuked-OPN2/ym3438.h" #include "sound/ymfm/ymfm_opn.h" -#include "sms.h" class DivYM2612Interface: public ymfm::ymfm_interface { }; -class DivPlatformGenesis: public DivDispatch { +class DivPlatformGenesis: public DivPlatformOPN { protected: + const unsigned short chanOffs[6]={ + 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 + }; + + const unsigned char konOffs[6]={ + 0, 1, 2, 4, 5, 6 + }; + struct Channel { DivInstrumentFM state; DivMacroInt std; @@ -97,21 +104,11 @@ class DivPlatformGenesis: public DivDispatch { Channel chan[10]; DivDispatchOscBuffer* oscBuf[10]; bool isMuted[10]; - struct QueuedWrite { - unsigned short addr; - unsigned char val; - bool addrOrVal; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} - }; - std::deque writes; ym3438_t fm; - int delay; - unsigned char lastBusy; ymfm::ym2612* fm_ymfm; ymfm::ym2612::output_data out_ymfm; DivYM2612Interface iface; - unsigned char regPool[512]; unsigned char lfoValue; @@ -120,9 +117,6 @@ class DivPlatformGenesis: public DivDispatch { bool extMode, softPCM, useYMFM; bool ladder; - short oldWrites[512]; - short pendingWrites[512]; - unsigned char dacVolTable[128]; friend void putDispatchChan(void*,int,int); @@ -135,6 +129,7 @@ class DivPlatformGenesis: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); @@ -157,6 +152,8 @@ class DivPlatformGenesis: public DivDispatch { const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); + DivPlatformGenesis(): + DivPlatformOPN(9440540.0, 72, 32) {} ~DivPlatformGenesis(); }; #endif diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index e105f5604..cd4d603c1 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -21,10 +21,8 @@ #include "../engine.h" #include -#include "genesisshared.h" - -#define CHIP_DIVIDER 72 -#define CHIP_FREQBASE 9440540 +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase int DivPlatformGenesisExt::dispatch(DivCommand c) { if (c.chan<2) { @@ -543,6 +541,12 @@ void* DivPlatformGenesisExt::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformGenesisExt::getChanMacroInt(int ch) { + if (ch>=6) return &chan[ch-3].std; + if (ch>=2) return NULL; // currently not implemented + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformGenesisExt::getOscBuffer(int ch) { if (ch>=6) return oscBuf[ch-3]; if (ch<3) return oscBuf[ch]; diff --git a/src/engine/platform/genesisext.h b/src/engine/platform/genesisext.h index f6c1e3dc9..07e0d5cc3 100644 --- a/src/engine/platform/genesisext.h +++ b/src/engine/platform/genesisext.h @@ -55,6 +55,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis { public: int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); void reset(); void forceIns(); diff --git a/src/engine/platform/genesisshared.h b/src/engine/platform/genesisshared.h deleted file mode 100644 index d58d339e6..000000000 --- a/src/engine/platform/genesisshared.h +++ /dev/null @@ -1,60 +0,0 @@ -/** - * 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. - */ - -static unsigned short chanOffs[6]={ - 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 -}; -static unsigned short opOffs[4]={ - 0x00, 0x04, 0x08, 0x0c -}; -static bool isOutput[8][4]={ - // 1 3 2 4 - {false,false,false,true}, - {false,false,false,true}, - {false,false,false,true}, - {false,false,false,true}, - {false,false,true ,true}, - {false,true ,true ,true}, - {false,true ,true ,true}, - {true ,true ,true ,true}, -}; -static unsigned char dtTable[8]={ - 7,6,5,0,1,2,3,4 -}; - -static int orderedOps[4]={ - 0,2,1,3 -}; - -#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} -#define immWrite(a,v) if (!skipRegisterWrites) {writes.push_back(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } -#define urgentWrite(a,v) if (!skipRegisterWrites) { \ - if (writes.empty()) { \ - writes.push_back(QueuedWrite(a,v)); \ - } else if (writes.size()>16 || writes.front().addrOrVal) { \ - writes.push_back(QueuedWrite(a,v)); \ - } else { \ - writes.push_front(QueuedWrite(a,v)); \ - } \ - if (dumpWrites) { \ - addWrite(a,v); \ - } \ -} - -#include "fmshared_OPN.h" diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index dce2fb044..4fd30db98 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -424,6 +424,10 @@ void* DivPlatformLynx::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformLynx::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformLynx::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/lynx.h b/src/engine/platform/lynx.h index 4f221d037..849c3182b 100644 --- a/src/engine/platform/lynx.h +++ b/src/engine/platform/lynx.h @@ -88,6 +88,7 @@ class DivPlatformLynx: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp index 97ed8ef6c..99e8043d1 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -354,6 +354,10 @@ void* DivPlatformMMC5::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformMMC5::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformMMC5::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/mmc5.h b/src/engine/platform/mmc5.h index 2213b995f..f09092d55 100644 --- a/src/engine/platform/mmc5.h +++ b/src/engine/platform/mmc5.h @@ -74,6 +74,7 @@ class DivPlatformMMC5: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/msm6258.cpp b/src/engine/platform/msm6258.cpp index 44986fc70..731bf8f28 100644 --- a/src/engine/platform/msm6258.cpp +++ b/src/engine/platform/msm6258.cpp @@ -254,6 +254,10 @@ void* DivPlatformMSM6258::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformMSM6258::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformMSM6258::getOscBuffer(int ch) { return oscBuf[ch]; } @@ -376,7 +380,7 @@ void DivPlatformMSM6258::setFlags(unsigned int flags) { chipClock=4000000; break; } - rate=chipClock/128; + rate=chipClock/256; for (int i=0; i<1; i++) { oscBuf[i]->rate=rate; } diff --git a/src/engine/platform/msm6258.h b/src/engine/platform/msm6258.h index f60f8bc9e..cd975c8f8 100644 --- a/src/engine/platform/msm6258.h +++ b/src/engine/platform/msm6258.h @@ -26,10 +26,6 @@ class DivPlatformMSM6258: public DivDispatch { protected: - const unsigned short chanOffs[6]={ - 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 - }; - struct Channel { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; @@ -77,12 +73,10 @@ class DivPlatformMSM6258: public DivDispatch { struct QueuedWrite { unsigned short addr; unsigned char val; - bool addrOrVal; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {} }; std::queue writes; okim6258_device* msm; - unsigned char regPool[512]; unsigned char lastBusy; unsigned char* adpcmMem; @@ -93,17 +87,13 @@ class DivPlatformMSM6258: public DivDispatch { int delay, updateOsc, sample, samplePos; - bool extMode; - - short oldWrites[512]; - short pendingWrites[512]; - 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); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/msm6295.cpp b/src/engine/platform/msm6295.cpp index 32de1e095..fc5f9ea30 100644 --- a/src/engine/platform/msm6295.cpp +++ b/src/engine/platform/msm6295.cpp @@ -24,6 +24,7 @@ #include #define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define rWriteDelay(a,v,d) if (!skipRegisterWrites) {writes.emplace(a,v,d); if (dumpWrites) {addWrite(a,v);} } const char** DivPlatformMSM6295::getRegisterSheet() { return NULL; @@ -38,8 +39,11 @@ const char* DivPlatformMSM6295::getEffectName(unsigned char effect) { return NULL; } -u8 DivMSM6295Interface::read_byte(u32 address) { - return adpcmMem[address&0xffff]; +u8 DivPlatformMSM6295::read_byte(u32 address) { + if (adpcmMem==NULL || address>=getSampleMemCapacity(0)) { + return 0; + } + return adpcmMem[address&0x3ffff]; } void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t len) { @@ -49,7 +53,7 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t QueuedWrite& w=writes.front(); switch (w.addr) { case 0: // command - msm->command_w(w.val); + msm.command_w(w.val); break; case 8: // chip clock select (VGM) case 9: @@ -57,7 +61,7 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t case 11: break; case 12: // rate select - msm->ss_w(!w.val); + msm.ss_w(!w.val); break; case 14: // enable bankswitch break; @@ -70,21 +74,21 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t break; } writes.pop(); - delay=32; + delay=w.delay; } } else { delay--; } - msm->tick(); + msm.tick(); - bufL[h]=msm->out()<<4; + bufL[h]=msm.out()<<4; if (++updateOsc>=22) { updateOsc=0; // TODO: per-channel osc for (int i=0; i<4; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=msm->m_voice[i].m_muted?0:(msm->m_voice[i].m_out<<6); + oscBuf[i]->data[oscBuf[i]->needle++]=msm.m_voice[i].m_muted?0:(msm.m_voice[i].m_out<<6); } } } @@ -118,7 +122,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - rWrite(0,(8<getSample(12*sampleBank+c.value%12); chan[c.chan].sample=12*sampleBank+c.value%12; - rWrite(0,(8<(parent->song.sample.size()/12)) { sampleBank=parent->song.sample.size()/12; } - iface.sampleBank=sampleBank; break; case DIV_CMD_LEGATO: { break; @@ -212,7 +215,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) { void DivPlatformMSM6295::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - msm->m_voice[ch].m_muted=mute; + msm.m_voice[ch].m_muted=mute; } void DivPlatformMSM6295::forceIns() { @@ -227,6 +230,10 @@ void* DivPlatformMSM6295::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformMSM6295::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformMSM6295::getOscBuffer(int ch) { return oscBuf[ch]; } @@ -249,8 +256,8 @@ void DivPlatformMSM6295::poke(std::vector& wlist) { void DivPlatformMSM6295::reset() { while (!writes.empty()) writes.pop(); - msm->reset(); - msm->ss_w(false); + msm.reset(); + msm.ss_w(rateSelInit); if (dumpWrites) { addWrite(0xffffffff,0); } @@ -264,7 +271,8 @@ void DivPlatformMSM6295::reset() { } sampleBank=0; - rateSel=false; + rateSel=rateSelInit; + rWrite(12,!rateSelInit); delay=0; } @@ -339,7 +347,9 @@ void DivPlatformMSM6295::renderSamples() { } void DivPlatformMSM6295::setFlags(unsigned int flags) { - switch (flags) { + rateSelInit=(flags>>7)&1; + switch (flags&0x7f) { + default: case 0: chipClock=4000000/4; break; @@ -379,22 +389,27 @@ void DivPlatformMSM6295::setFlags(unsigned int flags) { case 12: chipClock=1500000; break; - default: - chipClock=4000000/4; + case 13: + chipClock=3000000; + break; + case 14: + chipClock=COLOR_NTSC/3.0; break; } rate=chipClock/3; for (int i=0; i<4; i++) { oscBuf[i]->rate=rate/22; } + if (rateSel!=rateSelInit) { + rWrite(12,!rateSelInit); + rateSel=rateSelInit; + } } int DivPlatformMSM6295::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; adpcmMem=new unsigned char[getSampleMemCapacity(0)]; adpcmMemLen=0; - iface.adpcmMem=adpcmMem; - iface.sampleBank=0; dumpWrites=false; skipRegisterWrites=false; updateOsc=0; @@ -402,7 +417,6 @@ int DivPlatformMSM6295::init(DivEngine* p, int channels, int sugRate, unsigned i isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - msm=new msm6295_core(iface); setFlags(flags); reset(); return 4; @@ -412,7 +426,6 @@ void DivPlatformMSM6295::quit() { for (int i=0; i<4; i++) { delete oscBuf[i]; } - delete msm; delete[] adpcmMem; } diff --git a/src/engine/platform/msm6295.h b/src/engine/platform/msm6295.h index 2930a169b..f01c1b233 100644 --- a/src/engine/platform/msm6295.h +++ b/src/engine/platform/msm6295.h @@ -24,60 +24,31 @@ #include #include "sound/oki/msm6295.hpp" -class DivMSM6295Interface: public vgsound_emu_mem_intf { - public: - unsigned char* adpcmMem; - int sampleBank; - u8 read_byte(u32 address); - DivMSM6295Interface(): adpcmMem(NULL), sampleBank(0) {} -}; - -class DivPlatformMSM6295: public DivDispatch { +class DivPlatformMSM6295: public DivDispatch, public vgsound_emu_mem_intf { protected: - const unsigned short chanOffs[6]={ - 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 - }; - struct Channel { - unsigned char freqH, freqL; - int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; - unsigned char psgMode, autoEnvNum, autoEnvDen; + int note, ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, 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), - portaPauseFreq(0), note(0), ins(-1), - psgMode(1), - autoEnvNum(0), - autoEnvDen(0), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), - portaPause(false), - inPorta(false), furnacePCM(false), hardReset(false), vol(0), outVol(15), - sample(-1), - pan(3) {} + sample(-1) {} }; Channel chan[4]; DivDispatchOscBuffer* oscBuf[4]; @@ -85,56 +56,59 @@ class DivPlatformMSM6295: public DivDispatch { struct QueuedWrite { unsigned short addr; unsigned char val; - bool addrOrVal; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + unsigned short delay; + QueuedWrite(unsigned short a, unsigned char v, unsigned short d=32): + addr(a), + val(v), + delay(d) {} }; std::queue writes; - msm6295_core* msm; - unsigned char regPool[512]; + msm6295_core msm; unsigned char lastBusy; unsigned char* adpcmMem; size_t adpcmMemLen; - DivMSM6295Interface iface; unsigned char sampleBank; int delay, updateOsc; - bool extMode; - bool rateSel; + bool rateSel=false, rateSelInit=false; - short oldWrites[512]; - short pendingWrites[512]; - 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 keyOffAffectsArp(int ch); - float getPostAmp(); - void notifyInsChange(int ins); - void notifyInsDeletion(void* ins); - void poke(unsigned int addr, unsigned short val); - void poke(std::vector& wlist); - void setFlags(unsigned int flags); - 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(); + virtual u8 read_byte(u32 address) override; + virtual void acquire(short* bufL, short* bufR, size_t start, size_t len) override; + virtual int dispatch(DivCommand c) override; + virtual void* getChanState(int chan) override; + virtual DivMacroInt* getChanMacroInt(int ch) override; + virtual DivDispatchOscBuffer* getOscBuffer(int chan) override; + virtual unsigned char* getRegisterPool() override; + virtual int getRegisterPoolSize() override; + virtual void reset() override; + virtual void forceIns() override; + virtual void tick(bool sysTick=true) override; + virtual void muteChannel(int ch, bool mute) override; + virtual bool keyOffAffectsArp(int ch) override; + virtual float getPostAmp() override; + virtual void notifyInsChange(int ins) override; + virtual void notifyInsDeletion(void* ins) override; + virtual void poke(unsigned int addr, unsigned short val) override; + virtual void poke(std::vector& wlist) override; + virtual void setFlags(unsigned int flags) override; + virtual const char** getRegisterSheet() override; + virtual const char* getEffectName(unsigned char effect) override; + virtual const void* getSampleMem(int index) override; + virtual size_t getSampleMemCapacity(int index) override; + virtual size_t getSampleMemUsage(int index) override; + virtual void renderSamples() override; + + virtual int init(DivEngine* parent, int channels, int sugRate, unsigned int flags) override; + virtual void quit() override; + DivPlatformMSM6295(): + DivDispatch(), + vgsound_emu_mem_intf(), + msm(*this) {} ~DivPlatformMSM6295(); }; #endif diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 58a416936..50f9d8363 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -625,6 +625,10 @@ void* DivPlatformN163::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformN163::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformN163::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/n163.h b/src/engine/platform/n163.h index 6ecb1e0c5..5e44d3dd8 100644 --- a/src/engine/platform/n163.h +++ b/src/engine/platform/n163.h @@ -95,6 +95,7 @@ class DivPlatformN163: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/namcowsg.cpp b/src/engine/platform/namcowsg.cpp index 88cba3606..6bcf43d27 100644 --- a/src/engine/platform/namcowsg.cpp +++ b/src/engine/platform/namcowsg.cpp @@ -164,9 +164,6 @@ const char* DivPlatformNamcoWSG::getEffectName(unsigned char effect) { } void DivPlatformNamcoWSG::acquire(short* bufL, short* bufR, size_t start, size_t len) { - short* buf[2]={ - bufL+start, bufR+start - }; while (!writes.empty()) { QueuedWrite w=writes.front(); switch (devType) { @@ -186,7 +183,15 @@ void DivPlatformNamcoWSG::acquire(short* bufL, short* bufR, size_t start, size_t regPool[w.addr&0x3f]=w.val; writes.pop(); } - namco->sound_stream_update(buf,len); + for (size_t h=start; hsound_stream_update(buf,1); + for (int i=0; idata[oscBuf[i]->needle++]=namco->m_channel_list[i].last_out*chans; + } + } } void DivPlatformNamcoWSG::updateWave(int ch) { @@ -317,7 +322,7 @@ void DivPlatformNamcoWSG::tick(bool sysTick) { } rWrite((i<<3)+0x04,chan[i].freq&0xff); rWrite((i<<3)+0x05,(chan[i].freq>>8)&0xff); - rWrite((i<<3)+0x06,((chan[i].freq>>15)&15)|(i<<4)); + rWrite((i<<3)+0x06,((chan[i].freq>>16)&15)|(i<<4)); } break; case 30: @@ -331,7 +336,7 @@ void DivPlatformNamcoWSG::tick(bool sysTick) { } rWrite((i<<3)+0x103,chan[i].freq&0xff); rWrite((i<<3)+0x102,(chan[i].freq>>8)&0xff); - rWrite((i<<3)+0x101,((chan[i].freq>>15)&15)|(i<<4)); + rWrite((i<<3)+0x101,((chan[i].freq>>16)&15)|(i<<4)); } break; } @@ -467,6 +472,10 @@ void* DivPlatformNamcoWSG::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformNamcoWSG::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformNamcoWSG::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/namcowsg.h b/src/engine/platform/namcowsg.h index 652181e01..4ab81bdce 100644 --- a/src/engine/platform/namcowsg.h +++ b/src/engine/platform/namcowsg.h @@ -79,6 +79,7 @@ class DivPlatformNamcoWSG: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index b7ed8ae73..ae0aa84cd 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -65,7 +65,7 @@ const char** DivPlatformNES::getRegisterSheet() { const char* DivPlatformNES::getEffectName(unsigned char effect) { switch (effect) { case 0x11: - return "Write to delta modulation counter (0 to 7F)"; + return "11xx: 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)"; @@ -611,6 +611,10 @@ void* DivPlatformNES::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformNES::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformNES::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/nes.h b/src/engine/platform/nes.h index a03efc7a3..35c51df74 100644 --- a/src/engine/platform/nes.h +++ b/src/engine/platform/nes.h @@ -89,6 +89,7 @@ class DivPlatformNES: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 666826ba3..da760af7e 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -277,8 +277,13 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_ regPool[w.addr&511]=w.val; writes.pop(); } - - OPL3_Generate(&fm,o); os[0]+=o[0]; os[1]+=o[1]; + + if (downsample) { + OPL3_GenerateResampled(&fm,o); + } else { + OPL3_Generate(&fm,o); + } + os[0]+=o[0]; os[1]+=o[1]; if (adpcmChan>=0) { adpcmB->clock(); @@ -677,6 +682,9 @@ void DivPlatformOPL::muteChannel(int ch, bool mute) { fm.channel[outChanMap[ch]].muted=mute; } int ops=(slots[3][ch]!=255 && chan[ch].state.ops==4 && oplType==3)?4:2; + if (ch&1 && ch<12) { + if (chan[ch-1].fourOp) return; + } chan[ch].fourOp=(ops==4); update4OpMask=true; for (int i=0; i>2)&0xff); immWrite(12,(end>>10)&0xff); immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat - int freq=(65536.0*(double)s->rate)/(double)rate; + int freq=(65536.0*(double)s->rate)/(double)chipRateBase; immWrite(16,freq&0xff); immWrite(17,(freq>>8)&0xff); } @@ -1483,6 +1491,10 @@ void* DivPlatformOPL::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformOPL::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformOPL::getOscBuffer(int ch) { if (ch>=18) return NULL; return oscBuf[ch]; @@ -1504,7 +1516,12 @@ void DivPlatformOPL::reset() { fm_ymfm->reset(); } */ - OPL3_Reset(&fm,rate); + if (downsample) { + const unsigned int downsampledRate=(unsigned int)(49716.0*(double(rate)/chipRateBase)); + OPL3_Reset(&fm,downsampledRate); + } else { + OPL3_Reset(&fm,rate); + } if (dumpWrites) { addWrite(0xffffffff,0); } @@ -1621,6 +1638,7 @@ void DivPlatformOPL::setYMFM(bool use) { void DivPlatformOPL::setOPLType(int type, bool drums) { pretendYMU=false; + downsample=false; adpcmChan=-1; switch (type) { case 1: case 2: case 8950: @@ -1650,10 +1668,13 @@ void DivPlatformOPL::setOPLType(int type, bool drums) { if (type==759) { pretendYMU=true; adpcmChan=16; + } else if (type==4) { + downsample=true; } break; } - if (type==759) { + chipType=type; + if (type==759 || type==4) { oplType=3; } else if (type==8950) { oplType=1; @@ -1688,17 +1709,73 @@ void DivPlatformOPL::setFlags(unsigned int flags) { rate=chipClock/36; }*/ - if (oplType==3) { - chipClock=COLOR_NTSC*4.0; - rate=chipClock/288; - } else { - chipClock=COLOR_NTSC; - rate=chipClock/72; - } - - if (pretendYMU) { - rate=48000; - chipClock=rate*288; + switch (chipType) { + default: + case 1: case 2: case 8950: + switch (flags&0xff) { + case 0x00: + chipClock=COLOR_NTSC; + break; + case 0x01: + chipClock=COLOR_PAL*4.0/5.0; + break; + case 0x02: + chipClock=4000000.0; + break; + case 0x03: + chipClock=3000000.0; + break; + case 0x04: + chipClock=38400*13*8; // 31948800/8 + break; + case 0x05: + chipClock=3500000.0; + break; + } + rate=chipClock/72; + chipRateBase=double(rate); + break; + case 3: + switch (flags&0xff) { + case 0x00: + chipClock=COLOR_NTSC*4.0; + break; + case 0x01: + chipClock=COLOR_PAL*16.0/5.0; + break; + case 0x02: + chipClock=14000000.0; + break; + case 0x03: + chipClock=16000000.0; + break; + case 0x04: + chipClock=15000000.0; + break; + } + rate=chipClock/288; + chipRateBase=double(rate); + break; + case 4: + switch (flags&0xff) { + case 0x02: + chipClock=33868800.0; + break; + case 0x00: + chipClock=COLOR_NTSC*8.0; + break; + case 0x01: + chipClock=COLOR_PAL*32.0/5.0; + break; + } + chipRateBase=double(chipClock)/684.0; + rate=chipClock/768; + break; + case 759: + rate=48000; + chipRateBase=double(rate); + chipClock=rate*288; + break; } for (int i=0; i<18; i++) { diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index 61ea46481..5e8294c37 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -95,8 +95,8 @@ class DivPlatformOPL: public DivDispatch { const unsigned char** slots; const unsigned short* chanMap; const unsigned char* outChanMap; - double chipFreqBase; - int delay, oplType, chans, melodicChans, totalChans, adpcmChan, sampleBank; + double chipFreqBase, chipRateBase; + int delay, chipType, oplType, chans, melodicChans, totalChans, adpcmChan, sampleBank; unsigned char lastBusy; unsigned char drumState; unsigned char drumVol[5]; @@ -107,7 +107,7 @@ class DivPlatformOPL: public DivDispatch { unsigned char lfoValue; - bool useYMFM, update4OpMask, pretendYMU; + bool useYMFM, update4OpMask, pretendYMU, downsample; short oldWrites[512]; short pendingWrites[512]; @@ -125,6 +125,7 @@ class DivPlatformOPL: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 8ce215346..6d77ca1f7 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -896,6 +896,10 @@ void* DivPlatformOPLL::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformOPLL::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformOPLL::getOscBuffer(int ch) { if (ch>=9) return NULL; return oscBuf[ch]; diff --git a/src/engine/platform/opll.h b/src/engine/platform/opll.h index d2d208269..dad660dea 100644 --- a/src/engine/platform/opll.h +++ b/src/engine/platform/opll.h @@ -102,6 +102,7 @@ class DivPlatformOPLL: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index 2a535dbbc..82c8dff24 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -477,6 +477,10 @@ void* DivPlatformPCE::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformPCE::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformPCE::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/pce.h b/src/engine/platform/pce.h index 58fa6600a..870b5218a 100644 --- a/src/engine/platform/pce.h +++ b/src/engine/platform/pce.h @@ -89,6 +89,7 @@ class DivPlatformPCE: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index 9e8ba0f4c..57c72b677 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -27,11 +27,17 @@ #include #include #include +#ifdef HAVE_LINUX_INPUT #include +#endif +#ifdef HAVE_LINUX_KD #include +#endif #include +#ifdef HAVE_SYS_IO #include #endif +#endif #define PCSPKR_DIVIDER 4 #define CHIP_DIVIDER 1 @@ -80,6 +86,7 @@ void DivPlatformPCSpeaker::pcSpeakerThread() { } if (beepFD>=0) { switch (realOutMethod) { +#ifdef HAVE_LINUX_INPUT case 0: { // evdev static struct input_event ie; ie.time.tv_sec=r.tv_sec; @@ -98,11 +105,14 @@ void DivPlatformPCSpeaker::pcSpeakerThread() { } break; } +#endif +#ifdef HAVE_LINUX_KD case 1: // KIOCSOUND (on tty) if (ioctl(beepFD,KIOCSOUND,r.val)<0) { logW("ioctl error! %s",strerror(errno)); } break; +#endif case 2: { // /dev/port unsigned char bOut; bOut=0; @@ -144,11 +154,14 @@ void DivPlatformPCSpeaker::pcSpeakerThread() { } break; } +#ifdef HAVE_LINUX_KD case 3: // KIOCSOUND (on stdout) if (ioctl(beepFD,KIOCSOUND,r.val)<0) { logW("ioctl error! %s",strerror(errno)); } break; +#endif +#ifdef HAVE_SYS_IO case 4: // outb() if (r.val==0) { outb(inb(0x61)&(~3),0x61); @@ -163,6 +176,7 @@ void DivPlatformPCSpeaker::pcSpeakerThread() { } } break; +#endif } } else { //logV("not writing because fd is less than 0"); @@ -485,6 +499,10 @@ void* DivPlatformPCSpeaker::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformPCSpeaker::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformPCSpeaker::getOscBuffer(int ch) { return oscBuf; } @@ -540,6 +558,7 @@ void DivPlatformPCSpeaker::reset() { break; case 4: // outb() beepFD=-1; +#ifdef HAVE_SYS_IO if (ioperm(0x61,8,1)<0) { logW("ioperm 0x61: %s",strerror(errno)); break; @@ -553,6 +572,9 @@ void DivPlatformPCSpeaker::reset() { break; } beepFD=STDOUT_FILENO; +#else + errno=ENOSYS; +#endif break; } if (beepFD<0) { diff --git a/src/engine/platform/pcspkr.h b/src/engine/platform/pcspkr.h index 8b0371a89..cb6f070fe 100644 --- a/src/engine/platform/pcspkr.h +++ b/src/engine/platform/pcspkr.h @@ -98,6 +98,7 @@ class DivPlatformPCSpeaker: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/pet.cpp b/src/engine/platform/pet.cpp index 233027e21..9b3c99886 100644 --- a/src/engine/platform/pet.cpp +++ b/src/engine/platform/pet.cpp @@ -21,15 +21,15 @@ #include "../engine.h" #include -#define rWrite(a,v) {regPool[(a)]=(v)&0xff; if((a)==10) {chan.sreg=(v); chan.cnt=2;}} - #define CHIP_DIVIDER 16 #define SAMP_DIVIDER 4 const char* regCheatSheet6522[]={ "T2L", "08", + "T2H", "09", "SR", "0A", "ACR", "0B", + "PCR", "0C", NULL }; @@ -46,26 +46,45 @@ const char* DivPlatformPET::getEffectName(unsigned char effect) { return NULL; } +// high-level emulation of 6522 shift register and driver software for now +void DivPlatformPET::rWrite(unsigned int addr, unsigned char val) { + bool hwSROutput=((regPool[11]>>2)&7)==4; + switch (addr) { + case 9: + // simulate phase reset from switching between hw/sw shift registers + if ((regPool[9]==0)^(val==0)) { + chan.sreg=chan.wave; + } + break; + case 10: + chan.sreg=val; + if (hwSROutput) chan.cnt=2; + break; + } + regPool[addr]=val; +} + void DivPlatformPET::acquire(short* bufL, short* bufR, size_t start, size_t len) { - // high-level emulation of 6522 shift register for now - int t2=regPool[8]*2+4; - if (((regPool[11]>>2)&7)==4) { + bool hwSROutput=((regPool[11]>>2)&7)==4; + if (chan.enable) { + int reload=regPool[8]*2+4; + if (!hwSROutput) { + reload+=regPool[9]*512; + } for (size_t h=start; h0) { - int adv=MIN(cycs,chan.cnt); - chan.cnt-=adv; - cycs-=adv; - if (chan.cnt==0) { - chan.out=(chan.sreg&1)*32767; - chan.sreg=(chan.sreg>>1)|((chan.sreg&1)<<7); - chan.cnt=t2; - } + if (SAMP_DIVIDER>chan.cnt) { + chan.out=(chan.sreg&1)*32767; + chan.sreg=(chan.sreg>>1)|((chan.sreg&1)<<7); + chan.cnt+=reload-SAMP_DIVIDER; + } else { + chan.cnt-=SAMP_DIVIDER; } bufL[h]=chan.out; bufR[h]=chan.out; oscBuf->data[oscBuf->needle++]=chan.out; } + // emulate driver writes to PCR + if (!hwSROutput) regPool[12]=chan.out?0xe0:0xc0; } else { chan.out=0; for (size_t h=start; h0) { - if (regPool[11]!=16) { - rWrite(11,16); - rWrite(10,chan.wave); - } + chan.enable=true; + rWrite(11,regPool[9]==0?16:0); } else { + chan.enable=false; rWrite(11,0); } } @@ -118,21 +136,22 @@ void DivPlatformPET::tick(bool sysTick) { chan.freqChanged=true; } if (chan.freqChanged || chan.keyOn || chan.keyOff) { - chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2,chipClock,CHIP_DIVIDER); - if (chan.freq>257) chan.freq=257; - if (chan.freq<2) chan.freq=2; - rWrite(8,chan.freq-2); + chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2,chipClock,CHIP_DIVIDER)-2; + if (chan.freq>65535) chan.freq=65535; + if (chan.freq<0) chan.freq=0; + rWrite(8,chan.freq&0xff); + rWrite(9,chan.freq>>8); if (chan.keyOn) { if (!chan.std.vol.will) { chan.outVol=chan.vol; - writeOutVol(); } chan.keyOn=false; } if (chan.keyOff) { - rWrite(11,0); chan.keyOff=false; } + // update mode setting and channel enable + writeOutVol(); chan.freqChanged=false; } } @@ -249,6 +268,10 @@ void* DivPlatformPET::getChanState(int ch) { return &chan; } +DivMacroInt* DivPlatformPET::getChanMacroInt(int ch) { + return &chan.std; +} + DivDispatchOscBuffer* DivPlatformPET::getOscBuffer(int ch) { return oscBuf; } diff --git a/src/engine/platform/pet.h b/src/engine/platform/pet.h index 1e5e49ce5..06c7e736a 100644 --- a/src/engine/platform/pet.h +++ b/src/engine/platform/pet.h @@ -26,7 +26,7 @@ class DivPlatformPET: public DivDispatch { struct Channel { int freq, baseFreq, pitch, pitch2, note, ins; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, enable; int vol, outVol, wave; unsigned char sreg; int cnt; @@ -49,6 +49,7 @@ class DivPlatformPET: public DivDispatch { keyOn(false), keyOff(false), inPorta(false), + enable(false), vol(1), outVol(1), wave(0b00001111), @@ -66,6 +67,7 @@ class DivPlatformPET: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); @@ -84,6 +86,7 @@ class DivPlatformPET: public DivDispatch { ~DivPlatformPET(); private: void writeOutVol(); + void rWrite(unsigned int addr, unsigned char val); }; #endif diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index 6003ede02..33996a924 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -530,6 +530,10 @@ void* DivPlatformQSound::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformQSound::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformQSound::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/qsound.h b/src/engine/platform/qsound.h index d12e952ef..285760138 100644 --- a/src/engine/platform/qsound.h +++ b/src/engine/platform/qsound.h @@ -78,6 +78,7 @@ class DivPlatformQSound: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/rf5c68.cpp b/src/engine/platform/rf5c68.cpp index ae5138c15..2106a5a72 100644 --- a/src/engine/platform/rf5c68.cpp +++ b/src/engine/platform/rf5c68.cpp @@ -300,6 +300,10 @@ void* DivPlatformRF5C68::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformRF5C68::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformRF5C68::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/rf5c68.h b/src/engine/platform/rf5c68.h index 79d7d58b0..6946b4900 100644 --- a/src/engine/platform/rf5c68.h +++ b/src/engine/platform/rf5c68.h @@ -75,6 +75,7 @@ class DivPlatformRF5C68: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/saa.cpp b/src/engine/platform/saa.cpp index c3ce14442..c562838a2 100644 --- a/src/engine/platform/saa.cpp +++ b/src/engine/platform/saa.cpp @@ -71,28 +71,6 @@ const char* DivPlatformSAA1099::getEffectName(unsigned char effect) { return NULL; } -void DivPlatformSAA1099::acquire_mame(short* bufL, short* bufR, size_t start, size_t len) { - if (saaBufLenClear(); - break; - case DIV_SAA_CORE_E: - break; - } + saa_saaSound->Clear(); for (int i=0; i<6; i++) { chan[i]=DivPlatformSAA1099::Channel(); chan[i].std.setEngine(parent); @@ -496,16 +459,8 @@ void DivPlatformSAA1099::setFlags(unsigned int flags) { oscBuf[i]->rate=rate; } - switch (core) { - case DIV_SAA_CORE_MAME: - break; - case DIV_SAA_CORE_SAASOUND: - saa_saaSound->SetClockRate(chipClock); - saa_saaSound->SetSampleRate(rate); - break; - case DIV_SAA_CORE_E: - break; - } + saa_saaSound->SetClockRate(chipClock); + saa_saaSound->SetSampleRate(rate); } void DivPlatformSAA1099::poke(unsigned int addr, unsigned short val) { @@ -516,10 +471,6 @@ void DivPlatformSAA1099::poke(std::vector& wlist) { for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); } -void DivPlatformSAA1099::setCore(DivSAACores c) { - core=c; -} - int DivPlatformSAA1099::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; @@ -529,11 +480,9 @@ int DivPlatformSAA1099::init(DivEngine* p, int channels, int sugRate, unsigned i isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - if (core==DIV_SAA_CORE_SAASOUND) { - saa_saaSound=CreateCSAASound(); - saa_saaSound->SetOversample(1); - saa_saaSound->SetSoundParameters(SAAP_NOFILTER|SAAP_16BIT|SAAP_STEREO); - } + saa_saaSound=CreateCSAASound(); + saa_saaSound->SetOversample(1); + saa_saaSound->SetSoundParameters(SAAP_NOFILTER|SAAP_16BIT|SAAP_STEREO); setFlags(flags); saaBufLen=65536; for (int i=0; i<2; i++) saaBuf[i]=new short[saaBufLen]; diff --git a/src/engine/platform/saa.h b/src/engine/platform/saa.h index 70edaa249..0efd498d6 100644 --- a/src/engine/platform/saa.h +++ b/src/engine/platform/saa.h @@ -22,15 +22,8 @@ #include "../dispatch.h" #include "../macroInt.h" #include -#include "sound/saa1099.h" #include "../../../extern/SAASound/src/SAASound.h" -enum DivSAACores { - DIV_SAA_CORE_MAME=0, - DIV_SAA_CORE_SAASOUND, - DIV_SAA_CORE_E -}; - class DivPlatformSAA1099: public DivDispatch { protected: struct Channel { @@ -58,8 +51,6 @@ class DivPlatformSAA1099: public DivDispatch { QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} }; std::queue writes; - DivSAACores core; - saa1099_device saa; CSAASound* saa_saaSound; unsigned char regPool[32]; unsigned char lastBusy; @@ -83,14 +74,13 @@ class DivPlatformSAA1099: public DivDispatch { unsigned char saaNoise[2]; friend void putDispatchChan(void*,int,int); - void acquire_e(short* bufL, short* bufR, size_t start, size_t len); void acquire_saaSound(short* bufL, short* bufR, size_t start, size_t len); - void acquire_mame(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); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); @@ -98,7 +88,6 @@ class DivPlatformSAA1099: public DivDispatch { void forceIns(); void tick(bool sysTick=true); void muteChannel(int ch, bool mute); - void setCore(DivSAACores core); void setFlags(unsigned int flags); bool isStereo(); int getPortaFloor(int ch); diff --git a/src/engine/platform/scc.cpp b/src/engine/platform/scc.cpp index 2ab6f1aa4..8175bc01b 100644 --- a/src/engine/platform/scc.cpp +++ b/src/engine/platform/scc.cpp @@ -310,6 +310,10 @@ void* DivPlatformSCC::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformSCC::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformSCC::getOscBuffer(int ch) { return oscBuf[ch]; } @@ -328,7 +332,7 @@ void DivPlatformSCC::reset() { for (int i=0; i<5; i++) { chan[i]=DivPlatformSCC::Channel(); chan[i].std.setEngine(parent); - chan[i].ws.setEngine(parent); + chan[i].ws.setEngine(parent,128); chan[i].ws.init(NULL,32,255,false); chan[i].vol=15; chan[i].outVol=15; @@ -373,6 +377,27 @@ void DivPlatformSCC::setChipModel(bool isplus) { isPlus=isplus; } +void DivPlatformSCC::setFlags(unsigned int flags) { + switch (flags&0x7f) { + case 0x00: + chipClock=COLOR_NTSC/2.0; + break; + case 0x01: + chipClock=COLOR_PAL*2.0/5.0; + break; + case 0x02: + chipClock=3000000.0/2.0; + break; + case 0x03: + chipClock=4000000.0/2.0; + break; + } + rate=chipClock/8; + for (int i=0; i<5; i++) { + oscBuf[i]->rate=rate; + } +} + int DivPlatformSCC::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; @@ -382,11 +407,7 @@ int DivPlatformSCC::init(DivEngine* p, int channels, int sugRate, unsigned int f isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - chipClock=COLOR_NTSC/2.0; - rate=chipClock/8; - for (int i=0; i<5; i++) { - oscBuf[i]->rate=rate; - } + setFlags(flags); if (isPlus) { scc=new k052539_scc_core; regBase=0xa0; diff --git a/src/engine/platform/scc.h b/src/engine/platform/scc.h index df6ef84e5..43a8be5ae 100644 --- a/src/engine/platform/scc.h +++ b/src/engine/platform/scc.h @@ -70,6 +70,7 @@ class DivPlatformSCC: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); @@ -84,6 +85,7 @@ class DivPlatformSCC: public DivDispatch { void poke(std::vector& wlist); const char** getRegisterSheet(); const char* getEffectName(unsigned char effect); + void setFlags(unsigned int flags); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void setChipModel(bool isPlus); void quit(); diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 5a19340a3..250b6f2d2 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -400,6 +400,10 @@ void* DivPlatformSegaPCM::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformSegaPCM::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformSegaPCM::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/segapcm.h b/src/engine/platform/segapcm.h index f5fa4ecb9..6edc85302 100644 --- a/src/engine/platform/segapcm.h +++ b/src/engine/platform/segapcm.h @@ -101,6 +101,7 @@ class DivPlatformSegaPCM: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/sms.cpp b/src/engine/platform/sms.cpp index d9e2e5fc0..0556d2e45 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -21,15 +21,21 @@ #include "../engine.h" #include -#define rWrite(v) {if (!skipRegisterWrites) {writes.push(v); if (dumpWrites) {addWrite(0x200,v);}}} +#define rWrite(a,v) {if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(0x200+a,v);}}} const char* regCheatSheetSN[]={ "DATA", "0", NULL }; +const char* regCheatSheetGG[]={ + "DATA", "0", + "Stereo", "1", + NULL +}; + const char** DivPlatformSMS::getRegisterSheet() { - return regCheatSheetSN; + return stereo?regCheatSheetGG:regCheatSheetSN; } const char* DivPlatformSMS::getEffectName(unsigned char effect) { @@ -45,8 +51,10 @@ void DivPlatformSMS::acquire_nuked(short* bufL, short* bufR, size_t start, size_ int o=0; for (size_t h=start; h32767) o=32767; - bufL[h]=o; + bufL[h]=bufR[h]=o; for (int i=0; i<4; i++) { if (isMuted[i]) { oscBuf[i]->data[oscBuf[i]->needle++]=0; @@ -81,12 +89,20 @@ void DivPlatformSMS::acquire_nuked(short* bufL, short* bufR, size_t start, size_ void DivPlatformSMS::acquire_mame(short* bufL, short* bufR, size_t start, size_t len) { while (!writes.empty()) { - unsigned char w=writes.front(); - sn->write(w); + QueuedWrite w=writes.front(); + if (stereo && (w.addr==1)) + sn->stereo_w(w.val); + else if (w.addr==0) { + sn->write(w.val); + } writes.pop(); } for (size_t h=start; hsound_stream_update(bufL+h,1); + short* outs[2]={ + &bufL[h], + &bufR[h] + }; + sn->sound_stream_update(outs,1); for (int i=0; i<4; i++) { if (isMuted[i]) { oscBuf[i]->data[oscBuf[i]->needle++]=0; @@ -105,23 +121,17 @@ void DivPlatformSMS::acquire(short* bufL, short* bufR, size_t start, size_t len) } } -int DivPlatformSMS::acquireOne() { - short v; - sn->sound_stream_update(&v,1); - return v; -} - void DivPlatformSMS::tick(bool sysTick) { for (int i=0; i<4; i++) { - int CHIP_DIVIDER=64; - if (i==3 && isRealSN) CHIP_DIVIDER=60; + double CHIP_DIVIDER=toneDivider; + if (i==3) CHIP_DIVIDER=noiseDivider; 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)); if (chan[i].outVol<0) chan[i].outVol=0; // old formula // ((chan[i].vol&15)*MIN(15,chan[i].std.vol.val))>>4; - rWrite(0x90|(i<<5)|(isMuted[i]?15:(15-(chan[i].outVol&15)))); + rWrite(0,0x90|(i<<5)|(isMuted[i]?15:(15-(chan[i].outVol&15)))); } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { @@ -160,6 +170,13 @@ void DivPlatformSMS::tick(bool sysTick) { } } } + if (stereo) { + if (chan[i].std.panL.had) { + lastPan&=~(0x11<>1)&1)<<(i+4)); + rWrite(1,lastPan); + } + } if (chan[i].std.pitch.had) { if (chan[i].std.pitch.mode) { chan[i].pitch2+=chan[i].std.pitch.val; @@ -172,12 +189,12 @@ void DivPlatformSMS::tick(bool sysTick) { } for (int i=0; i<3; i++) { if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,64); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,toneDivider); if (chan[i].freq>1023) chan[i].freq=1023; 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); + rWrite(0,0x80|i<<5|(chan[i].freq&15)); + rWrite(0,chan[i].freq>>4); // what? /*if (i==2 && snNoiseMode&2) { chan[3].baseFreq=chan[2].baseFreq; @@ -187,24 +204,24 @@ void DivPlatformSMS::tick(bool sysTick) { } } if (chan[3].freqChanged || updateSNMode) { - chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2,chipClock,isRealSN?60:64); + chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2,chipClock,noiseDivider); 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 if (updateSNMode || resetPhase) { if (snNoiseMode&1) { - rWrite(0xe7); + rWrite(0,0xe7); } else { - rWrite(0xe3); + rWrite(0,0xe3); } if (updateSNMode) { - rWrite(0xdf); + rWrite(0,0xdf); } } if (chan[3].freqChanged) { - rWrite(0xc0|(chan[3].freq&15)); - rWrite(chan[3].freq>>4); + rWrite(0,0xc0|(chan[3].freq&15)); + rWrite(0,chan[3].freq>>4); } } else { // 3 fixed values unsigned char value; @@ -221,7 +238,7 @@ void DivPlatformSMS::tick(bool sysTick) { value=2-value; if (value!=oldValue || updateSNMode || resetPhase) { oldValue=value; - rWrite(0xe0|value|((snNoiseMode&1)<<2)); + rWrite(0,0xe0|value|((snNoiseMode&1)<<2)); } } } @@ -231,8 +248,8 @@ void DivPlatformSMS::tick(bool sysTick) { } int DivPlatformSMS::dispatch(DivCommand c) { - int CHIP_DIVIDER=64; - if (c.chan==3 && isRealSN) CHIP_DIVIDER=60; + double CHIP_DIVIDER=toneDivider; + if (c.chan==3) CHIP_DIVIDER=noiseDivider; switch (c.cmd) { case DIV_CMD_NOTE_ON: if (c.value!=DIV_NOTE_NULL) { @@ -242,7 +259,7 @@ int DivPlatformSMS::dispatch(DivCommand c) { chan[c.chan].actualNote=c.value; } chan[c.chan].active=true; - rWrite(0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15)))); + rWrite(0,0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15)))); chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD)); if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; @@ -250,7 +267,7 @@ int DivPlatformSMS::dispatch(DivCommand c) { break; case DIV_CMD_NOTE_OFF: chan[c.chan].active=false; - rWrite(0x9f|c.chan<<5); + rWrite(0,0x9f|c.chan<<5); chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: @@ -267,7 +284,7 @@ int DivPlatformSMS::dispatch(DivCommand c) { if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } - if (chan[c.chan].active) rWrite(0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15)))); + if (chan[c.chan].active) rWrite(0,0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15)))); } break; case DIV_CMD_GET_VOLUME: @@ -307,6 +324,19 @@ int DivPlatformSMS::dispatch(DivCommand c) { snNoiseMode=(c.value&1)|((c.value&16)>>3); updateSNMode=true; break; + case DIV_CMD_PANNING: { + if (stereo) { + if (c.chan>3) c.chan=3; + lastPan&=~(0x11<0) pan|=0x10; + if (c.value2>0) pan|=0x01; + if (pan==0) pan=0x11; + lastPan|=pan<device_start(); - YMPSG_Init(&sn_nuked,isRealSN); + YMPSG_Init(&sn_nuked,isRealSN,12,isRealSN?13:15,isRealSN?16383:32767); snNoiseMode=3; - rWrite(0xe7); + rWrite(0,0xe7); updateSNMode=false; oldValue=0xff; + lastPan=0xff; + if (stereo) { + rWrite(1,0xff); + } +} + +bool DivPlatformSMS::isStereo() { + return stereo; } bool DivPlatformSMS::keyOffAffectsArp(int ch) { @@ -392,45 +434,109 @@ void DivPlatformSMS::notifyInsDeletion(void* ins) { } void DivPlatformSMS::poke(unsigned int addr, unsigned short val) { - rWrite(val); + rWrite(addr,val); } void DivPlatformSMS::poke(std::vector& wlist) { - for (DivRegWrite& i: wlist) rWrite(i.val); + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); } void DivPlatformSMS::setFlags(unsigned int flags) { - if ((flags&3)==3) { - chipClock=COLOR_NTSC/2.0; - } else if ((flags&3)==2) { - chipClock=4000000; - } else if ((flags&3)==1) { - chipClock=COLOR_PAL*4.0/5.0; - } else { - chipClock=COLOR_NTSC; + switch (flags&0xff03) { + default: + case 0x0000: + chipClock=COLOR_NTSC; + break; + case 0x0001: + chipClock=COLOR_PAL*4.0/5.0; + break; + case 0x0002: + chipClock=4000000; + break; + case 0x0003: + chipClock=COLOR_NTSC/2.0; + break; + case 0x0100: + chipClock=3000000; + break; + case 0x0101: + chipClock=2000000; + break; + case 0x0102: + chipClock=COLOR_NTSC/8.0; + break; } resetPhase=!(flags&16); - + divider=16; + toneDivider=64.0; + noiseDivider=64.0; if (sn!=NULL) delete sn; - switch ((flags>>2)&3) { - case 1: // TI - sn=new sn76496_base_device(0x4000, 0x4000, 0x01, 0x02, true, 1, false, true); - isRealSN=true; - break; - case 2: // TI+Atari - sn=new sn76496_base_device(0x4000, 0x0f35, 0x01, 0x02, true, 1, false, true); - isRealSN=true; - break; - case 3: // Game Gear (not fully emulated yet!) - sn=new sn76496_base_device(0x8000, 0x8000, 0x01, 0x08, false, 1, false, false); - isRealSN=false; - break; + switch (flags&0xcc) { default: // Sega - sn=new sn76496_base_device(0x8000, 0x8000, 0x01, 0x08, false, 1, false, false); + case 0x00: + sn=new segapsg_device(); isRealSN=false; + stereo=false; + break; + case 0x04: // TI SN76489 + sn=new sn76489_device(); + isRealSN=true; + stereo=false; + noiseDivider=60.0; // 64 for match to tone frequency on non-Sega PSG but compatibility + break; + case 0x08: // TI+Atari + sn=new sn76496_base_device(0x4000, 0x0f35, 0x01, 0x02, true, false, 1/*8*/, false, true); + isRealSN=true; + stereo=false; + noiseDivider=60.0; + break; + case 0x0c: // Game Gear (not fully emulated yet!) + sn=new gamegear_device(); + isRealSN=false; + stereo=true; + break; + case 0x40: // TI SN76489A + sn=new sn76489a_device(); + isRealSN=false; // TODO + stereo=false; + noiseDivider=60.0; + break; + case 0x44: // TI SN76496 + sn=new sn76496_device(); + isRealSN=false; // TODO + stereo=false; + noiseDivider=60.0; + break; + case 0x48: // NCR 8496 + sn=new ncr8496_device(); + isRealSN=false; + stereo=false; + noiseDivider=60.0; + break; + case 0x4c: // Tandy PSSJ 3-voice sound + sn=new pssj3_device(); + isRealSN=false; + stereo=false; + noiseDivider=60.0; + break; + case 0x80: // TI SN94624 + sn=new sn94624_device(); + isRealSN=true; + stereo=false; + divider=2; + toneDivider=8.0; + noiseDivider=7.5; + break; + case 0x84: // TI SN76494 + sn=new sn76494_device(); + isRealSN=false; // TODO + stereo=false; + divider=2; + toneDivider=8.0; + noiseDivider=7.5; break; } - rate=chipClock/16; + rate=chipClock/divider; for (int i=0; i<4; i++) { oscBuf[i]->rate=rate; } @@ -446,6 +552,7 @@ int DivPlatformSMS::init(DivEngine* p, int channels, int sugRate, unsigned int f skipRegisterWrites=false; resetPhase=false; oldValue=0xff; + lastPan=0xff; for (int i=0; i<4; i++) { isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; diff --git a/src/engine/platform/sms.h b/src/engine/platform/sms.h index 3c97a9a5c..35bb44bab 100644 --- a/src/engine/platform/sms.h +++ b/src/engine/platform/sms.h @@ -58,29 +58,41 @@ class DivPlatformSMS: public DivDispatch { Channel chan[4]; DivDispatchOscBuffer* oscBuf[4]; bool isMuted[4]; + unsigned char lastPan; unsigned char oldValue; unsigned char snNoiseMode; + int divider=16; + double toneDivider=64.0; + double noiseDivider=64.0; bool updateSNMode; bool resetPhase; bool isRealSN; + bool stereo; bool nuked; sn76496_base_device* sn; ympsg_t sn_nuked; - std::queue writes; + struct QueuedWrite { + unsigned short addr; + unsigned char val; + bool addrOrVal; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + }; + std::queue writes; friend void putDispatchChan(void*,int,int); void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len); void acquire_mame(short* bufL, short* bufR, size_t start, size_t len); public: - int acquireOne(); void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); void reset(); void forceIns(); void tick(bool sysTick=true); void muteChannel(int ch, bool mute); + bool isStereo(); bool keyOffAffectsArp(int ch); bool keyOffAffectsPorta(int ch); int getPortaFloor(int ch); diff --git a/src/engine/platform/sound/c64/sid.cc b/src/engine/platform/sound/c64/sid.cc index d6ebbb449..aaed90a5f 100644 --- a/src/engine/platform/sound/c64/sid.cc +++ b/src/engine/platform/sound/c64/sid.cc @@ -60,6 +60,13 @@ SID::~SID() delete[] fir; } +// ---------------------------------------------------------------------------- +// Get DC offset of channel. +// ---------------------------------------------------------------------------- +sound_sample SID::get_dc(int ch) { + return voice[ch].getDC(); +} + // ---------------------------------------------------------------------------- // Mute/unmute channel. // ---------------------------------------------------------------------------- diff --git a/src/engine/platform/sound/c64/sid.h b/src/engine/platform/sound/c64/sid.h index f6b392713..e8b0d5c6d 100644 --- a/src/engine/platform/sound/c64/sid.h +++ b/src/engine/platform/sound/c64/sid.h @@ -34,6 +34,7 @@ public: sound_sample last_chan_out[3]; + sound_sample get_dc(int ch); void set_is_muted(int ch, bool val); void set_chip_model(chip_model model); void enable_filter(bool enable); diff --git a/src/engine/platform/sound/c64/voice.h b/src/engine/platform/sound/c64/voice.h index d248f32ae..85b4fedec 100644 --- a/src/engine/platform/sound/c64/voice.h +++ b/src/engine/platform/sound/c64/voice.h @@ -38,6 +38,7 @@ public: // Amplitude modulated waveform output. // Range [-2048*255, 2047*255]. RESID_INLINE sound_sample output(); + RESID_INLINE sound_sample getDC(); protected: WaveformGenerator wave; @@ -72,6 +73,12 @@ sound_sample Voice::output() return (wave.output() - wave_zero)*envelope.output() + voice_DC; } +RESID_INLINE +sound_sample Voice::getDC() +{ + return voice_DC; +} + #endif // RESID_INLINING || defined(__VOICE_CC__) #endif // not __VOICE_H__ diff --git a/src/engine/platform/sound/namco.cpp b/src/engine/platform/sound/namco.cpp index 82141301f..811c34ab8 100644 --- a/src/engine/platform/sound/namco.cpp +++ b/src/engine/platform/sound/namco.cpp @@ -172,10 +172,11 @@ void namco_audio_device::build_decoded_waveform(uint8_t *rgnbase) /* generate sound by oversampling */ -uint32_t namco_audio_device::namco_update_one(short* buffer, int size, const int16_t *wave, uint32_t counter, uint32_t freq) +uint32_t namco_audio_device::namco_update_one(short* buffer, int size, const int16_t *wave, uint32_t counter, uint32_t freq, int16_t& last_out) { for (int sampindex = 0; sampindex < size; sampindex++) { + last_out=wave[WAVEFORM_POSITION(counter)]; buffer[sampindex]+=wave[WAVEFORM_POSITION(counter)]; counter += freq; } @@ -700,7 +701,7 @@ void namco_audio_device::sound_stream_update(short** outputs, int len) const int16_t *lw = &m_waveform[lv][voice->waveform_select * 32]; /* generate sound into the buffer */ - c = namco_update_one(lmix, len, lw, voice->counter, voice->frequency); + c = namco_update_one(lmix, len, lw, voice->counter, voice->frequency, voice->last_out); } /* only update if we have non-zero right volume */ @@ -709,7 +710,7 @@ void namco_audio_device::sound_stream_update(short** outputs, int len) const int16_t *rw = &m_waveform[rv][voice->waveform_select * 32]; /* generate sound into the buffer */ - c = namco_update_one(rmix, len, rw, voice->counter, voice->frequency); + c = namco_update_one(rmix, len, rw, voice->counter, voice->frequency, voice->last_out); } /* update the counter for this voice */ @@ -789,7 +790,7 @@ void namco_audio_device::sound_stream_update(short** outputs, int len) const int16_t *w = &m_waveform[v][voice->waveform_select * 32]; /* generate sound into buffer and update the counter for this voice */ - voice->counter = namco_update_one(buffer, len, w, voice->counter, voice->frequency); + voice->counter = namco_update_one(buffer, len, w, voice->counter, voice->frequency, voice->last_out); } } } diff --git a/src/engine/platform/sound/namco.h b/src/engine/platform/sound/namco.h index 507eaed29..d3844abc1 100644 --- a/src/engine/platform/sound/namco.h +++ b/src/engine/platform/sound/namco.h @@ -31,6 +31,7 @@ public: uint32_t noise_counter; int32_t noise_hold; int32_t waveform_select; + int16_t last_out; }; namco_audio_device(uint32_t clock); @@ -43,7 +44,7 @@ public: void build_decoded_waveform( uint8_t *rgnbase ); void update_namco_waveform(int offset, uint8_t data); - uint32_t namco_update_one(short* buffer, int size, const int16_t *wave, uint32_t counter, uint32_t freq); + uint32_t namco_update_one(short* buffer, int size, const int16_t *wave, uint32_t counter, uint32_t freq, int16_t& last_out); /* waveform region */ uint8_t* m_wave_ptr; diff --git a/src/engine/platform/sound/sn76496.cpp b/src/engine/platform/sound/sn76496.cpp index 351c94d86..e76b403f8 100644 --- a/src/engine/platform/sound/sn76496.cpp +++ b/src/engine/platform/sound/sn76496.cpp @@ -128,7 +128,7 @@ 10/12/2019: Michael Zapf * READY line handling by own emu_timer, not depending on sound_stream_update - additional modifications by tildearrow for furnace + additional modifications by tildearrow, cam900 for furnace TODO: * Implement the TMS9919 - any difference to sn94624? * Implement the T6W28; has registers in a weird order, needs writes @@ -150,18 +150,20 @@ sn76496_base_device::sn76496_base_device( int feedbackmask, - int noise_start, + int noise_start, int noisetap1, int noisetap2, bool negate, + bool stereo, int clockdivider, bool ncr, bool sega) : m_feedback_mask(feedbackmask) - , m_noise_start(noise_start) + , m_noise_start(noise_start) , m_whitenoise_tap1(noisetap1) , m_whitenoise_tap2(noisetap2) - , m_negate(negate) + , m_negate(negate) + , m_stereo(stereo) , m_clock_divider(clockdivider) , m_ncr_style_psg(ncr) , m_sega_style_psg(sega) @@ -169,10 +171,54 @@ sn76496_base_device::sn76496_base_device( } sn76496_device::sn76496_device() - : sn76496_base_device(0x8000, 0x8000, 0x01, 0x08, false, 1, false, false) + : sn76496_base_device(0x10000, 0x04, 0x08, false, false, 1/*8*/, false, true) { } +y2404_device::y2404_device() + : sn76496_base_device(0x10000, 0x04, 0x08, false, false, 1/*8*/, false, true) +{ +} + +sn76489_device::sn76489_device() + : sn76496_base_device(0x4000, 0x01, 0x02, true, false, 1/*8*/, false, true) +{ +} + +sn76489a_device::sn76489a_device() + : sn76496_base_device(0x10000, 0x04, 0x08, false, false, 1/*8*/, false, true) +{ +} + +sn76494_device::sn76494_device() + : sn76496_base_device(0x10000, 0x04, 0x08, false, false, 1, false, true) +{ +} + +sn94624_device::sn94624_device() + : sn76496_base_device(0x4000, 0x01, 0x02, true, false, 1, false, true) +{ +} + +ncr8496_device::ncr8496_device() + : sn76496_base_device(0x8000, 0x02, 0x20, true, false, 1/*8*/, true, true) +{ +} + +pssj3_device::pssj3_device() + : sn76496_base_device(0x8000, 0x02, 0x20, false, false, 1/*8*/, true, true) +{ +} + +gamegear_device::gamegear_device() + : sn76496_base_device(0x8000, 0x01, 0x08, true, true, 1/*8*/, false, false) +{ +} + +segapsg_device::segapsg_device() + : sn76496_base_device(0x8000, 0x01, 0x08, true, false, 1/*8*/, false, false) +{ +} void sn76496_base_device::device_start() { @@ -199,6 +245,7 @@ void sn76496_base_device::device_start() m_RNG = m_feedback_mask; m_output[3] = m_RNG & 1; + m_stereo_mask = 0xFF; // all channels enabled m_current_clock = m_clock_divider-1; // set gain @@ -225,6 +272,11 @@ void sn76496_base_device::device_start() m_ready_state = true; } +void sn76496_base_device::stereo_w(u8 data) +{ + if (m_stereo) m_stereo_mask = data; +} + void sn76496_base_device::write(u8 data) { int n, r, c; @@ -285,7 +337,7 @@ inline bool sn76496_base_device::in_noise_mode() return ((m_register[6] & 4)!=0); } -void sn76496_base_device::sound_stream_update(short* outputs, int outLen) +void sn76496_base_device::sound_stream_update(short** outputs, int outLen) { int i; @@ -336,13 +388,30 @@ void sn76496_base_device::sound_stream_update(short* outputs, int outLen) } } + if (m_stereo) + { + out = ((((m_stereo_mask & 0x10)!=0) && (m_output[0]!=0))? m_volume[0] : 0) + + ((((m_stereo_mask & 0x20)!=0) && (m_output[1]!=0))? m_volume[1] : 0) + + ((((m_stereo_mask & 0x40)!=0) && (m_output[2]!=0))? m_volume[2] : 0) + + ((((m_stereo_mask & 0x80)!=0) && (m_output[3]!=0))? m_volume[3] : 0); + + out2= ((((m_stereo_mask & 0x1)!=0) && (m_output[0]!=0))? m_volume[0] : 0) + + ((((m_stereo_mask & 0x2)!=0) && (m_output[1]!=0))? m_volume[1] : 0) + + ((((m_stereo_mask & 0x4)!=0) && (m_output[2]!=0))? m_volume[2] : 0) + + ((((m_stereo_mask & 0x8)!=0) && (m_output[3]!=0))? m_volume[3] : 0); + } + else + { out= ((m_output[0]!=0)? m_volume[0]:0) +((m_output[1]!=0)? m_volume[1]:0) +((m_output[2]!=0)? m_volume[2]:0) +((m_output[3]!=0)? m_volume[3]:0); + } if (m_negate) { out = -out; out2 = -out2; } - outputs[sampindex]=out; + outputs[0][sampindex]=out; + if (m_stereo && (outputs[1] != nullptr)) + outputs[1][sampindex]=out2; } } diff --git a/src/engine/platform/sound/sn76496.h b/src/engine/platform/sound/sn76496.h index 4c24e938c..7a8a5c625 100644 --- a/src/engine/platform/sound/sn76496.h +++ b/src/engine/platform/sound/sn76496.h @@ -1,7 +1,7 @@ // license:BSD-3-Clause // copyright-holders:Nicola Salmoria -// additional modifications by tildearrow for furnace +// additional modifications by tildearrow, cam900 for furnace #ifndef MAME_SOUND_SN76496_H #define MAME_SOUND_SN76496_H @@ -14,34 +14,57 @@ class sn76496_base_device { public: void stereo_w(u8 data); 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); - } + 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( int feedbackmask, - int noise_start, + int noise_start, int noisetap1, int noisetap2, bool negate, + bool stereo, int clockdivider, bool ncr, bool sega); + sn76496_base_device( + int feedbackmask, + int noisetap1, + int noisetap2, + bool negate, + bool stereo, + int clockdivider, + bool ncr, + bool sega) + : sn76496_base_device( + feedbackmask, + feedbackmask, + noisetap1, + noisetap2, + negate, + stereo, + clockdivider, + ncr, + sega) + {} + private: inline bool in_noise_mode(); bool m_ready_state; - const int32_t m_feedback_mask; // mask for feedback - const int32_t m_noise_start; // noise start value - const int32_t m_whitenoise_tap1; // mask for white noise tap 1 (higher one, usually bit 14) - const int32_t m_whitenoise_tap2; // mask for white noise tap 2 (lower one, usually bit 13) - bool m_negate; // output negate flag - const int32_t m_clock_divider; // clock divider + const int32_t m_feedback_mask; // mask for feedback + const int32_t m_noise_start; // noise start value + const int32_t m_whitenoise_tap1; // mask for white noise tap 1 (higher one, usually bit 14) + const int32_t m_whitenoise_tap2; // mask for white noise tap 2 (lower one, usually bit 13) + bool m_negate; // output negate flag + const bool m_stereo; // whether we're dealing with stereo or not + const int32_t m_clock_divider; // clock divider const bool m_ncr_style_psg; // flag to ignore writes to regs 1,3,5,6,7 with bit 7 low const bool m_sega_style_psg; // flag to make frequency zero acts as if it is one more than max (0x3ff+1) or if it acts like 0; the initial register is pointing to 0x3 instead of 0x0; the volume reg is preloaded with 0xF instead of 0x0 @@ -51,6 +74,7 @@ private: int32_t m_volume[4]; // db volume of voice 0-2 and noise uint32_t m_RNG; // noise generator LFSR int32_t m_current_clock; + int32_t m_stereo_mask; // the stereo output mask int32_t m_period[4]; // Length of 1/2 of waveform int32_t m_count[4]; // Position within the waveform int32_t m_output[4]; // 1-bit output of each channel, pre-volume @@ -63,4 +87,67 @@ public: sn76496_device(); }; +// Y2404 not verified yet. todo: verify; (don't be fooled by the Y, it's a TI chip, not Yamaha) +class y2404_device : public sn76496_base_device +{ +public: + y2404_device(); +}; + +// SN76489 not verified yet. todo: verify; +class sn76489_device : public sn76496_base_device +{ +public: + sn76489_device(); +}; + +// SN76489A: whitenoise verified, phase verified, periodic verified (by plgdavid) +class sn76489a_device : public sn76496_base_device +{ +public: + sn76489a_device(); +}; + +// SN76494 not verified, (according to datasheet: same as sn76489a but without the /8 divider) +class sn76494_device : public sn76496_base_device +{ +public: + sn76494_device(); +}; + +// SN94624 whitenoise verified, phase verified, period verified; verified by PlgDavid +class sn94624_device : public sn76496_base_device +{ +public: + sn94624_device(); +}; + +// NCR8496 whitenoise verified, phase verified; verified by ValleyBell & NewRisingSun +class ncr8496_device : public sn76496_base_device +{ +public: + ncr8496_device(); +}; + +// PSSJ-3 whitenoise verified, phase verified; verified by ValleyBell & NewRisingSun +class pssj3_device : public sn76496_base_device +{ +public: + pssj3_device(); +}; + +// Verified by Justin Kerk +class gamegear_device : public sn76496_base_device +{ +public: + gamegear_device(); +}; + +// todo: verify; from smspower wiki, assumed to have same invert as gamegear +class segapsg_device : public sn76496_base_device +{ +public: + segapsg_device(); +}; + #endif // MAME_SOUND_SN76496_H diff --git a/src/engine/platform/sound/ymfm/ymfm_opn.h b/src/engine/platform/sound/ymfm/ymfm_opn.h index 34dc065d7..00fd61adf 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opn.h +++ b/src/engine/platform/sound/ymfm/ymfm_opn.h @@ -784,7 +784,7 @@ public: protected: // simulate the DAC discontinuity - constexpr int32_t dac_discontinuity(int32_t value) const { return (value < 0) ? (value - 2) : (value + 3); } + int32_t dac_discontinuity(int32_t value) const { return (value < 0) ? (value - 2) : (value + 3); } // internal state uint16_t m_address; // address register diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index db054e5c5..33a98a7e3 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -484,6 +484,10 @@ void* DivPlatformSoundUnit::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformSoundUnit::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformSoundUnit::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/su.h b/src/engine/platform/su.h index 857ae7251..1d39854f2 100644 --- a/src/engine/platform/su.h +++ b/src/engine/platform/su.h @@ -112,6 +112,7 @@ class DivPlatformSoundUnit: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 4462bb57b..0f9397e1b 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -347,7 +347,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.vol.had) { + if (!chan[c.chan].std.vol.has) { calcAndWriteOutVol(c.chan,15); } } @@ -464,6 +464,10 @@ void* DivPlatformSwan::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformSwan::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformSwan::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/swan.h b/src/engine/platform/swan.h index 32f400e5b..aafb17ace 100644 --- a/src/engine/platform/swan.h +++ b/src/engine/platform/swan.h @@ -79,6 +79,7 @@ class DivPlatformSwan: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/tia.cpp b/src/engine/platform/tia.cpp index 60e064b5a..da3472446 100644 --- a/src/engine/platform/tia.cpp +++ b/src/engine/platform/tia.cpp @@ -293,6 +293,10 @@ void* DivPlatformTIA::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformTIA::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformTIA::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/tia.h b/src/engine/platform/tia.h index 76064d069..cabe91533 100644 --- a/src/engine/platform/tia.h +++ b/src/engine/platform/tia.h @@ -52,6 +52,7 @@ class DivPlatformTIA: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index f15de0e71..123d1193c 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -22,43 +22,11 @@ #include #include -#include "fmshared_OPM.h" - // actually 0x40 but the upper bit of data selects address #define ADDR_WS_FINE 0x100 // actually 0xc0 but bit 5 of data selects address #define ADDR_EGS_REV 0x120 -static unsigned short chanOffs[8]={ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 -}; -static unsigned short opOffs[4]={ - 0x00, 0x08, 0x10, 0x18 -}; -static bool isOutput[8][4]={ - // 1 3 2 4 - {false,false,false,true}, - {false,false,false,true}, - {false,false,false,true}, - {false,false,false,true}, - {false,false,true ,true}, - {false,true ,true ,true}, - {false,true ,true ,true}, - {true ,true ,true ,true}, -}; -static unsigned char dtTable[8]={ - 7,6,5,0,1,2,3,4 -}; - -static int orderedOps[4]={ - 0,2,1,3 -}; - -#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 NOTE_LINEAR(x) (((x)<<6)+baseFreqOff+log2(parent->song.tuning/440.0)*12.0*64.0) - const char* regCheatSheetOPZ[]={ "Test", "00", "NoteCtl", "08", @@ -233,7 +201,7 @@ void DivPlatformTX81Z::acquire(short* bufL, short* bufR, size_t start, size_t le fm_ymfm->write(0x0+((w.addr>>8)<<1),w.addr); fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val); regPool[w.addr&0xff]=w.val; - writes.pop(); + writes.pop_front(); delay=1; } } @@ -1027,6 +995,10 @@ void* DivPlatformTX81Z::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformTX81Z::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformTX81Z::getOscBuffer(int ch) { return oscBuf[ch]; } @@ -1048,7 +1020,7 @@ void DivPlatformTX81Z::poke(std::vector& wlist) { } void DivPlatformTX81Z::reset() { - while (!writes.empty()) writes.pop(); + while (!writes.empty()) writes.pop_front(); memset(regPool,0,330); fm_ymfm->reset(); if (dumpWrites) { diff --git a/src/engine/platform/tx81z.h b/src/engine/platform/tx81z.h index 60ea66ae0..e867416cf 100644 --- a/src/engine/platform/tx81z.h +++ b/src/engine/platform/tx81z.h @@ -19,18 +19,22 @@ #ifndef _TX81Z_H #define _TX81Z_H -#include "../dispatch.h" +#include "fmshared_OPM.h" +#include "../macroInt.h" #include "../instrument.h" #include #include "sound/ymfm/ymfm_opz.h" -#include "../macroInt.h" class DivTXInterface: public ymfm::ymfm_interface { }; -class DivPlatformTX81Z: public DivDispatch { +class DivPlatformTX81Z: public DivPlatformOPM { protected: + const unsigned short chanOffs[8]={ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 + }; + struct Channel { DivInstrumentFM state; DivMacroInt std; @@ -69,31 +73,18 @@ class DivPlatformTX81Z: public DivDispatch { }; Channel chan[8]; DivDispatchOscBuffer* oscBuf[8]; - struct QueuedWrite { - unsigned short addr; - unsigned char val; - bool addrOrVal; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} - }; - std::queue writes; - int delay, baseFreqOff; + int baseFreqOff; int pcmL, pcmR, pcmCycles; - unsigned char lastBusy; unsigned char amDepth, pmDepth; ymfm::ym2414* fm_ymfm; ymfm::ym2414::output_data out_ymfm; DivTXInterface iface; - unsigned char regPool[330]; - bool extMode; bool isMuted[8]; - short oldWrites[330]; - short pendingWrites[330]; - int octave(int freq); int toFreq(int freq); @@ -103,6 +94,7 @@ class DivPlatformTX81Z: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 3be606968..18be8b09f 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -397,6 +397,10 @@ void* DivPlatformVERA::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformVERA::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformVERA::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h index 734db020b..612b4354b 100644 --- a/src/engine/platform/vera.h +++ b/src/engine/platform/vera.h @@ -66,6 +66,7 @@ class DivPlatformVERA: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index 708db4abf..8475b0e53 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -195,7 +195,7 @@ int DivPlatformVIC20::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.vol.had) { + if (!chan[c.chan].std.vol.has) { calcAndWriteOutVol(c.chan,15); } } @@ -278,6 +278,10 @@ void* DivPlatformVIC20::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformVIC20::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformVIC20::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/vic20.h b/src/engine/platform/vic20.h index f05ad8f2b..d23f27be8 100644 --- a/src/engine/platform/vic20.h +++ b/src/engine/platform/vic20.h @@ -68,6 +68,7 @@ class DivPlatformVIC20: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index bdf9aed86..ad1cdaa75 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -438,6 +438,10 @@ void* DivPlatformVRC6::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformVRC6::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformVRC6::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/vrc6.h b/src/engine/platform/vrc6.h index dd6863d5f..450e09b98 100644 --- a/src/engine/platform/vrc6.h +++ b/src/engine/platform/vrc6.h @@ -85,6 +85,7 @@ class DivPlatformVRC6: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 1cbfd68e7..dc5c7ed22 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -842,6 +842,10 @@ void* DivPlatformX1_010::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformX1_010::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformX1_010::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index 939280ef9..7a85b6336 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -129,6 +129,7 @@ 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); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index cebf70873..e0bbe1a32 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -23,16 +23,8 @@ #include #include -#include "sound/ymfm/ymfm_opn.h" -#include "ym2203shared.h" - -#include "fmshared_OPN.h" - -static unsigned char konOffs[3]={ - 0, 1, 2 -}; - -#define CHIP_DIVIDER 32 +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase const char* regCheatSheetYM2203[]={ // SSG @@ -299,7 +291,7 @@ void DivPlatformYM2203::acquire(short* bufL, short* bufR, size_t start, size_t l fm->write(0x0,w.addr); fm->write(0x1,w.val); regPool[w.addr&0xff]=w.val; - writes.pop(); + writes.pop_front(); delay=6; } } @@ -933,6 +925,11 @@ void* DivPlatformYM2203::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformYM2203::getChanMacroInt(int ch) { + if (ch>=3) return ay->getChanMacroInt(ch-3); + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformYM2203::getOscBuffer(int ch) { return oscBuf[ch]; } @@ -954,7 +951,7 @@ void DivPlatformYM2203::poke(std::vector& wlist) { } void DivPlatformYM2203::reset() { - while (!writes.empty()) writes.pop(); + while (!writes.empty()) writes.pop_front(); memset(regPool,0,256); if (dumpWrites) { addWrite(0xffffffff,0); @@ -984,6 +981,10 @@ void DivPlatformYM2203::reset() { extMode=false; + // set prescaler + immWrite(0x2d,0xff); + immWrite(prescale,0xff); + ay->reset(); ay->getRegisterWrites().clear(); ay->flushWrites(); @@ -1016,25 +1017,58 @@ void DivPlatformYM2203::setSkipRegisterWrites(bool value) { } void DivPlatformYM2203::setFlags(unsigned int flags) { - unsigned char ayFlags=16; - if (flags==3) { - chipClock=3000000.0; - ayFlags=20; - } else if (flags==2) { - chipClock=4000000.0; - ayFlags=19; - } else if (flags==1) { - chipClock=COLOR_PAL*4.0/5.0; - ayFlags=17; - } else { - chipClock=COLOR_NTSC; - ayFlags=16; + // Clock flags + switch (flags&0x1f) { + default: + case 0x00: + chipClock=COLOR_NTSC; + break; + case 0x01: + chipClock=COLOR_PAL*4.0/5.0; + break; + case 0x02: + chipClock=4000000.0; + break; + case 0x03: + chipClock=3000000.0; + break; + case 0x04: + chipClock=38400*13*8; // 31948800/8 + break; + case 0x05: + chipClock=3000000.0/2.0; + break; + } + // Prescaler flags + switch ((flags>>5)&0x3) { + default: + case 0x00: // /6 + prescale=0x2d; + fmFreqBase=4720270.0, + fmDivBase=36, + ayDiv=16; + break; + case 0x01: // /3 + prescale=0x2e; + fmFreqBase=4720270.0/2.0, + fmDivBase=18, + ayDiv=8; + break; + case 0x02: // /2 + prescale=0x2f; + fmFreqBase=4720270.0/3.0, + fmDivBase=12, + ayDiv=4; + break; } - ay->setFlags(ayFlags); rate=fm->sample_rate(chipClock); for (int i=0; i<6; i++) { oscBuf[i]->rate=rate; } + immWrite(0x2d,0xff); + immWrite(prescale,0xff); + ay->setExtClockDiv(chipClock,ayDiv); + ay->setFlags(16); } int DivPlatformYM2203::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { @@ -1048,13 +1082,13 @@ int DivPlatformYM2203::init(DivEngine* p, int channels, int sugRate, unsigned in fm=new ymfm::ym2203(iface); fm->set_fidelity(ymfm::OPN_FIDELITY_MIN); // YM2149, 2MHz - ay=new DivPlatformAY8910; - ay->init(p,3,sugRate,19); + ay=new DivPlatformAY8910(true,chipClock,ayDiv); + ay->init(p,3,sugRate,16); ay->toggleRegisterDump(true); setFlags(flags); reset(); - return 16; + return 6; } void DivPlatformYM2203::quit() { diff --git a/src/engine/platform/ym2203.h b/src/engine/platform/ym2203.h index ab527eeaa..d406e2f28 100644 --- a/src/engine/platform/ym2203.h +++ b/src/engine/platform/ym2203.h @@ -19,9 +19,8 @@ #ifndef _YM2203_H #define _YM2203_H -#include "../dispatch.h" +#include "fmshared_OPN.h" #include "../macroInt.h" -#include #include "sound/ymfm/ymfm_opn.h" #include "ay.h" @@ -30,12 +29,16 @@ class DivYM2203Interface: public ymfm::ymfm_interface { }; -class DivPlatformYM2203: public DivDispatch { +class DivPlatformYM2203: public DivPlatformOPN { protected: const unsigned short chanOffs[3]={ 0x00, 0x01, 0x02 }; + const unsigned char konOffs[3]={ + 0, 1, 2 + }; + struct Channel { DivInstrumentFM state; unsigned char freqH, freqL; @@ -79,35 +82,23 @@ class DivPlatformYM2203: public DivDispatch { Channel chan[6]; DivDispatchOscBuffer* oscBuf[6]; bool isMuted[6]; - struct QueuedWrite { - unsigned short addr; - unsigned char val; - bool addrOrVal; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} - }; - std::queue writes; ymfm::ym2203* fm; ymfm::ym2203::output_data fmout; DivYM2203Interface iface; - unsigned char regPool[512]; - unsigned char lastBusy; DivPlatformAY8910* ay; unsigned char sampleBank; - int delay; - bool extMode; + unsigned char prescale; - short oldWrites[256]; - short pendingWrites[256]; - 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); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); @@ -127,6 +118,9 @@ class DivPlatformYM2203: public DivDispatch { void setFlags(unsigned int flags); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); + DivPlatformYM2203(): + DivPlatformOPN(4720270.0, 36, 16), + prescale(0x2d) {} ~DivPlatformYM2203(); }; #endif diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index 1a4f60bb1..d3fd3487b 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -21,8 +21,8 @@ #include "../engine.h" #include -#include "ym2203shared.h" -#include "fmshared_OPN.h" +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase int DivPlatformYM2203Ext::dispatch(DivCommand c) { if (c.chan<2) { @@ -471,6 +471,12 @@ void* DivPlatformYM2203Ext::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformYM2203Ext::getChanMacroInt(int ch) { + if (ch>=6) return ay->getChanMacroInt(ch-6); + if (ch>=2) return NULL; // currently not implemented + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformYM2203Ext::getOscBuffer(int ch) { if (ch>=6) return oscBuf[ch-3]; if (ch<3) return oscBuf[ch]; @@ -510,7 +516,7 @@ int DivPlatformYM2203Ext::init(DivEngine* parent, int channels, int sugRate, uns } reset(); - return 19; + return 9; } void DivPlatformYM2203Ext::quit() { diff --git a/src/engine/platform/ym2203ext.h b/src/engine/platform/ym2203ext.h index 5025881cf..1a398d1a6 100644 --- a/src/engine/platform/ym2203ext.h +++ b/src/engine/platform/ym2203ext.h @@ -40,6 +40,7 @@ class DivPlatformYM2203Ext: public DivPlatformYM2203 { public: int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); void reset(); void forceIns(); diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index e472edfc0..e8f2aa659 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -24,16 +24,8 @@ #include #include -#include "sound/ymfm/ymfm_opn.h" -#include "ym2610shared.h" - -#include "fmshared_OPN.h" - -static unsigned char konOffs[6]={ - 0, 1, 2, 4, 5, 6 -}; - -#define CHIP_DIVIDER 32 +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase const char* regCheatSheetYM2608[]={ // SSG @@ -450,7 +442,7 @@ void DivPlatformYM2608::acquire(short* bufL, short* bufR, size_t start, size_t l fm->write(0x0+((w.addr>>8)<<1),w.addr); fm->write(0x1+((w.addr>>8)<<1),w.val); regPool[w.addr&0x1ff]=w.val; - writes.pop(); + writes.pop_front(); delay=4; } } @@ -1257,6 +1249,11 @@ void* DivPlatformYM2608::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformYM2608::getChanMacroInt(int ch) { + if (ch>=6 && ch<9) return ay->getChanMacroInt(ch-6); + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformYM2608::getOscBuffer(int ch) { return oscBuf[ch]; } @@ -1278,7 +1275,7 @@ void DivPlatformYM2608::poke(std::vector& wlist) { } void DivPlatformYM2608::reset() { - while (!writes.empty()) writes.pop(); + while (!writes.empty()) writes.pop_front(); memset(regPool,0,512); if (dumpWrites) { addWrite(0xffffffff,0); @@ -1328,6 +1325,10 @@ void DivPlatformYM2608::reset() { // enable 6 channel mode immWrite(0x29,0x80); + // set prescaler + immWrite(0x2d,0xff); + immWrite(prescale,0xff); + ay->reset(); ay->getRegisterWrites().clear(); ay->flushWrites(); @@ -1397,6 +1398,49 @@ void DivPlatformYM2608::renderSamples() { adpcmBMemLen=memPos+256; } +void DivPlatformYM2608::setFlags(unsigned int flags) { + // Clock flags + switch (flags&0x1f) { + default: + case 0x00: + chipClock=8000000.0; + break; + case 0x01: + chipClock=38400*13*16; // 31948800/4 + break; + } + // Prescaler flags + switch ((flags>>5)&0x3) { + default: + case 0x00: // /6 + prescale=0x2d; + fmFreqBase=9440540.0, + fmDivBase=72, + ayDiv=32; + break; + case 0x01: // /3 + prescale=0x2e; + fmFreqBase=9440540.0/2.0, + fmDivBase=36, + ayDiv=16; + break; + case 0x02: // /2 + prescale=0x2f; + fmFreqBase=9440540.0/3.0, + fmDivBase=24, + ayDiv=8; + break; + } + rate=fm->sample_rate(chipClock); + for (int i=0; i<16; i++) { + oscBuf[i]->rate=rate; + } + immWrite(0x2d,0xff); + immWrite(prescale,0xff); + ay->setExtClockDiv(chipClock,ayDiv); + ay->setFlags(16); +} + int DivPlatformYM2608::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; adpcmBMem=new unsigned char[getSampleMemCapacity(0)]; @@ -1409,17 +1453,13 @@ int DivPlatformYM2608::init(DivEngine* p, int channels, int sugRate, unsigned in isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - chipClock=8000000; fm=new ymfm::ym2608(iface); fm->set_fidelity(ymfm::OPN_FIDELITY_MIN); - rate=fm->sample_rate(chipClock); - for (int i=0; i<16; i++) { - oscBuf[i]->rate=rate; - } // YM2149, 2MHz - ay=new DivPlatformAY8910; - ay->init(p,3,sugRate,19); + ay=new DivPlatformAY8910(true,chipClock,ayDiv); + ay->init(p,3,sugRate,16); ay->toggleRegisterDump(true); + setFlags(flags); reset(); return 16; } diff --git a/src/engine/platform/ym2608.h b/src/engine/platform/ym2608.h index 5c6736295..ac38a8c08 100644 --- a/src/engine/platform/ym2608.h +++ b/src/engine/platform/ym2608.h @@ -19,9 +19,8 @@ #ifndef _YM2608_H #define _YM2608_H -#include "../dispatch.h" +#include "fmshared_OPN.h" #include "../macroInt.h" -#include #include "sound/ymfm/ymfm_opn.h" #include "ay.h" @@ -35,12 +34,16 @@ class DivYM2608Interface: public ymfm::ymfm_interface { DivYM2608Interface(): adpcmBMem(NULL), sampleBank(0) {} }; -class DivPlatformYM2608: public DivDispatch { +class DivPlatformYM2608: public DivPlatformOPN { protected: const unsigned short chanOffs[6]={ 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 }; + const unsigned char konOffs[6]={ + 0, 1, 2, 4, 5, 6 + }; + struct Channel { DivInstrumentFM state; unsigned char freqH, freqL; @@ -86,17 +89,8 @@ class DivPlatformYM2608: public DivDispatch { Channel chan[16]; DivDispatchOscBuffer* oscBuf[16]; bool isMuted[16]; - struct QueuedWrite { - unsigned short addr; - unsigned char val; - bool addrOrVal; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} - }; - std::queue writes; ymfm::ym2608* fm; ymfm::ym2608::output_data fmout; - unsigned char regPool[512]; - unsigned char lastBusy; unsigned char* adpcmBMem; size_t adpcmBMemLen; @@ -106,13 +100,9 @@ class DivPlatformYM2608: public DivDispatch { unsigned char sampleBank; unsigned char writeRSSOff, writeRSSOn; - int delay; - bool extMode; + unsigned char prescale; - short oldWrites[512]; - short pendingWrites[512]; - double NOTE_OPNB(int ch, int note); double NOTE_ADPCMB(int note); friend void putDispatchChan(void*,int,int); @@ -121,6 +111,7 @@ class DivPlatformYM2608: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); @@ -141,8 +132,12 @@ class DivPlatformYM2608: public DivDispatch { size_t getSampleMemCapacity(int index); size_t getSampleMemUsage(int index); void renderSamples(); + void setFlags(unsigned int flags); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); + DivPlatformYM2608(): + DivPlatformOPN(9440540.0, 72, 32), + prescale(0x2d) {} ~DivPlatformYM2608(); }; #endif diff --git a/src/engine/platform/ym2608ext.cpp b/src/engine/platform/ym2608ext.cpp index 4802cc658..91c3a897f 100644 --- a/src/engine/platform/ym2608ext.cpp +++ b/src/engine/platform/ym2608ext.cpp @@ -21,8 +21,8 @@ #include "../engine.h" #include -#include "ym2610shared.h" -#include "fmshared_OPN.h" +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase int DivPlatformYM2608Ext::dispatch(DivCommand c) { if (c.chan<2) { @@ -484,6 +484,13 @@ void* DivPlatformYM2608Ext::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformYM2608Ext::getChanMacroInt(int ch) { + if (ch>=9 && ch<12) return ay->getChanMacroInt(ch-9); + if (ch>=6) return &chan[ch-3].std; + if (ch>=2) return NULL; // currently not implemented + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformYM2608Ext::getOscBuffer(int ch) { if (ch>=6) return oscBuf[ch-3]; if (ch<3) return oscBuf[ch]; diff --git a/src/engine/platform/ym2608ext.h b/src/engine/platform/ym2608ext.h index a0966dfe1..bc3d4f991 100644 --- a/src/engine/platform/ym2608ext.h +++ b/src/engine/platform/ym2608ext.h @@ -40,6 +40,7 @@ class DivPlatformYM2608Ext: public DivPlatformYM2608 { public: int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); void reset(); void forceIns(); diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 00c0d54f9..cfb26d55a 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -24,19 +24,8 @@ #include #include -#include "ym2610shared.h" - -#include "fmshared_OPN.h" - -static unsigned char konOffs[4]={ - 1, 2, 5, 6 -}; - -static unsigned char bchOffs[4]={ - 1, 2, 4, 5 -}; - -#define CHIP_DIVIDER 32 +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase const char* regCheatSheetYM2610[]={ // SSG @@ -494,7 +483,7 @@ void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t l fm->write(0x0+((w.addr>>8)<<1),w.addr); fm->write(0x1+((w.addr>>8)<<1),w.val); regPool[w.addr&0x1ff]=w.val; - writes.pop(); + writes.pop_front(); delay=4; } } @@ -1304,6 +1293,11 @@ void* DivPlatformYM2610::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformYM2610::getChanMacroInt(int ch) { + if (ch>=4 && ch<7) return ay->getChanMacroInt(ch-4); + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformYM2610::getOscBuffer(int ch) { return oscBuf[ch]; } @@ -1325,7 +1319,7 @@ void DivPlatformYM2610::poke(std::vector& wlist) { } void DivPlatformYM2610::reset() { - while (!writes.empty()) writes.pop(); + while (!writes.empty()) writes.pop_front(); memset(regPool,0,512); if (dumpWrites) { addWrite(0xffffffff,0); @@ -1397,6 +1391,22 @@ void DivPlatformYM2610::setSkipRegisterWrites(bool value) { ay->setSkipRegisterWrites(value); } +void DivPlatformYM2610::setFlags(unsigned int flags) { + switch (flags&0xff) { + default: + case 0x00: + chipClock=8000000.0; + break; + case 0x01: + chipClock=24167829/3; + break; + } + rate=chipClock/16; + for (int i=0; i<14; i++) { + oscBuf[i]->rate=rate; + } +} + int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { DivPlatformYM2610Base::init(p, channels, sugRate, flags); dumpWrites=false; @@ -1405,15 +1415,11 @@ int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, unsigned in isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - chipClock=8000000; - rate=chipClock/16; - for (int i=0; i<14; i++) { - oscBuf[i]->rate=rate; - } fm=new ymfm::ym2610(iface); + setFlags(flags); // YM2149, 2MHz - ay=new DivPlatformAY8910; - ay->init(p,3,sugRate,19); + ay=new DivPlatformAY8910(true,chipClock,32); + ay->init(p,3,sugRate,16); ay->toggleRegisterDump(true); reset(); return 14; diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index 92f4ca077..dde7ed105 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -19,9 +19,8 @@ #ifndef _YM2610_H #define _YM2610_H -#include "../dispatch.h" +#include "fmshared_OPN.h" #include "../macroInt.h" -#include #include "ay.h" #include "sound/ymfm/ymfm_opn.h" @@ -35,7 +34,7 @@ class DivYM2610Interface: public ymfm::ymfm_interface { DivYM2610Interface(): adpcmAMem(NULL), adpcmBMem(NULL), sampleBank(0) {} }; -class DivPlatformYM2610Base: public DivDispatch { +class DivPlatformYM2610Base: public DivPlatformOPN { protected: unsigned char* adpcmAMem; size_t adpcmAMemLen; @@ -50,6 +49,8 @@ class DivPlatformYM2610Base: public DivDispatch { void renderSamples(); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); + DivPlatformYM2610Base(): + DivPlatformOPN(9440540.0, 72, 32) {} }; class DivPlatformYM2610: public DivPlatformYM2610Base { @@ -58,6 +59,14 @@ class DivPlatformYM2610: public DivPlatformYM2610Base { 0x01, 0x02, 0x101, 0x102 }; + const unsigned char konOffs[4]={ + 1, 2, 5, 6 + }; + + const unsigned char bchOffs[4]={ + 1, 2, 4, 5 + }; + struct Channel { DivInstrumentFM state; unsigned char freqH, freqL; @@ -103,29 +112,15 @@ class DivPlatformYM2610: public DivPlatformYM2610Base { Channel chan[14]; DivDispatchOscBuffer* oscBuf[14]; bool isMuted[14]; - struct QueuedWrite { - unsigned short addr; - unsigned char val; - bool addrOrVal; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} - }; - std::queue writes; ymfm::ym2610* fm; ymfm::ym2610::output_data fmout; DivPlatformAY8910* ay; - unsigned char regPool[512]; - unsigned char lastBusy; unsigned char sampleBank; - int delay; - bool extMode; - short oldWrites[512]; - short pendingWrites[512]; - double NOTE_OPNB(int ch, int note); double NOTE_ADPCMB(int note); friend void putDispatchChan(void*,int,int); @@ -134,6 +129,7 @@ class DivPlatformYM2610: public DivPlatformYM2610Base { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); @@ -150,6 +146,7 @@ class DivPlatformYM2610: public DivPlatformYM2610Base { void poke(std::vector& wlist); const char** getRegisterSheet(); const char* getEffectName(unsigned char effect); + void setFlags(unsigned int flags); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformYM2610(); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 2d1d97ced..af2ff2211 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -23,15 +23,8 @@ #include #include -#include "ym2610shared.h" - -#include "fmshared_OPN.h" - -static unsigned char konOffs[6]={ - 0, 1, 2, 4, 5, 6 -}; - -#define CHIP_DIVIDER 32 +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase const char* regCheatSheetYM2610B[]={ // SSG @@ -472,7 +465,7 @@ void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t fm->write(0x0+((w.addr>>8)<<1),w.addr); fm->write(0x1+((w.addr>>8)<<1),w.val); regPool[w.addr&0x1ff]=w.val; - writes.pop(); + writes.pop_front(); delay=4; } } @@ -1282,6 +1275,11 @@ void* DivPlatformYM2610B::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformYM2610B::getChanMacroInt(int ch) { + if (ch>=6 && ch<9) return ay->getChanMacroInt(ch-6); + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformYM2610B::getOscBuffer(int ch) { return oscBuf[ch]; } @@ -1303,7 +1301,7 @@ void DivPlatformYM2610B::poke(std::vector& wlist) { } void DivPlatformYM2610B::reset() { - while (!writes.empty()) writes.pop(); + while (!writes.empty()) writes.pop_front(); memset(regPool,0,512); if (dumpWrites) { addWrite(0xffffffff,0); @@ -1375,6 +1373,22 @@ void DivPlatformYM2610B::setSkipRegisterWrites(bool value) { ay->setSkipRegisterWrites(value); } +void DivPlatformYM2610B::setFlags(unsigned int flags) { + switch (flags&0xff) { + default: + case 0x00: + chipClock=8000000.0; + break; + case 0x01: + chipClock=24167829/3; + break; + } + rate=chipClock/16; + for (int i=0; i<16; i++) { + oscBuf[i]->rate=rate; + } +} + int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { DivPlatformYM2610Base::init(p, channels, sugRate, flags); dumpWrites=false; @@ -1383,15 +1397,11 @@ int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned i isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - chipClock=8000000; - rate=chipClock/16; - for (int i=0; i<16; i++) { - oscBuf[i]->rate=rate; - } fm=new ymfm::ym2610b(iface); + setFlags(flags); // YM2149, 2MHz - ay=new DivPlatformAY8910; - ay->init(p,3,sugRate,19); + ay=new DivPlatformAY8910(true,chipClock,32); + ay->init(p,3,sugRate,16); ay->toggleRegisterDump(true); reset(); return 16; diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index 3a034028a..fefb06929 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -19,12 +19,10 @@ #ifndef _YM2610B_H #define _YM2610B_H -#include "../dispatch.h" +#include "ym2610.h" #include "../macroInt.h" -#include #include "sound/ymfm/ymfm_opn.h" -#include "ym2610.h" class DivPlatformYM2610B: public DivPlatformYM2610Base { protected: @@ -32,6 +30,10 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base { 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 }; + const unsigned char konOffs[6]={ + 0, 1, 2, 4, 5, 6 + }; + struct Channel { DivInstrumentFM state; unsigned char freqH, freqL; @@ -77,27 +79,15 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base { Channel chan[16]; DivDispatchOscBuffer* oscBuf[16]; bool isMuted[16]; - struct QueuedWrite { - unsigned short addr; - unsigned char val; - bool addrOrVal; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} - }; - std::queue writes; ymfm::ym2610b* fm; ymfm::ym2610b::output_data fmout; - unsigned char regPool[512]; - unsigned char lastBusy; DivPlatformAY8910* ay; unsigned char sampleBank; - int delay; - bool extMode; - - short oldWrites[512]; - short pendingWrites[512]; + double fmFreqBase=9440540; + unsigned char ayDiv=32; double NOTE_OPNB(int ch, int note); double NOTE_ADPCMB(int note); @@ -107,6 +97,7 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); @@ -123,6 +114,7 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base { void poke(std::vector& wlist); const char** getRegisterSheet(); const char* getEffectName(unsigned char effect); + void setFlags(unsigned int flags); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformYM2610B(); diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index 42a1f2e10..b5e1ac9fb 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -21,8 +21,8 @@ #include "../engine.h" #include -#include "ym2610shared.h" -#include "fmshared_OPN.h" +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase int DivPlatformYM2610BExt::dispatch(DivCommand c) { if (c.chan<2) { @@ -484,6 +484,13 @@ void* DivPlatformYM2610BExt::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformYM2610BExt::getChanMacroInt(int ch) { + if (ch>=9 && ch<12) return ay->getChanMacroInt(ch-9); + if (ch>=6) return &chan[ch-3].std; + if (ch>=2) return NULL; // currently not implemented + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformYM2610BExt::getOscBuffer(int ch) { if (ch>=6) return oscBuf[ch-3]; if (ch<3) return oscBuf[ch]; diff --git a/src/engine/platform/ym2610bext.h b/src/engine/platform/ym2610bext.h index c17fd8d8a..732678fe5 100644 --- a/src/engine/platform/ym2610bext.h +++ b/src/engine/platform/ym2610bext.h @@ -40,6 +40,7 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B { public: int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); void reset(); void forceIns(); diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index 8e9403818..6f9c8700d 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -21,8 +21,8 @@ #include "../engine.h" #include -#include "ym2610shared.h" -#include "fmshared_OPN.h" +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase int DivPlatformYM2610Ext::dispatch(DivCommand c) { if (c.chan<1) { @@ -478,13 +478,19 @@ void DivPlatformYM2610Ext::forceIns() { } } - void* DivPlatformYM2610Ext::getChanState(int ch) { if (ch>=5) return &chan[ch-3]; if (ch>=1) return &opChan[ch-1]; return &chan[ch]; } +DivMacroInt* DivPlatformYM2610Ext::getChanMacroInt(int ch) { + if (ch>=7 && ch<10) return ay->getChanMacroInt(ch-7); + if (ch>=5) return &chan[ch-3].std; + if (ch>=1) return NULL; // currently not implemented + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformYM2610Ext::getOscBuffer(int ch) { if (ch>=5) return oscBuf[ch-3]; if (ch<2) return oscBuf[ch]; diff --git a/src/engine/platform/ym2610ext.h b/src/engine/platform/ym2610ext.h index 492eb5de4..119d63569 100644 --- a/src/engine/platform/ym2610ext.h +++ b/src/engine/platform/ym2610ext.h @@ -40,6 +40,7 @@ class DivPlatformYM2610Ext: public DivPlatformYM2610 { public: int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); void reset(); void forceIns(); diff --git a/src/engine/platform/ym2610shared.h b/src/engine/platform/ym2610shared.h deleted file mode 100644 index 8d8847c2e..000000000 --- a/src/engine/platform/ym2610shared.h +++ /dev/null @@ -1,45 +0,0 @@ -/** - * 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. - */ - -static unsigned short opOffs[4]={ - 0x00, 0x04, 0x08, 0x0c -}; -static bool isOutput[8][4]={ - // 1 3 2 4 - {false,false,false,true}, - {false,false,false,true}, - {false,false,false,true}, - {false,false,false,true}, - {false,false,true ,true}, - {false,true ,true ,true}, - {false,true ,true ,true}, - {true ,true ,true ,true}, -}; -static unsigned char dtTable[8]={ - 7,6,5,0,1,2,3,4 -}; - -static int orderedOps[4]={ - 0,2,1,3 -}; - -#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 CHIP_FREQBASE 9440540 diff --git a/src/engine/platform/ymz280b.cpp b/src/engine/platform/ymz280b.cpp index 1c4997c2e..45631a940 100644 --- a/src/engine/platform/ymz280b.cpp +++ b/src/engine/platform/ymz280b.cpp @@ -333,6 +333,10 @@ void* DivPlatformYMZ280B::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformYMZ280B::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformYMZ280B::getOscBuffer(int ch) { return oscBuf[ch]; } @@ -431,6 +435,42 @@ void DivPlatformYMZ280B::setChipModel(int type) { chipType=type; } +void DivPlatformYMZ280B::setFlags(unsigned int flags) { + switch (chipType) { + default: + case 280: + switch (flags&0xff) { + case 0x00: + chipClock=16934400; + break; + case 0x01: + chipClock=COLOR_NTSC*4.0; + break; + case 0x02: + chipClock=COLOR_PAL*16.0/5.0; + break; + case 0x03: + chipClock=16000000; + break; + case 0x04: + chipClock=50000000.0/3.0; + break; + case 0x05: + chipClock=14000000; + break; + } + rate=chipClock/384; + break; + case 759: + rate=32000; + chipClock=rate*384; + break; + } + for (int i=0; i<8; i++) { + oscBuf[i]->rate=rate; + } +} + int DivPlatformYMZ280B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; @@ -440,18 +480,12 @@ int DivPlatformYMZ280B::init(DivEngine* p, int channels, int sugRate, unsigned i isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - setFlags(flags); - - rate=(chipType==759)?32000:44100; - chipClock=rate*384; sampleMem=new unsigned char[getSampleMemCapacity()]; sampleMemLen=0; ymz280b.device_start(sampleMem); + setFlags(flags); reset(); - for (int i=0; i<8; i++) { - oscBuf[i]->rate=rate; - } return 8; } diff --git a/src/engine/platform/ymz280b.h b/src/engine/platform/ymz280b.h index 4957d8998..0d254c088 100644 --- a/src/engine/platform/ymz280b.h +++ b/src/engine/platform/ymz280b.h @@ -74,6 +74,7 @@ class DivPlatformYMZ280B: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); @@ -95,6 +96,7 @@ class DivPlatformYMZ280B: public DivDispatch { size_t getSampleMemCapacity(int index = 0); size_t getSampleMemUsage(int index = 0); void renderSamples(); + void setFlags(unsigned int flags); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); private: diff --git a/src/engine/platform/zxbeeper.cpp b/src/engine/platform/zxbeeper.cpp index ff14d161c..524d3c117 100644 --- a/src/engine/platform/zxbeeper.cpp +++ b/src/engine/platform/zxbeeper.cpp @@ -59,6 +59,7 @@ void DivPlatformZXBeeper::acquire(short* bufL, short* bufR, size_t start, size_t } o=sampleOut; bufL[h]=o?16384:0; + oscBuf[0]->data[oscBuf[0]->needle++]=o?16384:-16384; continue; } @@ -76,6 +77,7 @@ void DivPlatformZXBeeper::acquire(short* bufL, short* bufR, size_t start, size_t if (++curChan>=6) curChan=0; bufL[h]=o?16384:0; + oscBuf[0]->data[oscBuf[0]->needle++]=o?16384:-16384; } } @@ -251,8 +253,12 @@ void* DivPlatformZXBeeper::getChanState(int ch) { return &chan[ch]; } +DivMacroInt* DivPlatformZXBeeper::getChanMacroInt(int ch) { + return &chan[ch].std; +} + DivDispatchOscBuffer* DivPlatformZXBeeper::getOscBuffer(int ch) { - return oscBuf[ch]; + return (ch<1)?oscBuf[ch]:NULL; } unsigned char* DivPlatformZXBeeper::getRegisterPool() { diff --git a/src/engine/platform/zxbeeper.h b/src/engine/platform/zxbeeper.h index 9520ea71b..a9b400cb6 100644 --- a/src/engine/platform/zxbeeper.h +++ b/src/engine/platform/zxbeeper.h @@ -77,6 +77,7 @@ class DivPlatformZXBeeper: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index ac1c555b9..e5d9fde2d 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 "macroInt.h" #include #define _USE_MATH_DEFINES #include "dispatch.h" @@ -812,6 +813,9 @@ void DivEngine::nextRow() { printf("| %.2x:%s | \x1b[1;33m%3d%s\x1b[m\n",curOrder,pb1,curRow,pb3); } + prevOrder=curOrder; + prevRow=curRow; + for (int i=0; i=chans) { + pendingNotes.pop(); + continue; + } if (note.on) { dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,note.channel,note.ins,1)); dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,note.channel,note.note)); keyHit[note.channel]=true; chan[note.channel].noteOnInhibit=true; } else { - dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,note.channel)); + DivMacroInt* macroInt=disCont[dispatchOfChan[note.channel]].dispatch->getChanMacroInt(dispatchChanOfChan[note.channel]); + if (macroInt!=NULL) { + if (macroInt->hasRelease) { + dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF_ENV,note.channel)); + } else { + dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,note.channel)); + } + } else { + dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,note.channel)); + } } pendingNotes.pop(); } @@ -986,6 +1003,10 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { if (chan[i].vibratoDepth>0) { chan[i].vibratoPos+=chan[i].vibratoRate; if (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64; + + chan[i].vibratoPosGiant+=chan[i].vibratoRate; + if (chan[i].vibratoPos>=512) chan[i].vibratoPos-=512; + 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))); @@ -1094,6 +1115,8 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size) { + lastLoopPos=-1; + if (out!=NULL) { memset(out[0],0,size*sizeof(float)); memset(out[1],0,size*sizeof(float)); @@ -1374,6 +1397,9 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi } } if (nextTick()) { + lastLoopPos=size-(runLeftG>>MASTER_CLOCK_PREC); + logD("last loop pos: %d for a size of %d and runLeftG of %d",lastLoopPos,size,runLeftG); + totalLoops++; if (remainingLoops>0) { remainingLoops--; if (!remainingLoops) { diff --git a/src/engine/safeReader.cpp b/src/engine/safeReader.cpp index 173ffd235..3cb1f4b1f 100644 --- a/src/engine/safeReader.cpp +++ b/src/engine/safeReader.cpp @@ -82,7 +82,8 @@ short SafeReader::readS() { logD("SR: reading short %x:",curSeek); #endif if (curSeek+2>len) throw EndOfFileException(this,len); - short ret=*(short*)(&buf[curSeek]); + short ret; + memcpy(&ret,&buf[curSeek],2); #ifdef READ_DEBUG logD("SR: %.4x",ret); #endif @@ -92,7 +93,8 @@ short SafeReader::readS() { short SafeReader::readS_BE() { if (curSeek+2>len) throw EndOfFileException(this,len); - short ret=*(short*)(&buf[curSeek]); + short ret; + memcpy(&ret,&buf[curSeek],2); curSeek+=2; return ((ret>>8)&0xff)|(ret<<8); } @@ -102,7 +104,8 @@ int SafeReader::readI() { logD("SR: reading int %x:",curSeek); #endif if (curSeek+4>len) throw EndOfFileException(this,len); - int ret=*(int*)(&buf[curSeek]); + int ret; + memcpy(&ret,&buf[curSeek],4); curSeek+=4; #ifdef READ_DEBUG logD("SR: %.8x",ret); @@ -112,28 +115,32 @@ int SafeReader::readI() { int SafeReader::readI_BE() { if (curSeek+4>len) throw EndOfFileException(this,len); - unsigned int ret=*(unsigned int*)(&buf[curSeek]); + unsigned int ret; + memcpy(&ret,&buf[curSeek],4); curSeek+=4; return (int)((ret>>24)|((ret&0xff0000)>>8)|((ret&0xff00)<<8)|((ret&0xff)<<24)); } int64_t SafeReader::readL() { if (curSeek+8>len) throw EndOfFileException(this,len); - int64_t ret=*(int64_t*)(&buf[curSeek]); + int64_t ret; + memcpy(&ret,&buf[curSeek],8); curSeek+=8; return ret; } float SafeReader::readF() { if (curSeek+4>len) throw EndOfFileException(this,len); - float ret=*(float*)(&buf[curSeek]); + float ret; + memcpy(&ret,&buf[curSeek],4); curSeek+=4; return ret; } double SafeReader::readD() { if (curSeek+8>len) throw EndOfFileException(this,len); - double ret=*(double*)(&buf[curSeek]); + double ret; + memcpy(&ret,&buf[curSeek],8); curSeek+=8; return ret; } diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 514c9cf3b..b5028c9eb 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -55,7 +55,14 @@ bool DivSample::save(const char* path) { si.channels=1; si.samplerate=rate; - si.format=SF_FORMAT_PCM_16|SF_FORMAT_WAV; + switch (depth) { + case 8: // 8-bit + si.format=SF_FORMAT_PCM_U8|SF_FORMAT_WAV; + break; + default: // 16-bit + si.format=SF_FORMAT_PCM_16|SF_FORMAT_WAV; + break; + } f=sf_open(path,SFM_WRITE,&si); @@ -81,7 +88,21 @@ bool DivSample::save(const char* path) { } sf_command(f, SFC_SET_INSTRUMENT, &inst, sizeof(inst)); - sf_writef_short(f,data16,samples); + switch (depth) { + case 8: { + // convert from signed to unsigned + unsigned char* buf=new unsigned char[length8]; + for (size_t i=0; iwrite(writeX1010[i]->getSampleMem(),writeX1010[i]->getSampleMemUsage()); } if (writeZ280[i]!=NULL && writeZ280[i]->getSampleMemUsage()>0) { + // In VGM, YMZ280B's 16-bit PCM has an endianness swapped + // which have been fixed in the upstream MAME since 2013 + // in order to get Konami FireBeat working + // The reason given for VGM not applying this change was + // "It matches OPL4 and MAME probably did an endianness optimization" + size_t sampleMemLen=writeZ280[i]->getSampleMemUsage(); + unsigned char* sampleMem=new unsigned char[sampleMemLen]; + memcpy(sampleMem,writeZ280[i]->getSampleMem(),sampleMemLen); + for (int i=0; idepth==16) { + unsigned int pos=s->offYMZ280B; + for (unsigned int j=0; jsamples; j++) { + unsigned char lo=sampleMem[pos+j*2]; + unsigned char hi=sampleMem[pos+j*2+1]; + sampleMem[pos+j*2]=hi; + sampleMem[pos+j*2+1]=lo; + } + } + } w->writeC(0x67); w->writeC(0x66); w->writeC(0x86); w->writeI((writeZ280[i]->getSampleMemUsage()+8)|(i*0x80000000)); w->writeI(writeZ280[i]->getSampleMemCapacity()); w->writeI(0); - w->write(writeZ280[i]->getSampleMem(),writeZ280[i]->getSampleMemUsage()); + w->write(sampleMem,sampleMemLen); + delete[] sampleMem; } } diff --git a/src/engine/waveSynth.cpp b/src/engine/waveSynth.cpp index e69031686..82b5d5ddc 100644 --- a/src/engine/waveSynth.cpp +++ b/src/engine/waveSynth.cpp @@ -243,8 +243,13 @@ void DivWaveSynth::changeWave2(int num) { first=true; } -void DivWaveSynth::setEngine(DivEngine* engine) { +void DivWaveSynth::setEngine(DivEngine* engine, int waveFloor) { e=engine; + memset(wave1,waveFloor,256); + memset(wave2,waveFloor,256); + for (int i=0; i<256; i++) { + output[i]=waveFloor; + } } void DivWaveSynth::init(DivInstrument* which, int w, int h, bool insChanged) { diff --git a/src/engine/waveSynth.h b/src/engine/waveSynth.h index 8709b0846..26f5b259b 100644 --- a/src/engine/waveSynth.h +++ b/src/engine/waveSynth.h @@ -70,7 +70,7 @@ class DivWaveSynth { * @param insChanged whether the instrument has changed. */ void init(DivInstrument* which, int width, int height, bool insChanged=false); - void setEngine(DivEngine* engine); + void setEngine(DivEngine* engine, int waveFloor=0); DivWaveSynth(): e(NULL), pos(0), diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 76fc80ad2..27e5f6116 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -48,6 +48,7 @@ const char* aboutLine[]={ "-- graphics/UI design --", "tildearrow", "BlastBrothers", + "Mahbod Karamoozian", "Raijin", "", "-- documentation --", @@ -59,18 +60,38 @@ const char* aboutLine[]={ "", "-- demo songs --", "0x5066", + "Abstract 64", "ActualNK358", + "akumanatt", + "AmigaX", + "AURORA*FIELDS", + "BlueElectric05", "breakthetargets", "CaptainMalware", + "DeMOSic", + "DevEd", + "Dippy", + "Forte", + "Fragmare", + "freq-mod", + "iyatemu", "kleeder", + "jaezu", + "Laggy", + "LunaMoth", "Mahbod Karamoozian", + "Miker", "nicco1690", "NikonTeen", + "SnugglyValeria", "SuperJet Spade", "TheDuccinator", + "theloredev", "TheRealHedgehogSonic", "tildearrow", "Ultraprogramer", + "Weeppiko", + "ZoomTen (Zumi)", "", "-- additional feedback/fixes --", "fd", @@ -85,7 +106,9 @@ const char* aboutLine[]={ "and Mark Adler", "libsndfile by Erik de Castro Lopo", "Portable File Dialogs by Sam Hocevar", + "Native File Dialog by Frogtoss Games", "RtMidi by Gary P. Scavone", + "backward-cpp by Google", "adpcm by superctr", "Nuked-OPL3/OPLL/OPM/OPN2 by Nuke.YKT", "ymfm by Aaron Giles", @@ -93,21 +116,31 @@ const char* aboutLine[]={ "MAME AY-3-8910 by Couriersud", "with AY8930 fixes by Eulous, cam900 and Grauw", "MAME SAA1099 by Juergen Buchmueller and Manuel Abadia", + "MAME Namco WSG by Nicola Salmoria and Aaron Giles", + "MAME RF5C68 core by Olivier Galibert and Aaron Giles", + "MAME MSM6258 core by Barry Rodewald", + "MAME YMZ280B core by Aaron Giles", "SAASound by Dave Hooper and Simon Owen", "SameBoy by Lior Halphon", "Mednafen PCE and WonderSwan audio cores", "puNES (NES, MMC5 and FDS) by FHorse", + "NSFPlay (NES and FDS) by Brad Smith and Brezza", "reSID by Dag Lem", "Stella by Stella Team", - "QSound emulator by Ian Karlsson and Valley Bell", + "QSound emulator by superctr 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", + "Konami SCC emulator by cam900", + "MSM6295 emulator by cam900", "", - "greetings to all members of Deflers of Noice!", + "greetings to:", + "Fractal Sound team", + "NEOART Costa Rica", + "all members of Deflers of Noice!", "", "copyright © 2021-2022 tildearrow", "(and contributors).", diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index fd9e2e107..da82de277 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -26,6 +26,49 @@ #define FURNACE_FFT_RATE 80.0 #define FURNACE_FFT_CUTOFF 0.1 +const char* chanOscRefs[]={ + "None (0%)", + "None (50%)", + "None (100%)", + + "Frequency", + "Volume", + "Channel", + "Brightness", + + "Note Trigger" +}; + +float FurnaceGUI::computeGradPos(int type, int chan) { + switch (type) { + case GUI_OSCREF_NONE: + return 0.0f; + break; + case GUI_OSCREF_CENTER: + return 0.5f; + break; + case GUI_OSCREF_MAX: + return 1.0f; + break; + case GUI_OSCREF_FREQUENCY: + return chanOscPitch[chan]; + break; + case GUI_OSCREF_VOLUME: + return chanOscVol[chan]; + break; + case GUI_OSCREF_CHANNEL: + return (float)chan/(float)(e->getTotalChannelCount()-1); + break; + case GUI_OSCREF_BRIGHT: + return chanOscBright[chan]; + break; + case GUI_OSCREF_NOTE_TRIGGER: + return keyHit[chan]*5.0f; + break; + } + return 0.0f; +} + void FurnaceGUI::drawChanOsc() { if (nextWindow==GUI_WINDOW_CHAN_OSC) { chanOscOpen=true; @@ -34,36 +77,178 @@ void FurnaceGUI::drawChanOsc() { } if (!chanOscOpen) return; ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); - if (ImGui::Begin("Oscilloscope (per-channel)",&chanOscOpen,globalWinFlags)) { + if (ImGui::Begin("Oscilloscope (per-channel)",&chanOscOpen,globalWinFlags|((chanOscOptions)?0:ImGuiWindowFlags_NoTitleBar))) { bool centerSettingReset=false; - 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; + ImDrawList* dl=ImGui::GetWindowDrawList(); + if (chanOscOptions) { + 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(); + if (ImGui::Checkbox("Center waveform",&chanOscWaveCorr)) { + centerSettingReset=true; + } + + ImGui::EndTable(); } - 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::Checkbox("Gradient",&chanOscUseGrad); + + if (chanOscUseGrad) { + if (chanOscGradTex==NULL) { + chanOscGradTex=SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ABGR8888,SDL_TEXTUREACCESS_STREAMING,chanOscGrad.width,chanOscGrad.height); + + if (chanOscGradTex==NULL) { + logE("error while creating gradient texture! %s",SDL_GetError()); + } else { + updateChanOscGradTex=true; + } + } + + if (ImGui::BeginTable("ChanOscGradSet",2)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (chanOscGradTex!=NULL) { + if (updateChanOscGradTex) { + chanOscGrad.render(); + if (SDL_UpdateTexture(chanOscGradTex,NULL,chanOscGrad.grad.get(),chanOscGrad.width*4)==0) { + updateChanOscGradTex=false; + } else { + logE("error while updating gradient texture! %s",SDL_GetError()); + } + } + + ImVec2 gradLeft=ImGui::GetCursorPos(); + ImVec2 gradSize=ImVec2(400.0f*dpiScale,400.0f*dpiScale); + ImGui::Image(chanOscGradTex,gradSize); + ImVec2 gradLeftAbs=ImGui::GetItemRectMin(); + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + if (chanOscGrad.points.size()<32) { + chanOscGrad.points.push_back(Gradient2DPoint( + (ImGui::GetMousePos().x-gradLeftAbs.x)/gradSize.x, + (ImGui::GetMousePos().y-gradLeftAbs.y)/gradSize.y + )); + updateChanOscGradTex=true; + } + } + + ImVec2 oldCurPos=ImGui::GetCursorPos(); + int index=0; + int removePoint=-1; + for (Gradient2DPoint& i: chanOscGrad.points) { + ImGui::PushID(index+16); + ImGui::SetCursorPos(ImVec2(gradLeft.x+i.x*gradSize.x-8.0*dpiScale,gradLeft.y+i.y*gradSize.y-8.0*dpiScale)); + if (ImGui::InvisibleButton("gradPoint",ImVec2(16.0*dpiScale,16.0*dpiScale))) { + if (!i.grab) { + ImGui::OpenPopup("gradPointSettings"); + } + } + if (ImGui::IsItemHovered() || ImGui::IsItemActive()) { + ImGui::SetTooltip("(%.1f, %.1f)",i.x*100.0f,(1.0f-i.y)*100.0f); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) { + removePoint=index; + } + if (ImGui::IsItemActive()) { + float mX=(ImGui::GetMousePos().x-gradLeftAbs.x)/gradSize.x; + float mY=(ImGui::GetMousePos().y-gradLeftAbs.y)/gradSize.y; + + if (i.grab || (fabs(i.x-mX)>0.015 || fabs(i.y-mY)>0.015)) { + i.x=mX; + i.y=mY; + i.grab=true; + + if (i.x<0) i.x=0; + if (i.x>1) i.x=1; + if (i.y<0) i.y=0; + if (i.y>1) i.y=1; + updateChanOscGradTex=true; + } + } else { + i.grab=false; + i.prevX=i.x; + i.prevY=i.y; + } + if (ImGui::BeginPopup("gradPointSettings",ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::ColorPicker4("Color",(float*)&i.color)) { + updateChanOscGradTex=true; + } + ImGui::Text("Distance"); + ImGui::SameLine(); + float pDist=i.distance*100.0f; + if (ImGui::SliderFloat("##PDistance",&pDist,0.0f,150.0f,"%.1f%%")) { + i.distance=pDist/100.0f; + updateChanOscGradTex=true; + } + + ImGui::Text("Spread"); + ImGui::SameLine(); + float pSpread=i.spread*100.0f; + if (ImGui::SliderFloat("##PSpread",&pSpread,0.0f,150.0f,"%.1f%%")) { + i.spread=pSpread/100.0f; + updateChanOscGradTex=true; + } + + if (ImGui::Button("Remove")) { + removePoint=index; + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + + dl->AddCircle(ImVec2(gradLeftAbs.x+i.x*gradSize.x,gradLeftAbs.y+i.y*gradSize.y),8.0*dpiScale,ImGui::ColorConvertFloat4ToU32(ImVec4(0.5,0.5,0.5,1.0)),6,2.0f*dpiScale); + dl->AddCircle(ImVec2(gradLeftAbs.x+i.x*gradSize.x,gradLeftAbs.y+i.y*gradSize.y),5.0*dpiScale,ImGui::ColorConvertFloat4ToU32(ImVec4(0.1,0.1,0.1,1.0)),6,2.0f*dpiScale); + + ImGui::PopID(); + index++; + } + ImGui::SetCursorPos(oldCurPos); + + if (removePoint>=0) { + chanOscGrad.points.erase(chanOscGrad.points.begin()+removePoint); + updateChanOscGradTex=true; + } + } + + ImGui::TableNextColumn(); + if (ImGui::ColorEdit4("Background",(float*)&chanOscGrad.bgColor)) { + updateChanOscGradTex=true; + } + ImGui::Combo("X Axis##AxisX",&chanOscColorX,chanOscRefs,GUI_OSCREF_MAX); + ImGui::Combo("Y Axis##AxisY",&chanOscColorY,chanOscRefs,GUI_OSCREF_MAX); + + ImGui::EndTable(); + } + } else { + ImGui::SetNextItemWidth(400.0f*dpiScale); + ImGui::ColorPicker4("Color",(float*)&chanOscColor); } - ImGui::TableNextColumn(); - if (ImGui::Checkbox("Center waveform",&chanOscWaveCorr)) { - centerSettingReset=true; + if (ImGui::Button("OK")) { + chanOscOptions=false; } - - ImGui::EndTable(); } - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f)); float availY=ImGui::GetContentRegionAvail().y; @@ -72,16 +257,14 @@ void FurnaceGUI::drawChanOsc() { std::vector oscFFTs; 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) { + if (buf!=NULL && e->curSubSong->chanShow[i]) { oscBufs.push_back(buf); oscFFTs.push_back(&chanOscChan[i]); oscChans.push_back(i); @@ -135,88 +318,77 @@ void FurnaceGUI::drawChanOsc() { waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f)); } } else { + float minLevel=1.0f; + float maxLevel=-1.0f; + float dcOff=0.0f; unsigned short needlePos=buf->needle; - if (chanOscWaveCorr) { - /* - double fftDataRate=(FURNACE_FFT_SIZE*FURNACE_FFT_RATE)/((double)buf->rate); - while (buf->readNeedle!=needlePos) { - fft->inBufPosFrac+=fftDataRate; - while (fft->inBufPosFrac>=1.0) { - chanOscLP0[ch]+=FURNACE_FFT_CUTOFF*((float)buf->data[buf->readNeedle]-chanOscLP0[ch]); - chanOscLP1[ch]+=FURNACE_FFT_CUTOFF*(chanOscLP0[ch]-chanOscLP1[ch]); - fft->inBuf[fft->inBufPos]=(double)chanOscLP1[ch]/32768.0; - if (++fft->inBufPos>=FURNACE_FFT_SIZE) { - fftw_execute(fft->plan); - fft->inBufPos=0; - fft->needle=buf->readNeedle; - } - fft->inBufPosFrac-=1.0; - } - buf->readNeedle++; - }*/ - - for (int i=0; iinBuf[i]=(double)buf->data[(unsigned short)(needlePos-displaySize*2+((i*displaySize*2)/FURNACE_FFT_SIZE))]/32768.0; - } - fftw_execute(fft->plan); - - // find origin frequency - int point=1; - double candAmp=0.0; - for (unsigned short i=1; i<512; i++) { - fftw_complex& f=fft->outBuf[i]; - // AMPLITUDE - double amp=sqrt(pow(f[0],2.0)+pow(f[1],2.0))/pow((double)i,0.8); - if (amp>candAmp) { - point=i; - candAmp=amp; - } - } - - // PHASE - fftw_complex& candPoint=fft->outBuf[point]; - double phase=((double)(displaySize*2)/(double)point)*(0.5+(atan2(candPoint[1],candPoint[0])/(M_PI*2))); - - //needlePos=fft->needle; - needlePos-=phase; - - /* - int alignment=0; - for (unsigned short i=0; idata[(unsigned short)(needlePos-i)])>fabs(buf->data[(unsigned short)(needlePos-alignment)])) { - alignment=i; - } - } - needlePos-=alignment; - */ - - String cPhase=fmt::sprintf("%d cphase: %f",point,phase); - dl->AddText(inRect.Min,0xffffffff,cPhase.c_str()); - - 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)); - } - } else { - 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)); + for (int i=0; iinBuf[i]=(double)buf->data[(unsigned short)(needlePos-displaySize*2+((i*displaySize*2)/FURNACE_FFT_SIZE))]/32768.0; + } + fftw_execute(fft->plan); + + // find origin frequency + int point=1; + double candAmp=0.0; + for (unsigned short i=1; i<512; i++) { + fftw_complex& f=fft->outBuf[i]; + // AMPLITUDE + double amp=sqrt(pow(f[0],2.0)+pow(f[1],2.0))/pow((double)i,0.8); + if (amp>candAmp) { + point=i; + candAmp=amp; } } + + // PHASE + fftw_complex& candPoint=fft->outBuf[point]; + double phase=((double)(displaySize*2)/(double)point)*(0.5+(atan2(candPoint[1],candPoint[0])/(M_PI*2))); + + if (chanOscWaveCorr) { + needlePos-=phase; + } + chanOscPitch[ch]=(float)point/32.0f; + + /* + String cPhase=fmt::sprintf("%d cphase: %f vol: %f",point,phase,chanOscVol[ch]); + dl->AddText(inRect.Min,0xffffffff,cPhase.c_str()); + */ + + needlePos-=displaySize; + for (unsigned short i=0; i<512; i++) { + float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f; + if (minLevel>y) minLevel=y; + if (maxLeveldata[(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-dcOff))); + } + } + ImU32 color=ImGui::GetColorU32(chanOscColor); + if (chanOscUseGrad) { + float xVal=computeGradPos(chanOscColorX,ch); + float yVal=computeGradPos(chanOscColorY,ch); + + xVal=CLAMP(xVal,0.0f,1.0f); + yVal=CLAMP(yVal,0.0f,1.0f); + + color=chanOscGrad.get(xVal,1.0f-yVal); } dl->AddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale); } } } ImGui::EndTable(); + + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + chanOscOptions=!chanOscOptions; + } } ImGui::PopStyleVar(); } diff --git a/src/gui/cursor.cpp b/src/gui/cursor.cpp index e47dfd629..5a98a78c6 100644 --- a/src/gui/cursor.cpp +++ b/src/gui/cursor.cpp @@ -42,6 +42,22 @@ void FurnaceGUI::startSelection(int xCoarse, int xFine, int y, bool fullRow) { return; } } + + if ((settings.dragMovesSelection==1 || (settings.dragMovesSelection==2 && (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)))) && !fullRow) { + if (xCoarse>=selStart.xCoarse && (xFine>=selStart.xFine || xCoarse>selStart.xCoarse) && y>=selStart.y && + xCoarse<=selEnd.xCoarse && (xFine<=selEnd.xFine || xCoarsecurPat[selEnd.xCoarse].effectCols*2; - selEnd.y=y; + if (dragging) { + dragDestinationX=xCoarse; + dragDestinationY=y; + cursorDrag.xCoarse=xCoarse; + cursorDrag.xFine=xFine; + cursorDrag.y=y; + + int len=dragEnd.xCoarse-dragStart.xCoarse+1; + if (len<0) len=0; + + DETERMINE_FIRST_LAST; + + if (dragStart.xCoarse+(dragDestinationX-dragSourceX)=lastChannel) { + dragDestinationX=lastChannel-(dragEnd.xCoarse-dragSourceX)-1; + } + + if (dragStart.y+(dragDestinationY-dragSourceY)<0) { + dragDestinationY=dragSourceY-dragStart.y; + } + + if (dragEnd.y+(dragDestinationY-dragSourceY)>=e->curSubSong->patLen) { + dragDestinationY=e->curSubSong->patLen-(dragEnd.y-dragSourceY)-1; + } + + selStart.xCoarse=dragStart.xCoarse+(dragDestinationX-dragSourceX); + selStart.xFine=dragStart.xFine; + selStart.y=dragStart.y+(dragDestinationY-dragSourceY); + selEnd.xCoarse=dragEnd.xCoarse+(dragDestinationX-dragSourceX); + selEnd.xFine=dragEnd.xFine; + selEnd.y=dragEnd.y+(dragDestinationY-dragSourceY); } else { - selEnd.xCoarse=xCoarse; - selEnd.xFine=xFine; - selEnd.y=y; + if (selectingFull) { + DETERMINE_LAST; + selEnd.xCoarse=lastChannel-1; + selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2; + selEnd.y=y; + } else { + selEnd.xCoarse=xCoarse; + selEnd.xFine=xFine; + selEnd.y=y; + } } } @@ -103,6 +155,18 @@ void FurnaceGUI::finishSelection() { selecting=false; selectingFull=false; + if (dragging) { + if (dragSourceX==dragDestinationX && dragSourceY==dragDestinationY) { + cursor=cursorDrag; + selStart=cursorDrag; + selEnd=cursorDrag; + } else { // perform drag + doDrag(); + } + + dragging=false; + } + // boundary check int chanCount=e->getTotalChannelCount(); diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 2be83902e..29f75707f 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -372,6 +372,9 @@ void FurnaceGUI::drawDebug() { if (ImGui::Button("Inspect")) { inspectorOpen=!inspectorOpen; } + if (ImGui::Button("Spoiler")) { + spoilerOpen=!spoilerOpen; + } ImGui::TreePop(); } if (ImGui::TreeNode("Performance")) { diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 30a654b7b..6f952a448 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -87,6 +87,12 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_STOP: stop(); break; + case GUI_ACTION_PLAY_START: + e->setOrder(0); + if (!e->isPlaying()) { + play(); + } + break; case GUI_ACTION_PLAY_REPEAT: play(); e->setRepeatPattern(true); @@ -244,6 +250,9 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_WINDOW_CHAN_OSC: nextWindow=GUI_WINDOW_CHAN_OSC; break; + case GUI_ACTION_WINDOW_FIND: + nextWindow=GUI_WINDOW_FIND; + break; case GUI_ACTION_COLLAPSE_WINDOW: collapseWindow=true; @@ -325,6 +334,9 @@ void FurnaceGUI::doAction(int what) { case GUI_WINDOW_CHAN_OSC: chanOscOpen=false; break; + case GUI_WINDOW_FIND: + findOpen=false; + break; default: break; } diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index ea12eb591..9bd827951 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -62,10 +62,13 @@ void FurnaceGUI::prepareUndo(ActionType action) { case GUI_UNDO_PATTERN_FLIP: case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: + case GUI_UNDO_PATTERN_DRAG: for (int i=0; igetTotalChannelCount(); i++) { e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false)->copyOn(oldPat[i]); } break; + case GUI_UNDO_REPLACE: // this is handled by doReplace() + break; } } @@ -112,6 +115,7 @@ void FurnaceGUI::makeUndo(ActionType action) { case GUI_UNDO_PATTERN_FLIP: case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: + case GUI_UNDO_PATTERN_DRAG: for (int i=0; igetTotalChannelCount(); i++) { DivPattern* p=e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false); for (int j=0; jcurSubSong->patLen; j++) { @@ -126,6 +130,8 @@ void FurnaceGUI::makeUndo(ActionType action) { doPush=true; } break; + case GUI_UNDO_REPLACE: // this is handled by doReplace() + break; } if (doPush) { MARK_MODIFIED; @@ -914,6 +920,76 @@ void FurnaceGUI::doExpand(int multiplier) { makeUndo(GUI_UNDO_PATTERN_EXPAND); } +void FurnaceGUI::doDrag() { + DivPattern* patBuffer=NULL; + int len=dragEnd.xCoarse-dragStart.xCoarse+1; + + DETERMINE_FIRST_LAST; + + if (len<1) return; + + patBuffer=new DivPattern[len]; + prepareUndo(GUI_UNDO_PATTERN_DRAG); + + // copy and clear + { + int iCoarse=dragStart.xCoarse; + int iFine=dragStart.xFine; + int iCoarseP=0; + for (; iCoarse<=dragEnd.xCoarse; iCoarse++) { + if (!e->curSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsedata[j][iFine]; + pat->data[j][iFine]=0; + if (dragStart.y==dragEnd.y) pat->data[j][2]=-1; + } + patBuffer[iCoarseP].data[row][iFine+1]=pat->data[j][iFine+1]; + pat->data[j][iFine+1]=(iFine<1)?0:-1; + + if (dragStart.y==dragEnd.y && iFine>2 && iFine&1 && settings.effectDeletionAltersValue) { + pat->data[j][iFine+2]=-1; + } + row++; + } + } + iFine=0; + iCoarseP++; + } + } + + // replace + { + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int iCoarseP=0; + for (; iCoarse<=selEnd.xCoarse && iCoarsePlastChannel) continue; + if (!e->curSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse=e->curSubSong->patLen) continue; + if (iFine==0) { + pat->data[j][iFine]=patBuffer[iCoarseP].data[row][iFine]; + } + pat->data[j][iFine+1]=patBuffer[iCoarseP].data[row][iFine+1]; + } + } + iFine=0; + iCoarseP++; + } + } + + delete[] patBuffer; + makeUndo(GUI_UNDO_PATTERN_DRAG); +} + void FurnaceGUI::doUndo() { if (undoHist.empty()) return; UndoStep& us=undoHist.back(); @@ -943,18 +1019,22 @@ void FurnaceGUI::doUndo() { case GUI_UNDO_PATTERN_FLIP: case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: + case GUI_UNDO_PATTERN_DRAG: + case GUI_UNDO_REPLACE: for (UndoPatternData& i: us.pat) { e->changeSongP(i.subSong); DivPattern* p=e->curPat[i.chan].getPattern(i.pat,true); p->data[i.row][i.col]=i.oldVal; } - if (!e->isPlaying() || !followPattern) { - cursor=us.cursor; - selStart=us.selStart; - selEnd=us.selEnd; - curNibble=us.nibble; - updateScroll(cursor.y); - setOrder(us.order); + if (us.type!=GUI_UNDO_REPLACE) { + if (!e->isPlaying() || !followPattern) { + cursor=us.cursor; + selStart=us.selStart; + selEnd=us.selEnd; + curNibble=us.nibble; + updateScroll(cursor.y); + setOrder(us.order); + } } break; } @@ -991,18 +1071,22 @@ void FurnaceGUI::doRedo() { case GUI_UNDO_PATTERN_FLIP: case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: + case GUI_UNDO_PATTERN_DRAG: + case GUI_UNDO_REPLACE: for (UndoPatternData& i: us.pat) { e->changeSongP(i.subSong); DivPattern* p=e->curPat[i.chan].getPattern(i.pat,true); p->data[i.row][i.col]=i.newVal; } - if (!e->isPlaying()) { - cursor=us.cursor; - selStart=us.selStart; - selEnd=us.selEnd; - curNibble=us.nibble; - updateScroll(cursor.y); - setOrder(us.order); + if (us.type!=GUI_UNDO_REPLACE) { + if (!e->isPlaying() || !followPattern) { + cursor=us.cursor; + selStart=us.selStart; + selEnd=us.selEnd; + curNibble=us.nibble; + updateScroll(cursor.y); + setOrder(us.order); + } } break; diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index e50298c1a..f9d4ac4dd 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -2,7 +2,62 @@ #include "ImGuiFileDialog.h" #include "../ta-log.h" +#ifdef USE_NFD +#include +#else #include "../../extern/pfd-fixed/portable-file-dialogs.h" +#endif + +#ifdef USE_NFD +struct NFDState { + bool isSave; + String header; + std::vector filter; + String path; + FileDialogSelectCallback clickCallback; + NFDState(bool save, String h, std::vector filt, String pa, FileDialogSelectCallback cc): + isSave(save), + header(h), + filter(filt), + path(pa), + clickCallback(cc) { + } +}; + +// TODO: filter +void _nfdThread(const NFDState state, std::atomic* ok, String* result) { + nfdchar_t* out=NULL; + nfdresult_t ret=NFD_CANCEL; + + if (state.isSave) { + ret=NFD_SaveDialog(state.filter,state.path.c_str(),&out,state.clickCallback); + } else { + ret=NFD_OpenDialog(state.filter,state.path.c_str(),&out,state.clickCallback); + } + + switch (ret) { + case NFD_OKAY: + if (out!=NULL) { + (*result)=out; + } else { + (*result)=""; + } + break; + case NFD_CANCEL: + (*result)=""; + break; + case NFD_ERROR: + (*result)=""; + logE("NFD error! %s\n",NFD_GetError()); + break; + default: + logE("NFD unknown return code %d!\n",ret); + (*result)=""; + break; + } + (*ok)=true; +} +#endif bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale, FileDialogSelectCallback clickCallback) { if (opened) return false; @@ -10,7 +65,16 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, c curPath=path; logD("opening load file dialog with curPath %s",curPath.c_str()); if (sysDialog) { +#ifdef USE_NFD + dialogOK=false; +#ifdef NFD_NON_THREADED + _nfdThread(NFDState(false,header,filter,path,clickCallback),&dialogOK,&nfdResult); +#else + dialogO=new std::thread(_nfdThread,NFDState(false,header,filter,path,clickCallback),&dialogOK,&nfdResult); +#endif +#else dialogO=new pfd::open_file(header,path,filter); +#endif } else { ImGuiFileDialog::Instance()->DpiScale=dpiScale; ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,1,nullptr,0,clickCallback); @@ -25,7 +89,16 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, c curPath=path; logD("opening save file dialog with curPath %s",curPath.c_str()); if (sysDialog) { +#ifdef USE_NFD + dialogOK=false; +#ifdef NFD_NON_THREADED + _nfdThread(NFDState(true,header,filter,path,NULL),&dialogOK,&nfdResult); +#else + dialogS=new std::thread(_nfdThread,NFDState(true,header,filter,path,NULL),&dialogOK,&nfdResult); +#endif +#else dialogS=new pfd::save_file(header,path,filter); +#endif } else { ImGuiFileDialog::Instance()->DpiScale=dpiScale; ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); @@ -46,15 +119,24 @@ void FurnaceGUIFileDialog::close() { if (sysDialog) { if (saving) { if (dialogS!=NULL) { +#ifdef USE_NFD + dialogS->join(); +#endif delete dialogS; dialogS=NULL; } } else { if (dialogO!=NULL) { +#ifdef USE_NFD + dialogO->join(); +#endif delete dialogO; dialogO=NULL; } } +#ifdef USE_NFD + dialogOK=false; +#endif } else { ImGuiFileDialog::Instance()->Close(); } @@ -63,6 +145,15 @@ void FurnaceGUIFileDialog::close() { bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { if (sysDialog) { +#ifdef USE_NFD + if (dialogOK) { + fileName=nfdResult; + logD("returning %s",fileName.c_str()); + dialogOK=false; + return true; + } + return false; +#else if (saving) { if (dialogS!=NULL) { if (dialogS->ready(0)) { @@ -90,6 +181,7 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { } } return false; +#endif } else { return ImGuiFileDialog::Instance()->Display("FileDialog",ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove,min,max); } diff --git a/src/gui/fileDialog.h b/src/gui/fileDialog.h index 5eb67d853..6724eb951 100644 --- a/src/gui/fileDialog.h +++ b/src/gui/fileDialog.h @@ -3,10 +3,24 @@ #include #include +#if defined(_WIN32) || defined(__APPLE__) +#define USE_NFD +#endif + +#ifdef USE_NFD +#include +#include + +#ifdef __APPLE__ +#define NFD_NON_THREADED +#endif + +#else namespace pfd { class open_file; class save_file; } +#endif typedef std::function FileDialogSelectCallback; @@ -16,8 +30,15 @@ class FurnaceGUIFileDialog { bool saving; String curPath; String fileName; +#ifdef USE_NFD + std::thread* dialogO; + std::thread* dialogS; + std::atomic dialogOK; + String nfdResult; +#else pfd::open_file* dialogO; pfd::save_file* dialogS; +#endif public: 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); diff --git a/src/gui/findReplace.cpp b/src/gui/findReplace.cpp new file mode 100644 index 000000000..78abce7cb --- /dev/null +++ b/src/gui/findReplace.cpp @@ -0,0 +1,1060 @@ +/** + * 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 "IconsFontAwesome4.h" +#include "misc/cpp/imgui_stdlib.h" +#include "guiConst.h" +#include "intConst.h" +#include "../ta-log.h" + +const char* queryModes[GUI_QUERY_MAX]={ + "ignore", + "equals", + "not equal", + "between", + "not between", + "any", + "none" +}; + +const char* queryReplaceModes[GUI_QUERY_REPLACE_MAX]={ + "set", + "add", + "add (overflow)", + "clear" +}; + +int queryNote(int note, int octave) { + if (note==100) { + return 128; + } else if (note==101) { // note off and envelope release + return 129; + } else if (note==102) { // envelope release only + return 130; + } else if (octave==0 && note==0) { + return -61; + } else if (note==0 && octave!=0) { + return -61; // bug note? + } + int seek=(note+(signed char)octave*12); + if (seek<-60 || seek>=120) { + return -61; // out of range note + } + return seek; +} + +bool checkCondition(int mode, int arg, int argMax, int val, bool noteMode=false) { + const int emptyVal=noteMode?-61:-1; + switch (mode) { + case GUI_QUERY_IGNORE: + return true; + break; + case GUI_QUERY_MATCH: + return (val==arg); + break; + case GUI_QUERY_MATCH_NOT: + return (val!=emptyVal && val!=arg); + break; + case GUI_QUERY_RANGE: + return (val>=arg && val<=argMax); + break; + case GUI_QUERY_RANGE_NOT: + return (val!=emptyVal && (valargMax) && (!noteMode || val<120)); + break; + case GUI_QUERY_ANY: + return (val!=emptyVal); + break; + case GUI_QUERY_NONE: + return (val==emptyVal); + break; + } + return false; +} + +void FurnaceGUI::doFind() { + int firstOrder=0; + int lastOrder=e->curSubSong->ordersLen-1; + + if (curQueryRangeY==1 || curQueryRangeY==2) { + firstOrder=curOrder; + lastOrder=curOrder; + } + + int firstRow=0; + int lastRow=e->curSubSong->patLen-1; + + if (curQueryRangeY==1) { + firstRow=selStart.y; + lastRow=selEnd.y; + } + + int firstChan=0; + int lastChan=e->getTotalChannelCount()-1; + + if (curQueryRangeX) { + firstChan=curQueryRangeXMin; + lastChan=curQueryRangeXMax; + } + + curQueryResults.clear(); + + for (int i=firstOrder; i<=lastOrder; i++) { + for (int j=firstRow; j<=lastRow; j++) { + for (int k=firstChan; k<=lastChan; k++) { + DivPattern* p=e->curPat[k].getPattern(e->curOrders->ord[k][i],false); + bool matched=false; + for (FurnaceGUIFindQuery& l: curQuery) { + if (matched) break; + + if (!checkCondition(l.noteMode,l.note,l.noteMax,queryNote(p->data[j][0],p->data[j][1]),true)) continue; + if (!checkCondition(l.insMode,l.ins,l.insMax,p->data[j][2])) continue; + if (!checkCondition(l.volMode,l.vol,l.volMax,p->data[j][3])) continue; + + if (l.effectCount>0) { + bool notMatched=false; + switch (curQueryEffectPos) { + case 0: // no + for (int m=0; mcurPat[k].effectCols; n++) { + if (!checkCondition(l.effectMode[m],l.effect[m],l.effectMax[m],p->data[j][4+n*2])) continue; + if (!checkCondition(l.effectValMode[m],l.effectVal[m],l.effectValMax[m],p->data[j][5+n*2])) continue; + allGood=true; + break; + } + if (!allGood) { + notMatched=true; + break; + } + } + break; + case 1: { // lax + // locate first effect + int posOfFirst=-1; + for (int m=0; mcurPat[k].effectCols; m++) { + if (!checkCondition(l.effectMode[0],l.effect[0],l.effectMax[0],p->data[j][4+m*2])) continue; + if (!checkCondition(l.effectValMode[0],l.effectVal[0],l.effectValMax[0],p->data[j][5+m*2])) continue; + posOfFirst=m; + break; + } + if (posOfFirst<0) { + notMatched=true; + break; + } + // make sure we aren't too far to the right + if ((posOfFirst+l.effectCount)>e->curPat[k].effectCols) { + notMatched=true; + break; + } + // search from first effect location + for (int m=0; mdata[j][4+(m+posOfFirst)*2])) { + notMatched=true; + break; + } + if (!checkCondition(l.effectValMode[m],l.effectVal[m],l.effectValMax[m],p->data[j][5+(m+posOfFirst)*2])) { + notMatched=true; + break; + } + } + break; + } + case 2: // strict + int effectMax=l.effectCount; + if (effectMax>e->curPat[k].effectCols) { + notMatched=true; + } else { + for (int m=0; mdata[j][4+m*2])) { + notMatched=true; + break; + } + if (!checkCondition(l.effectValMode[m],l.effectVal[m],l.effectValMax[m],p->data[j][5+m*2])) { + notMatched=true; + break; + } + } + } + break; + } + if (notMatched) continue; + } + + matched=true; + } + if (matched) { + curQueryResults.push_back(FurnaceGUIQueryResult(e->getCurrentSubSong(),i,k,j)); + } + } + } + } + queryViewingResults=true; +} + +void FurnaceGUI::doReplace() { + doFind(); + queryViewingResults=false; + + bool* touched[DIV_MAX_CHANS]; + memset(touched,0,DIV_MAX_CHANS*sizeof(bool*)); + + UndoStep us; + us.type=GUI_UNDO_REPLACE; + + short prevVal[32]; + memset(prevVal,0,32*sizeof(short)); + + for (FurnaceGUIQueryResult& i: curQueryResults) { + int patIndex=e->song.subsong[i.subsong]->orders.ord[i.x][i.order]; + DivPattern* p=e->song.subsong[i.subsong]->pat[i.x].getPattern(patIndex,true); + if (touched[i.x]==NULL) { + touched[i.x]=new bool[256*256]; + memset(touched[i.x],0,256*256*sizeof(bool)); + } + if (touched[i.x][(patIndex<<8)|i.y]) continue; + touched[i.x][(patIndex<<8)|i.y]=true; + + memcpy(prevVal,p->data[i.y],32*sizeof(short)); + + if (queryReplaceNoteDo) { + switch (queryReplaceNoteMode) { + case GUI_QUERY_REPLACE_SET: + if (queryReplaceNote==130) { // macro release + p->data[i.y][0]=102; + p->data[i.y][1]=0; + } else if (queryReplaceNote==129) { // note release + p->data[i.y][0]=101; + p->data[i.y][1]=0; + } else if (queryReplaceNote==128) { // note off + p->data[i.y][0]=100; + p->data[i.y][1]=0; + } else if (queryReplaceNote>=-60 && queryReplaceNote<120) { // note + p->data[i.y][0]=(queryReplaceNote+60)%12; + if (p->data[i.y][0]==0) p->data[i.y][0]=12; + p->data[i.y][1]=(unsigned char)((queryReplaceNote-1)/12); + } else { // invalid + p->data[i.y][0]=0; + p->data[i.y][1]=0; + } + break; + case GUI_QUERY_REPLACE_ADD: + if (p->data[i.y][0]<100) { + int note=queryNote(p->data[i.y][0],p->data[i.y][1]); + if (note>=-60 && note<120) { + note+=queryReplaceNote; + if (note<-60) note=-60; + if (note>119) note=119; + + p->data[i.y][0]=(note+60)%12; + p->data[i.y][1]=(unsigned char)(((note+60)/12)-5); + if (p->data[i.y][0]==0) { + p->data[i.y][0]=12; + p->data[i.y][1]=(unsigned char)(p->data[i.y][1]-1); + } + } + } + break; + case GUI_QUERY_REPLACE_ADD_OVERFLOW: + if (p->data[i.y][0]<100) { + int note=queryNote(p->data[i.y][0],p->data[i.y][1]); + if (note>=-60 && note<120) { + note+=queryReplaceNote; + if (note<-60) { + while (note<-60) note+=180; + } else if (note>119) { + while (note>119) note-=180; + } + + p->data[i.y][0]=(note+60)%12; + p->data[i.y][1]=(unsigned char)(((note+60)/12)-5); + if (p->data[i.y][0]==0) { + p->data[i.y][0]=12; + p->data[i.y][1]=(unsigned char)(p->data[i.y][1]-1); + } + } + } + break; + case GUI_QUERY_REPLACE_CLEAR: + p->data[i.y][0]=0; + p->data[i.y][1]=0; + break; + } + } + + if (queryReplaceInsDo) { + switch (queryReplaceInsMode) { + case GUI_QUERY_REPLACE_SET: + p->data[i.y][2]=queryReplaceIns; + break; + case GUI_QUERY_REPLACE_ADD: + if (p->data[i.y][2]>=0) { + p->data[i.y][2]+=queryReplaceIns; + if (p->data[i.y][2]<0) p->data[i.y][2]=0; + if (p->data[i.y][2]>255) p->data[i.y][2]=255; + } + break; + case GUI_QUERY_REPLACE_ADD_OVERFLOW: + if (p->data[i.y][2]>=0) p->data[i.y][2]=(p->data[i.y][2]+queryReplaceIns)&0xff; + break; + case GUI_QUERY_REPLACE_CLEAR: + p->data[i.y][2]=-1; + break; + } + } + + if (queryReplaceVolDo) { + switch (queryReplaceVolMode) { + case GUI_QUERY_REPLACE_SET: + p->data[i.y][3]=queryReplaceVol; + break; + case GUI_QUERY_REPLACE_ADD: + if (p->data[i.y][3]>=0) { + p->data[i.y][3]+=queryReplaceVol; + if (p->data[i.y][3]<0) p->data[i.y][3]=0; + if (p->data[i.y][3]>255) p->data[i.y][3]=255; + } + break; + case GUI_QUERY_REPLACE_ADD_OVERFLOW: + if (p->data[i.y][3]>=0) p->data[i.y][3]=(p->data[i.y][3]+queryReplaceVol)&0xff; + break; + case GUI_QUERY_REPLACE_CLEAR: + p->data[i.y][3]=-1; + break; + } + } + + signed char effectOrder[8]; + memset(effectOrder,-1,8); + + switch (queryReplaceEffectPos) { + case 0: // clear + for (int j=0; jsong.subsong[i.subsong]->pat[i.x].effectCols; j++) { + effectOrder[j]=j; + } + break; + case 1: { // replace matches + int placementIndex=0; + for (int j=0; jsong.subsong[i.subsong]->pat[i.x].effectCols; j++) { + if (p->data[i.y][4+j*2]!=-1 || p->data[i.y][5+j*2]!=-1) { + effectOrder[placementIndex++]=j; + } + } + break; + } + case 2: { // replace matches then free spaces + int placementIndex=0; + for (int j=0; jsong.subsong[i.subsong]->pat[i.x].effectCols; j++) { + if (p->data[i.y][4+j*2]!=-1 || p->data[i.y][5+j*2]!=-1) { + effectOrder[placementIndex++]=j; + } + } + for (int j=0; jsong.subsong[i.subsong]->pat[i.x].effectCols; j++) { + if (p->data[i.y][4+j*2]==-1 && p->data[i.y][5+j*2]==-1) { + effectOrder[placementIndex++]=j; + } + } + break; + } + case 3: { // insert in free spaces + int placementIndex=0; + for (int j=0; jsong.subsong[i.subsong]->pat[i.x].effectCols; j++) { + if (p->data[i.y][4+j*2]==-1 && p->data[i.y][5+j*2]==-1) { + effectOrder[placementIndex++]=j; + } + } + break; + } + } + + for (int j=0; jdata[i.y][4+pos*2]=queryReplaceEffect[j]; + break; + case GUI_QUERY_REPLACE_ADD: + if (p->data[i.y][4+pos*2]>=0) { + p->data[i.y][4+pos*2]+=queryReplaceEffect[j]; + if (p->data[i.y][4+pos*2]<0) p->data[i.y][4+pos*2]=0; + if (p->data[i.y][4+pos*2]>255) p->data[i.y][4+pos*2]=255; + } + break; + case GUI_QUERY_REPLACE_ADD_OVERFLOW: + if (p->data[i.y][4+pos*2]>=0) p->data[i.y][4+pos*2]=(p->data[i.y][4+pos*2]+queryReplaceEffect[j])&0xff; + break; + case GUI_QUERY_REPLACE_CLEAR: + p->data[i.y][4+pos*2]=-1; + break; + } + } + + if (queryReplaceEffectValDo[j]) { + switch (queryReplaceEffectValMode[j]) { + case GUI_QUERY_REPLACE_SET: + p->data[i.y][5+pos*2]=queryReplaceEffectVal[j]; + break; + case GUI_QUERY_REPLACE_ADD: + if (p->data[i.y][5+pos*2]>=0) { + p->data[i.y][5+pos*2]+=queryReplaceEffectVal[j]; + if (p->data[i.y][5+pos*2]<0) p->data[i.y][5+pos*2]=0; + if (p->data[i.y][5+pos*2]>255) p->data[i.y][5+pos*2]=255; + } + break; + case GUI_QUERY_REPLACE_ADD_OVERFLOW: + if (p->data[i.y][5+pos*2]>=0) p->data[i.y][5+pos*2]=(p->data[i.y][5+pos*2]+queryReplaceEffectVal[j])&0xff; + break; + case GUI_QUERY_REPLACE_CLEAR: + p->data[i.y][5+pos*2]=-1; + break; + } + } + } + + // issue undo step + for (int j=0; j<32; j++) { + if (p->data[i.y][j]!=prevVal[j]) { + us.pat.push_back(UndoPatternData(i.subsong,i.x,patIndex,i.y,j,prevVal[j],p->data[i.y][j])); + } + } + } + + for (int i=0; isettings.maxUndoSteps) undoHist.pop_front(); + } +} + +#define FIRST_VISIBLE(x) (x==GUI_QUERY_MATCH || x==GUI_QUERY_MATCH_NOT || x==GUI_QUERY_RANGE || x==GUI_QUERY_RANGE_NOT) +#define SECOND_VISIBLE(x) (x==GUI_QUERY_RANGE || x==GUI_QUERY_RANGE_NOT) + +void FurnaceGUI::drawFindReplace() { + if (nextWindow==GUI_WINDOW_FIND) { + findOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!findOpen) return; + ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + if (ImGui::Begin("Find/Replace",&findOpen,globalWinFlags)) { + if (curQuery.empty()) { + curQuery.push_back(FurnaceGUIFindQuery()); + } + int index=0; + int eraseIndex=-1; + char tempID[1024]; + if (ImGui::BeginTabBar("FindOrReplace")) { + if (ImGui::BeginTabItem("Find")) { + if (queryViewingResults) { + if (!curQueryResults.empty()) { + ImVec2 avail=ImGui::GetContentRegionAvail(); + avail.y-=ImGui::GetFrameHeightWithSpacing(); + if (ImGui::BeginTable("FindResults",4,ImGuiTableFlags_Borders|ImGuiTableFlags_ScrollY,avail)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,ImGui::CalcTextSize("order").x); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,ImGui::CalcTextSize("row").x); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed); + + ImGui::TableSetupScrollFreeze(0,1); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("order"); + ImGui::TableNextColumn(); + ImGui::Text("row"); + ImGui::TableNextColumn(); + ImGui::Text("channel"); + ImGui::TableNextColumn(); + ImGui::Text("go"); + + int index=0; + for (FurnaceGUIQueryResult& i: curQueryResults) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (settings.orderRowsBase==1) { + ImGui::Text("%.2X",i.order); + } else { + ImGui::Text("%d",i.order); + } + ImGui::TableNextColumn(); + if (settings.patRowsBase==1) { + ImGui::Text("%.2X",i.y); + } else { + ImGui::Text("%d",i.y); + } + ImGui::TableNextColumn(); + ImGui::Text("%d (%s)",i.x+1,e->getChannelName(i.x)); + if (ImGui::TableNextColumn()) { + snprintf(tempID,1024,ICON_FA_CHEVRON_RIGHT "##_FR%d",index); + if (ImGui::Selectable(tempID)) { + e->changeSongP(i.subsong); + if (e->isPlaying()) { + followPattern=false; + } else { + e->setOrder(i.order); + } + curOrder=i.order; + cursor.xCoarse=i.x; + cursor.xFine=0; + cursor.y=i.y; + selStart=cursor; + selEnd=cursor; + demandScrollX=true; + updateScroll(cursor.y); + nextWindow=GUI_WINDOW_PATTERN; + } + } + index++; + } + ImGui::EndTable(); + } + } else { + ImGui::Text("no matches found!"); + } + if (ImGui::Button("Back")) { + queryViewingResults=false; + } + } else { + for (FurnaceGUIFindQuery& i: curQuery) { + ImGui::PushID(index+0x100); + if (ImGui::BeginTable("FindRep",4,ImGuiTableFlags_BordersOuter)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.5); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.25); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.25); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Note"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##NCondition",&i.noteMode,queryModes,GUI_QUERY_MAX); + ImGui::TableNextColumn(); + if (FIRST_VISIBLE(i.noteMode)) { + if ((i.noteMode==GUI_QUERY_RANGE || i.noteMode==GUI_QUERY_RANGE_NOT) && i.note>=120) { + i.note=0; + } + if (i.note==130) { + snprintf(tempID,1024,"REL"); + } else if (i.note==129) { + snprintf(tempID,1024,"==="); + } else if (i.note==128) { + snprintf(tempID,1024,"OFF"); + } else if (i.note>=-60 && i.note<120) { + snprintf(tempID,1024,"%s",noteNames[i.note+60]); + } else { + snprintf(tempID,1024,"???"); + i.note=0; + } + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("##NN1",tempID)) { + for (int j=0; j<180; j++) { + snprintf(tempID,1024,"%s",noteNames[j]); + if (ImGui::Selectable(tempID,i.note==(j-60))) { + i.note=j-60; + } + } + if (i.noteMode!=GUI_QUERY_RANGE && i.noteMode!=GUI_QUERY_RANGE_NOT) { + if (ImGui::Selectable("OFF",i.note==128)) { + i.note=128; + } + if (ImGui::Selectable("===",i.note==129)) { + i.note=129; + } + if (ImGui::Selectable("REL",i.note==130)) { + i.note=130; + } + } + ImGui::EndCombo(); + } + } + ImGui::TableNextColumn(); + if (SECOND_VISIBLE(i.noteMode)) { + if (i.noteMax<-60 || i.noteMax>=120) { + i.noteMax=0; + } + if (i.noteMax>=-60 && i.noteMax<120) { + snprintf(tempID,1024,"%s",noteNames[i.noteMax+60]); + } else { + snprintf(tempID,1024,"???"); + } + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("##NN2",tempID)) { + for (int j=0; j<180; j++) { + snprintf(tempID,1024,"%s",noteNames[j]); + if (ImGui::Selectable(tempID,i.noteMax==(j-60))) { + i.noteMax=j-60; + } + } + ImGui::EndCombo(); + } + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Ins"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##ICondition",&i.insMode,queryModes,GUI_QUERY_MAX); + ImGui::TableNextColumn(); + if (FIRST_VISIBLE(i.insMode)) { + snprintf(tempID,1024,"%.2X",i.ins); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("II1",tempID)) { + for (int j=0; j<256; j++) { + snprintf(tempID,1024,"%.2X",j); + if (ImGui::Selectable(tempID,i.ins==j)) { + i.ins=j; + } + } + ImGui::EndCombo(); + } + } + ImGui::TableNextColumn(); + if (SECOND_VISIBLE(i.insMode)) { + snprintf(tempID,1024,"%.2X",i.insMax); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("II2",tempID)) { + for (int j=0; j<256; j++) { + snprintf(tempID,1024,"%.2X",j); + if (ImGui::Selectable(tempID,i.insMax==j)) { + i.insMax=j; + } + } + ImGui::EndCombo(); + } + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Volume"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##VCondition",&i.volMode,queryModes,GUI_QUERY_MAX); + ImGui::TableNextColumn(); + if (FIRST_VISIBLE(i.volMode)) { + snprintf(tempID,1024,"%.2X",i.vol); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("VV1",tempID)) { + for (int j=0; j<256; j++) { + snprintf(tempID,1024,"%.2X",j); + if (ImGui::Selectable(tempID,i.vol==j)) { + i.vol=j; + } + } + ImGui::EndCombo(); + } + } + ImGui::TableNextColumn(); + if (SECOND_VISIBLE(i.volMode)) { + snprintf(tempID,1024,"%.2X",i.volMax); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("VV2",tempID)) { + for (int j=0; j<256; j++) { + snprintf(tempID,1024,"%.2X",j); + if (ImGui::Selectable(tempID,i.volMax==j)) { + i.volMax=j; + } + } + ImGui::EndCombo(); + } + } + + for (int j=0; j0) { + if (ImGui::Button("Remove effect")) { + i.effectCount--; + } + } + ImGui::EndTable(); + } + ImGui::PopID(); + index++; + } + if (eraseIndex>=0) { + curQuery.erase(curQuery.begin()+eraseIndex); + } + if (ImGui::Button(ICON_FA_PLUS "##AddQuery")) { + curQuery.push_back(FurnaceGUIFindQuery()); + } + + if (ImGui::BeginTable("QueryLimits",3)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.5f); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.5f); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + ImGui::Text("Search range:"); + + if (ImGui::RadioButton("Song",curQueryRangeY==0)) { + curQueryRangeY=0; + } + if (ImGui::RadioButton("Selection",curQueryRangeY==1)) { + curQueryRangeY=1; + } + if (ImGui::RadioButton("Pattern",curQueryRangeY==2)) { + curQueryRangeY=2; + } + + ImGui::TableNextColumn(); + ImGui::Checkbox("Confine to channels",&curQueryRangeX); + + ImGui::BeginDisabled(!curQueryRangeX); + snprintf(tempID,1024,"%d: %s",curQueryRangeXMin+1,e->getChannelName(curQueryRangeXMin)); + if (ImGui::BeginCombo("From",tempID)) { + for (int i=0; igetTotalChannelCount(); i++) { + snprintf(tempID,1024,"%d: %s",i+1,e->getChannelName(i)); + if (ImGui::Selectable(tempID,curQueryRangeXMin==i)) { + curQueryRangeXMin=i; + } + } + ImGui::EndCombo(); + } + + snprintf(tempID,1024,"%d: %s",curQueryRangeXMax+1,e->getChannelName(curQueryRangeXMax)); + if (ImGui::BeginCombo("To",tempID)) { + for (int i=0; igetTotalChannelCount(); i++) { + snprintf(tempID,1024,"%d: %s",i+1,e->getChannelName(i)); + if (ImGui::Selectable(tempID,curQueryRangeXMax==i)) { + curQueryRangeXMax=i; + } + } + ImGui::EndCombo(); + } + ImGui::EndDisabled(); + + ImGui::TableNextColumn(); + ImGui::Text("Match effect position:"); + + if (ImGui::RadioButton("No",curQueryEffectPos==0)) { + curQueryEffectPos=0; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("match effects regardless of position."); + } + if (ImGui::RadioButton("Lax",curQueryEffectPos==1)) { + curQueryEffectPos=1; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("match effects only if they appear in-order."); + } + if (ImGui::RadioButton("Strict",curQueryEffectPos==2)) { + curQueryEffectPos=2; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("match effects only if they appear exactly as specified."); + } + + ImGui::EndTable(); + } + + if (ImGui::Button("Find")) { + doFind(); + } + } + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Replace")) { + if (ImGui::BeginTable("QueryReplace",3,ImGuiTableFlags_BordersOuter)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.5); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.5); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Checkbox("Note",&queryReplaceNoteDo); + ImGui::TableNextColumn(); + ImGui::BeginDisabled(!queryReplaceNoteDo); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##NRMode",&queryReplaceNoteMode,queryReplaceModes,GUI_QUERY_REPLACE_MAX); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (queryReplaceNoteMode==GUI_QUERY_REPLACE_SET) { + if (queryReplaceNote==130) { + snprintf(tempID,1024,"REL"); + } else if (queryReplaceNote==129) { + snprintf(tempID,1024,"==="); + } else if (queryReplaceNote==128) { + snprintf(tempID,1024,"OFF"); + } else if (queryReplaceNote>=-60 && queryReplaceNote<120) { + snprintf(tempID,1024,"%s",noteNames[queryReplaceNote+60]); + } else { + snprintf(tempID,1024,"???"); + queryReplaceNote=0; + } + if (ImGui::BeginCombo("##NRValueC",tempID)) { + for (int j=0; j<180; j++) { + snprintf(tempID,1024,"%s",noteNames[j]); + if (ImGui::Selectable(tempID,queryReplaceNote==(j-60))) { + queryReplaceNote=j-60; + } + } + if (ImGui::Selectable("OFF",queryReplaceNote==128)) { + queryReplaceNote=128; + } + if (ImGui::Selectable("===",queryReplaceNote==129)) { + queryReplaceNote=129; + } + if (ImGui::Selectable("REL",queryReplaceNote==130)) { + queryReplaceNote=130; + } + ImGui::EndCombo(); + } + } else if (queryReplaceNoteMode==GUI_QUERY_REPLACE_ADD || queryReplaceNoteMode==GUI_QUERY_REPLACE_ADD_OVERFLOW) { + if (ImGui::InputInt("##NRValue",&queryReplaceNote,1,12)) { + if (queryReplaceNote<-180) queryReplaceNote=-180; + if (queryReplaceNote>180) queryReplaceNote=180; + } + } + ImGui::EndDisabled(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Checkbox("Ins",&queryReplaceInsDo); + ImGui::TableNextColumn(); + ImGui::BeginDisabled(!queryReplaceInsDo); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##IRMode",&queryReplaceInsMode,queryReplaceModes,GUI_QUERY_REPLACE_MAX); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (queryReplaceInsMode==GUI_QUERY_REPLACE_SET) { + if (ImGui::InputScalar("##IRValueH",ImGuiDataType_S32,&queryReplaceIns,&_ONE,&_SIXTEEN,"%.2X",ImGuiInputTextFlags_CharsHexadecimal)) { + if (queryReplaceIns<0) queryReplaceIns=0; + if (queryReplaceIns>255) queryReplaceIns=255; + } + } else if (queryReplaceInsMode==GUI_QUERY_REPLACE_ADD || queryReplaceInsMode==GUI_QUERY_REPLACE_ADD_OVERFLOW) { + if (ImGui::InputInt("##IRValue",&queryReplaceIns,1,12)) { + if (queryReplaceIns<-255) queryReplaceIns=-255; + if (queryReplaceIns>255) queryReplaceIns=255; + } + } + ImGui::EndDisabled(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Checkbox("Volume",&queryReplaceVolDo); + ImGui::TableNextColumn(); + ImGui::BeginDisabled(!queryReplaceVolDo); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##VRMode",&queryReplaceVolMode,queryReplaceModes,GUI_QUERY_REPLACE_MAX); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (queryReplaceVolMode==GUI_QUERY_REPLACE_SET) { + if (ImGui::InputScalar("##VRValueH",ImGuiDataType_S32,&queryReplaceVol,&_ONE,&_SIXTEEN,"%.2X",ImGuiInputTextFlags_CharsHexadecimal)) { + if (queryReplaceVol<0) queryReplaceVol=0; + if (queryReplaceVol>255) queryReplaceVol=255; + } + } else if (queryReplaceVolMode==GUI_QUERY_REPLACE_ADD || queryReplaceVolMode==GUI_QUERY_REPLACE_ADD_OVERFLOW) { + if (ImGui::InputInt("##VRValue",&queryReplaceVol,1,12)) { + if (queryReplaceVol<-255) queryReplaceVol=-255; + if (queryReplaceVol>255) queryReplaceVol=255; + } + } + ImGui::EndDisabled(); + + for (int i=0; i255) queryReplaceEffect[i]=255; + } + } else if (queryReplaceEffectMode[i]==GUI_QUERY_REPLACE_ADD || queryReplaceEffectMode[i]==GUI_QUERY_REPLACE_ADD_OVERFLOW) { + if (ImGui::InputInt("##ERValue",&queryReplaceEffect[i],1,12)) { + if (queryReplaceEffect[i]<-255) queryReplaceEffect[i]=-255; + if (queryReplaceEffect[i]>255) queryReplaceEffect[i]=255; + } + } + ImGui::EndDisabled(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Checkbox("Value",&queryReplaceEffectValDo[i]); + ImGui::TableNextColumn(); + ImGui::BeginDisabled(!queryReplaceEffectValDo[i]); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##ERMode",&queryReplaceEffectValMode[i],queryReplaceModes,GUI_QUERY_REPLACE_MAX); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (queryReplaceEffectValMode[i]==GUI_QUERY_REPLACE_SET) { + if (ImGui::InputScalar("##ERValueH",ImGuiDataType_S32,&queryReplaceEffectVal[i],&_ONE,&_SIXTEEN,"%.2X",ImGuiInputTextFlags_CharsHexadecimal)) { + if (queryReplaceEffectVal[i]<0) queryReplaceEffectVal[i]=0; + if (queryReplaceEffectVal[i]>255) queryReplaceEffectVal[i]=255; + } + } else if (queryReplaceEffectValMode[i]==GUI_QUERY_REPLACE_ADD || queryReplaceEffectValMode[i]==GUI_QUERY_REPLACE_ADD_OVERFLOW) { + if (ImGui::InputInt("##ERValue",&queryReplaceEffectVal[i],1,12)) { + if (queryReplaceEffectVal[i]<-255) queryReplaceEffectVal[i]=-255; + if (queryReplaceEffectVal[i]>255) queryReplaceEffectVal[i]=255; + } + } + ImGui::EndDisabled(); + + + ImGui::PopID(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + if (queryReplaceEffectCount<8) { + if (ImGui::Button("Add effect")) { + queryReplaceEffectCount++; + } + } + ImGui::TableNextColumn(); + if (queryReplaceEffectCount>0) { + if (ImGui::Button("Remove effect")) { + queryReplaceEffectCount--; + } + } + + ImGui::EndTable(); + } + ImGui::Text("Effect replace mode:"); + if (ImGui::RadioButton("Clear effects",queryReplaceEffectPos==0)) { + queryReplaceEffectPos=0; + } + if (ImGui::RadioButton("Replace matches only",queryReplaceEffectPos==1)) { + queryReplaceEffectPos=1; + } + if (ImGui::RadioButton("Replace matches, then free spaces",queryReplaceEffectPos==2)) { + queryReplaceEffectPos=2; + } + if (ImGui::RadioButton("Insert in free spaces",queryReplaceEffectPos==3)) { + queryReplaceEffectPos=3; + } + if (ImGui::Button("Replace##QueryReplace")) { + doReplace(); + } + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_FIND; + ImGui::End(); +} diff --git a/src/gui/gradient.cpp b/src/gui/gradient.cpp new file mode 100644 index 000000000..e4af10b57 --- /dev/null +++ b/src/gui/gradient.cpp @@ -0,0 +1,130 @@ +/** + * 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 +#include + +ImU32 Gradient2D::get(float x, float y) { + int xi=round(x*width); + int yi=round(y*height); + if (xi<0) xi=0; + if (xi>=(int)width) xi=width-1; + if (yi<0) yi=0; + if (yi>=(int)height) yi=height-1; + return grad[yi*width+xi]; +} + +String Gradient2D::toString() { + String ret=fmt::sprintf("GRAD #%.2X%.2X%.2X%.2X",(unsigned char)(bgColor.x*255.0f),(unsigned char)(bgColor.y*255.0f),(unsigned char)(bgColor.z*255.0f),(unsigned char)(bgColor.w*255.0f)); + for (Gradient2DPoint& i: points) { + ret+=fmt::sprintf(" %f,%f:%f,%f:#%.2X%.2X%.2X%.2X",i.x,i.y,i.distance,i.spread,(unsigned char)(i.color.x*255.0f),(unsigned char)(i.color.y*255.0f),(unsigned char)(i.color.z*255.0f),(unsigned char)(i.color.w*255.0f)); + } + return ret; +} + +bool Gradient2D::fromString(String val) { + std::vector split; + String cur; + for (char i: val) { + if (i==' ') { + if (!cur.empty()) { + split.push_back(cur); + cur=""; + } + } else { + cur+=i; + } + } + if (!cur.empty()) { + split.push_back(cur); + } + + if (split.size()<2) return false; + + if (split[0]!="GRAD") return false; + + ImU32 bgColorH=0; + if (sscanf(split[1].c_str(),"#%X",&bgColorH)!=1) return false; + bgColorH=(bgColorH>>24)|((bgColorH>>8)&0xff00)|((bgColorH<<8)&0xff0000)|(bgColorH<<24); + + bgColor=ImGui::ColorConvertU32ToFloat4(bgColorH); + + for (size_t i=2; i>24)|((colorH>>8)&0xff00)|((colorH<<8)&0xff0000)|(colorH<<24); + + point.color=ImGui::ColorConvertU32ToFloat4(colorH); + points.push_back(point); + } + return true; +} + +void Gradient2D::render() { + ImU32* g=grad.get(); + ImU32 bgColorU=ImGui::ColorConvertFloat4ToU32(bgColor); + + // 1. fill with background color + for (size_t i=0; i=pDistSquared) continue; + + float dist=(1.0-(sqrt(distSquared)/i.distance)); + if (dist<0) dist=0; + if (dist>1) dist=1; + + ImU32 shadeColor=ImGui::ColorConvertFloat4ToU32( + ImVec4( + i.color.x*i.color.w*dist, + i.color.y*i.color.w*dist, + i.color.z*i.color.w*dist, + 1.0f + ) + ); + + ImU32 origColor=g[j*width+k]; + g[j*width+k]=( + (MIN( 0xff, (origColor&0xff) + (shadeColor&0xff) )) | // R + (MIN( 0xff00, (origColor&0xff00) + (shadeColor&0xff00) )) | // G + (MIN(0xff0000,(origColor&0xff0000)+(shadeColor&0xff0000))) | // B + (origColor&0xff000000) // A + ); + } + } + } +} diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index bc18ae3c4..bc309bc4a 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1657,6 +1657,8 @@ int FurnaceGUI::load(String path) { curNibble=false; orderNibble=false; orderCursor=-1; + curOrder=0; + oldRow=0; samplePos=0; updateSampleTex=true; selStart=SelectionPoint(); @@ -1666,6 +1668,7 @@ int FurnaceGUI::load(String path) { undoHist.clear(); redoHist.clear(); updateWindowTitle(); + updateScroll(0); if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } @@ -1673,7 +1676,7 @@ int FurnaceGUI::load(String path) { } void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) { - e->saveAudio(path.c_str(),exportLoops+1,mode); + e->saveAudio(path.c_str(),exportLoops+1,mode,exportFadeOut); displayExporting=true; } @@ -1896,6 +1899,7 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { 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(); @@ -2197,6 +2201,17 @@ void FurnaceGUI::editOptions(bool topMenu) { if (ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(2); if (ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(2); + if (topMenu) { + ImGui::Separator(); + if (ImGui::MenuItem("find/replace",BIND_FOR(GUI_ACTION_WINDOW_FIND),findOpen)) { + if (findOpen) { + findOpen=false; + } else { + nextWindow=GUI_WINDOW_FIND; + } + } + } + /*if (topMenu) { ImGui::Separator(); ImGui::MenuItem("collapse pattern",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_PAT)); @@ -2824,6 +2839,9 @@ bool FurnaceGUI::loop() { if (ImGui::InputInt("Loops",&exportLoops,1,2)) { if (exportLoops<0) exportLoops=0; } + if (ImGui::InputDouble("Fade out (seconds)",&exportFadeOut,1.0,2.0,"%.1f")) { + if (exportFadeOut<0.0) exportFadeOut=0.0; + } ImGui::EndMenu(); } if (ImGui::BeginMenu("export VGM...")) { @@ -2923,6 +2941,8 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } if (ImGui::BeginMenu("edit")) { + ImGui::Text("..."); + ImGui::Separator(); if (ImGui::MenuItem("undo",BIND_FOR(GUI_ACTION_UNDO))) doUndo(); if (ImGui::MenuItem("redo",BIND_FOR(GUI_ACTION_REDO))) doRedo(); ImGui::Separator(); @@ -2986,6 +3006,7 @@ bool FurnaceGUI::loop() { 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; if (ImGui::MenuItem("statistics",BIND_FOR(GUI_ACTION_WINDOW_STATS),statsOpen)) statsOpen=!statsOpen; + if (spoilerOpen) if (ImGui::MenuItem("spoiler",NULL,spoilerOpen)) spoilerOpen=!spoilerOpen; ImGui::EndMenu(); } @@ -3078,6 +3099,8 @@ bool FurnaceGUI::loop() { ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoWindowMenuButton|ImGuiDockNodeFlags_NoMove|ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0); drawSubSongs(); + drawFindReplace(); + drawSpoiler(); drawPattern(); drawEditControls(); drawSongInfo(); @@ -3117,6 +3140,7 @@ bool FurnaceGUI::loop() { #endif } +#ifndef NFD_NON_THREADED if (fileDialog->isOpen() && settings.sysFileDialog) { ImGui::OpenPopup("System File Dialog Pending"); } @@ -3129,6 +3153,7 @@ bool FurnaceGUI::loop() { dl->AddRectFilled(ImVec2(0.0f,0.0f),ImVec2(scrW*dpiScale,scrH*dpiScale),ImGui::ColorConvertFloat4ToU32(uiColors[GUI_COLOR_MODAL_BACKDROP])); ImGui::EndPopup(); } +#endif if (fileDialog->render(ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale))) { bool openOpen=false; @@ -3330,8 +3355,16 @@ bool FurnaceGUI::loop() { if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } - for (DivInstrument* i: instruments) { - e->addInstrumentPtr(i); + if (instruments.size()>1) { // ask which instruments to load + for (DivInstrument* i: instruments) { + pendingIns.push_back(std::make_pair(i,false)); + } + displayPendingIns=true; + pendingInsSingle=false; + } else { // load the only instrument + for (DivInstrument* i: instruments) { + e->addInstrumentPtr(i); + } } } else { showError("cannot load instrument! ("+e->getLastError()+")"); @@ -3344,13 +3377,21 @@ bool FurnaceGUI::loop() { 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; + if (instruments.size()>1) { // ask which instrument + for (DivInstrument* i: instruments) { + pendingIns.push_back(std::make_pair(i,false)); + } + displayPendingIns=true; + pendingInsSingle=true; + } else { // replace with the only instrument + 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()+")"); @@ -3442,6 +3483,11 @@ bool FurnaceGUI::loop() { ImGui::OpenPopup("Error"); } + if (displayPendingIns) { + displayPendingIns=false; + ImGui::OpenPopup("Select Instrument"); + } + if (displayExporting) { displayExporting=false; ImGui::OpenPopup("Rendering..."); @@ -3792,6 +3838,86 @@ bool FurnaceGUI::loop() { ImGui::EndPopup(); } + // TODO: + // - multiple selection + // - replace instrument + if (ImGui::BeginPopupModal("Select Instrument",NULL,ImGuiWindowFlags_AlwaysAutoResize)) { + bool quitPlease=false; + if (pendingInsSingle) { + ImGui::Text("this is an instrument bank! select which one to use:"); + } else { + ImGui::Text("this is an instrument bank! select which ones to load:"); + ImGui::SameLine(); + if (ImGui::Button("All")) { + for (std::pair& i: pendingIns) { + i.second=true; + } + } + ImGui::SameLine(); + if (ImGui::Button("None")) { + for (std::pair& i: pendingIns) { + i.second=false; + } + } + } + bool anySelected=false; + float sizeY=ImGui::GetFrameHeightWithSpacing()*pendingIns.size(); + if (sizeY>(scrH-180.0)*dpiScale) { + sizeY=(scrH-180.0)*dpiScale; + if (sizeY<60.0*dpiScale) sizeY=60.0*dpiScale; + } + if (ImGui::BeginTable("PendingInsList",1,ImGuiTableFlags_ScrollY,ImVec2(0.0f,sizeY))) { + for (size_t i=0; iname); + if (pendingInsSingle) { + if (ImGui::Selectable(id.c_str())) { + pendingIns[i].second=true; + quitPlease=true; + } + } else { + ImGui::Checkbox(id.c_str(),&pendingIns[i].second); + } + if (pendingIns[i].second) anySelected=true; + } + ImGui::EndTable(); + } + if (!pendingInsSingle) { + ImGui::BeginDisabled(!anySelected); + if (ImGui::Button("OK")) { + quitPlease=true; + } + ImGui::EndDisabled(); + ImGui::SameLine(); + } + if (ImGui::Button("Cancel")) { + for (std::pair& i: pendingIns) { + i.second=false; + } + quitPlease=true; + } + if (quitPlease) { + ImGui::CloseCurrentPopup(); + for (std::pair& i: pendingIns) { + if (!i.second || pendingInsSingle) { + if (i.second) { + if (curIns>=0 && curIns<(int)e->song.ins.size()) { + *e->song.ins[curIns]=*i.first; + } else { + showError("...but you haven't selected an instrument!"); + } + } + delete i.first; + } else { + e->addInstrumentPtr(i.first); + } + } + pendingIns.clear(); + } + ImGui::EndPopup(); + } + layoutTimeEnd=SDL_GetPerformanceCounter(); // backup trigger @@ -3910,6 +4036,8 @@ bool FurnaceGUI::init() { logOpen=e->getConfBool("logOpen",false); effectListOpen=e->getConfBool("effectListOpen",false); subSongsOpen=e->getConfBool("subSongsOpen",true); + findOpen=e->getConfBool("findOpen",false); + spoilerOpen=e->getConfBool("spoilerOpen",false); tempoView=e->getConfBool("tempoView",true); waveHex=e->getConfBool("waveHex",false); @@ -3942,6 +4070,20 @@ bool FurnaceGUI::init() { pianoView=e->getConfInt("pianoView",pianoView); pianoInputPadMode=e->getConfInt("pianoInputPadMode",pianoInputPadMode); + chanOscCols=e->getConfInt("chanOscCols",3); + chanOscColorX=e->getConfInt("chanOscColorX",GUI_OSCREF_CENTER); + chanOscColorY=e->getConfInt("chanOscColorY",GUI_OSCREF_CENTER); + chanOscWindowSize=e->getConfFloat("chanOscWindowSize",20.0f); + chanOscWaveCorr=e->getConfBool("chanOscWaveCorr",true); + chanOscOptions=e->getConfBool("chanOscOptions",false); + chanOscColor.x=e->getConfFloat("chanOscColorR",1.0f); + chanOscColor.y=e->getConfFloat("chanOscColorG",1.0f); + chanOscColor.z=e->getConfFloat("chanOscColorB",1.0f); + chanOscColor.w=e->getConfFloat("chanOscColorA",1.0f); + chanOscUseGrad=e->getConfBool("chanOscUseGrad",false); + chanOscGrad.fromString(e->getConfString("chanOscGrad","")); + chanOscGrad.render(); + syncSettings(); if (settings.dpiScale>=0.5f) { @@ -3982,6 +4124,7 @@ bool FurnaceGUI::init() { #ifndef __APPLE__ if (settings.dpiScale<0.5f) { + // TODO: replace with a function to actually detect the display scaling factor as it's unreliable. SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(sdlWin),&dpiScaleF,NULL,NULL); dpiScale=round(dpiScaleF/96.0f); if (dpiScale<1) dpiScale=1; @@ -4131,6 +4274,8 @@ bool FurnaceGUI::finish() { e->setConf("logOpen",logOpen); e->setConf("effectListOpen",effectListOpen); e->setConf("subSongsOpen",subSongsOpen); + e->setConf("findOpen",findOpen); + e->setConf("spoilerOpen",spoilerOpen); // commit last window size e->setConf("lastWindowWidth",scrW); @@ -4163,6 +4308,20 @@ bool FurnaceGUI::finish() { e->setConf("pianoView",pianoView); e->setConf("pianoInputPadMode",pianoInputPadMode); + // commit per-chan osc state + e->setConf("chanOscCols",chanOscCols); + e->setConf("chanOscColorX",chanOscColorX); + e->setConf("chanOscColorY",chanOscColorY); + e->setConf("chanOscWindowSize",chanOscWindowSize); + e->setConf("chanOscWaveCorr",chanOscWaveCorr); + e->setConf("chanOscOptions",chanOscOptions); + e->setConf("chanOscColorR",chanOscColor.x); + e->setConf("chanOscColorG",chanOscColor.y); + e->setConf("chanOscColorB",chanOscColor.z); + e->setConf("chanOscColorA",chanOscColor.w); + e->setConf("chanOscUseGrad",chanOscUseGrad); + e->setConf("chanOscGrad",chanOscGrad.toString()); + for (int i=0; i #include #include +#include #include +#include #include #include "fileDialog.h" @@ -244,7 +246,9 @@ enum FurnaceGUIWindows { GUI_WINDOW_LOG, GUI_WINDOW_EFFECT_LIST, GUI_WINDOW_CHAN_OSC, - GUI_WINDOW_SUBSONGS + GUI_WINDOW_SUBSONGS, + GUI_WINDOW_FIND, + GUI_WINDOW_SPOILER }; enum FurnaceGUIFileDialogs { @@ -308,6 +312,7 @@ enum FurnaceGUIActions { GUI_ACTION_PLAY_TOGGLE, GUI_ACTION_PLAY, GUI_ACTION_STOP, + GUI_ACTION_PLAY_START, GUI_ACTION_PLAY_REPEAT, GUI_ACTION_PLAY_CURSOR, GUI_ACTION_STEP_ONE, @@ -352,6 +357,7 @@ enum FurnaceGUIActions { GUI_ACTION_WINDOW_EFFECT_LIST, GUI_ACTION_WINDOW_CHAN_OSC, GUI_ACTION_WINDOW_SUBSONGS, + GUI_ACTION_WINDOW_FIND, GUI_ACTION_COLLAPSE_WINDOW, GUI_ACTION_CLOSE_WINDOW, @@ -519,6 +525,21 @@ enum FurnaceGUIActions { GUI_ACTION_MAX }; +enum FurnaceGUIChanOscRef { + GUI_OSCREF_NONE=0, + GUI_OSCREF_CENTER, + GUI_OSCREF_FULL, + + GUI_OSCREF_FREQUENCY, + GUI_OSCREF_VOLUME, + GUI_OSCREF_CHANNEL, + GUI_OSCREF_BRIGHT, + + GUI_OSCREF_NOTE_TRIGGER, + + GUI_OSCREF_MAX +}; + enum PasteMode { GUI_PASTE_MODE_NORMAL=0, GUI_PASTE_MODE_MIX_FG, @@ -562,7 +583,9 @@ enum ActionType { GUI_UNDO_PATTERN_INVERT_VAL, GUI_UNDO_PATTERN_FLIP, GUI_UNDO_PATTERN_COLLAPSE, - GUI_UNDO_PATTERN_EXPAND + GUI_UNDO_PATTERN_EXPAND, + GUI_UNDO_PATTERN_DRAG, + GUI_UNDO_REPLACE }; struct UndoPatternData { @@ -750,6 +773,49 @@ struct TouchPoint { z(pressure) {} }; +struct Gradient2DPoint { + ImVec4 color; + float x, y, prevX, prevY; + float spread, distance; + bool selected, grab; + Gradient2DPoint(float xPos, float yPos): + color(1,1,1,1), + x(xPos), + y(yPos), + prevX(0.0f), + prevY(0.0f), + spread(0.0f), + distance(0.5f), + selected(false), + grab(false) {} + Gradient2DPoint(): + color(1,1,1,1), + x(0.0f), + y(0.0f), + spread(0.0f), + distance(0.5f), + selected(false), + grab(false) {} +}; + +struct Gradient2D { + ImVec4 bgColor; + std::vector points; + std::unique_ptr grad; + size_t width, height; + + String toString(); + bool fromString(String val); + void render(); + ImU32 get(float x, float y); + Gradient2D(size_t w, size_t h): + bgColor(0.0f,0.0f,0.0f,0.0f), + width(w), + height(h) { + grad=std::make_unique(width*height); + } +}; + struct FurnaceGUISysDef { const char* name; std::vector definition; @@ -800,6 +866,73 @@ struct FurnaceGUIMacroDesc { } }; +enum FurnaceGUIFindQueryModes { + GUI_QUERY_IGNORE=0, + GUI_QUERY_MATCH, + GUI_QUERY_MATCH_NOT, + GUI_QUERY_RANGE, + GUI_QUERY_RANGE_NOT, + GUI_QUERY_ANY, + GUI_QUERY_NONE, + + GUI_QUERY_MAX +}; + +enum FurnaceGUIFindQueryReplaceModes { + GUI_QUERY_REPLACE_SET=0, + GUI_QUERY_REPLACE_ADD, + GUI_QUERY_REPLACE_ADD_OVERFLOW, + GUI_QUERY_REPLACE_CLEAR, + + GUI_QUERY_REPLACE_MAX +}; + +struct FurnaceGUIFindQuery { + int noteMode, insMode, volMode, effectCount; + int effectMode[8]; + int effectValMode[8]; + int note, noteMax; + unsigned char ins, insMax; + unsigned char vol, volMax; + unsigned char effect[8]; + unsigned char effectMax[8]; + unsigned char effectVal[8]; + unsigned char effectValMax[8]; + + FurnaceGUIFindQuery(): + noteMode(GUI_QUERY_IGNORE), + insMode(GUI_QUERY_IGNORE), + volMode(GUI_QUERY_IGNORE), + effectCount(0), + note(0), + noteMax(0), + ins(0), + insMax(0), + vol(0), + volMax(0) { + memset(effectMode,0,8*sizeof(int)); + memset(effectValMode,0,8*sizeof(int)); + memset(effect,0,8); + memset(effectMax,0,8); + memset(effectVal,0,8); + memset(effectValMax,0,8); + } +}; + +struct FurnaceGUIQueryResult { + int subsong, order, x, y; + FurnaceGUIQueryResult(): + subsong(0), + order(0), + x(0), + y(0) {} + FurnaceGUIQueryResult(int ss, int o, int xPos, int yPos): + subsong(ss), + order(o), + x(xPos), + y(yPos) {} +}; + class FurnaceGUI { DivEngine* e; @@ -817,6 +950,7 @@ class FurnaceGUI { bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly; + bool displayPendingIns, pendingInsSingle; bool willExport[32]; int vgmExportVersion; int drawHalt; @@ -869,7 +1003,6 @@ class FurnaceGUI { int arcadeCore; int ym2612Core; int snCore; - int saaCore; int nesCore; int fdsCore; int pcSpeakerOutMethod; @@ -953,6 +1086,7 @@ class FurnaceGUI { int effectValCellSpacing; int doubleClickColumn; int blankIns; + int dragMovesSelection; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -970,7 +1104,6 @@ class FurnaceGUI { arcadeCore(0), ym2612Core(0), snCore(0), - saaCore(1), nesCore(0), fdsCore(0), pcSpeakerOutMethod(0), @@ -1054,6 +1187,7 @@ class FurnaceGUI { effectValCellSpacing(0), doubleClickColumn(1), blankIns(0), + dragMovesSelection(1), maxUndoSteps(100), mainFontPath(""), patFontPath(""), @@ -1068,24 +1202,18 @@ class FurnaceGUI { 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; + int wheelX, wheelY, dragSourceX, dragSourceY, dragDestinationX, dragDestinationY; + + double exportFadeOut; 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, effectListOpen, chanOscOpen; - bool subSongsOpen; + bool subSongsOpen, findOpen, spoilerOpen; - /* 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, logDocked, effectListDocked, chanOscDocked; - bool subSongsDocked; - */ - - SelectionPoint selStart, selEnd, cursor; - bool selecting, selectingFull, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI; + SelectionPoint selStart, selEnd, cursor, cursorDrag, dragStart, dragEnd; + bool selecting, selectingFull, dragging, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI; bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, lockLayout, editOptsVisible, latchNibble, nonLatchNibble; FurnaceGUIWindows curWindow, nextWindow, curWindowLast; float peak[2]; @@ -1121,6 +1249,32 @@ class FurnaceGUI { std::vector pgProgram; int pgSys, pgAddr, pgVal; + std::vector curQuery; + std::vector curQueryResults; + bool curQueryRangeX, curQueryBackwards; + int curQueryRangeXMin, curQueryRangeXMax; + int curQueryRangeY; + int curQueryEffectPos; + + int queryReplaceEffectCount; + int queryReplaceEffectPos; + int queryReplaceNoteMode; + int queryReplaceInsMode; + int queryReplaceVolMode; + int queryReplaceEffectMode[8]; + int queryReplaceEffectValMode[8]; + int queryReplaceNote; + int queryReplaceIns; + int queryReplaceVol; + int queryReplaceEffect[8]; + int queryReplaceEffectVal[8]; + bool queryReplaceNoteDo; + bool queryReplaceInsDo; + bool queryReplaceVolDo; + bool queryReplaceEffectDo[8]; + bool queryReplaceEffectValDo[8]; + bool queryViewingResults; + struct ActiveNote { int chan; int note; @@ -1131,7 +1285,7 @@ class FurnaceGUI { std::vector activeNotes; std::vector cmdStream; std::vector particles; - std::vector pendingIns; + std::vector> pendingIns; std::vector sysCategories; @@ -1248,11 +1402,17 @@ class FurnaceGUI { bool oscZoomSlider; // per-channel oscilloscope - int chanOscCols; + int chanOscCols, chanOscColorX, chanOscColorY; float chanOscWindowSize; - bool chanOscWaveCorr; + bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex, chanOscUseGrad; + ImVec4 chanOscColor; + Gradient2D chanOscGrad; + SDL_Texture* chanOscGradTex; float chanOscLP0[DIV_MAX_CHANS]; float chanOscLP1[DIV_MAX_CHANS]; + float chanOscVol[DIV_MAX_CHANS]; + float chanOscPitch[DIV_MAX_CHANS]; + float chanOscBright[DIV_MAX_CHANS]; unsigned short lastNeedlePos[DIV_MAX_CHANS]; unsigned short lastCorrPos[DIV_MAX_CHANS]; struct ChanOscStatus { @@ -1351,6 +1511,8 @@ class FurnaceGUI { void drawLog(); void drawEffectList(); void drawSubSongs(); + void drawFindReplace(); + void drawSpoiler(); void parseKeybinds(); void promptKey(int which); @@ -1363,6 +1525,8 @@ class FurnaceGUI { bool importLayout(String path); bool exportLayout(String path); + float computeGradPos(int type, int chan); + void resetColors(); void resetKeybinds(); @@ -1374,6 +1538,7 @@ class FurnaceGUI { void startSelection(int xCoarse, int xFine, int y, bool fullRow=false); void updateSelection(int xCoarse, int xFine, int y, bool fullRow=false); void finishSelection(); + void finishDrag(); void moveCursor(int x, int y, bool select); void moveCursorPrevChannel(bool overflow); @@ -1401,6 +1566,9 @@ class FurnaceGUI { void doExpand(int multiplier); void doUndo(); void doRedo(); + void doFind(); + void doReplace(); + void doDrag(); void editOptions(bool topMenu); void noteInput(int num, int key, int vol=-1); void valueInput(int num, bool direct=false, int target=-1); @@ -1447,6 +1615,8 @@ class FurnaceGUI { void addScroll(int amount); void setFileName(String name); void runBackupThread(); + void pushPartBlend(); + void popPartBlend(); int processEvent(SDL_Event* ev); bool loop(); bool finish(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 54e5c6d61..7c922afba 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -454,6 +454,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("PLAY_TOGGLE", "Play/Stop (toggle)", SDLK_RETURN), D("PLAY", "Play", 0), D("STOP", "Stop", 0), + D("PLAY_START", "Play (from beginning)", SDLK_F5), D("PLAY_REPEAT", "Play (repeat pattern)", 0), D("PLAY_CURSOR", "Play from cursor", FURKMOD_SHIFT|SDLK_RETURN), D("STEP_ONE", "Step row", FURKMOD_CMD|SDLK_RETURN), @@ -498,6 +499,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WINDOW_SUBSONGS", "Subsongs", 0), D("EFFECT_LIST", "Effect List", 0), D("WINDOW_CHAN_OSC", "Oscilloscope (per-channel)", 0), + D("WINDOW_FIND", "Find/Replace", FURKMOD_CMD|SDLK_f), D("COLLAPSE_WINDOW", "Collapse/expand current window", 0), D("CLOSE_WINDOW", "Close current window", FURKMOD_SHIFT|SDLK_ESCAPE), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index a5f9979db..2b84d98b2 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1408,6 +1408,9 @@ void FurnaceGUI::drawMacros(std::vector& macros) { #define CENTER_VSLIDER \ ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5f*ImGui::GetContentRegionAvail().x-10.0f*dpiScale); +#define CENTER_TEXT_20(text) \ + ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(20.0f*dpiScale-ImGui::CalcTextSize(text).x)); + void FurnaceGUI::drawInsEdit() { if (nextWindow==GUI_WINDOW_INS_EDIT) { insEditOpen=true; @@ -2079,7 +2082,452 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndTable(); } - } else { + } else if (settings.fmLayout>=4 && settings.fmLayout<=6) { // alternate + int columns=2; + switch (settings.fmLayout) { + case 4: // 2x2 + columns=2; + break; + case 5: // 1x4 + columns=1; + break; + case 6: // 4x1 + columns=opCount; + break; + } + char tempID[1024]; + ImVec2 oldPadding=ImGui::GetStyle().CellPadding; + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(8.0f*dpiScale,4.0f*dpiScale)); + if (ImGui::BeginTable("KGE93BSIEO3NOWBDJZBA",columns,ImGuiTableFlags_SizingStretchSame|ImGuiTableFlags_BordersInner)) { + for (int i=0; ifm.op[(opCount==4 && ins->type!=DIV_INS_OPL_DRUMS)?opOrder[i]:i]; + if ((settings.fmLayout!=6 && ((i+1)&1)) || i==0 || settings.fmLayout==5) ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushID(fmt::sprintf("op%d",i).c_str()); + + // push colors + if (settings.separateFMColors) { + bool mod=true; + if (ins->type==DIV_INS_OPL_DRUMS) { + mod=false; + } else 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_DRUMS) { + snprintf(tempID,1024,"%s",oplDrumNames[i]); + } else if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) { + if (i==1) { + snprintf(tempID,1024,"Envelope 2 (kick only)"); + } else { + snprintf(tempID,1024,"Envelope"); + } + } else { + snprintf(tempID,1024,"Operator %d",i+1); + } + CENTER_TEXT(tempID); + ImGui::TextUnformatted(tempID); + + float sliderHeight=200.0f*dpiScale; + float waveWidth=140.0*dpiScale; + float waveHeight=sliderHeight-ImGui::GetFrameHeightWithSpacing()*(ins->type==DIV_INS_OPLL?4.5f:5.5f); + + int maxTl=127; + if (ins->type==DIV_INS_OPLL) { + if (i==1) { + maxTl=15; + } else { + maxTl=63; + } + } + if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS) { + maxTl=63; + } + int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ)?31:15; + + bool ssgOn=op.ssgEnv&8; + bool ksrOn=op.ksr; + bool vibOn=op.vib; + bool egtOn=op.egt; + bool susOn=op.sus; // yawn + unsigned char ssgEnv=op.ssgEnv&7; + + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,oldPadding); + if (ImGui::BeginTable("opParams",4,ImGuiTableFlags_BordersInnerV)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,waveWidth); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + float textY=ImGui::GetCursorPosY(); + CENTER_TEXT_20(FM_SHORT_NAME(FM_AR)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_AR)); + ImGui::TableNextColumn(); + if (ins->type==DIV_INS_FM) { + ImGui::Text("SSG-EG"); + } else { + ImGui::Text("Waveform"); + } + ImGui::TableNextColumn(); + ImGui::Text("Envelope"); + ImGui::TableNextColumn(); + CENTER_TEXT(FM_SHORT_NAME(FM_TL)); + ImGui::Text("TL"); + + // A/D/S/R + ImGui::TableNextColumn(); + + op.ar&=maxArDr; + P(CWVSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); + + ImGui::SameLine(); + op.dr&=maxArDr; + float textX_DR=ImGui::GetCursorPosX(); + P(CWVSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); + + float textX_SL=0.0f; + if (settings.susPosition==0) { + ImGui::SameLine(); + op.sl&=15; + textX_SL=ImGui::GetCursorPosX(); + P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + } + + float textX_D2R=0.0f; + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + ImGui::SameLine(); + op.d2r&=31; + textX_D2R=ImGui::GetCursorPosX(); + P(CWVSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); + } + + ImGui::SameLine(); + op.rr&=15; + float textX_RR=ImGui::GetCursorPosX(); + P(CWVSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); + + if (settings.susPosition==1) { + ImGui::SameLine(); + op.sl&=15; + textX_SL=ImGui::GetCursorPosX(); + P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + } + + ImVec2 prevCurPos=ImGui::GetCursorPos(); + + // labels + ImGui::SetCursorPos(ImVec2(textX_DR,textY)); + CENTER_TEXT_20(FM_SHORT_NAME(FM_DR)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_DR)); + + ImGui::SetCursorPos(ImVec2(textX_SL,textY)); + CENTER_TEXT_20(FM_SHORT_NAME(FM_SL)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_SL)); + + ImGui::SetCursorPos(ImVec2(textX_RR,textY)); + CENTER_TEXT_20(FM_SHORT_NAME(FM_RR)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_RR)); + + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + ImGui::SetCursorPos(ImVec2(textX_D2R,textY)); + CENTER_TEXT_20(FM_SHORT_NAME(FM_D2R)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_D2R)); + } + + ImGui::SetCursorPos(prevCurPos); + + ImGui::TableNextColumn(); + switch (ins->type) { + case DIV_INS_FM: { + // SSG + ImGui::BeginDisabled(!ssgOn); + drawSSGEnv(op.ssgEnv&7,ImVec2(waveWidth,waveHeight)); + ImGui::EndDisabled(); + if (ImGui::Checkbox("##SSGOn",&ssgOn)) { PARAMETER + op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Only for OPN family chips"); + } + + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER + op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); + } + + // params + ImGui::Separator(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_MULT)); + P(CWSliderScalar("##MULT",ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN,tempID)); rightClickable + + int detune=(op.dt&7)-3; + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_DT)); + if (CWSliderInt("##DT",&detune,-3,4,tempID)) { PARAMETER + op.dt=detune+3; + } rightClickable + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_DT2)); + P(CWSliderScalar("##DT2",ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE,tempID)); rightClickable + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Only on YM2151 (OPM)"); + } + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_RS)); + P(CWSliderScalar("##RS",ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE,tempID)); rightClickable + + break; + } + case DIV_INS_OPLL: + // waveform + drawWaveform(i==0?(ins->fm.ams&1):(ins->fm.fms&1),ins->type==DIV_INS_OPZ,ImVec2(waveWidth,waveHeight)); + + // params + ImGui::Separator(); + if (ImGui::BeginTable("FMParamsInner",2)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + bool amOn=op.am; + if (ImGui::Checkbox(FM_NAME(FM_AM),&amOn)) { PARAMETER + op.am=amOn; + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox(FM_NAME(FM_KSR),&ksrOn)) { PARAMETER + op.ksr=ksrOn; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Checkbox(FM_NAME(FM_VIB),&vibOn)) { PARAMETER + op.vib=vibOn; + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox(FM_NAME(FM_EGS),&ssgOn)) { PARAMETER + op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); + } + + ImGui::EndTable(); + } + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_MULT)); + P(CWSliderScalar("##MULT",ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN,tempID)); rightClickable + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_KSL)); + P(CWSliderScalar("##KSL",ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE,tempID)); rightClickable + + break; + case DIV_INS_OPL: + // waveform + drawWaveform(op.ws&7,ins->type==DIV_INS_OPZ,ImVec2(waveWidth,waveHeight)); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + 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 || ins->type==DIV_INS_OPL_DRUMS) && ImGui::IsItemHovered()) { + ImGui::SetTooltip("OPL2/3 only (last 4 waveforms are OPL3 only)"); + } + + // params + ImGui::Separator(); + if (ImGui::BeginTable("FMParamsInner",2)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + bool amOn=op.am; + if (ImGui::Checkbox(FM_NAME(FM_AM),&amOn)) { PARAMETER + op.am=amOn; + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox(FM_NAME(FM_KSR),&ksrOn)) { PARAMETER + op.ksr=ksrOn; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Checkbox(FM_NAME(FM_VIB),&vibOn)) { PARAMETER + op.vib=vibOn; + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox(FM_NAME(FM_SUS),&susOn)) { PARAMETER + op.sus=susOn; + } + + ImGui::EndTable(); + } + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_MULT)); + P(CWSliderScalar("##MULT",ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN,tempID)); rightClickable + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_KSL)); + P(CWSliderScalar("##KSL",ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE,tempID)); rightClickable + + break; + case DIV_INS_OPZ: { + // waveform + drawWaveform(op.ws&7,ins->type==DIV_INS_OPZ,ImVec2(waveWidth,waveHeight)); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + 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 || ins->type==DIV_INS_OPL_DRUMS) && ImGui::IsItemHovered()) { + ImGui::SetTooltip("OPL2/3 only (last 4 waveforms are OPL3 only)"); + } + + // params + ImGui::Separator(); + if (egtOn) { + int block=op.dt; + int freqNum=(op.mult<<4)|(op.dvb&15); + ImGui::Text("Block"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImVec2 cursorAlign=ImGui::GetCursorPos(); + if (ImGui::InputInt("##Block",&block,1,1)) { + if (block<0) block=0; + if (block>7) block=7; + op.dt=block; + } + + ImGui::Text("Freq"); + ImGui::SameLine(); + ImGui::SetCursorPos(ImVec2(cursorAlign.x,ImGui::GetCursorPosY())); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##FreqNum",&freqNum,1,16)) { + if (freqNum<0) freqNum=0; + if (freqNum>255) freqNum=255; + op.mult=freqNum>>4; + op.dvb=freqNum&15; + } + } else { + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_MULT)); + P(CWSliderScalar("##MULT",ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN,tempID)); rightClickable + + int detune=(op.dt&7)-3; + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_DT)); + if (CWSliderInt("##DT",&detune,-3,4,tempID)) { PARAMETER + op.dt=detune+3; + } rightClickable + } + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_DT2)); + P(CWSliderScalar("##DT2",ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE,tempID)); rightClickable + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Only on YM2151 (OPM)"); + } + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_RS)); + P(CWSliderScalar("##RS",ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE,tempID)); rightClickable + break; + } + default: + break; + } + + ImGui::TableNextColumn(); + float envHeight=sliderHeight;//-ImGui::GetStyle().ItemSpacing.y*2.0f; + if (ins->type==DIV_INS_OPZ) { + envHeight-=ImGui::GetFrameHeightWithSpacing()*2.0f; + } + drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS || 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,envHeight),ins->type); + + if (ins->type==DIV_INS_OPZ) { + ImGui::Separator(); + if (ImGui::BeginTable("FMParamsInnerOPZ",2)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (!egtOn) { + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_FINE)); + P(CWSliderScalar("##FINE",ImGuiDataType_U8,&op.dvb,&_ZERO,&_FIFTEEN,tempID)); rightClickable + } + + ImGui::TableNextColumn(); + bool amOn=op.am; + if (ImGui::Checkbox(FM_NAME(FM_AM),&amOn)) { PARAMETER + op.am=amOn; + } + ImGui::SameLine(); + if (ImGui::Checkbox("Fixed",&egtOn)) { PARAMETER + op.egt=egtOn; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_EGSHIFT)); + P(CWSliderScalar("##EGShift",ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE,tempID)); rightClickable + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_REV)); + P(CWSliderScalar("##REV",ImGuiDataType_U8,&op.dam,&_ZERO,&_SEVEN,tempID)); rightClickable + + ImGui::TableNextColumn(); + + + ImGui::EndTable(); + } + } + + ImGui::TableNextColumn(); + op.tl&=maxTl; + P(CWVSliderScalar("##TL",ImVec2(ImGui::GetFrameHeight(),sliderHeight-(ins->type==DIV_INS_FM?(ImGui::GetFrameHeightWithSpacing()+ImGui::CalcTextSize(FM_SHORT_NAME(FM_AM)).y+ImGui::GetStyle().ItemSpacing.y):0.0f)),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); + + if (ins->type==DIV_INS_FM) { + CENTER_TEXT(FM_SHORT_NAME(FM_AM)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_AM)); + bool amOn=op.am; + if (ImGui::Checkbox("##AM",&amOn)) { PARAMETER + op.am=amOn; + } + } + + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + + if (settings.separateFMColors) { + popAccentColors(); + } + + ImGui::PopID(); + } + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + } else { // classic int columns=2; switch (settings.fmLayout) { case 1: // 2x2 @@ -3346,7 +3794,7 @@ void FurnaceGUI::drawInsEdit() { } const char* waveLabel="Waveform"; - int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VERA)?3:63; + int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VERA)?3:255; bool bitMode=false; if (ins->type==DIV_INS_C64 || ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) { bitMode=true; @@ -3403,7 +3851,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_X1_010) { dutyMax=0; ex1Max=7; - ex2Max=63; + ex2Max=255; ex2Bit=false; } if (ins->type==DIV_INS_N163) { @@ -3428,7 +3876,12 @@ void FurnaceGUI::drawInsEdit() { 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) { + if (ins->type==DIV_INS_STD ||//Game Gear + 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; } diff --git a/src/gui/intConst.cpp b/src/gui/intConst.cpp index 93829e356..9a41486e1 100644 --- a/src/gui/intConst.cpp +++ b/src/gui/intConst.cpp @@ -25,6 +25,7 @@ const int _THREE=3; const int _SEVEN=7; const int _TEN=10; const int _FIFTEEN=15; +const int _SIXTEEN=16; const int _THIRTY_ONE=31; const int _SIXTY_FOUR=64; const int _ONE_HUNDRED=100; diff --git a/src/gui/intConst.h b/src/gui/intConst.h index 6ff97e57e..ff11a4968 100644 --- a/src/gui/intConst.h +++ b/src/gui/intConst.h @@ -27,6 +27,7 @@ extern const int _THREE; extern const int _SEVEN; extern const int _TEN; extern const int _FIFTEEN; +extern const int _SIXTEEN; extern const int _THIRTY_ONE; extern const int _SIXTY_FOUR; extern const int _ONE_HUNDRED; diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index ee06a1b82..b7940a9e8 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -17,7 +17,6 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include #define _USE_MATH_DEFINES #include "gui.h" #include "../ta-log.h" @@ -31,6 +30,30 @@ inline float randRange(float min, float max) { return min+((float)rand()/(float)RAND_MAX)*(max-min); } +void _pushPartBlend(const ImDrawList* drawList, const ImDrawCmd* cmd) { + if (cmd!=NULL) { + if (cmd->UserCallbackData!=NULL) { + ((FurnaceGUI*)cmd->UserCallbackData)->pushPartBlend(); + } + } +} + +void _popPartBlend(const ImDrawList* drawList, const ImDrawCmd* cmd) { + if (cmd!=NULL) { + if (cmd->UserCallbackData!=NULL) { + ((FurnaceGUI*)cmd->UserCallbackData)->popPartBlend(); + } + } +} + +void FurnaceGUI::pushPartBlend() { + SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_ADD); +} + +void FurnaceGUI::popPartBlend() { + SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_BLEND); +} + // draw a pattern row inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache, bool inhibitSel) { static char id[32]; @@ -320,7 +343,13 @@ void FurnaceGUI::drawPattern() { bool inhibitMenu=false; float scrollX=0; - if (e->isPlaying() && followPattern && (!e->isStepping() || pendingStepUpdate)) cursor.y=oldRow+((pendingStepUpdate)?1:0); + if (e->isPlaying() && followPattern && (!e->isStepping() || pendingStepUpdate)) { + cursor.y=oldRow+((pendingStepUpdate)?1:0); + if (selStart.xCoarse==selEnd.xCoarse && selStart.xFine==selEnd.xFine && selStart.y==selEnd.y && !selecting) { + selStart=cursor; + selEnd=cursor; + } + } demandX=0; sel1=selStart; sel2=selEnd; @@ -686,11 +715,14 @@ void FurnaceGUI::drawPattern() { ImU32* color=noteGrad; switch (i.cmd) { - case DIV_CMD_NOTE_ON: + case DIV_CMD_NOTE_ON: { + float strength=CLAMP(i.value,0,119); partIcon=ICON_FA_ASTERISK; - life=96.0f; + life=80.0f+((i.value==DIV_NOTE_NULL)?0.0f:(strength*0.3f)); lifeSpeed=3.0f; + num=6+(strength/16); break; + } case DIV_CMD_LEGATO: partIcon=ICON_FA_COG; color=insGrad; @@ -788,7 +820,7 @@ void FurnaceGUI::drawPattern() { float frameTime=ImGui::GetIO().DeltaTime*60.0f; - // note slides + // note slides and vibrato ImVec2 arrowPoints[7]; if (e->isPlaying()) for (int i=0; icurSubSong->chanShow[i]) continue; @@ -843,11 +875,30 @@ void FurnaceGUI::drawPattern() { } } } + if (ch->vibratoDepth>0) { + ImVec4 col=uiColors[GUI_COLOR_PATTERN_EFFECT_PITCH]; + col.w*=0.2; + float width=patChanX[i+1]-patChanX[i]; + + particles.push_back(Particle( + pitchGrad, + ICON_FA_GLASS, + off.x+patChanX[i]+(width*0.5+0.5*sin(M_PI*(float)ch->vibratoPosGiant/64.0f)*width)-scrollX, + off.y+(ImGui::GetWindowHeight()*0.5f)+randRange(0,patFont->FontSize), + randRange(-4.0f,4.0f), + 2.0f*(3.0f+(rand()%5)+ch->vibratoRate), + 0.4f, + 1.0f, + 128.0f, + 4.0f + )); + } } // particle simulation ImDrawList* fdl=ImGui::GetForegroundDrawList(); if (!particles.empty()) WAKE_UP; + fdl->AddCallback(_pushPartBlend,this); for (size_t i=0; iAddCallback(_popPartBlend,this); } ImGui::PopStyleColor(3); diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 6aabe14e4..a29e9a625 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -56,13 +56,13 @@ void FurnaceGUI::initSystemPresets() { )); cat.systems.push_back(FurnaceGUISysDef( "Yamaha YM2608 (OPNA)", { - DIV_SYSTEM_PC98, 64, 0, 3, + DIV_SYSTEM_PC98, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( "Yamaha YM2608 (extended channel 3)", { - DIV_SYSTEM_PC98_EXT, 64, 0, 3, + DIV_SYSTEM_PC98_EXT, 64, 0, 0, 0 } )); @@ -221,18 +221,67 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "TI SN76489A", { + DIV_SYSTEM_SMS, 64, 0, 0x40, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "TI SN76496", { + DIV_SYSTEM_SMS, 64, 0, 0x44, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NCR 8496", { + DIV_SYSTEM_SMS, 64, 0, 0x48, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Tandy PSSJ 3-voice sound", { + DIV_SYSTEM_SMS, 64, 0, 0x4c, + // 8 bit DAC + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Sega PSG (SN76489-like)", { DIV_SYSTEM_SMS, 64, 0, 0, 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega PSG (SN76489-like, Stereo)", { + DIV_SYSTEM_SMS, 64, 0, 0xc, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "TI SN94624", { + DIV_SYSTEM_SMS, 64, 0, 0x182, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "TI SN76494", { + DIV_SYSTEM_SMS, 64, 0, 0x186, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "AY-3-8910", { DIV_SYSTEM_AY8910, 64, 0, 0, 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "AY-3-8914", { + DIV_SYSTEM_AY8910, 64, 0, 48, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Yamaha YM2149(F)", { DIV_SYSTEM_AY8910, 64, 0, 16, @@ -521,6 +570,12 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Game Gear", { + DIV_SYSTEM_SMS, 64, 0, 0xc, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Game Boy", { DIV_SYSTEM_GB, 64, 0, 0, @@ -609,13 +664,13 @@ void FurnaceGUI::initSystemPresets() { )); cat.systems.push_back(FurnaceGUISysDef( "Neo Geo AES", { - DIV_SYSTEM_YM2610_FULL, 64, 0, 0, + DIV_SYSTEM_YM2610_FULL, 64, 0, 1, 0 } )); cat.systems.push_back(FurnaceGUISysDef( "Neo Geo AES (extended channel 2)", { - DIV_SYSTEM_YM2610_FULL_EXT, 64, 0, 0, + DIV_SYSTEM_YM2610_FULL_EXT, 64, 0, 1, 0 } )); @@ -785,7 +840,7 @@ void FurnaceGUI::initSystemPresets() { cat.systems.push_back(FurnaceGUISysDef( "MSX + Playsoniq", { DIV_SYSTEM_AY8910, 64, 0, 16, - DIV_SYSTEM_SMS, 64, 0, 0, + DIV_SYSTEM_SMS, 64, 0, 0, // Sega VDP DIV_SYSTEM_C64_8580, 64, 0, 0, DIV_SYSTEM_SCC_PLUS, 64, 0, 0, 0 @@ -806,26 +861,110 @@ void FurnaceGUI::initSystemPresets() { } )); cat.systems.push_back(FurnaceGUISysDef( - "NEC PC-98 (with PC-9801-26K)", { - DIV_SYSTEM_OPN, 64, 0, 3, + "NEC PC-98 (with PC-9801-26/K)", { + DIV_SYSTEM_OPN, 64, 0, 4, // 3.9936MHz but some compatible card has 4MHz 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "NEC PC-98 (with PC-9801-26K; extended channel 3)", { - DIV_SYSTEM_OPN_EXT, 64, 0, 3, + "NEC PC-98 (with PC-9801-26/K; extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 4, // 3.9936MHz but some compatible card has 4MHz 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "NEC PC-98 (with PC-9801-86)", { - DIV_SYSTEM_PC98, 64, 0, 3, + "NEC PC-98 (with Sound Orchestra)", { + DIV_SYSTEM_OPN, 64, 0, 4, + DIV_SYSTEM_OPL2, 64, 0, 4, 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "NEC PC-98 (with PC-9801-86; extended channel 3)", { - DIV_SYSTEM_PC98_EXT, 64, 0, 3, + "NEC PC-98 (with Sound Orchestra; extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 4, + DIV_SYSTEM_OPL2, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Orchestra in drums mode)", { + DIV_SYSTEM_OPN, 64, 0, 4, + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Orchestra in drums mode; extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 4, + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Orchestra V)", { + DIV_SYSTEM_OPN, 64, 0, 4, + DIV_SYSTEM_Y8950, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Orchestra V; extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 4, + DIV_SYSTEM_Y8950, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Orchestra V in drums mode)", { + DIV_SYSTEM_OPN, 64, 0, 4, + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Orchestra V in drums mode; extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 4, + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with PC-9801-86)", { // -73 also has OPNA + DIV_SYSTEM_PC98, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with PC-9801-86; extended channel 3)", { // -73 also has OPNA + DIV_SYSTEM_PC98_EXT, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Blaster 16 for PC-9800 w/PC-9801-26/K compatible)", { + DIV_SYSTEM_OPN, 64, 0, 2, // 4MHz + DIV_SYSTEM_OPL3, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Blaster 16 for PC-9800 w/PC-9801-26/K compatible; extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 2, // 4MHz + DIV_SYSTEM_OPL3, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Blaster 16 for PC-9800 w/PC-9801-26/K compatible in drums mode)", { + DIV_SYSTEM_OPN, 64, 0, 2, // 4MHz + DIV_SYSTEM_OPL3_DRUMS, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Blaster 16 for PC-9800 w/PC-9801-26/K compatible in drums mode; extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 2, // 4MHz + DIV_SYSTEM_OPL3_DRUMS, 64, 0, 2, 0 } )); @@ -845,8 +984,76 @@ void FurnaceGUI::initSystemPresets() { cat.systems.push_back(FurnaceGUISysDef( "ZX Spectrum (128K) with TurboSound FM", { DIV_SYSTEM_AY8910, 64, 0, 1, - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_OPN, 64, 0, 0, + DIV_SYSTEM_OPN, 64, 0, 1, + DIV_SYSTEM_OPN, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K) with TurboSound FM (extended channel 3 on first OPN)", { + DIV_SYSTEM_AY8910, 64, 0, 1, + DIV_SYSTEM_OPN_EXT, 64, 0, 1, + DIV_SYSTEM_OPN, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K) with TurboSound FM (extended channel 3 on second OPN)", { + DIV_SYSTEM_AY8910, 64, 0, 1, + DIV_SYSTEM_OPN, 64, 0, 1, + DIV_SYSTEM_OPN_EXT, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K) with TurboSound FM (extended channel 3 on both OPNs)", { + DIV_SYSTEM_AY8910, 64, 0, 1, + DIV_SYSTEM_OPN_EXT, 64, 0, 1, + DIV_SYSTEM_OPN_EXT, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K) with TurboSound FM + SAA", { + DIV_SYSTEM_AY8910, 64, 0, 1, + DIV_SYSTEM_OPN, 64, 0, 1, + DIV_SYSTEM_OPN, 64, 0, 1, + DIV_SYSTEM_SAA1099, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K) with TurboSound FM + SAA (extended channel 3 on first OPN)", { + DIV_SYSTEM_AY8910, 64, 0, 1, + DIV_SYSTEM_OPN_EXT, 64, 0, 1, + DIV_SYSTEM_OPN, 64, 0, 1, + DIV_SYSTEM_SAA1099, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K) with TurboSound FM + SAA (extended channel 3 on second OPN)", { + DIV_SYSTEM_AY8910, 64, 0, 1, + DIV_SYSTEM_OPN, 64, 0, 1, + DIV_SYSTEM_OPN_EXT, 64, 0, 1, + DIV_SYSTEM_SAA1099, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K) with TurboSound FM + SAA (extended channel 3 on both OPNs)", { + DIV_SYSTEM_AY8910, 64, 0, 1, + DIV_SYSTEM_OPN_EXT, 64, 0, 1, + DIV_SYSTEM_OPN_EXT, 64, 0, 1, + DIV_SYSTEM_SAA1099, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K) with TurboSound", { + DIV_SYSTEM_AY8910, 64, 0, 1, + DIV_SYSTEM_AY8910, 64, 0, 1, // or YM2149 + DIV_SYSTEM_AY8910, 64, 0, 1, // or YM2149 0 } )); @@ -864,7 +1071,7 @@ void FurnaceGUI::initSystemPresets() { )); cat.systems.push_back(FurnaceGUISysDef( "BBC Micro", { - DIV_SYSTEM_SMS, 64, 0, 6, + DIV_SYSTEM_SMS, 64, 0, 0x42, // SN76489A 4MHz 0 } )); @@ -874,6 +1081,20 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "IBM PCjr", { + // it can be enable sound output at once + DIV_SYSTEM_SMS, 64, 0, 0x44, // SN76496 + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Tandy 1000", { + DIV_SYSTEM_SMS, 64, 0, 0x44, // NCR 8496 or SN76496 or Tandy PSSJ(with 8 bit DAC) + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "PC + Covox Sound Master", { DIV_SYSTEM_AY8930, 64, 0, 3, @@ -996,7 +1217,7 @@ void FurnaceGUI::initSystemPresets() { )); cat.systems.push_back(FurnaceGUISysDef( "FM Towns", { - DIV_SYSTEM_YM2612, 64, 0, 2, + DIV_SYSTEM_YM2612, 64, 0, 2, // YM3438 DIV_SYSTEM_RF5C68, 64, 0, 0, 0 } @@ -1008,186 +1229,620 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "TI-99/4A", { + DIV_SYSTEM_SMS, 64, 0, 0x182, // SN94624 447KHz + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Arcade systems","INSERT COIN"); cat.systems.push_back(FurnaceGUISysDef( "Bally Midway MCR", { - DIV_SYSTEM_AY8910, 64, 0, 0, - DIV_SYSTEM_AY8910, 64, 0, 0, + // SSIO sound board + DIV_SYSTEM_AY8910, 64, 0, 3, // 2MHz + DIV_SYSTEM_AY8910, 64, 0, 3, // 2MHz + // additional sound boards, mostly software controlled DAC 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "Gyruss", { + "Konami Gyruss", { DIV_SYSTEM_AY8910, 64, 0, 0, DIV_SYSTEM_AY8910, 64, 0, 0, DIV_SYSTEM_AY8910, 64, 0, 0, DIV_SYSTEM_AY8910, 64, 0, 0, DIV_SYSTEM_AY8910, 64, 0, 0, + // additional discrete sound logics + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Konami Bubble System", { + DIV_SYSTEM_AY8910, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 0, + DIV_SYSTEM_BUBSYS_WSG, 64, 0, 0, + // VLM5030 exists but not used for music at all + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Konami Hexion", { + DIV_SYSTEM_SCC, 64, 0, 2, // 1.5MHz (3MHz input) + DIV_SYSTEM_MSM6295, 64, 0, 1, 0 } )); cat.systems.push_back(FurnaceGUISysDef( "Sega Kyugo", { - DIV_SYSTEM_AY8910, 64, 0, 4, - DIV_SYSTEM_AY8910, 64, 0, 4, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Sega OutRun/X Board", { - DIV_SYSTEM_YM2151, 64, 0, 2, - DIV_SYSTEM_SEGAPCM, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Capcom CPS-1", { - DIV_SYSTEM_YM2151, 64, 0, 2, - DIV_SYSTEM_MSM6295, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Jaleco Mega System 1", { - DIV_SYSTEM_YM2151, 64, 0, 2, - DIV_SYSTEM_MSM6295, 64, 0, 0, - DIV_SYSTEM_MSM6295, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "NMK 16-bit Arcade", { - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_MSM6295, 64, 0, 2, - DIV_SYSTEM_MSM6295, 64, 0, 2, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Data East Arcade", { - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_OPL2, 64, 0, 0, - DIV_SYSTEM_MSM6295, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Kaneko Toybox System", { - DIV_SYSTEM_AY8910, 64, 0, 1, - DIV_SYSTEM_AY8910, 64, 0, 1, - DIV_SYSTEM_MSM6295, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Tecmo Arcade", { - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_MSM6295, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Seibu Kaihatsu Arcade", { - DIV_SYSTEM_OPL2, 64, 0, 0, - DIV_SYSTEM_MSM6295, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Data East Arcade (Dark Seal)", { - DIV_SYSTEM_YM2151, 64, 0, 2, - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_MSM6295, 64, 0, 0, - DIV_SYSTEM_MSM6295, 64, 0, 8, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Sunsoft Arcade", { - DIV_SYSTEM_YM2612, 64, 0, 4, - DIV_SYSTEM_MSM6295, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Atari Arcade (Rampart)", { - DIV_SYSTEM_OPLL, 64, 0, 0, - DIV_SYSTEM_MSM6295, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Data East Deco 156", { - DIV_SYSTEM_MSM6295, 64, 0, 0, - DIV_SYSTEM_MSM6295, 64, 0, 8, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "SNK Triple Z80 (Chopper)", { //or Namco? - DIV_SYSTEM_Y8950, 64, 0, 0, - DIV_SYSTEM_OPL2, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Sega System 18", { - DIV_SYSTEM_YM2612, 64, 0, 2, - DIV_SYSTEM_YM2612, 64, 0, 2, - DIV_SYSTEM_RF5C68, 64, 0, 1, + DIV_SYSTEM_AY8910, 64, 0, 14, + DIV_SYSTEM_AY8910, 64, 0, 14, 0 } )); cat.systems.push_back(FurnaceGUISysDef( "Sega System 1", { - DIV_SYSTEM_SMS, 64, 0, 2, - DIV_SYSTEM_SMS, 64, 0, 3, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Sega System 32", { - DIV_SYSTEM_YM2612, 64, 0, 4, - DIV_SYSTEM_YM2612, 64, 0, 4, - DIV_SYSTEM_RF5C68, 64, 0, 2, + DIV_SYSTEM_SMS, 64, 0, 0x42, // SN76489A 4MHz + DIV_SYSTEM_SMS, 64, 0, 0x0141, // SN76489A 2MHz 0 } )); cat.systems.push_back(FurnaceGUISysDef( "Sega Hang-On", { - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_SEGAPCM, 64, 0, 0, + DIV_SYSTEM_OPN, 64, 0, 2, // 4MHz + DIV_SYSTEM_SEGAPCM, 64, 0, 0, // discrete logics, 62.5KHz output rate 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "SNK Alpha-68K", { - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_OPLL, 64, 0, 0, + "Sega Hang-On (extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 2, // 4MHz + DIV_SYSTEM_SEGAPCM, 64, 0, 0, // discrete logics, 62.5KHz output rate + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega OutRun/X Board", { + DIV_SYSTEM_YM2151, 64, 0, 2, // 4MHz + DIV_SYSTEM_SEGAPCM, 64, 0, 0, // ASIC, 31.25KHz output rate + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega System 18", { + DIV_SYSTEM_YM2612, 64, 0, 2, // discrete 8MHz YM3438 + DIV_SYSTEM_YM2612, 64, 0, 2, // ^^ + DIV_SYSTEM_RF5C68, 64, 0, 1, // 10MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega System 18 (extended channel 3 on first OPN2C)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 2, // discrete 8MHz YM3438 + DIV_SYSTEM_YM2612, 64, 0, 2, // ^^ + DIV_SYSTEM_RF5C68, 64, 0, 1, // 10MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega System 18 (extended channel 3 on second OPN2C)", { + DIV_SYSTEM_YM2612, 64, 0, 2, // discrete 8MHz YM3438 + DIV_SYSTEM_YM2612_EXT, 64, 0, 2, // ^^ + DIV_SYSTEM_RF5C68, 64, 0, 1, // 10MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega System 18 (extended channel 3 on both OPN2Cs)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 2, // discrete 8MHz YM3438 + DIV_SYSTEM_YM2612_EXT, 64, 0, 2, // ^^ + DIV_SYSTEM_RF5C68, 64, 0, 1, // 10MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega System 32", { + DIV_SYSTEM_YM2612, 64, 0, 4, // discrete 8.05MHz YM3438 + DIV_SYSTEM_YM2612, 64, 0, 4, // ^^ + DIV_SYSTEM_RF5C68, 64, 0, 2, // 12.5MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega System 32 (extended channel 3 on first OPN2C)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 4, // discrete 8.05MHz YM3438 + DIV_SYSTEM_YM2612, 64, 0, 4, // ^^ + DIV_SYSTEM_RF5C68, 64, 0, 2, // 12.5MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega System 32 (extended channel 3 on second OPN2C)", { + DIV_SYSTEM_YM2612, 64, 0, 4, // discrete 8.05MHz YM3438 + DIV_SYSTEM_YM2612_EXT, 64, 0, 4, // ^^ + DIV_SYSTEM_RF5C68, 64, 0, 2, // 12.5MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega System 32 (extended channel 3 on both OPN2Cs)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 4, // discrete 8.05MHz YM3438 + DIV_SYSTEM_YM2612_EXT, 64, 0, 4, // ^^ + DIV_SYSTEM_RF5C68, 64, 0, 2, // 12.5MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom Exed Eyes", { + DIV_SYSTEM_AY8910, 64, 0, 4, // 1.5MHz + DIV_SYSTEM_SMS, 64, 0, 0x0104, // SN76489 3MHz + DIV_SYSTEM_SMS, 64, 0, 0x0104, // SN76489 3MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom Arcade", { // 1943, Side arms, etc + DIV_SYSTEM_OPN, 64, 0, 5, // 4 or 1.5MHz; various per games + DIV_SYSTEM_OPN, 64, 0, 5, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom Arcade (extended channel 3 on first OPN)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 5, + DIV_SYSTEM_OPN, 64, 0, 5, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom Arcade (extended channel 3 on second OPN)", { + DIV_SYSTEM_OPN, 64, 0, 5, + DIV_SYSTEM_OPN_EXT, 64, 0, 5, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom Arcade (extended channel 3 on both OPNs)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 5, + DIV_SYSTEM_OPN_EXT, 64, 0, 5, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom CPS-1", { + DIV_SYSTEM_YM2151, 64, 0, 0, // 3.58MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom CPS-2 (QSound)", { + DIV_SYSTEM_QSOUND, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Jaleco Ginga NinkyouDen", { + DIV_SYSTEM_AY8910, 64, 0, 16, // 1.79MHz + DIV_SYSTEM_Y8950, 64, 0, 0, // 3.58MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Jaleco Ginga NinkyouDen (drums mode)", { + DIV_SYSTEM_AY8910, 64, 0, 16, // 1.79MHz + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 0, // 3.58MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Jaleco Mega System 1", { + DIV_SYSTEM_YM2151, 64, 0, 1, // 3.5MHz (7MHz / 2) + DIV_SYSTEM_MSM6295, 64, 0, 2, // 4MHz + DIV_SYSTEM_MSM6295, 64, 0, 2, // 4MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NMK 16-bit Arcade", { + DIV_SYSTEM_OPN, 64, 0, 5, // 1.5MHz; optional + DIV_SYSTEM_MSM6295, 64, 0, 130, // 4MHz + DIV_SYSTEM_MSM6295, 64, 0, 130, // ^^ + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NMK 16-bit Arcade (extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 5, // 1.5MHz; optional + DIV_SYSTEM_MSM6295, 64, 0, 130, // 4MHz + DIV_SYSTEM_MSM6295, 64, 0, 130, // ^^ + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Kaneko DJ Boy", { + DIV_SYSTEM_OPN, 64, 0, 3, // 3MHz + DIV_SYSTEM_MSM6295, 64, -127, 12, // 1.5MHz, Left output + DIV_SYSTEM_MSM6295, 64, 127, 12, // 1.5MHz, Right output + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Kaneko DJ Boy (extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 3, // 3MHz + DIV_SYSTEM_MSM6295, 64, -127, 12, // 1.5MHz, Left output + DIV_SYSTEM_MSM6295, 64, 127, 12, // 1.5MHz, Right output + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Kaneko Air Buster", { + DIV_SYSTEM_OPN, 64, 0, 3, // 3MHz + DIV_SYSTEM_MSM6295, 64, 0, 141, // 3MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Kaneko Air Buster (extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 3, // 3MHz + DIV_SYSTEM_MSM6295, 64, 0, 141, // 3MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Kaneko Toybox System", { + DIV_SYSTEM_AY8910, 64, 0, 19, // YM2149 2MHz + DIV_SYSTEM_AY8910, 64, 0, 19, // ^^ + DIV_SYSTEM_MSM6295, 64, 0, 8, // 2MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Kaneko Jackie Chan", { + DIV_SYSTEM_YMZ280B, 64, 0, 3, // 16MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Super Kaneko Nova System", { + DIV_SYSTEM_YMZ280B, 64, 0, 4, // 16.67MHz (33.33MHz / 2) + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Tecmo Ninja Gaiden", { // Ninja Gaiden, Raiga, etc + DIV_SYSTEM_OPN, 64, 0, 2, // 4MHz + DIV_SYSTEM_OPN, 64, 0, 2, // 4MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Tecmo Ninja Gaiden (extended channel 3 on first OPN)", { // Ninja Gaiden, Raiga, etc + DIV_SYSTEM_OPN_EXT, 64, 0, 2, // 4MHz + DIV_SYSTEM_OPN, 64, 0, 2, // 4MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Tecmo Ninja Gaiden (extended channel 3 on second OPN)", { // Ninja Gaiden, Raiga, etc + DIV_SYSTEM_OPN, 64, 0, 2, // 4MHz + DIV_SYSTEM_OPN_EXT, 64, 0, 2, // 4MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Tecmo Ninja Gaiden (extended channel 3 on both OPNs)", { // Ninja Gaiden, Raiga, etc + DIV_SYSTEM_OPN_EXT, 64, 0, 2, // 4MHz + DIV_SYSTEM_OPN_EXT, 64, 0, 2, // 4MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Tecmo System", { + DIV_SYSTEM_OPL3, 64, 0, 0, + DIV_SYSTEM_YMZ280B, 64, 0, 0, + DIV_SYSTEM_MSM6295, 64, 0, 8, // 2MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Tecmo System (drums mode)", { + DIV_SYSTEM_OPL3_DRUMS, 64, 0, 0, + DIV_SYSTEM_YMZ280B, 64, 0, 0, + DIV_SYSTEM_MSM6295, 64, 0, 8, // 2MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Seibu Kaihatsu Raiden", { // Raiden, Seibu cup soccer, Zero team, etc + DIV_SYSTEM_OPL2, 64, 0, 0, // YM3812 3.58MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1 or 1.023MHz (28.636363MHz / 28); various per games + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Seibu Kaihatsu Raiden (drums mode)", { // Raiden, Seibu cup soccer, Zero team, etc + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 0, // YM3812 3.58MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1 or 1.023MHz (28.636363MHz / 28); various per games + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sunsoft Shanghai 3", { + DIV_SYSTEM_AY8910, 64, 0, 20, // YM2149 1.5MHz + DIV_SYSTEM_MSM6295, 64, 0, 1, // 1.056MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sunsoft Arcade", { + DIV_SYSTEM_YM2612, 64, 0, 2, // discrete YM3438 8MHz + DIV_SYSTEM_MSM6295, 64, 0, 1, // 1.056MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sunsoft Arcade (extended channel 3)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 2, // discrete YM3438 8MHz + DIV_SYSTEM_MSM6295, 64, 0, 1, // 1.056MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Atari Klax", { + DIV_SYSTEM_MSM6295, 64, 0, 7, // 0.895MHz (3.579545MHz / 4) + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Atari Rampart", { + DIV_SYSTEM_OPLL, 64, 0, 0, // 3.579545MHz + DIV_SYSTEM_MSM6295, 64, 0, 14, // 1.193MHz (3.579545MHz / 3) + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Atari Rampart (drums mode)", { + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, // 3.579545MHz + DIV_SYSTEM_MSM6295, 64, 0, 14, // 1.193MHz (3.579545MHz / 3) + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Atari JSA IIIs", { + DIV_SYSTEM_YM2151, 64, 0, 0, // 3.579545MHz + DIV_SYSTEM_MSM6295, 64, -127, 14, // 1.193MHz (3.579545MHz / 3), Left output + DIV_SYSTEM_MSM6295, 64, 127, 14, // 1.193MHz (3.579545MHz / 3), Right output 0 } )); cat.systems.push_back(FurnaceGUISysDef( "Data East Karnov", { - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_OPL, 64, 0, 0, + DIV_SYSTEM_OPN, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_OPL, 64, 0, 3, // 3MHz 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "Capcom Arcade", { - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_OPN, 64, 0, 0, + "Data East Karnov (extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_OPL, 64, 0, 3, // 3MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Karnov (drums mode)", { + DIV_SYSTEM_OPN, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_OPL_DRUMS, 64, 0, 3, // 3MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Karnov (extended channel 3; drums mode)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_OPL_DRUMS, 64, 0, 3, // 3MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Arcade", { // Bad dudes, Robocop, etc + DIV_SYSTEM_OPN, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_OPL2, 64, 0, 3, // 3MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1 to 1.056MHz; various per games or optional + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Arcade (extended channel 3)", { // Bad dudes, Robocop, etc + DIV_SYSTEM_OPN_EXT, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_OPL2, 64, 0, 3, // 3MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1 to 1.056MHz; various per games or optional + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Arcade (drums mode)", { // Bad dudes, Robocop, etc + DIV_SYSTEM_OPN, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 3, // 3MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1 to 1.056MHz; various per games or optional + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Arcade (extended channel 3; drums mode)", { // Bad dudes, Robocop, etc + DIV_SYSTEM_OPN_EXT, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 3, // 3MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1 to 1.056MHz; various per games or optional 0 } )); cat.systems.push_back(FurnaceGUISysDef( "Data East PCX", { - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_PCE, 64, 0, 2, + DIV_SYSTEM_OPN, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_PCE, 64, 0, 0, + // software controlled MSM5205 + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East PCX (extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_PCE, 64, 0, 0, + // software controlled MSM5205 + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Dark Seal", { // Dark Seal, Crude Buster, Vapor Trail, etc + DIV_SYSTEM_YM2151, 64, 0, 0, // 3.580MHz (32.22MHz / 9) + DIV_SYSTEM_OPN, 64, 0, 2, // 4.0275MHz (32.22MHz / 8); optional + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1.007MHz (32.22MHz / 32) + DIV_SYSTEM_MSM6295, 64, 0, 8, // 2.014MHz (32.22MHz / 16); optional + // HuC6280 is for control them, internal sound isn't used + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Dark Seal (extended channel 3)", { // Dark Seal, Crude Buster, Vapor Trail, etc + DIV_SYSTEM_YM2151, 64, 0, 0, // 3.580MHz (32.22MHz / 9) + DIV_SYSTEM_OPN_EXT, 64, 0, 2, // 4.0275MHz (32.22MHz / 8); optional + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1.007MHz (32.22MHz / 32) + DIV_SYSTEM_MSM6295, 64, 0, 8, // 2.014MHz (32.22MHz / 16); optional + // HuC6280 is for control them, internal sound isn't used + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Deco 156", { + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1 or 1.007MHz (32.22MHz / 32); various per games + DIV_SYSTEM_MSM6295, 64, 0, 8, // 1 or 2 or 2.014MHz (32.22MHz / 16); various per games + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East MLC", { + DIV_SYSTEM_YMZ280B, 64, 0, 5, // 14MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Ikari Warriors", { + DIV_SYSTEM_OPL, 64, 0, 2, + DIV_SYSTEM_OPL, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Ikari Warriors (drums mode on first OPL)", { + DIV_SYSTEM_OPL_DRUMS, 64, 0, 2, + DIV_SYSTEM_OPL, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Ikari Warriors (drums mode on second OPL)", { + DIV_SYSTEM_OPL, 64, 0, 2, + DIV_SYSTEM_OPL_DRUMS, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Ikari Warriors (drums mode on both OPLs)", { + DIV_SYSTEM_OPL_DRUMS, 64, 0, 2, + DIV_SYSTEM_OPL_DRUMS, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Triple Z80", { + DIV_SYSTEM_Y8950, 64, 0, 2, + DIV_SYSTEM_OPL, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Triple Z80 (drums mode on Y8950)", { + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 2, + DIV_SYSTEM_OPL, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Triple Z80 (drums mode on OPL)", { + DIV_SYSTEM_Y8950, 64, 0, 2, + DIV_SYSTEM_OPL_DRUMS, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Triple Z80 (drums mode on Y8950 and OPL)", { + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 2, + DIV_SYSTEM_OPL_DRUMS, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Chopper I", { + DIV_SYSTEM_Y8950, 64, 0, 2, + DIV_SYSTEM_OPL2, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Chopper I (drums mode on Y8950)", { + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 2, + DIV_SYSTEM_OPL2, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Chopper I (drums mode on OPL2)", { + DIV_SYSTEM_Y8950, 64, 0, 2, + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Chopper I (drums mode on Y8950 and OPL2)", { + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 2, + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Alpha denshi Alpha-68K", { + DIV_SYSTEM_OPN, 64, 0, 3, // 3MHz + DIV_SYSTEM_OPLL, 64, 0, 0, // 3.58MHz + // software controlled 8 bit DAC + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Alpha denshi Alpha-68K (extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 3, // 3MHz + DIV_SYSTEM_OPLL, 64, 0, 0, // 3.58MHz + // software controlled 8 bit DAC + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Alpha denshi Alpha-68K (drums mode)", { + DIV_SYSTEM_OPN, 64, 0, 3, // 3MHz + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, // 3.58MHz + // software controlled 8 bit DAC + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Alpha denshi Alpha-68K (extended channel 3; drums mode)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 3, // 3MHz + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, // 3.58MHz + // software controlled 8 bit DAC 0 } )); @@ -1204,35 +1859,34 @@ void FurnaceGUI::initSystemPresets() { } )); cat.systems.push_back(FurnaceGUISysDef( - "Capcom Exed Eyes", { - DIV_SYSTEM_AY8910, 64, 0, 0, - DIV_SYSTEM_SMS, 64, 0, 0, - DIV_SYSTEM_SMS, 64, 0, 0, + "Nichibutsu Mag Max", { + DIV_SYSTEM_AY8910, 64, 0, 13, + DIV_SYSTEM_AY8910, 64, 0, 13, + DIV_SYSTEM_AY8910, 64, 0, 13, 0 } - )); + )); cat.systems.push_back(FurnaceGUISysDef( - "Nichibutsu Arcade", { - DIV_SYSTEM_AY8910, 64, 0, 0, - DIV_SYSTEM_AY8910, 64, 0, 0, - DIV_SYSTEM_AY8910, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Namco (3-channel WSG)", { + "Namco (3-channel WSG)", { // Pac-Man, Galaga, Xevious, etc DIV_SYSTEM_NAMCO, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "Namco (8-channel WSG)", { + "Namco Mappy", { // Mappy, Super Pac-Man, Libble Rabble, etc DIV_SYSTEM_NAMCO_15XX, 64, 0, 0, 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Namco Pac-Land", { // Pac-Land, Baraduke, Sky kid, etc + DIV_SYSTEM_NAMCO_CUS30, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Namco System 1", { + DIV_SYSTEM_YM2151, 64, 0, 0, DIV_SYSTEM_NAMCO_CUS30, 64, 0, 0, 0 } @@ -1249,12 +1903,6 @@ void FurnaceGUI::initSystemPresets() { 0 } )); - cat.systems.push_back(FurnaceGUISysDef( - "Capcom CPS-2 (QSound)", { - DIV_SYSTEM_QSOUND, 64, 0, 0, - 0 - } - )); cat.systems.push_back(FurnaceGUISysDef( "Seta 1", { DIV_SYSTEM_X1_010, 64, 0, 0, @@ -1268,6 +1916,13 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta 1 + FM addon (extended channel 3)", { + DIV_SYSTEM_X1_010, 64, 0, 0, + DIV_SYSTEM_YM2612_EXT, 64, 0, 2, // Discrete YM3438 + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Seta 2", { DIV_SYSTEM_X1_010, 64, 0, 1, @@ -1281,18 +1936,67 @@ void FurnaceGUI::initSystemPresets() { } )); cat.systems.push_back(FurnaceGUISysDef( - "SNK Triple Z80", { - DIV_SYSTEM_Y8950, 64, 0, 0, - DIV_SYSTEM_OPL, 64, 0, 0, + "Coreland Cyber Tank", { + DIV_SYSTEM_Y8950, 64, -127, 0, // 3.58MHz, Left output + DIV_SYSTEM_Y8950, 64, 127, 0, // 3.58MHz, Right output 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "Konami Bubble System", { - DIV_SYSTEM_AY8910, 64, 0, 0, - DIV_SYSTEM_AY8910, 64, 0, 0, - DIV_SYSTEM_BUBSYS_WSG, 64, 0, 0, - // VLM5030 exists but not used for music at all + "Coreland Cyber Tank (drums mode)", { + DIV_SYSTEM_Y8950, 64, -127, 0, // 3.58MHz, Left output + DIV_SYSTEM_Y8950, 64, 127, 0, // 3.58MHz, Right output + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ICE Skimaxx", { + DIV_SYSTEM_MSM6295, 64, -127, 130, // 4MHz, Left output + DIV_SYSTEM_MSM6295, 64, 127, 130, // 4MHz, Right output + DIV_SYSTEM_MSM6295, 64, -127, 8, // 2MHz, Left output + DIV_SYSTEM_MSM6295, 64, 127, 8, // 2MHz, Right output + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Toaplan 1", { + DIV_SYSTEM_OPL2, 64, 0, 5, // 3.5MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Toaplan 1 (drums mode)", { + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 5, // 3.5MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Dynax/Nakanihon 3rd generation hardware", { + DIV_SYSTEM_AY8910, 64, 0, 0, // AY or YM, optional - 1.79MHz or 3.58MHz; various per game + DIV_SYSTEM_OPLL, 64, 0, 0, + DIV_SYSTEM_MSM6295, 64, 0, 6, // 1.023MHz mostly + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Dynax/Nakanihon 3rd generation hardware (drums mode)", { + DIV_SYSTEM_AY8910, 64, 0, 0, // AY or YM, optional - 1.79MHz or 3.58MHz; various per game + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, + DIV_SYSTEM_MSM6295, 64, 0, 6, // 1.023MHz mostly + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Dynax/Nakanihon Real Break", { + DIV_SYSTEM_OPLL, 64, 0, 0, + DIV_SYSTEM_YMZ280B, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Dynax/Nakanihon Real Break (drums mode)", { + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, + DIV_SYSTEM_YMZ280B, 64, 0, 0, 0 } )); diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 3c8053314..9b4bb17b5 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -830,16 +830,16 @@ void FurnaceGUI::drawSampleEdit() { 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 (CWSliderFloat("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 (CWSliderFloat("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%%")) { + if (CWSliderFloat("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; @@ -858,17 +858,17 @@ void FurnaceGUI::drawSampleEdit() { sampleFilterPower=3; } ImGui::Separator(); - if (ImGui::SliderFloat("Low-pass",&lowP,0.0f,100.0f,"%.1f%%")) { + if (CWSliderFloat("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%%")) { + if (CWSliderFloat("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%%")) { + if (CWSliderFloat("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; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 18490ad90..bdcc2de80 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -84,11 +84,6 @@ const char* snCores[]={ "Nuked-PSG Mod" }; -const char* saaCores[]={ - "MAME", - "SAASound" -}; - const char* nesCores[]={ "puNES", "NSFplay" @@ -416,7 +411,7 @@ void FurnaceGUI::drawSettings() { if (ImGui::Checkbox("Double click selects entire column",&doubleClickColumnB)) { settings.doubleClickColumn=doubleClickColumnB; } - + bool allowEditDockingB=settings.allowEditDocking; if (ImGui::Checkbox("Allow docking editors",&allowEditDockingB)) { settings.allowEditDocking=allowEditDockingB; @@ -524,6 +519,17 @@ void FurnaceGUI::drawSettings() { if (ImGui::RadioButton("Move to effect value/next effect and wrap around##eicb2",settings.effectCursorDir==2)) { settings.effectCursorDir=2; } + + ImGui::Text("Allow dragging selection:"); + if (ImGui::RadioButton("No##dms0",settings.dragMovesSelection==0)) { + settings.dragMovesSelection=0; + } + if (ImGui::RadioButton("Yes##dms1",settings.dragMovesSelection==1)) { + settings.dragMovesSelection=1; + } + if (ImGui::RadioButton("Yes (while holding Ctrl only)##dms2",settings.dragMovesSelection==2)) { + settings.dragMovesSelection=2; + } } ImGui::EndChild(); ImGui::EndTabItem(); @@ -899,10 +905,6 @@ void FurnaceGUI::drawSettings() { ImGui::SameLine(); ImGui::Combo("##SNCore",&settings.snCore,snCores,2); - ImGui::Text("SAA1099 core"); - ImGui::SameLine(); - ImGui::Combo("##SAACore",&settings.saaCore,saaCores,2); - ImGui::Text("NES core"); ImGui::SameLine(); ImGui::Combo("##NESCore",&settings.nesCore,nesCores,2); @@ -1126,6 +1128,15 @@ void FurnaceGUI::drawSettings() { if (ImGui::RadioButton("Compact (4x1)##fml3",settings.fmLayout==3)) { settings.fmLayout=3; } + if (ImGui::RadioButton("Alternate (2x2)##fml4",settings.fmLayout==4)) { + settings.fmLayout=4; + } + if (ImGui::RadioButton("Alternate (1x4)##fml5",settings.fmLayout==5)) { + settings.fmLayout=5; + } + if (ImGui::RadioButton("Alternate (4x1)##fml5",settings.fmLayout==6)) { + settings.fmLayout=6; + } ImGui::Text("Position of Sustain in FM editor:"); if (ImGui::RadioButton("Between Decay and Sustain Rate##susp0",settings.susPosition==0)) { @@ -1545,6 +1556,7 @@ void FurnaceGUI::drawSettings() { 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_START); UI_KEYBIND_CONFIG(GUI_ACTION_PLAY_REPEAT); UI_KEYBIND_CONFIG(GUI_ACTION_PLAY_CURSOR); UI_KEYBIND_CONFIG(GUI_ACTION_STEP_ONE); @@ -1953,7 +1965,6 @@ void FurnaceGUI::syncSettings() { settings.arcadeCore=e->getConfInt("arcadeCore",0); settings.ym2612Core=e->getConfInt("ym2612Core",0); settings.snCore=e->getConfInt("snCore",0); - settings.saaCore=e->getConfInt("saaCore",1); settings.nesCore=e->getConfInt("nesCore",0); settings.fdsCore=e->getConfInt("fdsCore",0); settings.pcSpeakerOutMethod=e->getConfInt("pcSpeakerOutMethod",0); @@ -2034,8 +2045,10 @@ void FurnaceGUI::syncSettings() { settings.insCellSpacing=e->getConfInt("insCellSpacing",0); settings.volCellSpacing=e->getConfInt("volCellSpacing",0); settings.effectCellSpacing=e->getConfInt("effectCellSpacing",0); + settings.effectValCellSpacing=e->getConfInt("effectValCellSpacing",0); settings.doubleClickColumn=e->getConfInt("doubleClickColumn",1); settings.blankIns=e->getConfInt("blankIns",0); + settings.dragMovesSelection=e->getConfInt("dragMovesSelection",2); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2047,7 +2060,6 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.arcadeCore,0,1); clampSetting(settings.ym2612Core,0,1); clampSetting(settings.snCore,0,1); - clampSetting(settings.saaCore,0,1); clampSetting(settings.nesCore,0,1); clampSetting(settings.fdsCore,0,1); clampSetting(settings.pcSpeakerOutMethod,0,4); @@ -2086,7 +2098,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.roundedMenus,0,1); clampSetting(settings.loadJapanese,0,1); clampSetting(settings.loadChinese,0,1); - clampSetting(settings.fmLayout,0,3); + clampSetting(settings.fmLayout,0,6); clampSetting(settings.susPosition,0,1); clampSetting(settings.effectCursorDir,0,2); clampSetting(settings.cursorPastePos,0,1); @@ -2121,6 +2133,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.effectValCellSpacing,0,32); clampSetting(settings.doubleClickColumn,0,1); clampSetting(settings.blankIns,0,1); + clampSetting(settings.dragMovesSelection,0,2); settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys","")); if (settings.initialSys.size()<4) { @@ -2170,7 +2183,6 @@ void FurnaceGUI::commitSettings() { e->setConf("arcadeCore",settings.arcadeCore); e->setConf("ym2612Core",settings.ym2612Core); e->setConf("snCore",settings.snCore); - e->setConf("saaCore",settings.saaCore); e->setConf("nesCore",settings.nesCore); e->setConf("fdsCore",settings.fdsCore); e->setConf("pcSpeakerOutMethod",settings.pcSpeakerOutMethod); @@ -2255,6 +2267,7 @@ void FurnaceGUI::commitSettings() { e->setConf("effectValCellSpacing",settings.effectValCellSpacing); e->setConf("doubleClickColumn",settings.doubleClickColumn); e->setConf("blankIns",settings.blankIns); + e->setConf("dragMovesSelection",settings.dragMovesSelection); // colors for (int i=0; i>2)&3)==0)) { - copyOfFlags=(flags&(~12))|0; + if (ImGui::RadioButton("Sega VDP/Master System",(flags&0xcc)==0x00)) { + copyOfFlags=(flags&(~0xcc))|0x00; } - if (ImGui::RadioButton("TI SN76489",((flags>>2)&3)==1)) { - copyOfFlags=(flags&(~12))|4; + if (ImGui::RadioButton("TI SN76489",(flags&0xcc)==0x04)) { + copyOfFlags=(flags&(~0xcc))|0x04; } - if (ImGui::RadioButton("TI SN76489 with Atari-like short noise",((flags>>2)&3)==2)) { - copyOfFlags=(flags&(~12))|8; + if (ImGui::RadioButton("TI SN76489 with Atari-like short noise",(flags&0xcc)==0x08)) { + copyOfFlags=(flags&(~0xcc))|0x08; + } + if (ImGui::RadioButton("Game Gear",(flags&0xcc)==0x0c)) { + copyOfFlags=(flags&(~0xcc))|0x0c; + } + if (ImGui::RadioButton("TI SN76489A",(flags&0xcc)==0x40)) { + copyOfFlags=(flags&(~0xcc))|0x40; + } + if (ImGui::RadioButton("TI SN76496",(flags&0xcc)==0x44)) { + copyOfFlags=(flags&(~0xcc))|0x44; + } + if (ImGui::RadioButton("NCR 8496",(flags&0xcc)==0x48)) { + copyOfFlags=(flags&(~0xcc))|0x48; + } + if (ImGui::RadioButton("Tandy PSSJ 3-voice sound",(flags&0xcc)==0x4c)) { + copyOfFlags=(flags&(~0xcc))|0x4c; + } + if (ImGui::RadioButton("TI SN94624",(flags&0xcc)==0x80)) { + copyOfFlags=(flags&(~0xcc))|0x80; + } + if (ImGui::RadioButton("TI SN76494",(flags&0xcc)==0x84)) { + copyOfFlags=(flags&(~0xcc))|0x84; } - /*if (ImGui::RadioButton("Game Gear",(flags>>2)==3)) { - copyOfFlags=(flags&3)|12); - }*/ bool noPhaseReset=flags&16; if (ImGui::Checkbox("Disable noise period change phase reset",&noPhaseReset)) { copyOfFlags=(flags&(~16))|(noPhaseReset<<4); @@ -152,6 +179,19 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool copyOfFlags=2; } break; + 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: + if (ImGui::RadioButton("8MHz (Neo Geo MVS)",(flags&0xff)==0)) { + copyOfFlags=(flags&(~0xff))|0; + } + if (ImGui::RadioButton("8.06MHz (Neo Geo AES)",(flags&0xff)==1)) { + copyOfFlags=(flags&(~0xff))|1; + } + break; case DIV_SYSTEM_AY8910: case DIV_SYSTEM_AY8930: { ImGui::Text("Clock rate:"); @@ -190,11 +230,15 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } if (ImGui::RadioButton("3.58MHz (Darky)",(flags&15)==11)) { copyOfFlags=(flags&(~15))|11; - } if (ImGui::RadioButton("3.6MHz (Darky)",(flags&15)==12)) { copyOfFlags=(flags&(~15))|12; - + } + if (ImGui::RadioButton("1.25MHz (Mag Max)",(flags&15)==13)) { + copyOfFlags=(flags&(~15))|13; + } + if (ImGui::RadioButton("1.536MHz (Kyugo)",(flags&15)==14)) { + copyOfFlags=(flags&(~15))|14; } if (type==DIV_SYSTEM_AY8910) { ImGui::Text("Chip type:"); @@ -221,7 +265,6 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool ImGui::BeginDisabled((type==DIV_SYSTEM_AY8910) && ((flags&0x30)!=16)); if (ImGui::Checkbox("Half Clock divider##_AY_CLKSEL",&clockSel)) { copyOfFlags=(flags&(~0x80))|(clockSel?0x80:0); - } ImGui::EndDisabled(); break; @@ -342,18 +385,57 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } rightClickable break; } - case DIV_SYSTEM_OPN: { - if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&3)==0)) { - copyOfFlags=(flags&0x80000000)|0; + case DIV_SYSTEM_OPN: + case DIV_SYSTEM_OPN_EXT: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("3.58MHz (NTSC)",(flags&31)==0)) { + copyOfFlags=(flags&(~31))|0; } - if (ImGui::RadioButton("PAL (3.54MHz)",(flags&3)==1)) { - copyOfFlags=(flags&0x80000000)|1; + if (ImGui::RadioButton("3.54MHz (PAL)",(flags&31)==1)) { + copyOfFlags=(flags&(~31))|1; } - if (ImGui::RadioButton("Arcade (4MHz)",(flags&3)==2)) { - copyOfFlags=(flags&0x80000000)|2; + if (ImGui::RadioButton("4MHz",(flags&31)==2)) { + copyOfFlags=(flags&(~31))|2; } - if (ImGui::RadioButton("PC-9801-26K? TODO: CONFIRM (3MHz)",(flags&3)==3)) { - copyOfFlags=(flags&0x80000000)|3; + if (ImGui::RadioButton("3MHz",(flags&31)==3)) { + copyOfFlags=(flags&(~31))|3; + } + if (ImGui::RadioButton("3.9936MHz (PC-88/PC-98)",(flags&31)==4)) { + copyOfFlags=(flags&(~31))|4; + } + if (ImGui::RadioButton("1.5MHz",(flags&31)==5)) { + copyOfFlags=(flags&(~31))|5; + } + ImGui::Text("Output rate:"); + if (ImGui::RadioButton("FM: clock / 72, SSG: clock / 16",(flags&96)==0)) { + copyOfFlags=(flags&(~96))|0; + } + if (ImGui::RadioButton("FM: clock / 36, SSG: clock / 8",(flags&96)==32)) { + copyOfFlags=(flags&(~96))|32; + } + if (ImGui::RadioButton("FM: clock / 24, SSG: clock / 4",(flags&96)==64)) { + copyOfFlags=(flags&(~96))|64; + } + break; + } + case DIV_SYSTEM_PC98: + case DIV_SYSTEM_PC98_EXT: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("8MHz (Arcade)",(flags&31)==0)) { + copyOfFlags=(flags&(~31))|0; + } + if (ImGui::RadioButton("7.987MHz (PC-88/PC-98)",(flags&31)==1)) { + copyOfFlags=(flags&(~31))|1; + } + ImGui::Text("Output rate:"); + if (ImGui::RadioButton("FM: clock / 144, SSG: clock / 32",(flags&96)==0)) { + copyOfFlags=(flags&(~96))|0; + } + if (ImGui::RadioButton("FM: clock / 72, SSG: clock / 16",(flags&96)==32)) { + copyOfFlags=(flags&(~96))|32; + } + if (ImGui::RadioButton("FM: clock / 48, SSG: clock / 8",(flags&96)==64)) { + copyOfFlags=(flags&(~96))|64; } break; } @@ -361,24 +443,19 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool ImGui::Text("Clock rate:"); if (ImGui::RadioButton("8MHz (FM Towns)",(flags&15)==0)) { copyOfFlags=(flags&(~15))|0; - } if (ImGui::RadioButton("10MHz (Sega System 18)",(flags&15)==1)) { copyOfFlags=(flags&(~15))|1; - } if (ImGui::RadioButton("12.5MHz (Sega CD/System 32)",(flags&15)==2)) { copyOfFlags=(flags&(~15))|2; - } ImGui::Text("Chip type:"); if (ImGui::RadioButton("RF5C68 (10-bit output)",((flags>>4)&15)==0)) { copyOfFlags=(flags&(~240))|0; - } if (ImGui::RadioButton("RF5C164 (16-bit output)",((flags>>4)&15)==1)) { copyOfFlags=(flags&(~240))|16; - } break; } @@ -400,44 +477,143 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } case DIV_SYSTEM_MSM6295: { ImGui::Text("Clock rate:"); - if (ImGui::RadioButton("1MHz",flags==0)) { - copyOfFlags=0; + if (ImGui::RadioButton("1MHz",(flags&127)==0)) { + copyOfFlags=(flags&(~127))|0; } - if (ImGui::RadioButton("1.056MHz",flags==1)) { - copyOfFlags=1; + if (ImGui::RadioButton("1.056MHz",(flags&127)==1)) { + copyOfFlags=(flags&(~127))|1; } - if (ImGui::RadioButton("4MHz",flags==2)) { - copyOfFlags=2; + if (ImGui::RadioButton("4MHz",(flags&127)==2)) { + copyOfFlags=(flags&(~127))|2; } - if (ImGui::RadioButton("4.224MHz",flags==3)) { - copyOfFlags=3; + if (ImGui::RadioButton("4.224MHz",(flags&127)==3)) { + copyOfFlags=(flags&(~127))|3; } - if (ImGui::RadioButton("3.58MHz",flags==4)) { - copyOfFlags=4; + if (ImGui::RadioButton("3.58MHz",(flags&127)==4)) { + copyOfFlags=(flags&(~127))|4; } - if (ImGui::RadioButton("1.79MHz",flags==5)) { - copyOfFlags=5; + if (ImGui::RadioButton("1.79MHz",(flags&127)==5)) { + copyOfFlags=(flags&(~127))|5; } - if (ImGui::RadioButton("1.02MHz",flags==6)) { - copyOfFlags=6; + if (ImGui::RadioButton("1.02MHz",(flags&127)==6)) { + copyOfFlags=(flags&(~127))|6; } - if (ImGui::RadioButton("0.89MHz",flags==7)) { - copyOfFlags=7; + if (ImGui::RadioButton("0.89MHz",(flags&127)==7)) { + copyOfFlags=(flags&(~127))|7; } - if (ImGui::RadioButton("2MHz",flags==8)) { - copyOfFlags=8; + if (ImGui::RadioButton("2MHz",(flags&127)==8)) { + copyOfFlags=(flags&(~127))|8; } - if (ImGui::RadioButton("2.112MHz",flags==9)) { - copyOfFlags=9; + if (ImGui::RadioButton("2.112MHz",(flags&127)==9)) { + copyOfFlags=(flags&(~127))|9; } - if (ImGui::RadioButton("0.875MHz",flags==10)) { - copyOfFlags=10; + if (ImGui::RadioButton("0.875MHz",(flags&127)==10)) { + copyOfFlags=(flags&(~127))|10; } - if (ImGui::RadioButton("0.9375MHz",flags==11)) { - copyOfFlags=11; + if (ImGui::RadioButton("0.9375MHz",(flags&127)==11)) { + copyOfFlags=(flags&(~127))|11; } - if (ImGui::RadioButton("1.5MHz",flags==12)) { - copyOfFlags=12; + if (ImGui::RadioButton("1.5MHz",(flags&127)==12)) { + copyOfFlags=(flags&(~127))|12; + } + if (ImGui::RadioButton("3MHz",(flags&127)==13)) { + copyOfFlags=(flags&(~127))|13; + } + if (ImGui::RadioButton("1.193MHz (Atari)",(flags&127)==14)) { + copyOfFlags=(flags&(~127))|14; + } + ImGui::Text("Output rate:"); + if (ImGui::RadioButton("clock / 132",(flags&128)==0)) { + copyOfFlags=(flags&(~128))|0; + } + if (ImGui::RadioButton("clock / 165",(flags&128)==128)) { + copyOfFlags=(flags&(~128))|128; + } + break; + } + case DIV_SYSTEM_SCC: + case DIV_SYSTEM_SCC_PLUS: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("1.79MHz (NTSC/MSX)",(flags&127)==0)) { + copyOfFlags=(flags&(~127))|0; + } + if (ImGui::RadioButton("1.77MHz (PAL)",(flags&127)==1)) { + copyOfFlags=(flags&(~127))|1; + } + if (ImGui::RadioButton("1.5MHz (Arcade)",(flags&127)==2)) { + copyOfFlags=(flags&(~127))|2; + } + if (ImGui::RadioButton("2MHz",(flags&127)==3)) { + copyOfFlags=(flags&(~127))|3; + } + break; + } + case DIV_SYSTEM_OPL: + case DIV_SYSTEM_OPL_DRUMS: + case DIV_SYSTEM_OPL2: + case DIV_SYSTEM_OPL2_DRUMS: + case DIV_SYSTEM_Y8950: + case DIV_SYSTEM_Y8950_DRUMS: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("3.58MHz (NTSC)",(flags&255)==0)) { + copyOfFlags=(flags&(~255))|0; + } + if (ImGui::RadioButton("3.54MHz (PAL)",(flags&255)==1)) { + copyOfFlags=(flags&(~255))|1; + } + if (ImGui::RadioButton("4MHz",(flags&255)==2)) { + copyOfFlags=(flags&(~255))|2; + } + if (ImGui::RadioButton("3MHz",(flags&255)==3)) { + copyOfFlags=(flags&(~255))|3; + } + if (ImGui::RadioButton("3.9936MHz (PC-88/PC-98)",(flags&255)==4)) { + copyOfFlags=(flags&(~255))|4; + } + if (ImGui::RadioButton("3.5MHz",(flags&255)==5)) { + copyOfFlags=(flags&(~255))|5; + } + break; + } + case DIV_SYSTEM_OPL3: + case DIV_SYSTEM_OPL3_DRUMS: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("14.32MHz (MTSC)",(flags&255)==0)) { + copyOfFlags=(flags&(~255))|0; + } + if (ImGui::RadioButton("14.19MHz (PAL)",(flags&255)==1)) { + copyOfFlags=(flags&(~255))|1; + } + if (ImGui::RadioButton("14MHz",(flags&255)==2)) { + copyOfFlags=(flags&(~255))|2; + } + if (ImGui::RadioButton("16MHz",(flags&255)==3)) { + copyOfFlags=(flags&(~255))|3; + } + if (ImGui::RadioButton("15MHz",(flags&255)==4)) { + copyOfFlags=(flags&(~255))|4; + } + break; + } + case DIV_SYSTEM_YMZ280B: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("16.9344MHz",(flags&255)==0)) { + copyOfFlags=(flags&(~255))|0; + } + if (ImGui::RadioButton("14.32MHz (MTSC)",(flags&255)==1)) { + copyOfFlags=(flags&(~255))|1; + } + if (ImGui::RadioButton("14.19MHz (PAL)",(flags&255)==3)) { + copyOfFlags=(flags&(~255))|2; + } + if (ImGui::RadioButton("16MHz",(flags&255)==3)) { + copyOfFlags=(flags&(~255))|3; + } + if (ImGui::RadioButton("16.67MHz",(flags&255)==4)) { + copyOfFlags=(flags&(~255))|4; + } + if (ImGui::RadioButton("14MHz",(flags&255)==5)) { + copyOfFlags=(flags&(~255))|5; } break; } @@ -445,17 +621,8 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool case DIV_SYSTEM_SWAN: case DIV_SYSTEM_VERA: case DIV_SYSTEM_BUBSYS_WSG: - 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_YMU759: case DIV_SYSTEM_PET: - case DIV_SYSTEM_SCC: - case DIV_SYSTEM_SCC_PLUS: - case DIV_SYSTEM_YMZ280B: ImGui::Text("nothing to configure"); break; default: diff --git a/src/main.cpp b/src/main.cpp index 079058528..23309d9a3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,6 +30,7 @@ #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include +#include #include #else #include @@ -152,6 +153,7 @@ TAParamResult pVersion(String) { printf("- puNES by FHorse (GPLv2)\n"); printf("- reSID by Dag Lem (GPLv2)\n"); printf("- Stella by Stella Team (GPLv2)\n"); + printf("- vgsound_emu by cam900 (BSD 3-clause)\n"); return TA_PARAM_QUIT; } @@ -243,6 +245,12 @@ void initParams() { // TODO: add crash log int main(int argc, char** argv) { initLog(); +#ifdef _WIN32 + HRESULT coResult=CoInitializeEx(NULL,COINIT_MULTITHREADED); + if (coResult!=S_OK) { + logE("CoInitializeEx failed!"); + } +#endif #if !(defined(__APPLE__) || defined(_WIN32) || defined(ANDROID)) // workaround for Wayland HiDPI issue if (getenv("SDL_VIDEODRIVER")==NULL) { @@ -446,6 +454,12 @@ int main(int argc, char** argv) { logI("stopping engine."); e.quit(); + +#ifdef _WIN32 + if (coResult==S_OK || coResult==S_FALSE) { + CoUninitialize(); + } +#endif return 0; }