diff --git a/.gitignore b/.gitignore index 06576ee5..566a3c3f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .vscode/ build/ +clangbuild/ nosdl/ release/ t/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bfac71c..028f5318 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -497,6 +497,7 @@ 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 @@ -512,6 +513,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 @@ -522,8 +524,17 @@ 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) @@ -551,6 +562,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() @@ -572,7 +588,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) diff --git a/TODO.md b/TODO.md index 5bd4e892..da090151 100644 --- a/TODO.md +++ b/TODO.md @@ -1,12 +1,13 @@ # to-do for 0.6pre1 -- rewrite the system name detection function anyway -- add another FM editor layout -- 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 b249c5bf..dad4f190 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 00000000..19f8ce54 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 00000000..54bdd36c 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 00000000..50d7e36d 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 00000000..5f2ffd24 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 00000000..a69f26e8 Binary files /dev/null and b/demos/Fusion.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 00000000..123381d8 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 ca8a0a9f..ca5f918c 100644 Binary files a/demos/UNATCOPCM.fur and b/demos/UNATCOPCM.fur differ diff --git a/demos/c64 ring test.fur b/demos/c64 ring test.fur new file mode 100644 index 00000000..72304532 Binary files /dev/null and b/demos/c64 ring test.fur differ diff --git a/demos/ecolove.fur b/demos/ecolove.fur index 9f287ec6..4fe641c9 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 00000000..297ca9f9 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 00000000..7d52d155 Binary files /dev/null and b/demos/going_up_a_step_at_time.fur differ diff --git a/demos/su_memory.fur b/demos/su_memory.fur index cb80b8b1..ff030dbe 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 00000000..5d70dca8 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 00000000..13268e48 Binary files /dev/null and b/demos/thick bass test.fur differ diff --git a/extern/Nuked-PSG/ympsg.c b/extern/Nuked-PSG/ympsg.c index 3df4f8e3..935b43ea 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 c00b3d72..97e039e3 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 f7a2be5b..776ad373 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 c6fec68b..93dfd534 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 00000000..5f144cbe --- /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 00000000..3ab103c5 --- /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 00000000..bb542f8b --- /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 00000000..fd7a9783 --- /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 00000000..a1f73271 --- /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 00000000..f6c3b54c --- /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 00000000..7745d323 --- /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 00000000..4e4ddcf7 --- /dev/null +++ b/extern/nfd-modified/src/include/nfd.h @@ -0,0 +1,71 @@ +/* + Native File Dialog + + User API + + http://www.frogtoss.com/labs + */ + + +#ifndef _NFD_H +#define _NFD_H + +#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 nfdchar_t *filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath, + nfdselcallback_t selCallback = NULL ); + +/* multiple file open dialog */ +nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList, + const nfdchar_t *defaultPath, + nfdpathset_t *outPaths, + nfdselcallback_t selCallback = NULL ); + +/* save dialog */ +nfdresult_t NFD_SaveDialog( const nfdchar_t *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 00000000..8eac6f79 --- /dev/null +++ b/extern/nfd-modified/src/nfd_cocoa.mm @@ -0,0 +1,300 @@ +/* + 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 char *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]; + + char typebuf[NFD_MAX_STRLEN] = {0}; + + size_t filterListLen = strlen(filterList); + char *p_typebuf = typebuf; + for ( size_t i = 0; i < filterListLen+1; ++i ) + { + if ( filterList[i] == ',' || filterList[i] == ';' || filterList[i] == '\0' ) + { + if (filterList[i] != '\0') + ++p_typebuf; + *p_typebuf = '\0'; + + // or this: NSString::stringWithUTF8String(typebuf); + // buildFilterList->addObject(thisType); + // really? did you have to make this mess?! + NSString *thisType = [NSString stringWithUTF8String: typebuf]; + [buildFilterList addObject:thisType]; + p_typebuf = typebuf; + *p_typebuf = '\0'; + } + else + { + *p_typebuf = filterList[i]; + ++p_typebuf; + + } + } + + NSArray *returnArray = [NSArray arrayWithArray:buildFilterList]; + + [buildFilterList release]; + return returnArray; +} + +static void AddFilterListToDialog( NSSavePanel *dialog, const char *filterList ) +{ + if ( !filterList || strlen(filterList) == 0 ) + 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 nfdchar_t *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 nfdchar_t *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 nfdchar_t *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 00000000..55517f5d --- /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 00000000..a1dd74b5 --- /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 00000000..7a9958ed --- /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 00000000..ea18d72b --- /dev/null +++ b/extern/nfd-modified/src/nfd_win.cpp @@ -0,0 +1,848 @@ +/* + 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 +} + + +/* ext is in format "jpg", no wildcards or separators */ +static int AppendExtensionToSpecBuf( const char *ext, char *specBuf, size_t specBufLen ) +{ + const char SEP[] = ";"; + assert( specBufLen > strlen(ext)+3 ); + + if ( strlen(specBuf) > 0 ) + { + strncat( specBuf, SEP, specBufLen - strlen(specBuf) - 1 ); + specBufLen += strlen(SEP); + } + + char extWildcard[NFD_MAX_STRLEN]; + int bytesWritten = sprintf_s( extWildcard, NFD_MAX_STRLEN, "*.%s", ext ); + assert( bytesWritten == (int)(strlen(ext)+2) ); + _NFD_UNUSED(bytesWritten); + + strncat( specBuf, extWildcard, specBufLen - strlen(specBuf) - 1 ); + + return NFD_OKAY; +} + +static nfdresult_t AddFiltersToDialog( ::IFileDialog *fileOpenDialog, const char *filterList ) +{ + const wchar_t WILDCARD[] = L"*.*"; + + if ( !filterList || strlen(filterList) == 0 ) + return NFD_OKAY; + + // Count rows to alloc + UINT filterCount = 1; /* guaranteed to have one filter on a correct, non-empty parse */ + const char *p_filterList; + for ( p_filterList = filterList; *p_filterList; ++p_filterList ) + { + if ( *p_filterList == ';' ) + ++filterCount; + } + + 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; + p_filterList = filterList; + char typebuf[NFD_MAX_STRLEN] = {0}; /* one per comma or semicolon */ + char *p_typebuf = typebuf; + + char specbuf[NFD_MAX_STRLEN] = {0}; /* one per semicolon */ + + while ( 1 ) + { + if ( NFDi_IsFilterSegmentChar(*p_filterList) ) + { + /* append a type to the specbuf (pending filter) */ + AppendExtensionToSpecBuf( typebuf, specbuf, 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 specList */ + + CopyNFDCharToWChar( specbuf, (wchar_t**)&specList[specIdx].pszName ); + CopyNFDCharToWChar( specbuf, (wchar_t**)&specList[specIdx].pszSpec ); + + memset( specbuf, 0, sizeof(char)*NFD_MAX_STRLEN ); + ++specIdx; + if ( specIdx == filterCount ) + break; + } + + if ( !NFDi_IsFilterSegmentChar( *p_filterList )) + { + *p_typebuf = *p_filterList; + ++p_typebuf; + } + + ++p_filterList; + } + + /* Add wildcard */ + specList[specIdx].pszSpec = WILDCARD; + specList[specIdx].pszName = WILDCARD; + + fileOpenDialog->SetFileTypes( 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 nfdchar_t *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 nfdchar_t *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 nfdchar_t *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 00000000..5f931337 --- /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 00000000..6308dfe0 --- /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 00000000..54bf3742 --- /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 00000000..45db6b65 --- /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 00000000..708fee85 --- /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 00000000..7ec37f08 --- /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/papers/doc/7-systems/sms.md b/papers/doc/7-systems/sms.md index 53560a97..882085f1 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/newIns.md b/papers/newIns.md new file mode 100644 index 00000000..fcc5f966 --- /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/src/audio/abstract.cpp b/src/audio/abstract.cpp index e44fdf7e..942e3143 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/rtmidi.cpp b/src/audio/rtmidi.cpp index a5c5bad0..31a3e66a 100644 --- a/src/audio/rtmidi.cpp +++ b/src/audio/rtmidi.cpp @@ -21,6 +21,26 @@ #include "../ta-log.h" #include "taAudio.h" +String sanitizePortName(const String& name) { +#if defined(_WIN32) + // remove port number + size_t namePos=name.rfind(' '); + if (namePos!=String::npos) { + return name.substr(0,namePos); + } + return name; +#elif defined(__linux__) + // remove port location + size_t namePos=name.rfind(' '); + if (namePos!=String::npos) { + return name.substr(0,namePos); + } + return name; +#else + return name; +#endif +} + // --- IN --- bool TAMidiInRtMidi::gather() { @@ -56,7 +76,7 @@ std::vector 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/engine/engine.cpp b/src/engine/engine.cpp index 76b2cd11..e8a766b3 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1431,7 +1431,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; @@ -2067,6 +2070,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; @@ -2183,6 +2188,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; @@ -2200,8 +2206,22 @@ 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) { + 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 (sf_read_raw(f,buf,si.frames*si.channels*sampleLen)!=(si.frames*si.channels*sampleLen)) { logW("sample read size mismatch!"); } DivSample* sample=new DivSample; @@ -2215,19 +2235,41 @@ int DivEngine::addSampleFromFile(const char* path) { sample->depth=16; } 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; @@ -2265,6 +2307,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]; @@ -2479,6 +2523,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]; @@ -2516,6 +2562,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]; @@ -2980,6 +3028,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) { @@ -2990,6 +3040,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 6eee5c79..401942a2 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -83,7 +83,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 +112,7 @@ struct DivChannelState { vibratoDepth(0), vibratoRate(0), vibratoPos(0), + vibratoPosGiant(0), vibratoDir(0), vibratoFine(15), tremoloDepth(0), diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 87c402e0..1be61fa2 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; } } @@ -938,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(); @@ -978,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 e73c0618..daa88362 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); diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 0bca2766..07377610 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)); } } @@ -565,8 +565,6 @@ void DivPlatformAY8910::reset() { delay=0; - extMode=false; - ioPortA=false; ioPortB=false; portAVal=0; @@ -595,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 0b05eb44..f67a2ad9 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); @@ -111,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 a92f185c..0fad4025 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)); } } diff --git a/src/engine/platform/bubsyswsg.cpp b/src/engine/platform/bubsyswsg.cpp index 85d50e66..48a07880 100644 --- a/src/engine/platform/bubsyswsg.cpp +++ b/src/engine/platform/bubsyswsg.cpp @@ -306,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/fmshared_OPM.h b/src/engine/platform/fmshared_OPM.h index 0ab05344..9b838c19 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 a709aa1a..0aaad16d 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 00000000..64099739 --- /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/genesis.cpp b/src/engine/platform/genesis.cpp index 46e28ccb..b3afdd2a 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: @@ -1247,12 +1241,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 14257409..c46a992d 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; @@ -92,21 +99,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; @@ -115,9 +112,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); @@ -153,6 +147,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 f7fdaf44..cd4d603c 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) { diff --git a/src/engine/platform/genesisshared.h b/src/engine/platform/genesisshared.h deleted file mode 100644 index d58d339e..00000000 --- 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/msm6258.cpp b/src/engine/platform/msm6258.cpp index db1848cf..731bf8f2 100644 --- a/src/engine/platform/msm6258.cpp +++ b/src/engine/platform/msm6258.cpp @@ -380,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 e3b50a17..cd975c8f 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,11 +87,6 @@ class DivPlatformMSM6258: public DivDispatch { int delay, updateOsc, sample, samplePos; - bool extMode; - - short oldWrites[512]; - short pendingWrites[512]; - friend void putDispatchChan(void*,int,int); public: diff --git a/src/engine/platform/msm6295.cpp b/src/engine/platform/msm6295.cpp index aeb29dd3..fc5f9ea3 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() { @@ -253,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); } @@ -268,7 +271,8 @@ void DivPlatformMSM6295::reset() { } sampleBank=0; - rateSel=false; + rateSel=rateSelInit; + rWrite(12,!rateSelInit); delay=0; } @@ -343,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; @@ -383,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; @@ -406,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; @@ -416,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 3e019208..f01c1b23 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,57 +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); - DivMacroInt* getChanMacroInt(int ch); - 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/namcowsg.cpp b/src/engine/platform/namcowsg.cpp index 1e8ff46d..43136d91 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) { diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 50e548ea..67ad2852 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)"; diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index fbf91448..da760af7 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); } @@ -1508,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); } @@ -1625,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: @@ -1654,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; @@ -1692,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 a051074a..5e8294c3 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]; diff --git a/src/engine/platform/pet.cpp b/src/engine/platform/pet.cpp index 0decd909..9b3c9988 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; } } diff --git a/src/engine/platform/pet.h b/src/engine/platform/pet.h index a5037045..06c7e736 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), @@ -85,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/scc.cpp b/src/engine/platform/scc.cpp index 011f63bc..8175bc01 100644 --- a/src/engine/platform/scc.cpp +++ b/src/engine/platform/scc.cpp @@ -332,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; @@ -377,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; @@ -386,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 31f6abc5..43a8be5a 100644 --- a/src/engine/platform/scc.h +++ b/src/engine/platform/scc.h @@ -85,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/sms.cpp b/src/engine/platform/sms.cpp index a32305a3..0556d2e4 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) { @@ -396,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; } @@ -450,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 9054eb7a..35bb44ba 100644 --- a/src/engine/platform/sms.h +++ b/src/engine/platform/sms.h @@ -58,21 +58,31 @@ 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); @@ -82,6 +92,7 @@ class DivPlatformSMS: public DivDispatch { 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/namco.cpp b/src/engine/platform/sound/namco.cpp index 82141301..811c34ab 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 507eaed2..d3844abc 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 351c94d8..e76b403f 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 4c24e938..7a8a5c62 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 34dc065d..00fd61ad 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/swan.cpp b/src/engine/platform/swan.cpp index 73dc7928..905fa82a 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); } } diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index 30db376d..123d1193 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; } } @@ -1052,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 ece914d1..e867416c 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); diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index a766ba44..8475b0e5 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); } } diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 5507b11c..e0bbe1a3 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; } } @@ -959,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); @@ -989,6 +981,10 @@ void DivPlatformYM2203::reset() { extMode=false; + // set prescaler + immWrite(0x2d,0xff); + immWrite(prescale,0xff); + ay->reset(); ay->getRegisterWrites().clear(); ay->flushWrites(); @@ -1021,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) { @@ -1053,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 f91b7e0f..d406e2f2 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,29 +82,16 @@ 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: @@ -128,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 3a38bde2..d3fd3487 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) { @@ -516,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/ym2608.cpp b/src/engine/platform/ym2608.cpp index 773f9ed5..733f0481 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; } } @@ -1283,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); @@ -1333,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(); @@ -1402,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)]; @@ -1414,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 f65c4cd6..ac38a8c0 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); @@ -142,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 f7d3217f..91c3a897 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) { diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index ccb9bfda..bed59c20 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; } } @@ -1330,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); @@ -1402,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; @@ -1410,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 1d807213..dde7ed10 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); @@ -151,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 c327645b..a2291c3f 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; } } @@ -1308,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); @@ -1380,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; @@ -1388,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 fe2126dd..fefb0692 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); @@ -124,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 f208bf18..b5e1ac9f 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) { diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index af9a3da0..6f9c8700 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) { diff --git a/src/engine/platform/ym2610shared.h b/src/engine/platform/ym2610shared.h deleted file mode 100644 index 8d8847c2..00000000 --- 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 ed50ccd4..45631a94 100644 --- a/src/engine/platform/ymz280b.cpp +++ b/src/engine/platform/ymz280b.cpp @@ -435,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; @@ -444,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 fbba3319..0d254c08 100644 --- a/src/engine/platform/ymz280b.h +++ b/src/engine/platform/ymz280b.h @@ -96,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 34265f8a..524d3c11 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; } } @@ -256,7 +258,7 @@ DivMacroInt* DivPlatformZXBeeper::getChanMacroInt(int ch) { } DivDispatchOscBuffer* DivPlatformZXBeeper::getOscBuffer(int ch) { - return oscBuf[ch]; + return (ch<1)?oscBuf[ch]:NULL; } unsigned char* DivPlatformZXBeeper::getRegisterPool() { diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index ed70da94..f8cb79ac 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -988,6 +988,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))); diff --git a/src/engine/safeReader.cpp b/src/engine/safeReader.cpp index 173ffd23..3cb1f4b1 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 53441095..36886856 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -51,7 +51,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); @@ -77,7 +84,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 e6903168..82b5d5dd 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 8709b084..26f5b259 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 76fc80ad..1b257af1 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", "SuperJet Spade", "TheDuccinator", + "theloredev", "TheRealHedgehogSonic", "tildearrow", "Ultraprogramer", + "Weeppiko", + "WitchyValeria", + "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 5bd945f1..da82de27 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 e47dfd62..5a98a78c 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 7d6d057f..22527a7b 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -369,6 +369,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/editing.cpp b/src/gui/editing.cpp index ea12eb59..9bd82795 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 e50298c1..64f9e989 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(NULL,state.path.c_str(),&out,state.clickCallback); + } else { + ret=NFD_OpenDialog(NULL,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 5eb67d85..6724eb95 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 index 0daf3412..78abce7c 100644 --- a/src/gui/findReplace.cpp +++ b/src/gui/findReplace.cpp @@ -1,8 +1,29 @@ +/** + * 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", @@ -17,9 +38,422 @@ const char* queryModes[GUI_QUERY_MAX]={ 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) @@ -38,341 +472,587 @@ void FurnaceGUI::drawFindReplace() { int index=0; int eraseIndex=-1; char tempID[1024]; - for (FurnaceGUIFindQuery& i: curQuery) { - 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::PushID(index); - 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]); + 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 { - snprintf(tempID,1024,"???"); - i.note=0; + ImGui::Text("no matches found!"); } - 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(); + if (ImGui::Button("Back")) { + queryViewingResults=false; } - } - 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; + } 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::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::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::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::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::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::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::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::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(); + } } - } - 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::Text("Effect"); + 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); - ImGui::Combo("##ECondition",&i.effectMode[j],queryModes,GUI_QUERY_MAX); - ImGui::TableNextColumn(); - if (FIRST_VISIBLE(i.effectMode[j])) { - snprintf(tempID,1024,"%.2X",i.effect[j]); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("EE1",tempID)) { - for (int k=0; k<256; k++) { - snprintf(tempID,1024,"%.2X",k); - if (ImGui::Selectable(tempID,i.effect[j]==k)) { - i.effect[j]=k; + 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(); } - } - ImGui::TableNextColumn(); - if (SECOND_VISIBLE(i.effectMode[j])) { - snprintf(tempID,1024,"%.2X",i.effectMax[j]); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("EE2",tempID)) { - for (int k=0; k<256; k++) { - snprintf(tempID,1024,"%.2X",k); - if (ImGui::Selectable(tempID,i.effectMax[j]==k)) { - i.effectMax[j]=k; - } - } - 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::Text("Value"); + 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); - ImGui::Combo("##EVCondition",&i.effectValMode[j],queryModes,GUI_QUERY_MAX); + 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(); - if (FIRST_VISIBLE(i.effectValMode[j])) { - snprintf(tempID,1024,"%.2X",i.effectVal[j]); + 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; } - ImGui::EndCombo(); + } 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 (SECOND_VISIBLE(i.effectValMode[j])) { - snprintf(tempID,1024,"%.2X",i.effectValMax[j]); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("EV2",tempID)) { - for (int k=0; k<256; k++) { - snprintf(tempID,1024,"%.2X",k); - if (ImGui::Selectable(tempID,i.effectValMax[j]==k)) { - i.effectValMax[j]=k; - } - } - ImGui::EndCombo(); + if (queryReplaceEffectCount>0) { + if (ImGui::Button("Remove effect")) { + queryReplaceEffectCount--; } } - ImGui::PopID(); + ImGui::EndTable(); } - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Button(ICON_FA_MINUS "##DelQuery")) { - eraseIndex=index; + ImGui::Text("Effect replace mode:"); + if (ImGui::RadioButton("Clear effects",queryReplaceEffectPos==0)) { + queryReplaceEffectPos=0; } - ImGui::TableNextColumn(); - if (i.effectCount<8) { - if (ImGui::Button("Add effect")) { - i.effectCount++; - } + if (ImGui::RadioButton("Replace matches only",queryReplaceEffectPos==1)) { + queryReplaceEffectPos=1; } - ImGui::TableNextColumn(); - if (i.effectCount>0) { - if (ImGui::Button("Remove effect")) { - i.effectCount--; - } + if (ImGui::RadioButton("Replace matches, then free spaces",queryReplaceEffectPos==2)) { + queryReplaceEffectPos=2; } - ImGui::PopID(); - ImGui::EndTable(); - } - index++; - } - if (ImGui::Button("Find")) { - - } - ImGui::SameLine(); - if (eraseIndex>=0) { - curQuery.erase(curQuery.begin()+eraseIndex); - } - if (ImGui::Button(ICON_FA_PLUS "##AddQuery")) { - curQuery.push_back(FurnaceGUIFindQuery()); - } - - if (ImGui::BeginTable("QueryLimits",2)) { - 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; - } + if (ImGui::RadioButton("Insert in free spaces",queryReplaceEffectPos==3)) { + queryReplaceEffectPos=3; } - 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; - } + if (ImGui::Button("Replace##QueryReplace")) { + doReplace(); } - ImGui::EndCombo(); + ImGui::EndTabItem(); } - ImGui::EndDisabled(); - - 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::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Checkbox("From start",&curQueryFromStart); - ImGui::TableNextColumn(); - ImGui::Checkbox("Backwards",&curQueryBackwards); - - ImGui::EndTable(); - } - - if (ImGui::TreeNode("Replace")) { - if (ImGui::BeginTable("QueryReplace",3)) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("..."); - ImGui::EndTable(); - } - if (ImGui::Button("Replace##QueryReplace")) { - // TODO - } - ImGui::TreePop(); + ImGui::EndTabBar(); } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_FIND; diff --git a/src/gui/gradient.cpp b/src/gui/gradient.cpp new file mode 100644 index 00000000..ac69bf86 --- /dev/null +++ b/src/gui/gradient.cpp @@ -0,0 +1,94 @@ +/** + * 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; +} + +// TODO: this one please +bool Gradient2D::fromString(String val) { + return false; +} + +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 + ); + } + } + } +} \ No newline at end of file diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 80484075..cf4760ca 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3036,7 +3036,8 @@ 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(); } if (ImGui::BeginMenu("help")) { @@ -3129,6 +3130,7 @@ bool FurnaceGUI::loop() { drawSubSongs(); drawFindReplace(); + drawSpoiler(); drawPattern(); drawEditControls(); drawSongInfo(); @@ -3168,6 +3170,7 @@ bool FurnaceGUI::loop() { #endif } +#ifndef NFD_NON_THREADED if (fileDialog->isOpen() && settings.sysFileDialog) { ImGui::OpenPopup("System File Dialog Pending"); } @@ -3180,6 +3183,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; @@ -4090,6 +4094,7 @@ bool FurnaceGUI::init() { 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); @@ -4122,6 +4127,19 @@ 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","")); + syncSettings(); if (settings.dpiScale>=0.5f) { @@ -4162,6 +4180,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; @@ -4313,6 +4332,7 @@ bool FurnaceGUI::finish() { e->setConf("effectListOpen",effectListOpen); e->setConf("subSongsOpen",subSongsOpen); e->setConf("findOpen",findOpen); + e->setConf("spoilerOpen",spoilerOpen); // commit last window size e->setConf("lastWindowWidth",scrW); @@ -4345,6 +4365,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 @@ -247,7 +248,8 @@ enum FurnaceGUIWindows { GUI_WINDOW_EFFECT_LIST, GUI_WINDOW_CHAN_OSC, GUI_WINDOW_SUBSONGS, - GUI_WINDOW_FIND + GUI_WINDOW_FIND, + GUI_WINDOW_SPOILER }; enum FurnaceGUIFileDialogs { @@ -525,6 +527,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, @@ -568,7 +585,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 { @@ -756,6 +775,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; @@ -820,6 +882,7 @@ enum FurnaceGUIFindQueryModes { enum FurnaceGUIFindQueryReplaceModes { GUI_QUERY_REPLACE_SET=0, GUI_QUERY_REPLACE_ADD, + GUI_QUERY_REPLACE_ADD_OVERFLOW, GUI_QUERY_REPLACE_CLEAR, GUI_QUERY_REPLACE_MAX @@ -857,6 +920,20 @@ struct FurnaceGUIFindQuery { } }; +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; @@ -1011,6 +1088,7 @@ class FurnaceGUI { int effectValCellSpacing; int doubleClickColumn; int blankIns; + int dragMovesSelection; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -1111,6 +1189,7 @@ class FurnaceGUI { effectValCellSpacing(0), doubleClickColumn(1), blankIns(0), + dragMovesSelection(1), maxUndoSteps(100), mainFontPath(""), patFontPath(""), @@ -1125,7 +1204,7 @@ 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; @@ -1133,10 +1212,10 @@ class FurnaceGUI { 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, findOpen; + bool subSongsOpen, findOpen, spoilerOpen; - 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]; @@ -1173,11 +1252,31 @@ class FurnaceGUI { int pgSys, pgAddr, pgVal; std::vector curQuery; - bool curQueryRangeX, curQueryFromStart, curQueryBackwards; + 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; @@ -1305,11 +1404,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 { @@ -1409,6 +1514,7 @@ class FurnaceGUI { void drawEffectList(); void drawSubSongs(); void drawFindReplace(); + void drawSpoiler(); void parseKeybinds(); void promptKey(int which); @@ -1421,6 +1527,8 @@ class FurnaceGUI { bool importLayout(String path); bool exportLayout(String path); + float computeGradPos(int type, int chan); + void resetColors(); void resetKeybinds(); @@ -1432,6 +1540,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); @@ -1459,6 +1568,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); @@ -1505,6 +1617,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/insEdit.cpp b/src/gui/insEdit.cpp index 7d7fa050..275a34f9 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1345,6 +1345,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; @@ -2016,7 +2019,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 @@ -2558,7 +3006,8 @@ void FurnaceGUI::drawInsEdit() { ImGui::BeginDisabled(ins->amiga.useWave); P(ImGui::Checkbox("Use sample map (does not work yet!)",&ins->amiga.useNoteMap)); if (ins->amiga.useNoteMap) { - if (ImGui::BeginTable("NoteMap",3,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { + // TODO: frequency map? + if (ImGui::BeginTable("NoteMap",2,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); @@ -2569,8 +3018,8 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::TableNextColumn(); ImGui::Text("Sample"); - ImGui::TableNextColumn(); - ImGui::Text("Frequency"); + /*ImGui::TableNextColumn(); + ImGui::Text("Frequency");*/ for (int i=0; i<120; i++) { ImGui::TableNextRow(); ImGui::PushID(fmt::sprintf("NM_%d",i).c_str()); @@ -2598,12 +3047,12 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndCombo(); } - ImGui::TableNextColumn(); + /*ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::InputInt("##SF",&ins->amiga.noteFreq[i],50,500)) { PARAMETER if (ins->amiga.noteFreq[i]<0) ins->amiga.noteFreq[i]=0; if (ins->amiga.noteFreq[i]>262144) ins->amiga.noteFreq[i]=262144; - } + }*/ ImGui::PopID(); } ImGui::EndTable(); @@ -3034,7 +3483,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; @@ -3071,7 +3520,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) { @@ -3092,7 +3541,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 9c7f53b9..8bee0f88 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 c6b13b9a..98c6c34f 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 fa00243c..b7940a9e 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]; @@ -692,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; @@ -794,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; @@ -849,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 994902e8..67acca82 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, @@ -515,6 +564,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, @@ -603,13 +658,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 } )); @@ -779,7 +834,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 @@ -800,26 +855,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 } )); @@ -839,8 +978,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 } )); @@ -858,7 +1065,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 } )); @@ -868,6 +1075,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, @@ -990,7 +1211,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 } @@ -1002,186 +1223,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 } )); @@ -1198,35 +1853,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 } @@ -1243,12 +1897,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, @@ -1262,6 +1910,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, @@ -1275,18 +1930,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 19fd7596..7264baed 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -765,16 +765,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; @@ -793,17 +793,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 a7d60229..50e513e0 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -411,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; @@ -519,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(); @@ -1117,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)) { @@ -2029,6 +2049,7 @@ void FurnaceGUI::syncSettings() { 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); @@ -2078,7 +2099,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); @@ -2113,6 +2134,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) { @@ -2246,6 +2268,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); - } break; } @@ -97,37 +115,29 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool ImGui::Text("Clock rate:"); if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&15)==0)) { copyOfFlags=(flags&(~15))|0; - } if (ImGui::RadioButton("PAL (3.55MHz)",(flags&15)==1)) { copyOfFlags=(flags&(~15))|1; - } if (ImGui::RadioButton("BBC Micro (4MHz)",(flags&15)==2)) { copyOfFlags=(flags&(~15))|2; - } if (ImGui::RadioButton("Half NTSC (1.79MHz)",(flags&15)==3)) { copyOfFlags=(flags&(~15))|3; - } if (type!=DIV_SYSTEM_VRC7) { ImGui::Text("Patch set:"); if (ImGui::RadioButton("Yamaha YM2413",((flags>>4)&15)==0)) { copyOfFlags=(flags&(~0xf0))|0; - } if (ImGui::RadioButton("Yamaha YMF281",((flags>>4)&15)==1)) { copyOfFlags=(flags&(~0xf0))|0x10; - } if (ImGui::RadioButton("Yamaha YM2423",((flags>>4)&15)==2)) { copyOfFlags=(flags&(~0xf0))|0x20; - } if (ImGui::RadioButton("Konami VRC7",((flags>>4)&15)==3)) { copyOfFlags=(flags&(~0xf0))|0x30; - } } break; @@ -135,15 +145,12 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool case DIV_SYSTEM_YM2151: if (ImGui::RadioButton("NTSC/X16 (3.58MHz)",flags==0)) { copyOfFlags=0; - } if (ImGui::RadioButton("PAL (3.55MHz)",flags==1)) { copyOfFlags=1; - } if (ImGui::RadioButton("X1/X68000 (4MHz)",flags==2)) { copyOfFlags=2; - } break; case DIV_SYSTEM_NES: @@ -152,30 +159,37 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool case DIV_SYSTEM_MMC5: if (ImGui::RadioButton("NTSC (1.79MHz)",flags==0)) { copyOfFlags=0; - } if (ImGui::RadioButton("PAL (1.67MHz)",flags==1)) { copyOfFlags=1; - } if (ImGui::RadioButton("Dendy (1.77MHz)",flags==2)) { copyOfFlags=2; - } break; case DIV_SYSTEM_C64_8580: case DIV_SYSTEM_C64_6581: if (ImGui::RadioButton("NTSC (1.02MHz)",flags==0)) { copyOfFlags=0; - } if (ImGui::RadioButton("PAL (0.99MHz)",flags==1)) { copyOfFlags=1; - } if (ImGui::RadioButton("SSI 2001 (0.89MHz)",flags==2)) { 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: @@ -183,87 +197,74 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool ImGui::Text("Clock rate:"); if (ImGui::RadioButton("1.79MHz (ZX Spectrum NTSC/MSX)",(flags&15)==0)) { copyOfFlags=(flags&(~15))|0; - } if (ImGui::RadioButton("1.77MHz (ZX Spectrum)",(flags&15)==1)) { copyOfFlags=(flags&(~15))|1; - } if (ImGui::RadioButton("1.75MHz (ZX Spectrum)",(flags&15)==2)) { copyOfFlags=(flags&(~15))|2; - } if (ImGui::RadioButton("2MHz (Atari ST/Sharp X1)",(flags&15)==3)) { copyOfFlags=(flags&(~15))|3; - } if (ImGui::RadioButton("1.5MHz (Vectrex)",(flags&15)==4)) { copyOfFlags=(flags&(~15))|4; - } if (ImGui::RadioButton("1MHz (Amstrad CPC)",(flags&15)==5)) { copyOfFlags=(flags&(~15))|5; - } if (ImGui::RadioButton("0.89MHz (Pre-divided Sunsoft 5B)",(flags&15)==6)) { copyOfFlags=(flags&(~15))|6; - } if (ImGui::RadioButton("1.67MHz (?)",(flags&15)==7)) { copyOfFlags=(flags&(~15))|7; - } if (ImGui::RadioButton("0.83MHz (Pre-divided Sunsoft 5B on PAL)",(flags&15)==8)) { copyOfFlags=(flags&(~15))|8; - } if (ImGui::RadioButton("1.10MHz (Gamate/VIC-20 PAL)",(flags&15)==9)) { copyOfFlags=(flags&(~15))|9; - } if (ImGui::RadioButton("2^21Hz (Game Boy)",(flags&15)==10)) { copyOfFlags=(flags&(~15))|10; - } 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:"); if (ImGui::RadioButton("AY-3-8910",(flags&0x30)==0)) { copyOfFlags=(flags&(~0x30))|0; - } if (ImGui::RadioButton("YM2149(F)",(flags&0x30)==16)) { copyOfFlags=(flags&(~0x30))|16; - } if (ImGui::RadioButton("Sunsoft 5B",(flags&0x30)==32)) { copyOfFlags=(flags&(~0x30))|32; - } if (ImGui::RadioButton("AY-3-8914",(flags&0x30)==48)) { copyOfFlags=(flags&(~0x30))|48; - } } bool stereo=flags&0x40; ImGui::BeginDisabled((type==DIV_SYSTEM_AY8910) && ((flags&0x30)==32)); if (ImGui::Checkbox("Stereo##_AY_STEREO",&stereo)) { copyOfFlags=(flags&(~0x40))|(stereo?0x40:0); - } ImGui::EndDisabled(); bool clockSel=flags&0x80; 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; @@ -375,18 +376,57 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } 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; } @@ -394,24 +434,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; } @@ -433,44 +468,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; } @@ -478,17 +612,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 f2274562..60c74de7 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 @@ -153,6 +154,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; } @@ -251,6 +253,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) { @@ -455,6 +463,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; }