diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e85c4de77..2646eb8d4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ defaults: shell: bash env: - BUILD_TYPE: Debug + BUILD_TYPE: RelWithDebInfo jobs: build: @@ -82,7 +82,7 @@ jobs: package_ext=".dmg" else package_name="${package_name}-Linux-${{ matrix.config.arch }}" - package_ext=".AppImage" + package_ext=".tar.gz" fi echo "Package identifier: ${package_name}" @@ -128,10 +128,7 @@ jobs: librtmidi-dev \ libsndfile1-dev \ zlib1g-dev \ - libjack-jackd2-dev \ - appstream - wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" || wget "https://tildearrow.org/storage/furnace/ci/appimagetool-x86_64.AppImage" - chmod +x appimagetool-x86_64.AppImage + libjack-jackd2-dev - name: Install Dependencies [Linux armhf] if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'armhf' }} @@ -151,9 +148,6 @@ jobs: libsndfile1-dev:armhf \ zlib1g-dev:armhf \ libjack-jackd2-dev:armhf - wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" || wget "https://tildearrow.org/storage/furnace/ci/appimagetool-x86_64.AppImage" - wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-armhf" || wget "https://tildearrow.org/storage/furnace/ci/runtime-armhf" - chmod +x appimagetool-x86_64.AppImage ls /usr/arm-linux-gnueabihf/lib - name: Configure (System Libraries) @@ -243,6 +237,7 @@ jobs: -DCMAKE_INSTALL_PREFIX=/usr \ -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ -DWARNINGS_ARE_ERRORS=${USE_WAE} \ + -DWITH_DEMOS=OFF -DWITH_INSTRUMENTS=OFF -DWITH_WAVETABLES=OFF \ "${CMAKE_EXTRA_ARGS[@]}" - name: Build @@ -300,24 +295,37 @@ jobs: # strip -s build/furnace #fi - mkdir -p target/furnace.AppDir - make -C ${PWD}/build DESTDIR=${PWD}/target/furnace.AppDir install - pushd target + mkdir -p target/furnace + make -C ${PWD}/build DESTDIR=${PWD}/target/furnace install + pushd target/furnace + + cp -v ../../res/logo.png .DirIcon + + cd usr + + mv bin/furnace .. + rmdir bin + + rm -r share/applications + rm -r share/doc + rm -r share/icons + rm -r share/licenses + rm -r share/metainfo + + rmdir share + + cd .. + + cp ../../LICENSE . + cp ../../README.md . + cp -r ../../papers papers + rmdir usr - pushd furnace.AppDir - cp -v usr/share/{icons/hicolor/1024x1024/apps/furnace.png,applications/furnace.desktop} ./ - ln -s furnace.png .DirIcon - mv -v usr/share/metainfo/{furnace.appdata,org.tildearrow.furnace.metainfo}.xml - cp -v ../../res/AppRun ./ popd - if [ '${{ matrix.config.arch }}' == 'armhf' ]; then - ../appimagetool-x86_64.AppImage --runtime-file=../runtime-armhf furnace.AppDir - else - ../appimagetool-x86_64.AppImage furnace.AppDir - fi - mv Furnace-*.AppImage ../${{ steps.package-identify.outputs.filename }} - popd + cd target + + tar -zcv -f ../${{ steps.package-identify.outputs.filename }} furnace - name: Upload artifact if: ${{ github.repository == 'tildearrow/furnace' && github.ref_name == 'master' }} diff --git a/CMakeLists.txt b/CMakeLists.txt index d405d4726..3e2f5ee5b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -556,6 +556,9 @@ src/engine/platform/dummy.cpp src/engine/export/abstract.cpp src/engine/export/amigaValidation.cpp + +src/engine/effect/abstract.cpp +src/engine/effect/dummy.cpp ) if (USE_SNDFILE) @@ -792,7 +795,7 @@ endif() if(ANDROID AND NOT TERMUX) add_library(furnace SHARED ${USED_SOURCES}) elseif(WIN32) - add_executable(furnace ${USED_SOURCES}) + add_executable(furnace WIN32 ${USED_SOURCES}) else() add_executable(furnace ${USED_SOURCES}) endif() diff --git a/README.md b/README.md index 4f94337a4..7f2a34cf2 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ # Furnace (chiptune tracker) -![screenshot](papers/screenshot2.png) +![screenshot](papers/screenshot3.png) the biggest multi-system chiptune tracker ever made! -[downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [FAQ](#frequently-asked-questions) +[downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [Unix/Linux packages](#packages) | [FAQ](#frequently-asked-questions) --- ## downloads check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage). -[see here](https://nightly.link/tildearrow/furnace/workflows/build/master) for unstable developer builds. +[see here](https://nightly.link/tildearrow/furnace/workflows/build/master) for the latest unstable build. ## features @@ -66,9 +66,12 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a - Family Noraebang (OPLL) - SID (6581/8580) used in Commodore 64 - Mikey used in Atari Lynx - - ZX Spectrum beeper (SFX-like engine) + - ZX Spectrum beeper + - SFX-like engine + - QuadTone engine - Pokémon Mini - Commodore PET + - Casio PV-1000 - TIA used in Atari 2600 - POKEY used in Atari 8-bit computers - Game Boy @@ -80,7 +83,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a - over 200 ready to use presets from computers, game consoles and arcade boards... - ...or create your own - up to 32 of them or a total of 128 channels! - DefleMask compatibility - - loads .dmf modules from all versions (beta 1 to 1.1.7) + - loads .dmf modules from all versions (beta 1 to 1.1.9) - saves .dmf modules - both modern and legacy - Furnace doubles as a module downgrader - loads/saves .dmp instruments and .dmw wavetables as well @@ -116,18 +119,19 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a --- # quick references - - **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z). - - **help**: check out the [documentation](papers/doc/README.md). it's mostly incomplete, but has details on effects. +- **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or the [official Discord server](https://discord.gg/EfrwT2wq7z). +- **help**: check out the [documentation](papers/doc/README.md). it's incomplete, but has details on effects. -## unofficial packages +## packages [![Packaging status](https://repology.org/badge/tiny-repos/furnace.svg)](https://repology.org/project/furnace/versions) some people have provided packages for Unix/Unix-like distributions. here's a list. - - **Arch Linux**: [furnace](https://archlinux.org/packages/community/x86_64/furnace/) is now in the community repo! - - **FreeBSD**: [a package in ports](https://www.freshports.org/audio/furnace/) is available courtesy of ehaupt. - - **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608. - - **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari. + +- **Arch Linux**: [furnace](https://archlinux.org/packages/extra/x86_64/furnace/) is in the official repositories. +- **FreeBSD**: [a package in ports](https://www.freshports.org/audio/furnace/) is available courtesy of ehaupt (warning: 0.5.8!). +- **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608. +- **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari. --- # developer info @@ -141,7 +145,9 @@ if you can't download these artifacts (because GitHub requires you to be logged ## dependencies - CMake +- Git (for cloning the repository) - JACK (optional, macOS/Linux only) +- a C/C++ compiler (e.g. Visual Studio or MinGW on Windows, Xcode (the command-line tools are enough) on macOS or GCC on Linux) if building under Windows or macOS, no additional dependencies are required. otherwise, you may also need the following: @@ -178,10 +184,32 @@ from the developer tools command prompt: mkdir build cd build cmake .. +``` + +then open the solution file in Visual Studio and build. + +alternatively, do: + +``` msbuild ALL_BUILD.vcxproj ``` -### macOS and Linux +### Windows using MinGW + +setting up MinGW is a bit more complicated. two benefits are a faster, hotter Furnace, and Windows XP support. + +however, one huge drawback is lack of backtrace support, so you'll have to use gdb when diagnosing a crash. + +``` +mkdir build +cd build +cmake -G "MinGW Makefiles" .. +mingw32-make +``` + +you may use "MSYS Makefiles" instead, depending on how you installed MinGW. + +### macOS, Linux and other Unix/Unix-like ``` mkdir build @@ -189,7 +217,16 @@ cd build cmake .. make ``` -Alternatively, build scripts are provided in the `scripts/` folder in the root of the repository. + +on macOS you may do the following instead: + +``` +mkdir build +cd build +cmake -G Xcode .. +``` + +...and then load the project on Xcode or type `xcodebuild`. ### CMake options @@ -220,6 +257,8 @@ Available options: ## console usage +(note: if on Windows, type `furnace.exe` instead, or `Debug\furnace.exe` on MSVC) + ``` ./furnace ``` @@ -238,23 +277,17 @@ this will play a compatible file. this will play a compatible file and enable the commands view. -**note that these commands only actually work in Linux environments. on other command lines, such as Windows' Command Prompt, or MacOS Terminal, it may not work correctly.** +**note that console mode may not work correctly on Windows. you may have to quit using the Task Manager.** --- # frequently asked questions -> woah! 50 sound chips?! I can't believe it! - -yup, it's real. - -> where's the manual? - -see [papers/](papers/doc/README.md). it's kind of incomplete, but at least the sound chips section is there. - > it doesn't open under macOS! this is due to Apple's application signing policy. a workaround is to right click on the Furnace app icon and select Open. +> it says "Furnace" is damaged and can't be opened! + **as of Monterey, this workaround no longer works (especially on ARM).** yeah, Apple has decided to be strict on the matter. if you happen to be on that version, use this workaround instead (on a Terminal): @@ -266,24 +299,25 @@ xattr -d com.apple.quarantine /path/to/Furnace.app you may need to log out and/or reboot after doing this. +> where's the manual? + +see [papers/](papers/doc/README.md). it's kind of incomplete, but at least the sound chips section is there. + +> is there a tutorial? + +sadly, the in-program tutorial isn't ready yet. however, [a video tutorial is available on YouTube](https://youtube.com/playlist?list=PLCELB6AsTZUnwv0PC5AAGHjvg47F44YQ1), made by Spinning Square Waves. + +> I've lost my song! + +Furnace keeps backups of the songs you've worked on before. go to **file > restore backup**. + > .spc export? **not yet!** coming in 0.7 though, eventually... -> how do I use C64 absolute filter/duty? +> ROM export? -on Instrument Editor in the C64 tab there are two options to toggle these. -also provided are two effects: - -- `3xxx`: set fine duty. -- `4xxx`: set fine cutoff. `xxx` range is 000-7ff. -additionally, you can change the cutoff and/or duty as a macro inside an instrument by clicking the `absolute cutoff macro` and/or `absolute duty macro` checkbox at the bottom of the instrument. (for the filter, you also need to click the checkbox that says `volume macro is cutoff macro`.) - -> how do I use PCM on a PCM-capable chip? - -two possibilities: -- the recommended way is by creating the "Sample" type instrument and assigning a sample to it. -- otherwise you may employ the DefleMask-compatible method, using `17xx` effect. +**not yet!** coming in 0.7 though, eventually... > my .dmf song sounds odd at a certain point @@ -293,10 +327,6 @@ Furnace's .dmf compatibility isn't perfect and it's mostly because DefleMask doe you should only save as .dmf if you're really sure, because the DefleMask format has several limitations. save in Furnace song format instead (.fur). -> how do I solo channels? - -right click on the channel name. - --- # footnotes @@ -309,4 +339,5 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY 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. -despite the fact this program works with the .dmf, .dmp and .dmw file formats (besides its native .fur format), it is NOT affiliated with Delek or DefleMask in any way, nor it is a replacement for the original program. +Furnace is NOT affiliated with Delek or DefleMask in any form, regardless of its ability to load and save the .dmf, .dmp and .dmw file formats. +additionally, Furnace does not intend to replace DefleMask, nor any other program. diff --git a/android/app/build.gradle b/android/app/build.gradle index 3ceb1765e..483ea52c4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -15,8 +15,8 @@ android { } minSdkVersion 21 targetSdkVersion 26 - versionCode 143 - versionName "0.6pre4" + versionCode 158 + versionName "0.6pre5" externalNativeBuild { cmake { arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static" diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8bad4fe2e..3deb416b9 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ diff --git a/demos/amiga/m7 vibe.fur b/demos/amiga/m7 vibe.fur index e9870ad2c..a9682b9e0 100644 Binary files a/demos/amiga/m7 vibe.fur and b/demos/amiga/m7 vibe.fur differ diff --git a/demos/arcade/Eternal_Forest_TaitoArcade.fur b/demos/arcade/Eternal_Forest_TaitoArcade.fur index 28c0cd002..e15c1f9ce 100644 Binary files a/demos/arcade/Eternal_Forest_TaitoArcade.fur and b/demos/arcade/Eternal_Forest_TaitoArcade.fur differ diff --git a/demos/arcade/Neo_Seaside_Volley_Court_NeoGeo.fur b/demos/arcade/Neo_Seaside_Volley_Court_NeoGeo.fur index 2ab629c23..0b64aced1 100644 Binary files a/demos/arcade/Neo_Seaside_Volley_Court_NeoGeo.fur and b/demos/arcade/Neo_Seaside_Volley_Court_NeoGeo.fur differ diff --git a/demos/arcade/UT99_Run_TaitoArcade.fur b/demos/arcade/UT99_Run_TaitoArcade.fur index 232ece3cd..2bdf02421 100644 Binary files a/demos/arcade/UT99_Run_TaitoArcade.fur and b/demos/arcade/UT99_Run_TaitoArcade.fur differ diff --git a/demos/arcade/iji_tor_segaxboard.fur b/demos/arcade/iji_tor_segaxboard.fur index 602382bc9..111097017 100644 Binary files a/demos/arcade/iji_tor_segaxboard.fur and b/demos/arcade/iji_tor_segaxboard.fur differ diff --git a/demos/ay8910/Flat Wave Society.fur b/demos/ay8910/Flat Wave Society.fur new file mode 100644 index 000000000..1c5cb61a7 Binary files /dev/null and b/demos/ay8910/Flat Wave Society.fur differ diff --git a/demos/ay8930/joyful_.fur b/demos/ay8930/joyful_.fur new file mode 100644 index 000000000..a486bfd62 Binary files /dev/null and b/demos/ay8930/joyful_.fur differ diff --git a/demos/c64/yeah!.fur b/demos/c64/yeah!.fur new file mode 100644 index 000000000..54312aff2 Binary files /dev/null and b/demos/c64/yeah!.fur differ diff --git a/demos/misc/Night_Market_TI994A.fur b/demos/misc/Night_Market_TI994A.fur new file mode 100644 index 000000000..cff985fe5 Binary files /dev/null and b/demos/misc/Night_Market_TI994A.fur differ diff --git a/demos/misc/RunningOnThePlayground_2xT6W28.fur b/demos/misc/RunningOnThePlayground_2xT6W28.fur new file mode 100644 index 000000000..769f12373 Binary files /dev/null and b/demos/misc/RunningOnThePlayground_2xT6W28.fur differ diff --git a/demos/misc/meaningful_connection_es5506.fur b/demos/misc/meaningful_connection_es5506.fur new file mode 100644 index 000000000..18ff606a7 Binary files /dev/null and b/demos/misc/meaningful_connection_es5506.fur differ diff --git a/demos/misc/mushroomhill_SM8521.fur b/demos/misc/mushroomhill_SM8521.fur new file mode 100644 index 000000000..928ac7679 Binary files /dev/null and b/demos/misc/mushroomhill_SM8521.fur differ diff --git a/demos/msx/WakingUpWhenMorningAlarmRings_OPLL.fur b/demos/msx/WakingUpWhenMorningAlarmRings_OPLL.fur new file mode 100644 index 000000000..36968ed0c Binary files /dev/null and b/demos/msx/WakingUpWhenMorningAlarmRings_OPLL.fur differ diff --git a/demos/multichip/ridiculous_game.fur b/demos/multichip/ridiculous_game.fur index 18e690b6b..b0d340fce 100644 Binary files a/demos/multichip/ridiculous_game.fur and b/demos/multichip/ridiculous_game.fur differ diff --git a/demos/nes/Super_Space_Invaders_Title.fur b/demos/nes/Super_Space_Invaders_Title.fur index fe964b0e9..712d94457 100644 Binary files a/demos/nes/Super_Space_Invaders_Title.fur and b/demos/nes/Super_Space_Invaders_Title.fur differ diff --git a/demos/nes/invicibility_mmc5_n163_fds.fur b/demos/nes/invicibility_mmc5_n163_fds.fur new file mode 100644 index 000000000..ef9b05bf9 Binary files /dev/null and b/demos/nes/invicibility_mmc5_n163_fds.fur differ diff --git a/demos/x16/TFV_Rise.fur b/demos/x16/TFV_Rise.fur new file mode 100644 index 000000000..6787eef34 Binary files /dev/null and b/demos/x16/TFV_Rise.fur differ diff --git a/demos/x16/richca.fur b/demos/x16/richca.fur new file mode 100644 index 000000000..209f02f2c Binary files /dev/null and b/demos/x16/richca.fur differ diff --git a/extern/imgui_patched/backends/imgui_impl_sdlrenderer.cpp b/extern/imgui_patched/backends/imgui_impl_sdlrenderer.cpp index 8125b119a..8cc79f7e9 100644 --- a/extern/imgui_patched/backends/imgui_impl_sdlrenderer.cpp +++ b/extern/imgui_patched/backends/imgui_impl_sdlrenderer.cpp @@ -94,13 +94,14 @@ static void ImGui_ImplSDLRenderer_SetupRenderState() SDL_RenderSetClipRect(bd->SDLRenderer, NULL); } -void ImGui_ImplSDLRenderer_NewFrame() +bool ImGui_ImplSDLRenderer_NewFrame() { ImGui_ImplSDLRenderer_Data* bd = ImGui_ImplSDLRenderer_GetBackendData(); IM_ASSERT(bd != NULL && "Did you call ImGui_ImplSDLRenderer_Init()?"); if (!bd->FontTexture) - ImGui_ImplSDLRenderer_CreateDeviceObjects(); + return ImGui_ImplSDLRenderer_CreateDeviceObjects(); + return true; } void ImGui_ImplSDLRenderer_RenderDrawData(ImDrawData* draw_data) diff --git a/extern/imgui_patched/backends/imgui_impl_sdlrenderer.h b/extern/imgui_patched/backends/imgui_impl_sdlrenderer.h index 78d9bcbe1..3281a7586 100644 --- a/extern/imgui_patched/backends/imgui_impl_sdlrenderer.h +++ b/extern/imgui_patched/backends/imgui_impl_sdlrenderer.h @@ -21,7 +21,7 @@ struct SDL_Renderer; IMGUI_IMPL_API bool ImGui_ImplSDLRenderer_Init(SDL_Renderer* renderer); IMGUI_IMPL_API void ImGui_ImplSDLRenderer_Shutdown(); -IMGUI_IMPL_API void ImGui_ImplSDLRenderer_NewFrame(); +IMGUI_IMPL_API bool ImGui_ImplSDLRenderer_NewFrame(); IMGUI_IMPL_API void ImGui_ImplSDLRenderer_RenderDrawData(ImDrawData* draw_data); // Called by Init/NewFrame/Shutdown diff --git a/extern/imgui_patched/imgui.cpp b/extern/imgui_patched/imgui.cpp index 4d57efc02..e2552fe2b 100644 --- a/extern/imgui_patched/imgui.cpp +++ b/extern/imgui_patched/imgui.cpp @@ -12478,9 +12478,12 @@ bool ImGui::LoadIniSettingsFromDisk(const char* ini_filename, bool redundancy) if (!file_data) continue; for (size_t j=0; jSDLRenderer, NULL); } -void ImGui_ImplSDLRenderer_NewFrame() +bool ImGui_ImplSDLRenderer_NewFrame() { ImGui_ImplSDLRenderer_Data* bd = ImGui_ImplSDLRenderer_GetBackendData(); IM_ASSERT(bd != NULL && "Did you call ImGui_ImplSDLRenderer_Init()?"); if (!bd->FontTexture) - ImGui_ImplSDLRenderer_CreateDeviceObjects(); + return ImGui_ImplSDLRenderer_CreateDeviceObjects(); + + return true; } void ImGui_ImplSDLRenderer_RenderDrawData(ImDrawData* draw_data) diff --git a/extern/imgui_patched/imgui_impl_sdlrenderer.h b/extern/imgui_patched/imgui_impl_sdlrenderer.h index 37adce66a..379265de0 100644 --- a/extern/imgui_patched/imgui_impl_sdlrenderer.h +++ b/extern/imgui_patched/imgui_impl_sdlrenderer.h @@ -21,7 +21,7 @@ struct SDL_Renderer; IMGUI_IMPL_API bool ImGui_ImplSDLRenderer_Init(SDL_Renderer* renderer); IMGUI_IMPL_API void ImGui_ImplSDLRenderer_Shutdown(); -IMGUI_IMPL_API void ImGui_ImplSDLRenderer_NewFrame(); +IMGUI_IMPL_API bool ImGui_ImplSDLRenderer_NewFrame(); IMGUI_IMPL_API void ImGui_ImplSDLRenderer_RenderDrawData(ImDrawData* draw_data); // Called by Init/NewFrame/Shutdown diff --git a/extern/imgui_patched/imgui_widgets.cpp b/extern/imgui_patched/imgui_widgets.cpp index 737c6e093..45bfd82c9 100644 --- a/extern/imgui_patched/imgui_widgets.cpp +++ b/extern/imgui_patched/imgui_widgets.cpp @@ -3112,8 +3112,9 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; ItemSize(bb, style.FramePadding.y); - if (!ItemAdd(frame_bb, id, NULL, ImGuiItemFlags_NoInertialScroll)) + if (!ItemAdd(frame_bb, id, NULL, (temp_input_allowed ? ImGuiItemFlags_Inputable : 0) | ImGuiItemFlags_NoInertialScroll)) return false; // Default format string when passing NULL @@ -3122,13 +3123,29 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.) format = PatchFormatStringFloatToInt(format); + // Tabbing or CTRL-clicking on Slider turns it into an input box const bool hovered = ItemHoverable(frame_bb, id); - if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavActivateInputId == id) + bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); + if (!temp_input_is_active) { - SetActiveID(id, window); - SetFocusID(id, window); - FocusWindow(window); - g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + const bool input_requested_by_tabbing = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0; + const bool clicked = (hovered && g.IO.MouseClicked[0]); + if (input_requested_by_tabbing || clicked || g.NavActivateId == id || g.NavActivateInputId == id) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + if (temp_input_allowed && (input_requested_by_tabbing || (clicked && g.IO.KeyCtrl) || g.NavActivateInputId == id)) + temp_input_is_active = true; + } + } + + if (temp_input_is_active) + { + // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set + const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0; + return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL); } // Draw frame @@ -3154,6 +3171,7 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return value_changed; } diff --git a/instruments/OPLL/Almost_Sine.fui b/instruments/OPLL/Almost_Sine.fui new file mode 100644 index 000000000..b1c2a4274 Binary files /dev/null and b/instruments/OPLL/Almost_Sine.fui differ diff --git a/instruments/OPLL/Alt_Oboe.fui b/instruments/OPLL/Alt_Oboe.fui new file mode 100644 index 000000000..41b40d489 Binary files /dev/null and b/instruments/OPLL/Alt_Oboe.fui differ diff --git a/instruments/OPLL/Better_Electric_Guitar.fui b/instruments/OPLL/Better_Electric_Guitar.fui new file mode 100644 index 000000000..3f422f582 Binary files /dev/null and b/instruments/OPLL/Better_Electric_Guitar.fui differ diff --git a/instruments/OPLL/Chime.fui b/instruments/OPLL/Chime.fui new file mode 100644 index 000000000..2de2d0230 Binary files /dev/null and b/instruments/OPLL/Chime.fui differ diff --git a/instruments/OPLL/Electric_Guitar_2.fui b/instruments/OPLL/Electric_Guitar_2.fui new file mode 100644 index 000000000..2e559b02d Binary files /dev/null and b/instruments/OPLL/Electric_Guitar_2.fui differ diff --git a/instruments/OPLL/Electric_Guitar_3.fui b/instruments/OPLL/Electric_Guitar_3.fui new file mode 100644 index 000000000..9887b6dfb Binary files /dev/null and b/instruments/OPLL/Electric_Guitar_3.fui differ diff --git a/instruments/OPLL/Finger_Bass.fui b/instruments/OPLL/Finger_Bass.fui new file mode 100644 index 000000000..e4a02d141 Binary files /dev/null and b/instruments/OPLL/Finger_Bass.fui differ diff --git a/instruments/OPLL/Glass_and_Wood.fui b/instruments/OPLL/Glass_and_Wood.fui new file mode 100644 index 000000000..3824575a3 Binary files /dev/null and b/instruments/OPLL/Glass_and_Wood.fui differ diff --git a/instruments/OPLL/Half_Sine.fui b/instruments/OPLL/Half_Sine.fui new file mode 100644 index 000000000..a131373bd Binary files /dev/null and b/instruments/OPLL/Half_Sine.fui differ diff --git a/instruments/OPLL/Hihat_Plus_Brass.fui b/instruments/OPLL/Hihat_Plus_Brass.fui new file mode 100644 index 000000000..0b0e71aa9 Binary files /dev/null and b/instruments/OPLL/Hihat_Plus_Brass.fui differ diff --git a/instruments/OPLL/Overdriven_Guitar.fui b/instruments/OPLL/Overdriven_Guitar.fui new file mode 100644 index 000000000..4b65305c8 Binary files /dev/null and b/instruments/OPLL/Overdriven_Guitar.fui differ diff --git a/instruments/OPLL/Reedlike_Sound.fui b/instruments/OPLL/Reedlike_Sound.fui new file mode 100644 index 000000000..df99db746 Binary files /dev/null and b/instruments/OPLL/Reedlike_Sound.fui differ diff --git a/instruments/OPLL/Soft_Bass.fui b/instruments/OPLL/Soft_Bass.fui new file mode 100644 index 000000000..4589e2435 Binary files /dev/null and b/instruments/OPLL/Soft_Bass.fui differ diff --git a/instruments/OPLL/Soft_Clarinet.fui b/instruments/OPLL/Soft_Clarinet.fui new file mode 100644 index 000000000..7cc9ded84 Binary files /dev/null and b/instruments/OPLL/Soft_Clarinet.fui differ diff --git a/instruments/OPLL/Soft_EP.fui b/instruments/OPLL/Soft_EP.fui new file mode 100644 index 000000000..0542a6dce Binary files /dev/null and b/instruments/OPLL/Soft_EP.fui differ diff --git a/instruments/OPLL/Soft_Flute.fui b/instruments/OPLL/Soft_Flute.fui new file mode 100644 index 000000000..6676e7d26 Binary files /dev/null and b/instruments/OPLL/Soft_Flute.fui differ diff --git a/instruments/OPLL/Steel_Guitar.fui b/instruments/OPLL/Steel_Guitar.fui new file mode 100644 index 000000000..b8ce64352 Binary files /dev/null and b/instruments/OPLL/Steel_Guitar.fui differ diff --git a/instruments/OPLL/Triangle_Recorder.fui b/instruments/OPLL/Triangle_Recorder.fui new file mode 100644 index 000000000..daa0bae48 Binary files /dev/null and b/instruments/OPLL/Triangle_Recorder.fui differ diff --git a/papers/doc/3-pattern/effects.md b/papers/doc/3-pattern/effects.md index 9d784d380..54d180006 100644 --- a/papers/doc/3-pattern/effects.md +++ b/papers/doc/3-pattern/effects.md @@ -121,8 +121,8 @@ ID | macro 33 | KSR ---|----------------------------- 40 | operator 2 macros -60 | operator 2 macros -80 | operator 2 macros +60 | operator 3 macros +80 | operator 4 macros the interpretation of duty, wave and extra macros depends on chip/instrument type: diff --git a/papers/doc/4-instrument/c64.md b/papers/doc/4-instrument/c64.md index 93eb261ab..9d612f478 100644 --- a/papers/doc/4-instrument/c64.md +++ b/papers/doc/4-instrument/c64.md @@ -25,6 +25,6 @@ C64 instrument editor consists of two tabs: one controlling various parameters o - [Arpeggio] - pitch sequence - [Duty cycle] - pulse duty cycle sequence - [Waveform] - select the waveform used by instrument -- [Filter mode] - select the filter mode/squence +- [Filter mode] - select the filter mode/sequence - [Resonance] - filter resonance sequence - [Special] - ring and oscillator sync selector diff --git a/papers/doc/4-instrument/fm.md b/papers/doc/4-instrument/fm.md index aea2bc6b0..68c8f32d9 100644 --- a/papers/doc/4-instrument/fm.md +++ b/papers/doc/4-instrument/fm.md @@ -8,7 +8,7 @@ FM editor is divided into 7 tabs: - [Macros (OP2)] - for macros controlling FM paramets of operator 2 - [Macros (OP3)] - for macros controlling FM paramets of operator 3 - [Macros (OP4)] - for macros controlling FM paramets of operator 4 -- [Macros] - for miscellanous macros controlling volume, argeggio and YM2151 noise generator. +- [Macros] - for miscellaneous macros controlling volume, argeggio and YM2151 noise generator. ## FM @@ -21,7 +21,7 @@ FM synthesizers Furnace supports are for-operator, meaning it takes four oscilla - [Sustain Level(SL)] - Determines the point at which the sound ceases to decay and changes to a sound having a constant level. The sustain level is expressed as a fraction of the maximum level. (0-15 range) - [Total Level (TL)] - Represents the envelope’s highest amplitude, with 0 being the largest and 127 (decimal) the smallest. A change of one unit is about 0.75 dB. - [Envelope Scale (KSR)] - A parameter that determines the degree to which the envelope execution speed increases according to the pitch. (0-3 range) -- [Frequency Multiplier (MULT)] - Determines the operator frequncy in relation to the pitch. (0-15 range) +- [Frequency Multiplier (MULT)] - Determines the operator frequency in relation to the pitch. (0-15 range) - [Fine Detune (DT)] - Shifts the pitch a little (0-7 range) - [Coarse Detune (DT2)] - Shifts the pitch by tens of cents (0-3 range) WARNING: this parameter affects only YM2151 sound source!!! - [Hardware Envelope Generator (SSG-EG)] - Executes the built-in envelope, inherited from AY-3-8910 PSG. Speed of execution is controlled via Decay Rate. WARNING: this parameter affects only YM2610/YM2612 sound source!!! @@ -33,14 +33,14 @@ FM synthesizers Furnace supports are for-operator, meaning it takes four oscilla ## Macros -Macros define the squence of values passed to the given parameter. Via macro, aside previously mentioned parameters, the following can be controlled: +Macros define the sequence of values passed to the given parameter. Via macro, aside previously mentioned parameters, the following can be controlled: - LFO Frequency - LFO waveform selection WARNING: this parameter affects only YM2151 sound source!!! - Amplitude Modulation Depth WARNING: this parameter affects only YM2151 sound source!!! - Frequency Modulation Depth WARNING: this parameter affects only YM2151 sound source!!! - Arpeggio Macro: pitch change sequence in semitones. Two modes are available: -Absolute (defult) - Executes the pitch with absolute change based on the pitch of the actual note. +Absolute (default) - Executes the pitch with absolute change based on the pitch of the actual note. Fixed - Executes at the pitch specified in the sequence regardless of the note pitch. - Noise Frequency: specifies the noise frequency in noise mode of YM2151's Channel 8 Operator 4 special mode. diff --git a/papers/doc/5-wave/README.md b/papers/doc/5-wave/README.md index e04834cc9..19a07b104 100644 --- a/papers/doc/5-wave/README.md +++ b/papers/doc/5-wave/README.md @@ -1,8 +1,14 @@ # wavetable editor -Wavetable synthesizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy. +Wavetable synthesizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.6pre4, wavetable editor affects PC Engine, WonderSwan, Namco WSGs, Virtual Boy, Game.com, SCC, FDS, Seta X1-010, Konami Bubble System WSG, SNES, Amiga and channel 3 of Game Boy. -Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS, Bubble System and N163, and 32-level height for PCE. If a larger wave is defined for these chips, it will be squashed to fit within the constraints of the chips. +Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan, Namco WSG, N163, Game.com, Virtual Boy and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS, Bubble System, SNES, Namco WSG and N163, 32-level height for PCE and 64-level height for Virtual Boy. If a larger wave is defined for these chips, it will be squashed to fit within the constraints of the chips. + +Furnace's wavetable editor features multiple ways of creating desired waveform shape: + +- Shape tab allows you to select a few predefined basic shapes and indirectly edit it via "Duty", "Exponent" and "XOR Point" sliders TODO: what the last two are doing? What is amplitude/phase for?) +- FM is for creating the waveform with frequency modulation synthesis principles: One can set carrier/modulation levels, frquency multiplier, connection between operators and FM waveforms of these operators. +- WaveTools allows user to fine-tune the waveform: scale said waveform in both X and Y axes, smoothen, amplify, normalize, convert to signed/unisgned, invert or even randomize the wavetable. ## wavetable synthesizer diff --git a/papers/doc/6-sample/README.md b/papers/doc/6-sample/README.md index ec6f230a6..05891a49a 100644 --- a/papers/doc/6-sample/README.md +++ b/papers/doc/6-sample/README.md @@ -64,4 +64,4 @@ In there, you can modify certain data pertaining to your sample, such as the: - what frequencies to filter, along with filter level/sweep and resonance options (much like the C64) - and many more. -The changes you make will be applied as soon as you've committed them to your sample, but they can be undoed and redoed, just like text. +The changes you make will be applied as soon as you've committed them to your sample, but they can be undone and redoed, just like text. diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 29c08c2ee..4101aa958 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -38,11 +38,11 @@ this is a list of sound chips that Furnace supports, including effects. - [VERA](vera.md) - [WonderSwan](wonderswan.md) - [Virtual Boy](virtual-boy.md) -- [Yamaha OPLL](opll.md) +- [Yamaha YM2413 (OPLL)](opll.md) - [Yamaha OPL](opl.md) - [Yamaha YM2151](ym2151.md) - [Yamaha YM2203](ym2203.md) -- [Yamaha YM2413](opz.md) +- [Yamaha YM2414 (OPZ)](opz.md) - [Yamaha YM2608](ym2608.md) - [Neo Geo/YM2610](ym2610.md) - [Taito Arcade/YM2610B](ym2610b.md) diff --git a/papers/doc/7-systems/es5506.md b/papers/doc/7-systems/es5506.md new file mode 100644 index 000000000..acdcd0a43 --- /dev/null +++ b/papers/doc/7-systems/es5506.md @@ -0,0 +1,41 @@ +# Ensoniq ES5506 (OTTO) + +Sample-based synthesis chip used in a bunch of Taito arcade machines and PC sound cards like Soundscape Elite. A variant of it was the heart of the well-known Gravis Ultrasound. + +it supports a whooping 32 channels of 16-bit PCM and: + +- Real time digital filters +- Frequency interpolation +- Loop start and stop positions for each voice (bidirectional and reverse looping) +- Internal volume multiplication and stereo panning +- Hardware support for envelopes + +# effects + +- `10xx`: set waveform. +- `11xx`: set filter mode (0-3) +- `120x`: set pause (bit 0). Pauses the sample until the bit is unset, where it will then resume where it left off. +- `14xx`: set filter coefficient K1 low byte. +- `15xx`: set filter coefficient K1 high byte. +- `16xx`: set filter coefficient K2 low byte. +- `17xx`: set filter coefficient K2 high byte. +- `18xx`: set filter coefficient K1 slide up. +- `19xx`: set filter coefficient K1 slide down. +- `1Axx`: set filter coefficient K2 slide up. +- `1Bxx`: set filter coefficient K2 slide down. +- `20xx`: set envelope count. +- `22xx`: set envelope left volume ramp. +- `23xx`: set envelope right volume ramp. +- `24xx`: set envelope filter coefficient K1 ramp. +- `25xx`: set envelope filter coefficient K1 ramp (slower). +- `26xx`: set envelope filter coefficient K2 ramp. +- `27xx`: set envelope filter coefficient K2 ramp (slower). +- `3xxx`: set coarse filter coefficient K1. +- `4xxx`: set coarse filter coefficient K2. +- `81xx`: set panning (left channel). +- `82xx`: set panning (right channel). +- `88xx`: set panning (rear channels). +- `89xx`: set panning (rear left channel). +- `8Axx`: set panning (rear right channel). +- `9xxx`: set sample offset (x256). +- `DFxx`: set sample playback direction. diff --git a/papers/doc/7-systems/genesis.md b/papers/doc/7-systems/genesis.md index ceebf88aa..335466d0a 100644 --- a/papers/doc/7-systems/genesis.md +++ b/papers/doc/7-systems/genesis.md @@ -16,7 +16,7 @@ this console is powered by two sound chips: the [Yamaha YM2612](ym2612.md) and [ - `15xx`: set operator 4 level. - `16xy`: set multiplier of operator. - `x` is the operator (1-4). - - `y` is the mutliplier. + - `y` is the multiplier. - `17xx`: enable PCM channel. - this only works on channel 6. - **this effect is there for compatibility reasons** - it is otherwise recommended to use Sample type instruments (which automatically enable PCM mode when used). diff --git a/papers/doc/7-systems/nes.md b/papers/doc/7-systems/nes.md index 37d4a7037..8367c5891 100644 --- a/papers/doc/7-systems/nes.md +++ b/papers/doc/7-systems/nes.md @@ -42,6 +42,33 @@ also known as Famicom. it is a five-channel sound generator: first two channels - `00`: PCM (software). - `01`: DPCM (hardware). - when in DPCM mode, samples will sound muffled (due to its nature), availables pitches are limited and loop point is ignored. +- `19xx`: set triangle linear counter. + - `00` to `7F` set the counter. + - `80` and higher halt it. +- `20xx`: set DPCM frequency. + - only works in DPCM mode. + - see table below for possible values. + +# DPCM frequency table + +val | NTSC | PAL +----|-----------|----------- + 00 | 4181.7Hz | 4177.4Hz + 01 | 4709.9Hz | 4696.6Hz + 02 | 5264.0Hz | 5261.4Hz + 03 | 5593.0Hz | 5579.2Hz + 04 | 6257.9Hz | 6023.9Hz + 05 | 7046.3Hz | 7044.9Hz + 06 | 7919.3Hz | 7917.2Hz + 07 | 8363.4Hz | 8397.0Hz + 08 | 9419.9Hz | 9446.6Hz + 09 | 11186.1Hz | 11233.8Hz + 0A | 12604.0Hz | 12595.5Hz + 0B | 13982.6Hz | 14089.9Hz + 0C | 16884.6Hz | 16965.4Hz + 0D | 21306.8Hz | 21315.5Hz + 0E | 24858.0Hz | 25191.0Hz + 0F | 33143.9Hz | 33252.1Hz # length counter table diff --git a/papers/doc/7-systems/opl.md b/papers/doc/7-systems/opl.md index 33bf627e5..b4a86e6db 100644 --- a/papers/doc/7-systems/opl.md +++ b/papers/doc/7-systems/opl.md @@ -29,7 +29,7 @@ afterwards everyone moved to Windows and software mixed PCM streaming... - only in 4-op mode (OPL3). - `16xy`: set multiplier of operator. - `x` is the operator (1-4; last 2 operators only in 4-op mode). - - `y` is the mutliplier. + - `y` is the multiplier. - 17xx: set vibrato depth. the following values are accepted: - 0: normal - 1: double diff --git a/papers/doc/7-systems/opll.md b/papers/doc/7-systems/opll.md index 0f9c0f9d8..8b16e8881 100644 --- a/papers/doc/7-systems/opll.md +++ b/papers/doc/7-systems/opll.md @@ -30,7 +30,7 @@ the YM2413 is equipped with the following features: - `13xx`: set operator 2 level. - `16xy`: set multiplier of operator. - `x` is the operator (1 or 2). - - `y` is the mutliplier. + - `y` is the multiplier. - `18xx`: toggle drums mode. - 0 disables it and 1 enables it. - only in drums chip. diff --git a/papers/doc/7-systems/opz.md b/papers/doc/7-systems/opz.md index 555df814f..507e4ab1d 100644 --- a/papers/doc/7-systems/opz.md +++ b/papers/doc/7-systems/opz.md @@ -33,7 +33,7 @@ no plans have been made for TX81Z MIDI passthrough, because: - `15xx`: set operator 4 level. - `16xy`: set multiplier of operator. - `x` is the operator (1-4). - - `y` is the mutliplier. + - `y` is the multiplier. - `17xx`: set LFO speed. - `18xx`: set LFO waveform. `xx` may be one of the following: - `00`: saw diff --git a/papers/doc/7-systems/qsound.md b/papers/doc/7-systems/qsound.md index 7089ec0e8..81bfe4b9c 100644 --- a/papers/doc/7-systems/qsound.md +++ b/papers/doc/7-systems/qsound.md @@ -8,7 +8,7 @@ because the chip lacks sample interpolation, it is recommended that you try to p the QSound chip also has a small echo buffer, somewhat similar to the SNES, although with a very basic (and non-adjustable) filter. it is however possible to adjust the feedback and length of the echo buffer (the initial values can be set in the "configure chip" option in the file menu or the chip manager). -there are also 3 ADPCM channels, however they cannot be used in Furnace yet. they have been reserved in case this feature is added later. ADPCM samples are limited to 8012 Hz. +there are also 3 ADPCM channels. ADPCM samples are fixed to 8012 Hz. # effects diff --git a/papers/doc/7-systems/sm8521.md b/papers/doc/7-systems/sm8521.md index c14c91873..e68461987 100644 --- a/papers/doc/7-systems/sm8521.md +++ b/papers/doc/7-systems/sm8521.md @@ -2,7 +2,7 @@ The SM8521 is the CPU and sound chip of the Game.com, a handheld console released in 1997 as a competitor to the infamous Nintendo Virtual Boy. -Ultimately, most of the games for the Game.com ended up being failiures in the eyes of reviewers, thus giving the Game.com a pretty bad reputation. This was one of the reasons that the Game.com only ended up selling at least 300,000 units. For these reasons and more, the Game.com ended up being discontinued in 2000. +Ultimately, most of the games for the Game.com ended up being failures in the eyes of reviewers, thus giving the Game.com a pretty bad reputation. This was one of the reasons that the Game.com only ended up selling at least 300,000 units. For these reasons and more, the Game.com ended up being discontinued in 2000. However, for its time, it was a pretty competitively priced system. The Gameboy Color was to be released in a year for $79.95, while the Game.com was released for $69.99, and its later model, the Pocket Pro, was released in mid-1999 for $29.99 due to the Game.com's apparent significant decrease in value. diff --git a/papers/doc/7-systems/sms.md b/papers/doc/7-systems/sms.md index 86e045396..37097ccdc 100644 --- a/papers/doc/7-systems/sms.md +++ b/papers/doc/7-systems/sms.md @@ -2,7 +2,7 @@ a relatively simple sound chip made by Texas Instruments. a derivative of it is used in Sega's Master System, the predecessor to Genesis. -the original iteration of the SN76489 used in the TI-99/4A computers was clocked at 447 KHz, being able to play as low as 13.670 Hz (A -1). consequentially, pitch accuracy for higher notes is compromised. +the original iteration of the SN76489 used in the TI-99/4A computers was clocked at 447 KHz, being able to play as low as 13.670 Hz (A -1). consequently, pitch accuracy for higher notes is compromised. on the other hand, the chip was clocked at a much higher speed on Master System and Genesis, which makes it rather poor in the bass range. diff --git a/papers/doc/7-systems/snes.md b/papers/doc/7-systems/snes.md index a2ecd9aee..61b30635d 100644 --- a/papers/doc/7-systems/snes.md +++ b/papers/doc/7-systems/snes.md @@ -54,6 +54,10 @@ Furnace also allows the SNES to use wavetables (and the wavetable synthesizer) i - 80 to FF for -128 to -1. - setting this to -128 is not recommended as it may cause echo output to overflow and therefore click. - `1Dxx`: set noise generator frequency (00 to 1F). +- `1Exx`: set left dry/global volume. + - this does not affect echo. +- `1Fxx`: set right dry/global volume. + - this does not affect echo. - `20xx`: set attack (0 to F). - only in ADSR envelope mode. - `21xx`: set decay (0 to 7). diff --git a/papers/doc/7-systems/ym2151.md b/papers/doc/7-systems/ym2151.md index 1923a18ad..cef663c8d 100644 --- a/papers/doc/7-systems/ym2151.md +++ b/papers/doc/7-systems/ym2151.md @@ -16,7 +16,7 @@ in most arcade boards the chip was used in combination with a PCM chip, like [Se - `15xx`: set operator 4 level. - `16xy`: set multiplier of operator. - `x` is the operator (1-4). - - `y` is the mutliplier. + - `y` is the multiplier. - `17xx`: set LFO speed. - `18xx`: set LFO waveform. `xx` may be one of the following: - `00`: saw diff --git a/papers/doc/7-systems/ym2203.md b/papers/doc/7-systems/ym2203.md index 2c1f53b40..b9f76b8ec 100644 --- a/papers/doc/7-systems/ym2203.md +++ b/papers/doc/7-systems/ym2203.md @@ -18,7 +18,7 @@ several variants of this chip were released as well, with more features. - `15xx`: set operator 4 level. - `16xy`: set multiplier of operator. - `x` is the operator (1-4). - - `y` is the mutliplier. + - `y` is the multiplier. - `18xx`: toggle extended channel 3 mode. - 0 disables it and 1 enables it. - only in extended channel 3 chip. diff --git a/papers/doc/7-systems/ym2608.md b/papers/doc/7-systems/ym2608.md index 1581189ef..5eda41c1e 100644 --- a/papers/doc/7-systems/ym2608.md +++ b/papers/doc/7-systems/ym2608.md @@ -18,7 +18,7 @@ the YM2610 (OPNB) and YM2610B chips are very similar to this one, but the built- - `15xx`: set operator 4 level. - `16xy`: set multiplier of operator. - `x` is the operator (1-4). - - `y` is the mutliplier. + - `y` is the multiplier. - `18xx`: toggle extended channel 3 mode. - 0 disables it and 1 enables it. - only in extended channel 3 chip. diff --git a/papers/doc/7-systems/ym2610.md b/papers/doc/7-systems/ym2610.md index 22df25d87..a18888e60 100644 --- a/papers/doc/7-systems/ym2610.md +++ b/papers/doc/7-systems/ym2610.md @@ -16,7 +16,7 @@ its soundchip is a 4-in-1: 4ch 4-op FM, YM2149 (AY-3-8910 clone) and 2 different - `15xx`: set operator 4 level. - `16xy`: set multiplier of operator. - `x` is the operator (1-4). - - `y` is the mutliplier. + - `y` is the multiplier. - `18xx`: toggle extended channel 2 mode. - 0 disables it and 1 enables it. - only in extended channel 2 chip. diff --git a/papers/doc/7-systems/ym2610b.md b/papers/doc/7-systems/ym2610b.md index 241366f44..f20e82ed5 100644 --- a/papers/doc/7-systems/ym2610b.md +++ b/papers/doc/7-systems/ym2610b.md @@ -15,7 +15,7 @@ it is backward compatible with the original chip. - `15xx`: set operator 4 level. - `16xy`: set multiplier of operator. - `x` is the operator (1-4). - - `y` is the mutliplier. + - `y` is the multiplier. - `18xx`: toggle extended channel 3 mode. - 0 disables it and 1 enables it. - only in extended channel 3 chip. diff --git a/papers/doc/7-systems/ym2612.md b/papers/doc/7-systems/ym2612.md index ac17cedc3..1eeb148e2 100644 --- a/papers/doc/7-systems/ym2612.md +++ b/papers/doc/7-systems/ym2612.md @@ -14,7 +14,7 @@ one of two chips that powered the Sega Genesis. It is a six-channel, four-operat - `15xx`: set operator 4 level. - `16xy`: set multiplier of operator. - `x` is the operator (1-4). - - `y` is the mutliplier. + - `y` is the multiplier. - `17xx`: enable PCM channel. - this only works on channel 6. - `18xx`: toggle extended channel 3 mode. diff --git a/papers/doc/README.md b/papers/doc/README.md index 94e9f0b9d..d8a14be24 100644 --- a/papers/doc/README.md +++ b/papers/doc/README.md @@ -21,6 +21,7 @@ writers: - cam900 - host12prog - WindowxDeveloper +- polluks other: diff --git a/papers/format.md b/papers/format.md index a2c42f04d..248a98d05 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,6 +32,12 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: +- 158: Furnace 0.6pre5 +- 157: Furnace dev157 +- 156: Furnace dev156 +- 155: Furnace dev155 +- 154: Furnace dev154 +- 153: Furnace dev153 - 152: Furnace dev152 - 151: Furnace dev151 - 150: Furnace dev150 @@ -422,7 +428,8 @@ size | description 1 | automatic patchbay (>=136) --- | **a couple more compat flags** (>=138) 1 | broken portamento during legato - 7 | reserved + 1 | broken macro during note off in some FM chips (>=155) + 6 | reserved --- | **speed pattern of first song** (>=139) 1 | length of speed pattern (fail if this is lower than 0 or higher than 16) 16 | speed pattern (this overrides speed 1 and speed 2 settings) @@ -431,12 +438,16 @@ size | description ??? | groove entries. the format is: | - 1 byte: length of groove | - 16 bytes: groove pattern + --- | **pointers to asset directories** (>=156) + 4 | instrument directories + 4 | wavetable directories + 4 | sample directories ``` # patchbay Furnace dev135 adds a "patchbay" which allows for arbitrary connection of chip outputs to system outputs. -it eventually will allow connecting outputs to effects and so on. +it also allows connecting outputs to effects and so on. a connection is represented as an unsigned int in the following format: @@ -522,6 +533,22 @@ clock=4000000 stereo=true ``` +# asset directories (>=156) + +also known as "folder" in the user interface. + +``` +size | description +-----|------------------------------------ + 4 | "ADIR" block ID + 4 | size of this block + 4 | number of directories + --- | **asset directory** (×numberOfDirs) + STR | name (if empty, this is the uncategorized directory) + 2 | number of assets + 1?? | assets in this directory +``` + # instrument (>=127) Furnace dev127 and higher use the new instrument format. @@ -1253,7 +1280,58 @@ size | description | - version>=58 size is length ``` -# pattern +# pattern (>=157) + +``` +size | description +-----|------------------------------------ + 4 | "PATN" block ID + 4 | size of this block + 1 | subsong + 1 | channel + 2 | pattern index + STR | pattern name (>=51) + ??? | pattern data + | - read a byte per row. + | - if it is 0xff, end of pattern. + | - if bit 7 is set, then read bit 0-6 as "skip N+2 rows". + | - if bit 7 is clear, then: + | - bit 0: note present + | - bit 1: ins present + | - bit 2: volume present + | - bit 3: effect 0 present + | - bit 4: effect value 0 present + | - bit 5: other effects (0-3) present + | - bit 6: other effects (4-7) present + | - if bit 5 is set, read another byte: + | - bit 0: effect 0 present + | - bit 1: effect value 0 present + | - bit 2: effect 1 present + | - bit 3: effect value 1 present + | - bit 4: effect 2 present + | - bit 5: effect value 2 present + | - bit 6: effect 3 present + | - bit 7: effect value 3 present + | - if bit 6 is set, read another byte: + | - bit 0: effect 4 present + | - bit 1: effect value 4 present + | - bit 2: effect 5 present + | - bit 3: effect value 5 present + | - bit 4: effect 6 present + | - bit 5: effect value 6 present + | - bit 6: effect 7 present + | - bit 7: effect value 7 present + | - then read note, ins, volume, effects and effect values depending on what is present. + | - for note: + | - 0 is C-(-5) + | - 179 is B-9 + | - 180 is note off + | - 181 is note release + | - 182 is macro release +``` + + +# old pattern (<157) ``` size | description @@ -1283,8 +1361,8 @@ size | description | - 12: C (of next octave) | - this is actually a leftover of the .dmf format. | - 100: note off - | - 100: note release - | - 100: macro release + | - 101: note release + | - 102: macro release | - octave | - this is an signed char stored in a short. | - therefore octave value 255 is actually octave -1. diff --git a/papers/screenshot3.png b/papers/screenshot3.png new file mode 100644 index 000000000..da5983669 Binary files /dev/null and b/papers/screenshot3.png differ diff --git a/res/Info.plist b/res/Info.plist index 39a9ac844..026051de2 100644 --- a/res/Info.plist +++ b/res/Info.plist @@ -15,17 +15,17 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString - 0.6pre4 + 0.6pre5 CFBundleName Furnace CFBundlePackageType APPL CFBundleShortVersionString - 0.6pre4 + 0.6pre5 CFBundleSignature ???? CFBundleVersion - 0.6pre4 + 0.6pre5 NSHumanReadableCopyright NSHighResolutionCapable diff --git a/res/furnace.appdata.xml b/res/furnace.appdata.xml index 2214cb10d..5271379ab 100644 --- a/res/furnace.appdata.xml +++ b/res/furnace.appdata.xml @@ -19,6 +19,11 @@ it also offers DefleMask compatibility, allowing you to import your songs and even export them back for interoperability.

+ + + intense + mild + furnace.desktop diff --git a/scripts/release-linux-AppImage.sh b/scripts/release-linux-AppImage.sh new file mode 100755 index 000000000..7c20476f9 --- /dev/null +++ b/scripts/release-linux-AppImage.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# make linux release +# run on an Ubuntu 16.04 machine or VM for best results. + +if [ ! -e /tmp/furnace ]; then + ln -s "$PWD" /tmp/furnace || exit 1 +fi + +cd /tmp/furnace + +if [ ! -e linuxbuild ]; then + mkdir linuxbuild || exit 1 +fi + +cd linuxbuild + +cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Werror" -DWITH_DEMOS=OFF -DWITH_INSTRUMENTS=OFF -DWITH_WAVETABLES=OFF .. || exit 1 +make -j4 || exit 1 + +cd .. + +mkdir -p release/linux/furnace.AppDir || exit 1 +cd linuxbuild + +make DESTDIR=/tmp/furnace/release/linux/furnace.AppDir install || exit 1 + +cd ../release/linux/furnace.AppDir + +cp -v ../../../res/logo.png furnace.png || exit 1 +ln -s furnace.png .DirIcon || exit 1 +cp -v ../../../res/furnace.desktop . || exit 1 +#mkdir -p usr/share/metainfo || exit 1 +cp -v ../../../res/furnace.appdata.xml usr/share/metainfo/org.tildearrow.furnace.metainfo.xml || exit 1 +rm usr/share/metainfo/furnace.appdata.xml || exit 1 +cp -v ../../../res/AppRun . || exit 1 + +#cp /usr/lib/libm.so.6 usr/lib/ || exit 1 +#cp /usr/lib/libstdc++.so.6 usr/lib/ || exit 1 +#cp /usr/lib/libc.so.6 usr/lib/ || exit 1 +#cp /usr/lib/libgcc_s.so.1 usr/lib/ || exit 1 + +cd usr/bin +strip -s furnace + +cd ../../.. + +[ -e appimagetool-x86_64.AppImage ] || { wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" && chmod 755 appimagetool-x86_64.AppImage; } +./appimagetool-x86_64.AppImage --appimage-extract +ARCH=$(uname -m) ./squashfs-root/AppRun furnace.AppDir + +#zip -r furnace.zip LICENSE.txt Furnace*.dmg README.txt papers diff --git a/scripts/release-linux.sh b/scripts/release-linux.sh index ecd033140..570712f06 100755 --- a/scripts/release-linux.sh +++ b/scripts/release-linux.sh @@ -19,32 +19,42 @@ make -j4 || exit 1 cd .. -mkdir -p release/linux/furnace.AppDir || exit 1 +mkdir -p release/linux/furnace || exit 1 cd linuxbuild -make DESTDIR=/tmp/furnace/release/linux/furnace.AppDir install || exit 1 +make DESTDIR=/tmp/furnace/release/linux/furnace install || exit 1 -cd ../release/linux/furnace.AppDir +cd ../release/linux/furnace -cp -v ../../../res/logo.png furnace.png || exit 1 -ln -s furnace.png .DirIcon || exit 1 -cp -v ../../../res/furnace.desktop . || exit 1 -#mkdir -p usr/share/metainfo || exit 1 -cp -v ../../../res/furnace.appdata.xml usr/share/metainfo/org.tildearrow.furnace.metainfo.xml || exit 1 -cp -v ../../../res/AppRun . || exit 1 +cp -v ../../../res/logo.png .DirIcon || exit 1 +#cp -v ../../../res/furnace.desktop . || exit 1 -#cp /usr/lib/libm.so.6 usr/lib/ || exit 1 -#cp /usr/lib/libstdc++.so.6 usr/lib/ || exit 1 -#cp /usr/lib/libc.so.6 usr/lib/ || exit 1 -#cp /usr/lib/libgcc_s.so.1 usr/lib/ || exit 1 +cd usr + +mv bin/furnace .. || exit 1 +rmdir bin || exit 1 + +rm -r share/applications +rm -r share/doc +mv share/icons .. +rm -r share/licenses +rm -r share/metainfo + +mv share/furnace/demos .. +mv share/furnace/instruments .. +mv share/furnace/wavetables .. +rmdir share/furnace || exit 1 +rmdir share || exit 1 + +cd .. + +cp ../../../LICENSE . || exit 1 +cp ../../../README.md . || exit 1 +cp -r ../../../papers papers || exit 1 +rmdir usr || exit 1 -cd usr/bin strip -s furnace -cd ../../.. +cd .. -[ -e appimagetool-x86_64.AppImage ] || { wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" && chmod 755 appimagetool-x86_64.AppImage; } -./appimagetool-x86_64.AppImage --appimage-extract -ARCH=$(uname -m) ./squashfs-root/AppRun furnace.AppDir - -#zip -r furnace.zip LICENSE.txt Furnace*.dmg README.txt papers +tar -zcv -f furnace.tar.gz furnace diff --git a/scripts/release-win32.sh b/scripts/release-win32.sh index 9faf9c2fa..df603dbab 100755 --- a/scripts/release-win32.sh +++ b/scripts/release-win32.sh @@ -17,7 +17,6 @@ cd win32build # TODO: potential Arch-ism? i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" -DBUILD_SHARED_LIBS=OFF -DSUPPORT_XP=ON .. || exit 1 make -j8 || exit 1 -#i686-w64-mingw32-strip -s furnace.exe || exit 1 cd .. @@ -32,6 +31,8 @@ cp -r ../../demos demos || exit 1 cp -r ../../instruments instruments || exit 1 cp -r ../../wavetables wavetables || exit 1 +i686-w64-mingw32-strip -s furnace.exe || exit 1 + zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos instruments wavetables furName=$(git describe --tags | sed "s/v0/0/") diff --git a/scripts/release-win64.sh b/scripts/release-win64.sh index b349e571f..e1678eda1 100755 --- a/scripts/release-win64.sh +++ b/scripts/release-win64.sh @@ -17,7 +17,6 @@ cd winbuild # TODO: potential Arch-ism? x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Wno-deprecated-declarations -Werror" .. || exit 1 make -j8 || exit 1 -#x86_64-w64-mingw32-strip -s furnace.exe || exit 1 cd .. @@ -32,6 +31,8 @@ cp -r ../../demos demos || exit 1 cp -r ../../instruments instruments || exit 1 cp -r ../../wavetables wavetables || exit 1 +x86_64-w64-mingw32-strip -s furnace.exe || exit 1 + zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos instruments wavetables furName=$(git describe --tags | sed "s/v0/0/") diff --git a/src/engine/config.cpp b/src/engine/config.cpp index 137dbdaff..6d90a49f1 100644 --- a/src/engine/config.cpp +++ b/src/engine/config.cpp @@ -148,9 +148,13 @@ bool DivConfig::loadFromFile(const char* path, bool createOnFail, bool redundanc } for (size_t j=0; j alwaysSetVol DIV_CMD_MAX diff --git a/src/engine/effect.h b/src/engine/effect.h new file mode 100644 index 000000000..5c735d31d --- /dev/null +++ b/src/engine/effect.h @@ -0,0 +1,189 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 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 _EFFECT_H +#define _EFFECT_H + +#include +#include "../ta-utils.h" + +class DivEngine; + +class DivEffect { + protected: + DivEngine* parent; + public: + /** + * fill a buffer with sound data. + * @param in pointers to input buffers. + * @param out pointers to output buffers. + * @param len the amount of samples in input and output. + */ + virtual void acquire(float** in, float** out, size_t len); + + /** + * reset the state of this effect. + */ + virtual void reset(); + + /** + * get the number of inputs this effect requests. + * @return number of inputs. SHALL NOT be less than zero. + */ + virtual int getInputCount(); + + /** + * get the number of outputs this effect provides. + * @return number of outputs. SHALL NOT be less than one. + */ + virtual int getOutputCount(); + + /** + * called when the sample rate changes. + * @param rate the new sample rate. + */ + virtual void rateChanged(double rate); + + /** + * get the value of a parameter. + * @param param parameter ID. + * @return a String with the value. + * @throws std::out_of_range if the parameter ID is out of range. + */ + virtual String getParam(size_t param); + + /** + * set the value of a parameter. + * @param param parameter ID. + * @param value the value. + * @return whether the parameter was set successfully. + */ + virtual bool setParam(size_t param, String value); + + /** + * get a list of parameters. + * @return a C string with a list of parameters, or NULL if there are none. + * PARAMETER TYPES + * + * Parameter + * id:type:name:description:[...] + * type may be one of the following: + * - s: string + * - i: integer + * - I: integer slider + * - r: integer list (radio button) + * - R: integer list (combo box) + * - h: integer hex + * - f: float + * - F: float slider + * - d: double + * - D: double slider + * - b: boolean (checkbox) + * - t: boolean (toggle button) + * - x: X/Y integer + * - X: X/Y float + * - c: color (RGB) + * - C: color (RGBA) + * - B: button + * + * SameLine + * !s + * + * Separator + * --- + * + * Indent/Unindent + * > Indent + * < Unindent + * + * TreeNode + * >> Begin + * << End + * + * Tabs + * >TABBAR BeginTabBar + * >TAB:name Begin + * + +void DivEffect::acquire(float** in, float** out, size_t len) { +} + +void DivEffect::reset() { +} + +int DivEffect::getInputCount() { + return 0; +} + +int DivEffect::getOutputCount() { + return 0; +} + +void DivEffect::rateChanged(double rate) { +} + +String DivEffect::getParam(size_t param) { + throw std::out_of_range("param"); + + // unreachable + return ""; +} + +bool DivEffect::setParam(size_t param, String value) { + return false; +} + +const char* DivEffect::getParams() { + return NULL; +} + +size_t DivEffect::getParamCount() { + return 0; +} + +String DivEffect::getDynamicText(size_t id) { + throw std::out_of_range("param"); + + // unreachable + return ""; +} + +bool DivEffect::load(unsigned short version, const unsigned char* data, size_t len) { + return false; +} + +unsigned char* DivEffect::save(unsigned short* version, size_t* len) { + *len=0; + *version=0; + return NULL; +} + +bool DivEffect::init(DivEngine* parent, double rate, unsigned short version, const unsigned char* data, size_t len) { + return false; +} + +void DivEffect::quit() { +} + +DivEffect::~DivEffect() { +} \ No newline at end of file diff --git a/src/engine/effect/dummy.cpp b/src/engine/effect/dummy.cpp new file mode 100644 index 000000000..13df4f3f5 --- /dev/null +++ b/src/engine/effect/dummy.cpp @@ -0,0 +1,60 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 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 "dummy.h" + +void DivEffectDummy::acquire(float** in, float** out, size_t len) { + memcpy(out[0],in[0],len*sizeof(float)); +} + +void DivEffectDummy::reset() { +} + +int DivEffectDummy::getInputCount() { + return 1; +} + +int DivEffectDummy::getOutputCount() { + return 1; +} + +const char* DivEffectDummy::getParams() { + return NULL; +} + +size_t DivEffectDummy::getParamCount() { + return 0; +} + +bool DivEffectDummy::load(unsigned short version, const unsigned char* data, size_t len) { + return true; +} + +unsigned char* DivEffectDummy::save(unsigned short* version, size_t* len) { + *len=0; + *version=0; + return NULL; +} + +bool DivEffectDummy::init(DivEngine* parent, double rate, unsigned short version, const unsigned char* data, size_t len) { + return false; +} + +void DivEffectDummy::quit() { +} \ No newline at end of file diff --git a/src/engine/effect/dummy.h b/src/engine/effect/dummy.h new file mode 100644 index 000000000..c8a42919d --- /dev/null +++ b/src/engine/effect/dummy.h @@ -0,0 +1,34 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 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 "../effect.h" + +class DivEffectDummy: public DivEffect { + public: + void acquire(float** in, float** out, size_t len); + void reset(); + int getInputCount(); + int getOutputCount(); + const char* getParams(); + size_t getParamCount(); + bool load(unsigned short version, const unsigned char* data, size_t len); + unsigned char* save(unsigned short* version, size_t* len); + bool init(DivEngine* parent, double rate, unsigned short version, const unsigned char* data, size_t len); + void quit(); +}; \ No newline at end of file diff --git a/src/engine/effectContainer.cpp b/src/engine/effectContainer.cpp new file mode 100644 index 000000000..d51ddfe57 --- /dev/null +++ b/src/engine/effectContainer.cpp @@ -0,0 +1,95 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 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 "engine.h" +#include "effect/dummy.h" + +void DivEffectContainer::preAcquire(size_t count) { + if (!count) return; + + int inCount=effect->getInputCount(); + + if (inLengetOutputCount(); + + if (outLenacquire(in,out,count); +} + +bool DivEffectContainer::init(DivEffectType effectType, DivEngine* eng, double rate, unsigned short version, const unsigned char* data, size_t len) { + switch (effectType) { + case DIV_EFFECT_DUMMY: + default: + effect=new DivEffectDummy; + } + return effect->init(eng,rate,version,data,len); +} + +void DivEffectContainer::quit() { + effect->quit(); + delete effect; + effect=NULL; + + for (int i=0; i& dir, int before, int after) { + if (before<0 || after<0) return; + for (DivAssetDir& i: dir) { + for (size_t j=0; j& dir, int entry) { + if (entry<0) return; + for (DivAssetDir& i: dir) { + for (size_t j=0; jentry) { + i.entries[j]--; + } + } + } +} + void DivEngine::checkAssetDir(std::vector& dir, size_t entries) { bool* inAssetDir=new bool[entries]; memset(inAssetDir,0,entries*sizeof(bool)); @@ -1580,9 +1613,16 @@ void DivEngine::checkAssetDir(std::vector& dir, size_t entries) { j--; continue; } + + // erase duplicate entry + if (inAssetDir[i.entries[j]]) { + i.entries.erase(i.entries.begin()+j); + j--; + continue; + } // mark entry as present - inAssetDir[j]=true; + inAssetDir[i.entries[j]]=true; } } @@ -1595,15 +1635,14 @@ void DivEngine::checkAssetDir(std::vector& dir, size_t entries) { } } - // create unsorted directory if it doesn't exist - if (unsortedDir==NULL) { - dir.push_back(DivAssetDir("")); - unsortedDir=&(*dir.rbegin()); - } - // add missing items to unsorted directory for (size_t i=0; ientries.push_back(i); } } @@ -2030,11 +2069,17 @@ String DivEngine::getPlaybackDebugInfo() { "divider: %f\n" "cycles: %d\n" "clockDrift: %f\n" + "midiClockCycles: %d\n" + "midiClockDrift: %f\n" + "midiTimeCycles: %d\n" + "midiTimeDrift: %f\n" "changeOrd: %d\n" "changePos: %d\n" "totalSeconds: %d\n" "totalTicks: %d\n" "totalTicksR: %d\n" + "curMidiClock: %d\n" + "curMidiTime: %d\n" "totalCmds: %d\n" "lastCmds: %d\n" "cmdsPerSecond: %d\n" @@ -2044,7 +2089,8 @@ String DivEngine::getPlaybackDebugInfo() { "totalProcessed: %d\n" "bufferPos: %d\n", curOrder,prevOrder,curRow,prevRow,ticks,subticks,totalLoops,lastLoopPos,nextSpeed,divider,cycles,clockDrift, - changeOrd,changePos,totalSeconds,totalTicks,totalTicksR,totalCmds,lastCmds,cmdsPerSecond,globalPitch, + midiClockCycles,midiClockDrift,midiTimeCycles,midiTimeDrift,changeOrd,changePos,totalSeconds,totalTicks, + totalTicksR,curMidiClock,curMidiTime,totalCmds,lastCmds,cmdsPerSecond,globalPitch, (int)extValue,(int)tempoAccum,(int)totalProcessed,(int)bufferPos ); } @@ -2161,16 +2207,27 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { prevOrder=0; prevRow=0; stepPlay=0; - int prevDrift; + if (curSubSong!=NULL) curSubSong->arpLen=1; + int prevDrift, prevMidiClockDrift, prevMidiTimeDrift; prevDrift=clockDrift; + prevMidiClockDrift=midiClockDrift; + prevMidiTimeDrift=midiTimeDrift; clockDrift=0; cycles=0; + midiClockCycles=0; + midiClockDrift=0; + midiTimeCycles=0; + midiTimeDrift=0; if (!preserveDrift) { ticks=1; tempoAccum=0; totalTicks=0; totalSeconds=0; totalTicksR=0; + curMidiClock=0; + curMidiTime=0; + curMidiTimeCode=0; + curMidiTimePiece=0; totalLoops=0; lastLoopPos=-1; } @@ -2187,6 +2244,10 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { skipping=false; return; } + if (!preserveDrift) { + runMidiClock(cycles); + runMidiTime(cycles); + } } int oldOrder=curOrder; while (playing && (curRow1)) { @@ -2194,6 +2255,10 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { skipping=false; return; } + if (!preserveDrift) { + runMidiClock(cycles); + runMidiTime(cycles); + } if (oldOrder!=curOrder) break; if (ticks-((tempoAccum+curSubSong->virtualTempoN)/MAX(1,curSubSong->virtualTempoD))<1 && curRow>=goalRow) break; } @@ -2207,9 +2272,22 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { repeatPattern=oldRepeatPattern; if (preserveDrift) { clockDrift=prevDrift; + midiClockDrift=prevMidiClockDrift; + midiTimeDrift=prevMidiTimeDrift; } else { clockDrift=0; cycles=0; + midiClockCycles=0; + midiClockDrift=0; + midiTimeCycles=0; + midiTimeDrift=0; + if (curMidiTime>0) { + curMidiTime--; + } + if (curMidiClock>0) { + curMidiClock--; + } + curMidiTimePiece=0; } if (!preserveDrift) { ticks=1; @@ -2378,9 +2456,74 @@ void DivEngine::play() { for (int i=0; imidiOut!=NULL) { - int pos=totalTicksR/6; - output->midiOut->send(TAMidiMessage(TA_MIDI_POSITION,(pos>>7)&0x7f,pos&0x7f)); + if (midiOutClock) { + output->midiOut->send(TAMidiMessage(TA_MIDI_POSITION,(curMidiClock>>7)&0x7f,curMidiClock&0x7f)); + } + if (midiOutTime) { + TAMidiMessage msg; + msg.type=TA_MIDI_SYSEX; + msg.sysExData.reset(new unsigned char[10],std::default_delete()); + msg.sysExLen=10; + unsigned char* msgData=msg.sysExData.get(); + int actualTime=curMidiTime; + int timeRate=midiOutTimeRate; + int drop=0; + if (timeRate<1 || timeRate>4) { + if (curSubSong->hz>=47.98 && curSubSong->hz<=48.02) { + timeRate=1; + } else if (curSubSong->hz>=49.98 && curSubSong->hz<=50.02) { + timeRate=2; + } else if (curSubSong->hz>=59.9 && curSubSong->hz<=60.11) { + timeRate=4; + } else { + timeRate=4; + } + } + + switch (timeRate) { + case 1: // 24 + msgData[5]=(actualTime/(60*60*24))%24; + msgData[6]=(actualTime/(60*24))%60; + msgData[7]=(actualTime/24)%60; + msgData[8]=actualTime%24; + break; + case 2: // 25 + msgData[5]=(actualTime/(60*60*25))%24; + msgData[6]=(actualTime/(60*25))%60; + msgData[7]=(actualTime/25)%60; + msgData[8]=actualTime%25; + break; + case 3: // 29.97 (NTSC drop) + // drop + drop=((actualTime/(30*60))-(actualTime/(30*600)))*2; + actualTime+=drop; + + msgData[5]=(actualTime/(60*60*30))%24; + msgData[6]=(actualTime/(60*30))%60; + msgData[7]=(actualTime/30)%60; + msgData[8]=actualTime%30; + break; + case 4: // 30 (NTSC non-drop) + default: + msgData[5]=(actualTime/(60*60*30))%24; + msgData[6]=(actualTime/(60*30))%60; + msgData[7]=(actualTime/30)%60; + msgData[8]=actualTime%30; + break; + } + + msgData[5]|=(timeRate-1)<<5; + + msgData[0]=0xf0; + msgData[1]=0x7f; + msgData[2]=0x7f; + msgData[3]=0x01; + msgData[4]=0x01; + msgData[9]=0xf7; + output->midiOut->send(msg); + } output->midiOut->send(TAMidiMessage(TA_MIDI_MACHINE_PLAY,0,0)); } BUSY_END; @@ -2419,6 +2562,13 @@ void DivEngine::stepOne(int row) { void DivEngine::stop() { BUSY_BEGIN; freelance=false; + if (!playing) { + //Send midi panic + if (output) if (output->midiOut!=NULL) { + output->midiOut->send(TAMidiMessage(TA_MIDI_CONTROL,0x7B,0)); + logV("Midi panic sent"); + } + } playing=false; extValuePresent=false; endOfSong=false; // what? @@ -2668,10 +2818,17 @@ void DivEngine::previewSampleNoLock(int sample, int note, int pStart, int pEnd) if (rate<=0) rate=song.sample[sample]->centerRate; } if (rate<100) rate=100; + double rateOrig=rate; + sPreview.rateMul=1; + while (sPreview.rateMul<0x40000000 && rate=0)?sPreview.pBegin:0; + sPreview.posSub=0; sPreview.sample=sample; sPreview.wave=-1; sPreview.dir=false; @@ -2696,10 +2853,17 @@ void DivEngine::previewWaveNoLock(int wave, int note) { blip_clear(samp_bb); double rate=song.wave[wave]->len*((song.tuning*0.0625)*pow(2.0,(double)(note+3)/12.0)); if (rate<100) rate=100; + double rateOrig=rate; + sPreview.rateMul=1; + while (sPreview.rateMul<0x40000000 && rate256) { throw EndOfFileException(&reader,reader.size()); } + wave->len=len; wave->max=(unsigned char)reader.readC(); if (wave->max==255) { // new wavetable format unsigned char waveVersion=reader.readC(); logI("reading modern .dmw..."); logD("wave version %d",waveVersion); - wave->max=reader.readC(); + wave->max=(unsigned char)reader.readC(); for (int i=0; idata[i]=reader.readI(); } @@ -3138,6 +3310,8 @@ void DivEngine::delWave(int index) { delete song.wave[index]; song.wave.erase(song.wave.begin()+index); song.waveLen=song.wave.size(); + removeAsset(song.waveDir,index); + checkAssetDir(song.waveDir,song.wave.size()); } saveLock.unlock(); BUSY_END; @@ -3158,6 +3332,7 @@ int DivEngine::addSample() { sPreview.sample=-1; sPreview.pos=0; sPreview.dir=false; + checkAssetDir(song.sampleDir,song.sample.size()); saveLock.unlock(); renderSamples(); BUSY_END; @@ -3175,6 +3350,7 @@ int DivEngine::addSamplePtr(DivSample* which) { saveLock.lock(); song.sample.push_back(which); song.sampleLen=sampleCount+1; + checkAssetDir(song.sampleDir,song.sample.size()); saveLock.unlock(); renderSamples(); BUSY_END; @@ -3644,6 +3820,8 @@ void DivEngine::delSample(int index) { delete song.sample[index]; song.sample.erase(song.sample.begin()+index); song.sampleLen=song.sample.size(); + removeAsset(song.sampleDir,index); + checkAssetDir(song.sampleDir,song.sample.size()); renderSamples(); } saveLock.unlock(); @@ -3842,6 +4020,7 @@ bool DivEngine::moveInsUp(int which) { saveLock.lock(); song.ins[which]=song.ins[which-1]; song.ins[which-1]=prev; + moveAsset(song.insDir,which,which-1); exchangeIns(which,which-1); saveLock.unlock(); BUSY_END; @@ -3855,6 +4034,7 @@ bool DivEngine::moveWaveUp(int which) { saveLock.lock(); song.wave[which]=song.wave[which-1]; song.wave[which-1]=prev; + moveAsset(song.waveDir,which,which-1); saveLock.unlock(); BUSY_END; return true; @@ -3870,6 +4050,7 @@ bool DivEngine::moveSampleUp(int which) { saveLock.lock(); song.sample[which]=song.sample[which-1]; song.sample[which-1]=prev; + moveAsset(song.sampleDir,which,which-1); saveLock.unlock(); renderSamples(); BUSY_END; @@ -3884,6 +4065,7 @@ bool DivEngine::moveInsDown(int which) { song.ins[which]=song.ins[which+1]; song.ins[which+1]=prev; exchangeIns(which,which+1); + moveAsset(song.insDir,which,which+1); saveLock.unlock(); BUSY_END; return true; @@ -3896,6 +4078,7 @@ bool DivEngine::moveWaveDown(int which) { saveLock.lock(); song.wave[which]=song.wave[which+1]; song.wave[which+1]=prev; + moveAsset(song.waveDir,which,which+1); saveLock.unlock(); BUSY_END; return true; @@ -3911,6 +4094,7 @@ bool DivEngine::moveSampleDown(int which) { saveLock.lock(); song.sample[which]=song.sample[which+1]; song.sample[which+1]=prev; + moveAsset(song.sampleDir,which,which+1); saveLock.unlock(); renderSamples(); BUSY_END; @@ -3955,6 +4139,10 @@ void DivEngine::autoPatchbayP() { BUSY_END; } +void DivEngine::recalcPatchbay() { + +} + bool DivEngine::patchConnect(unsigned int src, unsigned int dest) { unsigned int armed=(src<<16)|(dest&0xffff); for (unsigned int i: song.patchbay) { @@ -4340,6 +4528,10 @@ void DivEngine::quitDispatch() { } cycles=0; clockDrift=0; + midiClockCycles=0; + midiClockDrift=0; + midiTimeCycles=0; + midiTimeDrift=0; chans=0; playing=false; curSpeed=0; @@ -4356,6 +4548,10 @@ void DivEngine::quitDispatch() { totalTicks=0; totalSeconds=0; totalTicksR=0; + curMidiClock=0; + curMidiTime=0; + curMidiTimeCode=0; + curMidiTimePiece=0; totalCmds=0; lastCmds=0; cmdsPerSecond=0; @@ -4382,6 +4578,8 @@ bool DivEngine::initAudioBackend() { lowLatency=getConfInt("lowLatency",0); metroVol=(float)(getConfInt("metroVol",100))/100.0f; midiOutClock=getConfInt("midiOutClock",0); + midiOutTime=getConfInt("midiOutTime",0); + midiOutTimeRate=getConfInt("midiOutTimeRate",0); midiOutProgramChange = getConfInt("midiOutProgramChange",0); midiOutMode=getConfInt("midiOutMode",DIV_MIDI_MODE_NOTE); if (metroVol<0.0f) metroVol=0.0f; diff --git a/src/engine/engine.h b/src/engine/engine.h index 85da26ea9..bf842aaeb 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -23,6 +23,7 @@ #include "instrument.h" #include "song.h" #include "dispatch.h" +#include "effect.h" #include "export.h" #include "dataErrors.h" #include "safeWriter.h" @@ -53,8 +54,8 @@ #define EXTERN_BUSY_BEGIN_SOFT e->softLocked=true; e->isBusy.lock(); #define EXTERN_BUSY_END e->isBusy.unlock(); e->softLocked=false; -#define DIV_VERSION "dev152" -#define DIV_ENGINE_VERSION 152 +#define DIV_VERSION "0.6pre5" +#define DIV_ENGINE_VERSION 158 // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 @@ -105,7 +106,7 @@ struct DivChannelState { int delayOrder, delayRow, retrigSpeed, retrigTick; int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoDir, vibratoFine; int tremoloDepth, tremoloRate, tremoloPos; - unsigned char arp, arpStage, arpTicks, panL, panR, panRL, panRR; + unsigned char arp, arpStage, arpTicks, panL, panR, panRL, panRR, lastVibrato, lastPorta; bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff; bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, wasShorthandPorta, noteOnInhibit, resetArp; bool wentThroughNote, goneThroughNote; @@ -146,6 +147,8 @@ struct DivChannelState { panR(255), panRL(0), panRR(0), + lastVibrato(0), + lastPorta(0), doNote(false), legato(false), portaStop(false), @@ -220,6 +223,25 @@ struct DivDispatchContainer { } }; +struct DivEffectContainer { + DivEffect* effect; + float* in[DIV_MAX_OUTPUTS]; + float* out[DIV_MAX_OUTPUTS]; + size_t inLen, outLen; + + void preAcquire(size_t count); + void acquire(size_t count); + bool init(DivEffectType effectType, DivEngine* eng, double rate, unsigned short version, const unsigned char* data, size_t len); + void quit(); + DivEffectContainer(): + effect(NULL), + inLen(0), + outLen(0) { + memset(in,0,DIV_MAX_OUTPUTS*sizeof(float*)); + memset(out,0,DIV_MAX_OUTPUTS*sizeof(float*)); + } +}; + typedef int EffectValConversion(unsigned char,unsigned char); struct EffectHandler { @@ -367,8 +389,10 @@ class DivEngine { bool systemsRegistered; bool hasLoadedSomething; bool midiOutClock; + bool midiOutTime; bool midiOutProgramChange; int midiOutMode; + int midiOutTimeRate; int softLockCount; int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed, elapsedBars, elapsedBeats, curSpeed; size_t curSubSongIndex; @@ -376,8 +400,13 @@ class DivEngine { double divider; int cycles; double clockDrift; + int midiClockCycles; + double midiClockDrift; + int midiTimeCycles; + double midiTimeDrift; int stepPlay; - int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, totalCmds, lastCmds, cmdsPerSecond, globalPitch; + int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, curMidiClock, curMidiTime, totalCmds, lastCmds, cmdsPerSecond, globalPitch; + int curMidiTimePiece, curMidiTimeCode; unsigned char extValue, pendingMetroTick; DivGroovePattern speeds; short tempoAccum; @@ -402,6 +431,7 @@ class DivEngine { std::vector midiOuts; std::vector cmdStream; std::vector possibleInsTypes; + std::vector effectInst; static DivSysDef* sysDefs[DIV_MAX_CHIP_DEFS]; static DivSystem sysFileMapFur[DIV_MAX_CHIP_DEFS]; static DivSystem sysFileMapDMF[DIV_MAX_CHIP_DEFS]; @@ -414,6 +444,7 @@ class DivEngine { int wave; int pos; int pBegin, pEnd; + int rateMul, posSub; bool dir; SamplePreview(): rate(0.0), @@ -422,6 +453,8 @@ class DivEngine { pos(0), pBegin(-1), pEnd(-1), + rateMul(1), + posSub(0), dir(false) {} } sPreview; @@ -429,6 +462,7 @@ class DivEngine { short tremTable[128]; int reversePitchTable[4096]; int pitchTable[4096]; + short effectSlotMap[4096]; char c163NameCS[1024]; int midiBaseChan; bool midiPoly; @@ -463,6 +497,8 @@ class DivEngine { void recalcChans(); void reset(); void playSub(bool preserveDrift, int goalRow=0); + void runMidiClock(int totalCycles=1); + void runMidiTime(int totalCycles=1); void testFunction(); @@ -500,11 +536,21 @@ class DivEngine { void swapChannels(int src, int dest); void stompChannel(int ch); + // recalculate patchbay (UNSAFE) + void recalcPatchbay(); + // change song (UNSAFE) void changeSong(size_t songIndex); - // check whether an asset directory is complete - void checkAssetDir(std::vector& dir, size_t entries); + // move an asset + void moveAsset(std::vector& dir, int before, int after); + + // remove an asset + void removeAsset(std::vector& dir, int entry); + + // read/write asset dir + void putAssetDirData(SafeWriter* w, std::vector& dir); + DivDataErrors readAssetDirData(SafeReader& reader, std::vector& dir); // add every export method here friend class DivROMExport; @@ -545,7 +591,7 @@ class DivEngine { SafeWriter* saveDMF(unsigned char version); // save as .fur. // if notPrimary is true then the song will not be altered - SafeWriter* saveFur(bool notPrimary=false); + SafeWriter* saveFur(bool notPrimary=false, bool newPatternFormat=true); // build a ROM file (TODO). // specify system to build ROM for. std::vector buildROM(DivROMExportOptions sys); @@ -583,6 +629,8 @@ class DivEngine { // convert old flags static void convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivSystem sys); + // check whether an asset directory is complete (UNSAFE) + void checkAssetDir(std::vector& dir, size_t entries); // benchmark (returns time in seconds) double benchmarkPlayback(); @@ -1026,6 +1074,12 @@ class DivEngine { // move system bool swapSystem(int src, int dest, bool preserveOrder=true); + // add effect + bool addEffect(DivEffectType which); + + // remove effect + bool removeEffect(int index); + // write to register on system void poke(int sys, unsigned int addr, unsigned short val); @@ -1119,8 +1173,10 @@ class DivEngine { systemsRegistered(false), hasLoadedSomething(false), midiOutClock(false), + midiOutTime(false), midiOutProgramChange(false), midiOutMode(DIV_MIDI_MODE_NOTE), + midiOutTimeRate(0), softLockCount(0), subticks(0), ticks(0), @@ -1141,16 +1197,24 @@ class DivEngine { divider(60), cycles(0), clockDrift(0), + midiClockCycles(0), + midiClockDrift(0), + midiTimeCycles(0), + midiTimeDrift(0), stepPlay(0), changeOrd(-1), changePos(0), totalSeconds(0), totalTicks(0), totalTicksR(0), + curMidiClock(0), + curMidiTime(0), totalCmds(0), lastCmds(0), cmdsPerSecond(0), globalPitch(0), + curMidiTimePiece(0), + curMidiTimeCode(0), extValue(0), pendingMetroTick(0), tempoAccum(0), @@ -1198,6 +1262,7 @@ class DivEngine { memset(tremTable,0,128*sizeof(short)); memset(reversePitchTable,0,4096*sizeof(int)); memset(pitchTable,0,4096*sizeof(int)); + memset(effectSlotMap,-1,4096*sizeof(short)); memset(sysDefs,0,DIV_MAX_CHIP_DEFS*sizeof(void*)); memset(walked,0,8192); memset(oscBuf,0,DIV_MAX_OUTPUTS*(sizeof(float*))); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 0ca5dff69..d3ae54212 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "dataErrors.h" #include "engine.h" #include "../ta-log.h" #include "instrument.h" @@ -1040,6 +1041,11 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.systemFlags[0].set("noEasyNoise",true); } + // NES PCM + if (ds.system[0]==DIV_SYSTEM_NES) { + ds.systemFlags[0].set("dpcmMode",false); + } + ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0)); if (active) quitDispatch(); @@ -1644,18 +1650,58 @@ void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivS } } +short newFormatNotes[180]={ + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -5 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -4 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -3 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -2 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -1 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 0 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 1 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 2 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 3 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 4 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 5 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 6 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 7 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 8 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 // 9 +}; + +short newFormatOctaves[180]={ + 250, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, // -5 + 251, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, // -4 + 252, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, // -3 + 253, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, // -2 + 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // -1 + 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 2 + 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 3 + 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 4 + 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 5 + 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, // 6 + 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // 7 + 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 8 + 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 9 +}; + bool DivEngine::loadFur(unsigned char* file, size_t len) { unsigned int insPtr[256]; unsigned int wavePtr[256]; unsigned int samplePtr[256]; unsigned int subSongPtr[256]; unsigned int sysFlagsPtr[DIV_MAX_CHIPS]; - std::vector patPtr; + unsigned int assetDirPtr[3]; + std::vector patPtr; int numberOfSubSongs=0; char magic[5]; memset(magic,0,5); SafeReader reader=SafeReader(file,len); warnings=""; + assetDirPtr[0]=0; + assetDirPtr[1]=0; + assetDirPtr[2]=0; try { DivSong ds; DivSubSong* subSong=ds.subsong[0]; @@ -1787,6 +1833,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<138) { ds.brokenPortaLegato=true; } + if (ds.version<155) { + ds.brokenFMOff=true; + } ds.isDMF=false; reader.readS(); // reserved @@ -2295,7 +2344,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version>=138) { ds.brokenPortaLegato=reader.readC(); - for (int i=0; i<7; i++) { + if (ds.version>=155) { + ds.brokenFMOff=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<6; i++) { reader.readC(); } } @@ -2319,6 +2373,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } } + if (ds.version>=156) { + assetDirPtr[0]=reader.readI(); + assetDirPtr[1]=reader.readI(); + assetDirPtr[2]=reader.readI(); + } + // read system flags if (ds.version>=119) { logD("reading chip flags..."); @@ -2353,6 +2413,53 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } } + // read asset directories + if (ds.version>=156) { + logD("reading asset directories..."); + + if (!reader.seek(assetDirPtr[0],SEEK_SET)) { + logE("couldn't seek to ins dir!"); + lastError=fmt::sprintf("couldn't read instrument directory"); + ds.unload(); + delete[] file; + return false; + } + if (readAssetDirData(reader,ds.insDir)!=DIV_DATA_SUCCESS) { + lastError="invalid instrument directory data!"; + ds.unload(); + delete[] file; + return false; + } + + if (!reader.seek(assetDirPtr[1],SEEK_SET)) { + logE("couldn't seek to wave dir!"); + lastError=fmt::sprintf("couldn't read wavetable directory"); + ds.unload(); + delete[] file; + return false; + } + if (readAssetDirData(reader,ds.waveDir)!=DIV_DATA_SUCCESS) { + lastError="invalid wavetable directory data!"; + ds.unload(); + delete[] file; + return false; + } + + if (!reader.seek(assetDirPtr[2],SEEK_SET)) { + logE("couldn't seek to sample dir!"); + lastError=fmt::sprintf("couldn't read sample directory"); + ds.unload(); + delete[] file; + return false; + } + if (readAssetDirData(reader,ds.sampleDir)!=DIV_DATA_SUCCESS) { + lastError="invalid sample directory data!"; + ds.unload(); + delete[] file; + return false; + } + } + // read subsongs if (ds.version>=95) { for (int i=0; i=95) { - subs=reader.readS(); - } else { - reader.readS(); - } - reader.readS(); + if (isNewFormat) { + int subs=(unsigned char)reader.readC(); + int chan=(unsigned char)reader.readC(); + int index=reader.readS(); - logD("- %d, %d, %d",subs,chan,index); + logD("- %d, %d, %d (new)",subs,chan,index); - if (chan<0 || chan>=tchans) { - logE("pattern channel out of range!",i); - lastError="pattern channel out of range!"; - ds.unload(); - delete[] file; - return false; - } - if (index<0 || index>(DIV_MAX_PATTERNS-1)) { - logE("pattern index out of range!",i); - lastError="pattern index out of range!"; - ds.unload(); - delete[] file; - return false; - } - if (subs<0 || subs>=(int)ds.subsong.size()) { - logE("pattern subsong out of range!",i); - lastError="pattern subsong out of range!"; - ds.unload(); - delete[] file; - return false; - } - - DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true); - for (int j=0; jpatLen; j++) { - pat->data[j][0]=reader.readS(); - pat->data[j][1]=reader.readS(); - pat->data[j][2]=reader.readS(); - pat->data[j][3]=reader.readS(); - for (int k=0; kpat[chan].effectCols; k++) { - pat->data[j][4+(k<<1)]=reader.readS(); - pat->data[j][5+(k<<1)]=reader.readS(); + if (chan<0 || chan>=tchans) { + logE("pattern channel out of range!",i); + lastError="pattern channel out of range!"; + ds.unload(); + delete[] file; + return false; + } + if (index<0 || index>(DIV_MAX_PATTERNS-1)) { + logE("pattern index out of range!",i); + lastError="pattern index out of range!"; + ds.unload(); + delete[] file; + return false; + } + if (subs<0 || subs>=(int)ds.subsong.size()) { + logE("pattern subsong out of range!",i); + lastError="pattern subsong out of range!"; + ds.unload(); + delete[] file; + return false; } - } - if (ds.version>=51) { + DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true); pat->name=reader.readString(); + + // read new pattern + for (int j=0; jpatLen; j++) { + unsigned char mask=reader.readC(); + unsigned short effectMask=0; + + if (mask==0xff) break; + if (mask&128) { + j+=(mask&127)+1; + continue; + } + + if (mask&32) { + effectMask|=(unsigned char)reader.readC(); + } + if (mask&64) { + effectMask|=((unsigned short)reader.readC()&0xff)<<8; + } + if (mask&8) effectMask|=1; + if (mask&16) effectMask|=2; + + if (mask&1) { // note + unsigned char note=reader.readC(); + if (note==180) { + pat->data[j][0]=100; + pat->data[j][1]=0; + } else if (note==181) { + pat->data[j][0]=101; + pat->data[j][1]=0; + } else if (note==182) { + pat->data[j][0]=102; + pat->data[j][1]=0; + } else if (note<180) { + pat->data[j][0]=newFormatNotes[note]; + pat->data[j][1]=newFormatOctaves[note]; + } else { + pat->data[j][0]=0; + pat->data[j][1]=0; + } + } + if (mask&2) { // instrument + pat->data[j][2]=(unsigned char)reader.readC(); + } + if (mask&4) { // volume + pat->data[j][3]=(unsigned char)reader.readC(); + } + for (unsigned char k=0; k<16; k++) { + if (effectMask&(1<data[j][4+k]=(unsigned char)reader.readC(); + } + } + } + } else { + int chan=reader.readS(); + int index=reader.readS(); + int subs=0; + if (ds.version>=95) { + subs=reader.readS(); + } else { + reader.readS(); + } + reader.readS(); + + logD("- %d, %d, %d (old)",subs,chan,index); + + if (chan<0 || chan>=tchans) { + logE("pattern channel out of range!",i); + lastError="pattern channel out of range!"; + ds.unload(); + delete[] file; + return false; + } + if (index<0 || index>(DIV_MAX_PATTERNS-1)) { + logE("pattern index out of range!",i); + lastError="pattern index out of range!"; + ds.unload(); + delete[] file; + return false; + } + if (subs<0 || subs>=(int)ds.subsong.size()) { + logE("pattern subsong out of range!",i); + lastError="pattern subsong out of range!"; + ds.unload(); + delete[] file; + return false; + } + + DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true); + for (int j=0; jpatLen; j++) { + pat->data[j][0]=reader.readS(); + pat->data[j][1]=reader.readS(); + pat->data[j][2]=reader.readS(); + pat->data[j][3]=reader.readS(); + for (int k=0; kpat[chan].effectCols; k++) { + pat->data[j][4+(k<<1)]=reader.readS(); + pat->data[j][5+(k<<1)]=reader.readS(); + } + } + + if (ds.version>=51) { + pat->name=reader.readString(); + } } } @@ -2715,6 +2912,24 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } } + // SrgaPCM slide compat + if (ds.version<153) { + for (int i=0; i& dir) { + size_t blockStartSeek, blockEndSeek; + + w->write("ADIR",4); + blockStartSeek=w->tell(); + w->writeI(0); + + w->writeI(dir.size()); + + for (DivAssetDir& i: dir) { + w->writeString(i.name,false); + w->writeS(i.entries.size()); + for (int j: i.entries) { + w->writeC(j); + } + } + + blockEndSeek=w->tell(); + w->seek(blockStartSeek,SEEK_SET); + w->writeI(blockEndSeek-blockStartSeek-4); + w->seek(0,SEEK_END); +} + +DivDataErrors DivEngine::readAssetDirData(SafeReader& reader, std::vector& dir) { + char magic[4]; + reader.read(magic,4); + if (memcmp(magic,"ADIR",4)!=0) { + logV("header is invalid: %c%c%c%c",magic[0],magic[1],magic[2],magic[3]); + return DIV_DATA_INVALID_HEADER; + } + reader.readI(); // reserved + + unsigned int numDirs=reader.readI(); + + for (unsigned int i=0; i subSongPtr; std::vector sysFlagsPtr; @@ -4820,7 +5085,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { std::vector wavePtr; std::vector samplePtr; std::vector patPtr; - size_t ptrSeek, subSongPtrSeek, sysFlagsPtrSeek, blockStartSeek, blockEndSeek; + int assetDirPtr[3]; + size_t ptrSeek, subSongPtrSeek, sysFlagsPtrSeek, blockStartSeek, blockEndSeek, assetDirPtrSeek; size_t subSongIndex=0; DivSubSong* subSong=song.subsong[subSongIndex]; warnings=""; @@ -5119,6 +5385,12 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { } } + // asset dir pointers (we'll seek here later) + assetDirPtrSeek=w->tell(); + w->writeI(0); + w->writeI(0); + w->writeI(0); + blockEndSeek=w->tell(); w->seek(blockStartSeek,SEEK_SET); w->writeI(blockEndSeek-blockStartSeek-4); @@ -5206,6 +5478,14 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->seek(0,SEEK_END); } + /// ASSET DIRECTORIES + assetDirPtr[0]=w->tell(); + putAssetDirData(w,song.insDir); + assetDirPtr[1]=w->tell(); + putAssetDirData(w,song.waveDir); + assetDirPtr[2]=w->tell(); + putAssetDirData(w,song.sampleDir); + /// INSTRUMENT for (int i=0; ipat[i.chan].getPattern(i.pat,false); patPtr.push_back(w->tell()); - w->write("PATR",4); - blockStartSeek=w->tell(); - w->writeI(0); - w->writeS(i.chan); - w->writeS(i.pat); - w->writeS(i.subsong); + if (newPatternFormat) { + w->write("PATN",4); + blockStartSeek=w->tell(); + w->writeI(0); - w->writeS(0); // reserved + w->writeC(i.subsong); + w->writeC(i.chan); + w->writeS(i.pat); + w->writeString(pat->name,false); - for (int j=0; jpatLen; j++) { - w->writeS(pat->data[j][0]); // note - w->writeS(pat->data[j][1]); // octave - w->writeS(pat->data[j][2]); // instrument - w->writeS(pat->data[j][3]); // volume -#ifdef TA_BIG_ENDIAN - for (int k=0; kpat[i.chan].effectCols*2; k++) { - w->writeS(pat->data[j][4+k]); + unsigned char emptyRows=0; + + for (int j=0; jpatLen; j++) { + unsigned char mask=0; + unsigned char finalNote=255; + unsigned short effectMask=0; + + if (pat->data[j][0]==100) { + finalNote=180; + } else if (pat->data[j][0]==101) { // note release + finalNote=181; + } else if (pat->data[j][0]==102) { // macro release + finalNote=182; + } else if (pat->data[j][1]==0 && pat->data[j][0]==0) { + finalNote=255; + } else { + int seek=(pat->data[j][0]+(signed char)pat->data[j][1]*12)+60; + if (seek<0 || seek>=180) { + finalNote=255; + } else { + finalNote=seek; + } + } + + if (finalNote!=255) mask|=1; // note + if (pat->data[j][2]!=-1) mask|=2; // instrument + if (pat->data[j][3]!=-1) mask|=4; // volume + for (int k=0; kpat[i.chan].effectCols*2; k+=2) { + if (k==0) { + if (pat->data[j][4+k]!=-1) mask|=8; + if (pat->data[j][5+k]!=-1) mask|=16; + } else if (k<8) { + if (pat->data[j][4+k]!=-1 || pat->data[j][5+k]!=-1) mask|=32; + } else { + if (pat->data[j][4+k]!=-1 || pat->data[j][5+k]!=-1) mask|=64; + } + + if (pat->data[j][4+k]!=-1) effectMask|=(1<data[j][5+k]!=-1) effectMask|=(2<127) { + w->writeC(128|(emptyRows-2)); + emptyRows=0; + } + } else { + if (emptyRows>1) { + w->writeC(128|(emptyRows-2)); + emptyRows=0; + } else if (emptyRows) { + w->writeC(0); + emptyRows=0; + } + + w->writeC(mask); + + if (mask&32) w->writeC(effectMask&0xff); + if (mask&64) w->writeC((effectMask>>8)&0xff); + + if (mask&1) w->writeC(finalNote); + if (mask&2) w->writeC(pat->data[j][2]); + if (mask&4) w->writeC(pat->data[j][3]); + if (mask&8) w->writeC(pat->data[j][4]); + if (mask&16) w->writeC(pat->data[j][5]); + if (mask&32) { + if (effectMask&4) w->writeC(pat->data[j][6]); + if (effectMask&8) w->writeC(pat->data[j][7]); + if (effectMask&16) w->writeC(pat->data[j][8]); + if (effectMask&32) w->writeC(pat->data[j][9]); + if (effectMask&64) w->writeC(pat->data[j][10]); + if (effectMask&128) w->writeC(pat->data[j][11]); + } + if (mask&64) { + if (effectMask&256) w->writeC(pat->data[j][12]); + if (effectMask&512) w->writeC(pat->data[j][13]); + if (effectMask&1024) w->writeC(pat->data[j][14]); + if (effectMask&2048) w->writeC(pat->data[j][15]); + if (effectMask&4096) w->writeC(pat->data[j][16]); + if (effectMask&8192) w->writeC(pat->data[j][17]); + if (effectMask&16384) w->writeC(pat->data[j][18]); + if (effectMask&32768) w->writeC(pat->data[j][19]); + } + } } -#else - w->write(&pat->data[j][4],2*song.subsong[i.subsong]->pat[i.chan].effectCols*2); // effects -#endif - } - w->writeString(pat->name,false); + // stop + w->writeC(0xff); + } else { + w->write("PATR",4); + blockStartSeek=w->tell(); + w->writeI(0); + + w->writeS(i.chan); + w->writeS(i.pat); + w->writeS(i.subsong); + + w->writeS(0); // reserved + + for (int j=0; jpatLen; j++) { + w->writeS(pat->data[j][0]); // note + w->writeS(pat->data[j][1]); // octave + w->writeS(pat->data[j][2]); // instrument + w->writeS(pat->data[j][3]); // volume +#ifdef TA_BIG_ENDIAN + for (int k=0; kpat[i.chan].effectCols*2; k++) { + w->writeS(pat->data[j][4+k]); + } +#else + w->write(&pat->data[j][4],2*song.subsong[i.subsong]->pat[i.chan].effectCols*2); // effects +#endif + } + + w->writeString(pat->name,false); + } blockEndSeek=w->tell(); w->seek(blockStartSeek,SEEK_SET); @@ -5297,6 +5679,12 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeI(sysFlagsPtr[i]); } + // asset dir pointers + w->seek(assetDirPtrSeek,SEEK_SET); + for (size_t i=0; i<3; i++) { + w->writeI(assetDirPtr[i]); + } + saveLock.unlock(); return w; } diff --git a/src/engine/pattern.cpp b/src/engine/pattern.cpp index f7b833e18..ea0cca7e0 100644 --- a/src/engine/pattern.cpp +++ b/src/engine/pattern.cpp @@ -23,11 +23,7 @@ static DivPattern emptyPat; DivPattern::DivPattern() { - memset(data,-1,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short)); - for (int i=0; idata,data,sizeof(data)); } +void DivPattern::clear() { + memset(data,-1,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short)); + for (int i=0; irate && !end) { + while (chan[i].dac.period>dacRate && !end) { DivSample* s=parent->getSample(chan[i].dac.sample); if (s->samples<=0) { chan[i].dac.sample=-1; @@ -143,7 +143,7 @@ void DivPlatformAY8910::runDAC() { end=true; break; } - chan[i].dac.period-=rate; + chan[i].dac.period-=dacRate; } if (changed && !end) { if (!isMuted[i]) { @@ -187,9 +187,9 @@ void DivPlatformAY8910::acquire(short** buf, size_t len) { buf[0][i]=ayBuf[0][0]; buf[1][i]=buf[0][i]; - oscBuf[0]->data[oscBuf[0]->needle++]=sunsoftVolTable[31-(ay->lastIndx&31)]>>3; - oscBuf[1]->data[oscBuf[1]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>5)&31)]>>3; - oscBuf[2]->data[oscBuf[2]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>10)&31)]>>3; + oscBuf[0]->data[oscBuf[0]->needle++]=sunsoftVolTable[31-(ay->lastIndx&31)]<<3; + oscBuf[1]->data[oscBuf[1]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>5)&31)]<<3; + oscBuf[2]->data[oscBuf[2]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>10)&31)]<<3; } } else { for (size_t i=0; i>8); @@ -796,6 +797,7 @@ void DivPlatformAY8910::setFlags(const DivConfig& flags) { chipClock=extClock; rate=chipClock/extDiv; clockSel=false; + dacRate=chipClock/dacRateDiv; } else { clockSel=flags.getBool("halfClock",false); switch (flags.getInt("clockSel",0)) { @@ -850,6 +852,7 @@ void DivPlatformAY8910::setFlags(const DivConfig& flags) { } CHECK_CUSTOM_CLOCK; rate=chipClock/8; + dacRate=rate; } 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 8f938d0a9..04e3aed12 100644 --- a/src/engine/platform/ay.h +++ b/src/engine/platform/ay.h @@ -104,7 +104,9 @@ class DivPlatformAY8910: public DivDispatch { bool extMode; unsigned int extClock; + int dacRate; unsigned char extDiv; + unsigned char dacRateDiv; bool stereo, sunsoft, intellivision, clockSel; bool ioPortA, ioPortB; @@ -119,7 +121,6 @@ class DivPlatformAY8910: public DivDispatch { short* ayBuf[3]; size_t ayBufLen; - void runDAC(); void checkWrites(); void updateOutSel(bool immediate=false); @@ -127,6 +128,7 @@ class DivPlatformAY8910: public DivDispatch { friend void putDispatchChan(void*,int,int); public: + void runDAC(); void setExtClockDiv(unsigned int eclk=COLOR_NTSC, unsigned char ediv=8); void acquire(short** buf, size_t len); int dispatch(DivCommand c); @@ -151,10 +153,11 @@ class DivPlatformAY8910: public DivDispatch { const char** getRegisterSheet(); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); - DivPlatformAY8910(bool useExtMode=false, unsigned int eclk=COLOR_NTSC, unsigned char ediv=8): + DivPlatformAY8910(bool useExtMode=false, unsigned int eclk=COLOR_NTSC, unsigned char ediv=8, unsigned char ddiv=24): DivDispatch(), extMode(useExtMode), extClock(eclk), - extDiv(ediv) {} + extDiv(ediv), + dacRateDiv(ddiv) {} }; #endif diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 139b1525b..977e39511 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -105,6 +105,7 @@ void DivPlatformC64::updateFilter() { } void DivPlatformC64::tick(bool sysTick) { + bool willUpdateFilter=false; for (int i=0; i<3; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { @@ -117,10 +118,10 @@ void DivPlatformC64::tick(bool sysTick) { if (filtCut>2047) filtCut=2047; if (filtCut<0) filtCut=0; } - updateFilter(); + willUpdateFilter=true; } else { vol=MIN(15,chan[i].std.vol.val); - updateFilter(); + willUpdateFilter=true; } } if (NEW_ARP_STRAT) { @@ -156,11 +157,11 @@ void DivPlatformC64::tick(bool sysTick) { } if (chan[i].std.ex1.had) { filtControl=chan[i].std.ex1.val&15; - updateFilter(); + willUpdateFilter=true; } if (chan[i].std.ex2.had) { filtRes=chan[i].std.ex2.val&15; - updateFilter(); + willUpdateFilter=true; } if (chan[i].std.ex3.had) { chan[i].sync=chan[i].std.ex3.val&1; @@ -207,6 +208,7 @@ void DivPlatformC64::tick(bool sysTick) { chan[i].freqChanged=false; } } + if (willUpdateFilter) updateFilter(); } int DivPlatformC64::dispatch(DivCommand c) { diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index e94970145..9a5d6d7d4 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -82,8 +82,12 @@ void DivPlatformGB::acquire(short** buf, size_t len) { void DivPlatformGB::updateWave() { rWrite(0x1a,0); for (int i=0; i<16; i++) { - int nibble1=15-ws.output[((i<<1)+antiClickWavePos-1)&31]; - int nibble2=15-ws.output[((1+(i<<1))+antiClickWavePos-1)&31]; + int nibble1=ws.output[((i<<1)+antiClickWavePos)&31]; + int nibble2=ws.output[((1+(i<<1))+antiClickWavePos)&31]; + if (invertWave) { + nibble1^=15; + nibble2^=15; + } rWrite(0x30+i,(nibble1<<4)|nibble2); } antiClickWavePos&=31; @@ -658,6 +662,7 @@ void DivPlatformGB::setFlags(const DivConfig& flags) { model=GB_MODEL_AGB; break; } + invertWave=flags.getBool("invertWave",true); enoughAlready=flags.getBool("enoughAlready",false); } diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h index f75cc62c2..8ba70a913 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -57,6 +57,7 @@ class DivPlatformGB: public DivDispatch { DivDispatchOscBuffer* oscBuf[4]; bool isMuted[4]; bool antiClickEnabled; + bool invertWave; bool enoughAlready; unsigned char lastPan; DivWaveSynth ws; diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 67cdc620c..a25568d0c 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -211,7 +211,7 @@ void DivPlatformNES::tick(bool sysTick) { chan[i].outVol=VOL_SCALE_LINEAR_BROKEN(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15); if (chan[i].outVol<0) chan[i].outVol=0; if (i==2) { // triangle - rWrite(0x4000+i*4,(chan[i].outVol==0)?0:255); + rWrite(0x4000+i*4,(chan[i].outVol==0)?0:linearCount); chan[i].freqChanged=true; } else { rWrite(0x4000+i*4,(chan[i].envMode<<4)|chan[i].outVol|((chan[i].duty&3)<<6)); @@ -262,7 +262,7 @@ void DivPlatformNES::tick(bool sysTick) { //rWrite(16+i*5,chan[i].sweep); } } - if (i<2) if (chan[i].std.phaseReset.had) { + if (i<3) if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1) { chan[i].freqChanged=true; chan[i].prevFreq=-1; @@ -337,14 +337,22 @@ void DivPlatformNES::tick(bool sysTick) { goingToLoop=parent->getSample(dacSample)->isLoopable(); // write DPCM rWrite(0x4015,15); - rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0)); + if (nextDPCMFreq>=0) { + rWrite(0x4010,nextDPCMFreq|(goingToLoop?0x40:0)); + nextDPCMFreq=-1; + } else { + rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0)); + } rWrite(0x4012,(dpcmAddr>>6)&0xff); rWrite(0x4013,dpcmLen&0xff); rWrite(0x4015,31); dpcmBank=dpcmAddr>>14; } } else { - if (dpcmMode) { + if (nextDPCMFreq>=0) { + rWrite(0x4010,nextDPCMFreq|(goingToLoop?0x40:0)); + nextDPCMFreq=-1; + } else { rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0)); } } @@ -353,6 +361,8 @@ void DivPlatformNES::tick(bool sysTick) { if (chan[4].keyOn) chan[4].keyOn=false; chan[4].freqChanged=false; } + + nextDPCMFreq=-1; } int DivPlatformNES::dispatch(DivCommand c) { @@ -401,12 +411,17 @@ int DivPlatformNES::dispatch(DivCommand c) { chan[c.chan].furnaceDac=false; if (dpcmMode && !skipRegisterWrites) { unsigned int dpcmAddr=sampleOffDPCM[dacSample]; - unsigned int dpcmLen=(parent->getSample(dacSample)->lengthDPCM+15)>>4; + unsigned int dpcmLen=parent->getSample(dacSample)->lengthDPCM>>4; if (dpcmLen>255) dpcmLen=255; goingToLoop=parent->getSample(dacSample)->isLoopable(); // write DPCM rWrite(0x4015,15); - rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0)); + if (nextDPCMFreq>=0) { + rWrite(0x4010,nextDPCMFreq|(goingToLoop?0x40:0)); + nextDPCMFreq=-1; + } else { + rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0)); + } rWrite(0x4012,(dpcmAddr>>6)&0xff); rWrite(0x4013,dpcmLen&0xff); rWrite(0x4015,31); @@ -434,7 +449,7 @@ int DivPlatformNES::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; } if (c.chan==2) { - rWrite(0x4000+c.chan*4,0xff); + rWrite(0x4000+c.chan*4,linearCount); } else if (!parent->song.brokenOutVol2) { rWrite(0x4000+c.chan*4,(chan[c.chan].envMode<<4)|chan[c.chan].vol|((chan[c.chan].duty&3)<<6)); } @@ -466,7 +481,7 @@ int DivPlatformNES::dispatch(DivCommand c) { } if (chan[c.chan].active) { if (c.chan==2) { - rWrite(0x4000+c.chan*4,0xff); + rWrite(0x4000+c.chan*4,linearCount); } else { rWrite(0x4000+c.chan*4,(chan[c.chan].envMode<<4)|chan[c.chan].vol|((chan[c.chan].duty&3)<<6)); } @@ -542,6 +557,16 @@ int DivPlatformNES::dispatch(DivCommand c) { countMode=c.value; rWrite(0x4017,countMode?0x80:0); break; + case DIV_CMD_NES_LINEAR_LENGTH: + if (c.chan==2) { + linearCount=c.value; + if (chan[c.chan].active) { + rWrite(0x4000+c.chan*4,(chan[c.chan].outVol==0)?0:linearCount); + chan[c.chan].freqChanged=true; + chan[c.chan].prevFreq=-1; + } + } + break; case DIV_CMD_NES_DMC: rWrite(0x4011,c.value&0x7f); break; @@ -555,6 +580,14 @@ int DivPlatformNES::dispatch(DivCommand c) { rWrite(0x4013,0); rWrite(0x4015,31); break; + case DIV_CMD_SAMPLE_FREQ: { + bool goingToLoop=parent->getSample(dacSample)->isLoopable(); + if (dpcmMode) { + nextDPCMFreq=c.value&15; + rWrite(0x4010,(c.value&15)|(goingToLoop?0x40:0)); + } + break; + } case DIV_CMD_SAMPLE_BANK: sampleBank=c.value; if (sampleBank>(parent->song.sample.size()/12)) { @@ -655,9 +688,11 @@ void DivPlatformNES::reset() { dacSample=-1; sampleBank=0; dpcmBank=0; - dpcmMode=false; + dpcmMode=dpcmModeDefault; goingToLoop=false; countMode=false; + nextDPCMFreq=-1; + linearCount=255; if (useNP) { nes1_NP->Reset(); @@ -709,6 +744,8 @@ void DivPlatformNES::setFlags(const DivConfig& flags) { for (int i=0; i<5; i++) { oscBuf[i]->rate=rate/32; } + + dpcmModeDefault=flags.getBool("dpcmMode",true); } void DivPlatformNES::notifyInsDeletion(void* ins) { diff --git a/src/engine/platform/nes.h b/src/engine/platform/nes.h index cb30dbf78..da0a848dd 100644 --- a/src/engine/platform/nes.h +++ b/src/engine/platform/nes.h @@ -52,7 +52,10 @@ class DivPlatformNES: public DivDispatch { unsigned char sampleBank; unsigned char writeOscBuf; unsigned char apuType; + unsigned char linearCount; + signed char nextDPCMFreq; bool dpcmMode; + bool dpcmModeDefault; bool dacAntiClickOn; bool useNP; bool goingToLoop; diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 6fea25c3c..a54667a7a 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -1558,7 +1558,7 @@ DivMacroInt* DivPlatformOPL::getChanMacroInt(int ch) { } DivDispatchOscBuffer* DivPlatformOPL::getOscBuffer(int ch) { - if (oplType==759) { + if (oplType==759 || chipType==8950) { if (ch>=totalChans+1) return NULL; } else { if (ch>=totalChans) return NULL; diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 4f3d12098..8f30a9dd0 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -409,10 +409,6 @@ int DivPlatformOPLL::dispatch(DivCommand c) { case 8: case 9: chan[c.chan].fixedFreq=(chan[c.chan].state.tomTopFreq&511)<<(chan[c.chan].state.tomTopFreq>>9); break; - default: - chan[c.chan].fixedFreq=0; - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); - break; } } else { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); diff --git a/src/engine/platform/pcmdac.cpp b/src/engine/platform/pcmdac.cpp index 0b59accb1..283b6e24e 100644 --- a/src/engine/platform/pcmdac.cpp +++ b/src/engine/platform/pcmdac.cpp @@ -42,12 +42,65 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) { while (chan[0].audSub>=0x10000) { chan[0].audSub-=0x10000; chan[0].audPos+=((!chan[0].useWave) && chan[0].audDir)?-1:1; + if (chan[0].audPos>=(int)chan[0].audLen) { + chan[0].audPos%=chan[0].audLen; + chan[0].audDir=false; + } + chan[0].audDat[0]=chan[0].audDat[1]; + chan[0].audDat[1]=chan[0].audDat[2]; + chan[0].audDat[2]=chan[0].audDat[3]; + chan[0].audDat[3]=chan[0].audDat[4]; + chan[0].audDat[4]=chan[0].audDat[5]; + chan[0].audDat[5]=chan[0].audDat[6]; + chan[0].audDat[6]=chan[0].audDat[7]; + chan[0].audDat[7]=(chan[0].ws.output[chan[0].audPos]-0x80)<<8; } - if (chan[0].audPos>=(int)chan[0].audLen) { - chan[0].audPos%=chan[0].audLen; - chan[0].audDir=false; + + const short s0=chan[0].audDat[0]; + const short s1=chan[0].audDat[1]; + const short s2=chan[0].audDat[2]; + const short s3=chan[0].audDat[3]; + const short s4=chan[0].audDat[4]; + const short s5=chan[0].audDat[5]; + const short s6=chan[0].audDat[6]; + const short s7=chan[0].audDat[7]; + + switch (interp) { + case 1: // linear + output=s6+((s7-s6)*(chan[0].audSub&0xffff)>>16); + break; + case 2: { // cubic + float* cubicTable=DivFilterTables::getCubicTable(); + float* t=&cubicTable[((chan[0].audSub&0xffff)>>6)<<2]; + float result=(float)s4*t[0]+(float)s5*t[1]+(float)s6*t[2]+(float)s7*t[3]; + if (result<-32768) result=-32768; + if (result>32767) result=32767; + output=result; + break; + } + case 3: { // sinc + float* sincTable=DivFilterTables::getSincTable8(); + float* t1=&sincTable[(8191-((chan[0].audSub&0xffff)>>3))<<2]; + float* t2=&sincTable[((chan[0].audSub&0xffff)>>3)<<2]; + float result=( + s0*t2[3]+ + s1*t2[2]+ + s2*t2[1]+ + s3*t2[0]+ + s4*t1[0]+ + s5*t1[1]+ + s6*t1[2]+ + s7*t1[3] + ); + if (result<-32768) result=-32768; + if (result>32767) result=32767; + output=result; + break; + } + default: // none + output=s7; + break; } - output=(chan[0].ws.output[chan[0].audPos]-0x80)<<8; } else { DivSample* s=parent->getSample(chan[0].sample); if (s->samples>0) { diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index ae4b311e1..8ee3a169c 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -156,7 +156,7 @@ void DivPlatformPOKEY::tick(bool sysTick) { for (int i=0; i<4; i++) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,parent->song.linearPitch?chan[i].pitch:0,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,parent->song.linearPitch?chan[i].pitch2:0,chipClock,CHIP_DIVIDER); if ((i==0 && !(audctl&64)) || (i==2 && !(audctl&32)) || i==1 || i==3) { chan[i].freq/=7; @@ -194,6 +194,11 @@ void DivPlatformPOKEY::tick(bool sysTick) { chan[i].freq>>=2; } + // non-linear pitch + if (parent->song.linearPitch==0) { + chan[i].freq-=chan[i].pitch; + } + if (--chan[i].freq<0) chan[i].freq=0; // snap buzz periods diff --git a/src/engine/platform/pv1000.cpp b/src/engine/platform/pv1000.cpp index d5f54e6a6..4d32fc5a0 100644 --- a/src/engine/platform/pv1000.cpp +++ b/src/engine/platform/pv1000.cpp @@ -29,6 +29,7 @@ const char* regCheatSheetPV1000[]={ "CH1_Pitch", "00", "CH2_Pitch", "01", "CH3_Pitch", "02", + "Control", "03", NULL }; @@ -38,11 +39,10 @@ const char** DivPlatformPV1000::getRegisterSheet() { void DivPlatformPV1000::acquire(short** buf, size_t len) { for (size_t h=0; hdata[oscBuf[i]->needle++]=(d65010g031.square[i].out<<12); + oscBuf[i]->data[oscBuf[i]->needle++]=(d65010g031.out[i]); } } } @@ -73,17 +73,17 @@ void DivPlatformPV1000::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { chan[i].freq=0x3f-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); - if (chan[i].freq<1) chan[i].freq=1; + if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>62) chan[i].freq=62; if (isMuted[i]) chan[i].keyOn=false; if (chan[i].keyOn) { - rWrite(i,(isMuted[i] || (chan[i].outVol<=0)) ? 0 : chan[i].freq); + rWrite(i,(isMuted[i] || (chan[i].outVol<=0)) ? 0x3f : chan[i].freq); chan[i].keyOn=false; } else if (chan[i].freqChanged && chan[i].active && !isMuted[i]) { - rWrite(i,(isMuted[i] || (chan[i].outVol<=0)) ? 0 : chan[i].freq); + rWrite(i,(isMuted[i] || (chan[i].outVol<=0)) ? 0x3f : chan[i].freq); } if (chan[i].keyOff) { - rWrite(i,0); + rWrite(i,0x3f); chan[i].keyOff=false; } chan[i].freqChanged=false; @@ -137,6 +137,13 @@ int DivPlatformPV1000::dispatch(DivCommand c) { chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; break; + case DIV_CMD_STD_NOISE_MODE: // ring modulation + if (c.value&1) { + rWrite(3,3); + } else { + rWrite(3,2); + } + break; case DIV_CMD_NOTE_PORTA: { int destFreq=NOTE_PERIODIC(c.value2); bool return2=false; @@ -224,16 +231,21 @@ unsigned char* DivPlatformPV1000::getRegisterPool() { } int DivPlatformPV1000::getRegisterPoolSize() { - return 3; + return 4; } void DivPlatformPV1000::reset() { - memset(regPool,0,3); + memset(regPool,0,4); for (int i=0; i<3; i++) { chan[i]=Channel(); chan[i].std.setEngine(parent); } d65010g031_reset(&d65010g031); + // mute + rWrite(0,0x3f); + rWrite(1,0x3f); + rWrite(2,0x3f); + rWrite(3,2); } int DivPlatformPV1000::getOutputCount() { diff --git a/src/engine/platform/pv1000.h b/src/engine/platform/pv1000.h index c32540763..852bf1204 100644 --- a/src/engine/platform/pv1000.h +++ b/src/engine/platform/pv1000.h @@ -33,7 +33,7 @@ class DivPlatformPV1000: public DivDispatch { DivDispatchOscBuffer* oscBuf[3]; bool isMuted[3]; - unsigned char regPool[3]; + unsigned char regPool[4]; d65010g031_t d65010g031; friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index 2e23abb4e..b1aeab3e7 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -319,7 +319,7 @@ void DivPlatformQSound::tick(bool sysTick) { if (length > 65536 - 16) { length = 65536 - 16; } - if (loopStart == -1 || loopStart >= length) { + if (!s->isLoopable()) { if (i<16) { qsound_end = offPCM[chan[i].sample] + length + 15; } else { @@ -466,6 +466,7 @@ int DivPlatformQSound::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; + chan[c.chan].keyOff=false; chan[c.chan].macroInit(ins); if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; @@ -609,6 +610,7 @@ void DivPlatformQSound::forceIns() { for (int i=0; i<19; i++) { chan[i].insChanged=true; chan[i].freqChanged=true; + chan[i].keyOff=true; //chan[i].sample=-1; } } diff --git a/src/engine/platform/rf5c68.cpp b/src/engine/platform/rf5c68.cpp index fbf8e00dd..7be438010 100644 --- a/src/engine/platform/rf5c68.cpp +++ b/src/engine/platform/rf5c68.cpp @@ -151,8 +151,8 @@ void DivPlatformRF5C68::tick(bool sysTick) { if (s->isLoopable()) { loop=start+s->loopStart; } - start=MIN(start,getSampleMemCapacity()-31); - loop=MIN(loop,getSampleMemCapacity()-31); + start=MIN(start,getSampleMemCapacity()-32); + loop=MIN(loop,getSampleMemCapacity()-32); rWrite(8,keyoff); // force keyoff first chWrite(i,6,start>>8); chWrite(i,4,loop&0xff); @@ -425,7 +425,7 @@ void DivPlatformRF5C68::renderSamples(int sysID) { } int length=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT); - int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-31,length); + int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-32,length); if (actualLength>0) { sampleOffRFC[i]=memPos; for (int j=0; j0)?(val|0x80):(0-val); } // write end of sample marker - memset(&sampleMem[memPos],0xff,31); - memPos+=31; + memset(&sampleMem[memPos],0xff,32); + memPos+=32; } if (actualLengthcalcArp(chan[i].note,chan[i].std.arp.val)<<6); + chan[i].baseFreq=(parent->calcArp(chan[i].note,chan[i].std.arp.val)<<7); } chan[i].freqChanged=true; } @@ -106,21 +106,22 @@ void DivPlatformSegaPCM::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=chan[i].baseFreq+(chan[i].pitch>>1)-64; + chan[i].freq=chan[i].baseFreq+(chan[i].pitch)-128+(oldSlides?0:chan[i].pitch2); if (!parent->song.oldArpStrategy) { if (chan[i].fixedArp) { - chan[i].freq=(chan[i].baseNoteOverride<<6)+(chan[i].pitch>>1)-64+chan[i].pitch2; + chan[i].freq=(chan[i].baseNoteOverride<<7)+chan[i].pitch-128+(chan[i].pitch2<<(oldSlides?1:0)); } else { - chan[i].freq+=chan[i].arpOff<<6; + chan[i].freq+=chan[i].arpOff<<7; } } + if (oldSlides) chan[i].freq&=~1; if (chan[i].furnacePCM) { double off=1.0; if (chan[i].pcm.sample>=0 && chan[i].pcm.samplesong.sampleLen) { DivSample* s=parent->getSample(chan[i].pcm.sample); off=(double)s->centerRate/8363.0; } - chan[i].pcm.freq=MIN(255,(15625+(off*parent->song.tuning*pow(2.0,double(chan[i].freq+256)/(64.0*12.0)))*255)/31250)+chan[i].pitch2; + chan[i].pcm.freq=MIN(255,((rate*0.5)+(off*parent->song.tuning*pow(2.0,double(chan[i].freq+512)/(128.0*12.0)))*255)/rate)+(oldSlides?chan[i].pitch2:0); rWrite(7+(i<<3),chan[i].pcm.freq); } chan[i].freqChanged=false; @@ -137,7 +138,7 @@ void DivPlatformSegaPCM::tick(bool sysTick) { rWrite(0x84+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample])&0xff); rWrite(0x85+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample]>>8)&0xff); rWrite(6+(i<<3),sampleEndSegaPCM[chan[i].pcm.sample]); - if (loopStart<0 || loopStart>=actualLength) { + if (!s->isLoopable()) { rWrite(0x86+(i<<3),2+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3)); } else { int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart; @@ -155,7 +156,7 @@ void DivPlatformSegaPCM::tick(bool sysTick) { rWrite(0x84+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample])&0xff); rWrite(0x85+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample]>>8)&0xff); rWrite(6+(i<<3),sampleEndSegaPCM[chan[i].pcm.sample]); - if (loopStart<0 || loopStart>=actualLength) { + if (!s->isLoopable()) { rWrite(0x86+(i<<3),2+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3)); } else { int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart; @@ -201,7 +202,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; - chan[c.chan].baseFreq=(c.value<<6); + chan[c.chan].baseFreq=(c.value<<7); chan[c.chan].freqChanged=true; } chan[c.chan].furnacePCM=true; @@ -219,7 +220,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { rWrite(0x86+(c.chan<<3),3); break; } - chan[c.chan].pcm.freq=MIN(255,(parent->getSample(chan[c.chan].pcm.sample)->rate*255)/31250); + chan[c.chan].pcm.freq=MIN(255,(parent->getSample(chan[c.chan].pcm.sample)->rate*255)/rate); chan[c.chan].furnacePCM=false; chan[c.chan].active=true; chan[c.chan].keyOn=true; @@ -289,17 +290,18 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - int destFreq=(c.value2<<6); + int destFreq=(c.value2<<7); int newFreq; + int mul=(oldSlides || parent->song.linearPitch!=2)?8:1; bool return2=false; if (destFreq>chan[c.chan].baseFreq) { - newFreq=chan[c.chan].baseFreq+c.value*4; + newFreq=chan[c.chan].baseFreq+c.value*mul; if (newFreq>=destFreq) { newFreq=destFreq; return2=true; } } else { - newFreq=chan[c.chan].baseFreq-c.value*4; + newFreq=chan[c.chan].baseFreq-c.value*mul; if (newFreq<=destFreq) { newFreq=destFreq; return2=true; @@ -314,7 +316,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { break; } case DIV_CMD_LEGATO: { - chan[c.chan].baseFreq=(c.value<<6); + chan[c.chan].baseFreq=(c.value<<7); chan[c.chan].freqChanged=true; break; } @@ -337,7 +339,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { return 127; break; case DIV_CMD_PRE_PORTA: - if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=(chan[c.chan].note<<6); + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=(chan[c.chan].note<<7); chan[c.chan].inPorta=c.value; break; case DIV_CMD_PRE_NOTE: @@ -504,6 +506,8 @@ void DivPlatformSegaPCM::setFlags(const DivConfig& flags) { for (int i=0; i<16; i++) { oscBuf[i]->rate=rate; } + + oldSlides=flags.getBool("oldSlides",false); } int DivPlatformSegaPCM::getOutputCount() { diff --git a/src/engine/platform/segapcm.h b/src/engine/platform/segapcm.h index 9e2ad5df1..b818306b1 100644 --- a/src/engine/platform/segapcm.h +++ b/src/engine/platform/segapcm.h @@ -65,6 +65,7 @@ class DivPlatformSegaPCM: public DivDispatch { segapcm_device pcm; int delay; int pcmL, pcmR, pcmCycles; + bool oldSlides; unsigned char sampleBank; unsigned char lastBusy; diff --git a/src/engine/platform/snes.cpp b/src/engine/platform/snes.cpp index 48351493c..0c9734c29 100644 --- a/src/engine/platform/snes.cpp +++ b/src/engine/platform/snes.cpp @@ -202,6 +202,7 @@ void DivPlatformSNES::tick(bool sysTick) { } } for (int i=0; i<8; i++) { + // TODO: if wavetable length is higher than 32, we lose precision! if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { DivSample* s=parent->getSample(chan[i].sample); double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0; @@ -221,7 +222,7 @@ void DivPlatformSNES::tick(bool sysTick) { if (chan[i].audPos>0) { start=start+MIN(chan[i].audPos,s->lengthBRR-1)/16*9; } - if (s->loopStart>=0) { + if (s->isLoopable()) { loop=((s->depth!=DIV_SAMPLE_DEPTH_BRR)?9:0)+start+((s->loopStart/16)*9); } } else { @@ -300,6 +301,11 @@ void DivPlatformSNES::tick(bool sysTick) { rWrite(0x4d,echoBits); writeEcho=false; } + if (writeDryVol) { + rWrite(0x0c,dryVolL); + rWrite(0x1c,dryVolR); + writeDryVol=false; + } for (int i=0; i<8; i++) { if (chan[i].shallWriteEnv) { writeEnv(i); @@ -563,6 +569,14 @@ int DivPlatformSNES::dispatch(DivCommand c) { rWrite(0x3c,echoVolR); } break; + case DIV_CMD_SNES_GLOBAL_VOL_LEFT: + dryVolL=c.value; + writeDryVol=true; + break; + case DIV_CMD_SNES_GLOBAL_VOL_RIGHT: + dryVolR=c.value; + writeDryVol=true; + break; case DIV_CMD_GET_VOLMAX: return 127; break; @@ -673,6 +687,7 @@ void DivPlatformSNES::forceIns() { writeNoise=true; writePitchMod=true; writeEcho=true; + writeDryVol=true; initEcho(); } @@ -761,6 +776,10 @@ void DivPlatformSNES::reset() { writeNoise=false; writePitchMod=false; writeEcho=true; + writeDryVol=false; + + dryVolL=127; + dryVolR=127; echoDelay=initEchoDelay; echoFeedback=initEchoFeedback; diff --git a/src/engine/platform/snes.h b/src/engine/platform/snes.h index 3c3426466..68637be82 100644 --- a/src/engine/platform/snes.h +++ b/src/engine/platform/snes.h @@ -59,6 +59,7 @@ class DivPlatformSNES: public DivDispatch { unsigned char noiseFreq; signed char delay; signed char echoVolL, echoVolR, echoFeedback; + signed char dryVolL, dryVolR; signed char echoFIR[8]; unsigned char echoDelay; size_t sampleTableBase; @@ -66,6 +67,7 @@ class DivPlatformSNES: public DivDispatch { bool writeNoise; bool writePitchMod; bool writeEcho; + bool writeDryVol; bool echoOn; bool initEchoOn; diff --git a/src/engine/platform/sound/d65modified.c b/src/engine/platform/sound/d65modified.c index 8f53b34c8..4a0ec1ed1 100644 --- a/src/engine/platform/sound/d65modified.c +++ b/src/engine/platform/sound/d65modified.c @@ -67,7 +67,7 @@ int d65010g031_square_tick(struct d65010g031_square_t *square, const int cycle) { if (square->period > 0) { - int period = d65010g031_max(1, (0x3f - square->period)); + const int period = square->period; square->counter += cycle; while (square->counter >= period) { @@ -82,9 +82,9 @@ int d65010g031_square_tick(struct d65010g031_square_t *square, const int cycle) // this is the bit I altered // THIS IS **NOT** THE ORIGINAL SOFTWARE! I am plainly marking it as such! const int d65Volumes[3]={ - 3840, - 5120, - 8192 + 3840, // -6dB + 5120, // -3dB + 8192 // 0dB }; int d65010g031_sound_tick(struct d65010g031_t *d65010g031, const int cycle) @@ -92,7 +92,29 @@ int d65010g031_sound_tick(struct d65010g031_t *d65010g031, const int cycle) int out = 0; for (int i = 0; i < 3; i++) { - out += d65010g031_square_tick(&d65010g031->square[i], cycle)?d65Volumes[i]:-d65Volumes[i]; + d65010g031->out[i] = 0; + } + if (d65010g031->ctrl & 2) + { + if (d65010g031->ctrl & 1) // ring modulation + { + int sout[3] = { + d65010g031_square_tick(&d65010g031->square[0], cycle), + d65010g031_square_tick(&d65010g031->square[1], cycle), + d65010g031_square_tick(&d65010g031->square[2], cycle), + }; + d65010g031->out[0] = (sout[0] ^ sout[1]) ? d65Volumes[0] : -d65Volumes[0]; + d65010g031->out[1] = (sout[1] ^ sout[2]) ? d65Volumes[1] : -d65Volumes[1]; + d65010g031->out[2] = (sout[2] ? d65Volumes[2] : -d65Volumes[2]); + } + else + { + for (int i = 0; i < 3; i++) + { + d65010g031->out[i] = d65010g031_square_tick(&d65010g031->square[i], cycle)?d65Volumes[i]:-d65Volumes[i]; + } + } + out = d65010g031->out[0] + d65010g031->out[1] + d65010g031->out[2]; } return out; } @@ -105,12 +127,25 @@ void d65010g031_reset(struct d65010g031_t *d65010g031) d65010g031->square[i].counter = 0; d65010g031->square[i].out = 0; } + d65010g031->ctrl = 0; } void d65010g031_write(struct d65010g031_t *d65010g031, const unsigned char a, const unsigned char d) { - if (a < 3) + switch (a) { - d65010g031->square[a].period = d & 0x3f; + case 3: + d65010g031->ctrl = d; + break; + default: + { + const unsigned char per = (unsigned char)(~d) & 0x3f; + if ((per == 0) && (d65010g031->square[a].period != 0)) + { + d65010g031->square[a].out ^= 1; + } + d65010g031->square[a].period = per; + break; + } } } diff --git a/src/engine/platform/sound/d65modified.h b/src/engine/platform/sound/d65modified.h index 49dc13a15..eae0f8988 100644 --- a/src/engine/platform/sound/d65modified.h +++ b/src/engine/platform/sound/d65modified.h @@ -54,6 +54,8 @@ struct d65010g031_square_t struct d65010g031_t { struct d65010g031_square_t square[3]; + signed short out[3]; + unsigned char ctrl; }; int d65010g031_square_tick(struct d65010g031_square_t *square, const int cycle); diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 638001740..44f94d991 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -222,7 +222,8 @@ void DivPlatformX1_010::acquire(short** buf, size_t len) { if (stereo) buf[1][h]=tempR; for (int i=0; i<16; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(x1_010.voice_out(i,0)+x1_010.voice_out(i,1))>>1; + int vo=(x1_010.voice_out(i,0)+x1_010.voice_out(i,1))<<3; + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(vo,-32768,32767); } } } diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 8f6cd3e32..cc2d1f3f7 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -169,6 +169,15 @@ void DivPlatformYM2203::acquire_combo(short** buf, size_t len) { static short ignored[2]; for (size_t h=0; h OPN + ay->runDAC(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); + os=0; // Nuked part for (unsigned int i=0; i OPN + ay->runDAC(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); + os=0; if (!writes.empty()) { if (--delay<1) { @@ -567,7 +585,7 @@ int DivPlatformYM2203::dispatch(DivCommand c) { chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; - chan[c.chan].macroInit(NULL); + if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: chan[c.chan].keyOff=true; diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 92c1caa13..7c10e76c2 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -321,6 +321,15 @@ void DivPlatformYM2608::acquire_combo(short** buf, size_t len) { } for (size_t h=0; h OPN + ay->runDAC(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); + os[0]=0; os[1]=0; // Nuked part for (int i=0; i OPN + ay->runDAC(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); + os[0]=0; os[1]=0; if (!writes.empty()) { if (--delay<1) { @@ -1007,7 +1025,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; - chan[c.chan].macroInit(NULL); + if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: chan[c.chan].keyOff=true; @@ -1675,7 +1693,7 @@ int DivPlatformYM2608::init(DivEngine* p, int channels, int sugRate, const DivCo fm=new ymfm::ym2608(iface); fm->set_fidelity(ymfm::OPN_FIDELITY_MIN); // YM2149, 2MHz - ay=new DivPlatformAY8910(true,chipClock,ayDiv); + ay=new DivPlatformAY8910(true,chipClock,ayDiv,48); ay->init(p,3,sugRate,ayFlags); ay->toggleRegisterDump(true); setFlags(flags); diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index cbc2847c9..e9cb021d0 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -256,6 +256,15 @@ void DivPlatformYM2610::acquire_combo(short** buf, size_t len) { } for (size_t h=0; h OPN + ay->runDAC(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); + os[0]=0; os[1]=0; // Nuked part for (int i=0; i<24; i++) { @@ -360,6 +369,15 @@ void DivPlatformYM2610::acquire_ymfm(short** buf, size_t len) { } for (size_t h=0; h OPN + ay->runDAC(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); + os[0]=0; os[1]=0; if (!writes.empty()) { if (--delay<1 && !(fm->read(0)&0x80)) { @@ -979,7 +997,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; - chan[c.chan].macroInit(NULL); + if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: chan[c.chan].keyOff=true; diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 28ded57d9..7d28a8016 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -320,7 +320,16 @@ void DivPlatformYM2610B::acquire_combo(short** buf, size_t len) { } for (size_t h=0; h OPN + ay->runDAC(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); os[0]=0; os[1]=0; + // Nuked part for (int i=0; i<24; i++) { if (!writes.empty()) { @@ -426,6 +435,15 @@ void DivPlatformYM2610B::acquire_ymfm(short** buf, size_t len) { } for (size_t h=0; h OPN + ay->runDAC(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); + os[0]=0; os[1]=0; if (!writes.empty()) { if (--delay<1 && !(fm->read(0)&0x80)) { @@ -1046,7 +1064,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; - chan[c.chan].macroInit(NULL); + if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: chan[c.chan].keyOff=true; diff --git a/src/engine/platform/ym2610shared.h b/src/engine/platform/ym2610shared.h index 5af52ba6f..36db018f2 100644 --- a/src/engine/platform/ym2610shared.h +++ b/src/engine/platform/ym2610shared.h @@ -250,7 +250,7 @@ class DivPlatformYM2610Base: public DivPlatformOPN { fm->set_fidelity(ymfm::OPN_FIDELITY_MED); setFlags(flags); // YM2149, 2MHz - ay=new DivPlatformAY8910(true,chipClock,32); + ay=new DivPlatformAY8910(true,chipClock,32,144); ay->init(p,3,sugRate,ayFlags); ay->toggleRegisterDump(true); return 0; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 1ca9df58c..00b4a1d2b 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -231,6 +231,11 @@ const char* cmdName[]={ "HINT_ARP_TIME", + "SNES_GLOBAL_VOL_LEFT", + "SNES_GLOBAL_VOL_RIGHT", + + "NES_LINEAR_LENGTH", + "ALWAYS_SET_VOLUME" }; @@ -412,6 +417,7 @@ void DivEngine::processRow(int i, bool afterDelay) { short effectVal=pat->data[whatRow][5+(j<<1)]; if (effectVal==-1) effectVal=0; + effectVal&=255; switch (effect) { case 0x09: // select groove pattern/speed 1 @@ -592,6 +598,7 @@ void DivEngine::processRow(int i, bool afterDelay) { short effectVal=pat->data[whatRow][5+(j<<1)]; if (effectVal==-1) effectVal=0; + effectVal&=255; // per-system effect if (!perSystemEffect(i,effect,effectVal)) switch (effect) { @@ -680,6 +687,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].inPorta=false; dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); } else { + chan[i].lastPorta=effectVal; calledPorta=true; if (chan[i].note==chan[i].oldNote && !chan[i].inPorta && song.buggyPortaAfterSlide) { chan[i].portaNote=chan[i].note; @@ -700,11 +708,78 @@ void DivEngine::processRow(int i, bool afterDelay) { } break; case 0x04: // vibrato + if (effectVal) chan[i].lastVibrato=effectVal; chan[i].vibratoDepth=effectVal&15; chan[i].vibratoRate=effectVal>>4; dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO,i,chan[i].vibratoDepth,chan[i].vibratoRate)); dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); break; + case 0x05: // vol slide + vibrato + if (effectVal==0) { + chan[i].vibratoDepth=0; + chan[i].vibratoRate=0; + } else { + chan[i].vibratoDepth=chan[i].lastVibrato&15; + chan[i].vibratoRate=chan[i].lastVibrato>>4; + } + dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO,i,chan[i].vibratoDepth,chan[i].vibratoRate)); + dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); + // TODO: non-0x-or-x0 value should be treated as 00 + if (effectVal!=0) { + if ((effectVal&15)!=0) { + chan[i].volSpeed=-(effectVal&15)*64; + } else { + chan[i].volSpeed=(effectVal>>4)*64; + } + // tremolo and vol slides are incompatible + chan[i].tremoloDepth=0; + chan[i].tremoloRate=0; + } else { + chan[i].volSpeed=0; + } + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); + break; + case 0x06: // vol slide + porta + if (effectVal==0 || chan[i].lastPorta==0) { + chan[i].portaNote=-1; + chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); + chan[i].inPorta=false; + dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); + } else { + calledPorta=true; + if (chan[i].note==chan[i].oldNote && !chan[i].inPorta && song.buggyPortaAfterSlide) { + chan[i].portaNote=chan[i].note; + chan[i].portaSpeed=-1; + } else { + chan[i].portaNote=chan[i].note; + chan[i].portaSpeed=chan[i].lastPorta; + chan[i].inPorta=true; + chan[i].wasShorthandPorta=false; + } + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); + chan[i].portaStop=true; + if (chan[i].keyOn) chan[i].doNote=false; + chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?! + chan[i].scheduledSlideReset=false; + dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,1)); + lastSlide=0x1337; // i hate this so much + } + // TODO: non-0x-or-x0 value should be treated as 00 + if (effectVal!=0) { + if ((effectVal&15)!=0) { + chan[i].volSpeed=-(effectVal&15)*64; + } else { + chan[i].volSpeed=(effectVal>>4)*64; + } + // tremolo and vol slides are incompatible + chan[i].tremoloDepth=0; + chan[i].tremoloRate=0; + } else { + chan[i].volSpeed=0; + } + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); + break; case 0x07: // tremolo // TODO // this effect is really weird. i thought it would alter the tremolo depth but turns out it's completely different @@ -991,6 +1066,7 @@ void DivEngine::processRow(int i, bool afterDelay) { short effectVal=pat->data[whatRow][5+(j<<1)]; if (effectVal==-1) effectVal=0; + effectVal&=255; perSystemPostEffect(i,effect,effectVal); } } @@ -1125,25 +1201,47 @@ void DivEngine::nextRow() { bool wantPreNote=false; if (disCont[dispatchOfChan[i]].dispatch!=NULL) { wantPreNote=disCont[dispatchOfChan[i]].dispatch->getWantPreNote(); - if (wantPreNote) dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks)); + if (wantPreNote) { + int addition=0; + for (int j=0; jdata[curRow][4+(j<<1)]==0xed) { + if (pat->data[curRow][5+(j<<1)]>0) { + addition=pat->data[curRow][5+(j<<1)]&255; + break; + } + } + } + dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks+addition)); + } } if (song.oneTickCut) { bool doPrepareCut=true; + int addition=0; for (int j=0; jdata[curRow][4+(j<<1)]==0x03) { doPrepareCut=false; break; } + if (pat->data[curRow][4+(j<<1)]==0x06) { + doPrepareCut=false; + break; + } if (pat->data[curRow][4+(j<<1)]==0xea) { if (pat->data[curRow][5+(j<<1)]>0) { doPrepareCut=false; break; } } + if (pat->data[curRow][4+(j<<1)]==0xed) { + if (pat->data[curRow][5+(j<<1)]>0) { + addition=pat->data[curRow][5+(j<<1)]&255; + break; + } + } } - if (doPrepareCut && !wantPreNote && chan[i].cut<=0) chan[i].cut=ticks; + if (doPrepareCut && !wantPreNote && chan[i].cut<=0) chan[i].cut=ticks+addition; } } } @@ -1219,11 +1317,6 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { if (--subticks<=0) { subticks=tickMult; - // MIDI clock - if (output) if (!skipping && output->midiOut!=NULL && midiOutClock) { - output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0)); - } - if (stepPlay!=1) { tempoAccum+=curSubSong->virtualTempoN; while (tempoAccum>=curSubSong->virtualTempoD) { @@ -1293,10 +1386,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; + while (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64; chan[i].vibratoPosGiant+=chan[i].vibratoRate; - if (chan[i].vibratoPos>=512) chan[i].vibratoPos-=512; + while (chan[i].vibratoPos>=512) chan[i].vibratoPos-=512; switch (chan[i].vibratoDir) { case 1: // up @@ -1453,6 +1546,147 @@ int DivEngine::getBufferPos() { return bufferPos>>MASTER_CLOCK_PREC; } +void DivEngine::runMidiClock(int totalCycles) { + if (freelance) return; + midiClockCycles-=totalCycles; + while (midiClockCycles<=0) { + curMidiClock++; + if (output) if (!skipping && output->midiOut!=NULL && midiOutClock) { + output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0)); + } + + double hl=curSubSong->hilightA; + if (hl<=0.0) hl=4.0; + double timeBase=curSubSong->timeBase+1; + double speedSum=0; + double vD=curSubSong->virtualTempoD; + for (int i=0; ivirtualTempoN/vD; + + midiClockCycles+=got.rate*pow(2,MASTER_CLOCK_PREC)/(bpm); + midiClockDrift+=fmod(got.rate*pow(2,MASTER_CLOCK_PREC),(double)(bpm)); + if (midiClockDrift>=(bpm)) { + midiClockDrift-=(bpm); + midiClockCycles++; + } + } +} + +void DivEngine::runMidiTime(int totalCycles) { + if (freelance) return; + midiTimeCycles-=totalCycles; + while (midiTimeCycles<=0) { + if (curMidiTimePiece==0) { + curMidiTimeCode=curMidiTime; + } + if (!(curMidiTimePiece&3)) curMidiTime++; + + double frameRate=96.0; + int timeRate=midiOutTimeRate; + if (timeRate<1 || timeRate>4) { + if (curSubSong->hz>=47.98 && curSubSong->hz<=48.02) { + timeRate=1; + } else if (curSubSong->hz>=49.98 && curSubSong->hz<=50.02) { + timeRate=2; + } else if (curSubSong->hz>=59.9 && curSubSong->hz<=60.11) { + timeRate=4; + } else { + timeRate=4; + } + } + + int hour=0; + int minute=0; + int second=0; + int frame=0; + int drop=0; + int actualTime=curMidiTimeCode; + + switch (timeRate) { + case 1: // 24 + frameRate=96.0; + hour=(actualTime/(60*60*24))%24; + minute=(actualTime/(60*24))%60; + second=(actualTime/24)%60; + frame=actualTime%24; + break; + case 2: // 25 + frameRate=100.0; + hour=(actualTime/(60*60*25))%24; + minute=(actualTime/(60*25))%60; + second=(actualTime/25)%60; + frame=actualTime%25; + break; + case 3: // 29.97 (NTSC drop) + frameRate=120.0*(1000.0/1001.0); + + // drop + drop=((actualTime/(30*60))-(actualTime/(30*600)))*2; + actualTime+=drop; + + hour=(actualTime/(60*60*30))%24; + minute=(actualTime/(60*30))%60; + second=(actualTime/30)%60; + frame=actualTime%30; + break; + case 4: // 30 (NTSC non-drop) + default: + frameRate=120.0; + hour=(actualTime/(60*60*30))%24; + minute=(actualTime/(60*30))%60; + second=(actualTime/30)%60; + frame=actualTime%30; + break; + } + + if (output) if (!skipping && output->midiOut!=NULL && midiOutTime) { + unsigned char val=0; + switch (curMidiTimePiece) { + case 0: + val=frame&15; + break; + case 1: + val=frame>>4; + break; + case 2: + val=second&15; + break; + case 3: + val=second>>4; + break; + case 4: + val=minute&15; + break; + case 5: + val=minute>>4; + break; + case 6: + val=hour&15; + break; + case 7: + val=(hour>>4)|((timeRate-1)<<1); + break; + } + val|=curMidiTimePiece<<4; + output->midiOut->send(TAMidiMessage(TA_MIDI_MTC_FRAME,val,0)); + } + curMidiTimePiece=(curMidiTimePiece+1)&7; + + midiTimeCycles+=got.rate*pow(2,MASTER_CLOCK_PREC)/(frameRate); + midiTimeDrift+=fmod(got.rate*pow(2,MASTER_CLOCK_PREC),(double)(frameRate)); + if (midiTimeDrift>=(frameRate)) { + midiTimeDrift-=(frameRate); + midiTimeCycles++; + } + } +} + void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size) { lastLoopPos=-1; @@ -1541,10 +1775,13 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi samp_temp=0; } else { samp_temp=s->data16[sPreview.pos]; - if (sPreview.dir) { - sPreview.pos--; - } else { - sPreview.pos++; + if (--sPreview.posSub<=0) { + sPreview.posSub=sPreview.rateMul; + if (sPreview.dir) { + sPreview.pos--; + } else { + sPreview.pos++; + } } } blip_add_delta(samp_bb,i,samp_temp-samp_prevSample); @@ -1649,8 +1886,11 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi } else { samp_temp=((MIN(wave->data[sPreview.pos],wave->max)<<14)/wave->max)-8192; } - if (++sPreview.pos>=wave->len) { - sPreview.pos=0; + if (--sPreview.posSub<=0) { + sPreview.posSub=sPreview.rateMul; + if (++sPreview.pos>=wave->len) { + sPreview.pos=0; + } } blip_add_delta(samp_bb,i,samp_temp-samp_prevSample); samp_prevSample=samp_temp; @@ -1731,7 +1971,18 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi pendingMetroTick=0; } } else { - // 3. tick the clock and fill buffers as needed + // 3. run MIDI clock + int midiTotal=MIN(cycles,runLeftG); + for (int i=0; i #include #ifdef HAVE_SNDFILE @@ -444,6 +445,35 @@ bool DivSample::save(const char* path) { #endif } +bool DivSample::saveRaw(const char* path) { + if (samples<1) { + logE("sample is empty though!"); + return false; + } + + FILE* f=ps_fopen(path,"wb"); + if (f==NULL) { + logE("could not save sample: %s!",strerror(errno)); + return false; + } + if (depth==DIV_SAMPLE_DEPTH_BRR) { + if (isLoopable()) { + unsigned short loopPos=getLoopStartPosition(DIV_SAMPLE_DEPTH_BRR); + fputc(loopPos&0xff,f); + fputc(loopPos>>8,f); + } else { + fputc(0,f); + fputc(0,f); + } + } + + if (fwrite(getCurBuf(),1,getCurBufLen(),f)!=getCurBufLen()) { + logW("did not write entire instrument!"); + } + fclose(f); + return true; +} + // 16-bit memory is padded to 512, to make things easier for ADPCM-A/B. bool DivSample::initInternal(DivSampleDepth d, int count) { switch (d) { @@ -455,9 +485,9 @@ bool DivSample::initInternal(DivSampleDepth d, int count) { break; case DIV_SAMPLE_DEPTH_1BIT_DPCM: // DPCM if (dataDPCM!=NULL) delete[] dataDPCM; - lengthDPCM=(count+7)/8; + lengthDPCM=1+((((count+7)/8)+15)&(~15)); dataDPCM=new unsigned char[lengthDPCM]; - memset(dataDPCM,0,lengthDPCM); + memset(dataDPCM,0xaa,lengthDPCM); break; case DIV_SAMPLE_DEPTH_YMZ_ADPCM: // YMZ ADPCM if (dataZ!=NULL) delete[] dataZ; @@ -554,7 +584,34 @@ bool DivSample::strip(unsigned int begin, unsigned int end) { if (begin>samples) begin=samples; if (end>samples) end=samples; int count=samples-(end-begin); - if (count<=0) return resize(0); + if (count<=0) { + loopStart=-1; + loopEnd=-1; + loop=false; + return resize(0); + } + if (loopStart>(int)begin && loopEnd<(int)end) { + loopStart=-1; + loopEnd=-1; + loop=false; + } else { + if (loopStart<(int)end && loopStart>(int)begin) { + loopStart=end; + } + if (loopStart>(int)begin && loopEnd>(int)begin) { + loopStart-=end-begin; + loopEnd-=end-begin; + if (loopEnd<0) loopEnd=0; + if (loopStart<0) loopStart=0; + } else if (loopEnd>(int)begin) { + loopEnd=begin; + } + } + if (loopStart>loopEnd) { + loopStart=-1; + loopEnd=-1; + loop=false; + } if (depth==DIV_SAMPLE_DEPTH_8BIT) { if (data8!=NULL) { signed char* oldData8=data8; @@ -599,6 +656,16 @@ bool DivSample::trim(unsigned int begin, unsigned int end) { int count=end-begin; if (count==0) return true; if (begin==0 && end==samples) return true; + if (((int)beginloopEnd && (int)end>loopEnd)) { + loopStart=-1; + loopEnd=-1; + loop=false; + } else { + loopStart-=begin; + loopEnd-=begin; + if (loopStart<0) loopStart=0; + if (loopEnd>count) loopEnd=count; + } if (depth==DIV_SAMPLE_DEPTH_8BIT) { if (data8!=NULL) { signed char* oldData8=data8; @@ -669,9 +736,43 @@ bool DivSample::insert(unsigned int pos, unsigned int length) { return false; } +void DivSample::convert(DivSampleDepth newDepth) { + render(); + depth=newDepth; + switch (depth) { + case DIV_SAMPLE_DEPTH_1BIT: + setSampleCount((samples+7)&(~7)); + break; + case DIV_SAMPLE_DEPTH_1BIT_DPCM: + setSampleCount((1+((((samples+7)/8)+15)&(~15)))<<3); + break; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: + setSampleCount(((lengthZ+3)&(~0x03))*2); + break; + case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: // QSound ADPCM + setSampleCount((samples+1)&(~1)); + break; + case DIV_SAMPLE_DEPTH_ADPCM_A: // ADPCM-A + setSampleCount((samples+1)&(~1)); + break; + case DIV_SAMPLE_DEPTH_ADPCM_B: // ADPCM-B + setSampleCount((samples+1)&(~1)); + break; + case DIV_SAMPLE_DEPTH_BRR: // BRR + setSampleCount(16*(lengthBRR/9)); + break; + case DIV_SAMPLE_DEPTH_VOX: // VOX + setSampleCount((samples+1)&(~1)); + break; + default: + break; + } + render(); +} + #define RESAMPLE_BEGIN \ if (samples<1) return true; \ - int finalCount=(double)samples*(r/(double)rate); \ + int finalCount=(double)samples*(tRate/sRate); \ signed char* oldData8=data8; \ short* oldData16=data16; \ if (depth==DIV_SAMPLE_DEPTH_16BIT) { \ @@ -689,10 +790,10 @@ bool DivSample::insert(unsigned int pos, unsigned int length) { } #define RESAMPLE_END \ - if (loopStart>=0) loopStart=(double)loopStart*(r/(double)rate); \ - if (loopEnd>=0) loopEnd=(double)loopEnd*(r/(double)rate); \ - centerRate=(int)((double)centerRate*(r/(double)rate)); \ - rate=r; \ + if (loopStart>=0) loopStart=(double)loopStart*(tRate/sRate); \ + if (loopEnd>=0) loopEnd=(double)loopEnd*(tRate/sRate); \ + centerRate=(int)((double)centerRate*(tRate/sRate)); \ + rate=(int)((double)rate*(tRate/sRate)); \ samples=finalCount; \ if (depth==DIV_SAMPLE_DEPTH_16BIT) { \ delete[] oldData16; \ @@ -700,12 +801,12 @@ bool DivSample::insert(unsigned int pos, unsigned int length) { delete[] oldData8; \ } -bool DivSample::resampleNone(double r) { +bool DivSample::resampleNone(double sRate, double tRate) { RESAMPLE_BEGIN; if (depth==DIV_SAMPLE_DEPTH_16BIT) { for (int i=0; i=samples) { data16[i]=0; } else { @@ -714,7 +815,7 @@ bool DivSample::resampleNone(double r) { } } else if (depth==DIV_SAMPLE_DEPTH_8BIT) { for (int i=0; i=samples) { data8[i]=0; } else { @@ -727,12 +828,12 @@ bool DivSample::resampleNone(double r) { return true; } -bool DivSample::resampleLinear(double r) { +bool DivSample::resampleLinear(double sRate, double tRate) { RESAMPLE_BEGIN; double posFrac=0; unsigned int posInt=0; - double factor=(double)rate/r; + double factor=sRate/tRate; if (depth==DIV_SAMPLE_DEPTH_16BIT) { for (int i=0; i127) result=127; - data16[i]=round(result); + data8[i]=round(result); } } delete[] floatData; @@ -904,12 +1005,12 @@ bool DivSample::resampleBlep(double r) { return true; } -bool DivSample::resampleSinc(double r) { +bool DivSample::resampleSinc(double sRate, double tRate) { RESAMPLE_BEGIN; double posFrac=0; unsigned int posInt=0; - double factor=(double)rate/r; + double factor=sRate/tRate; float* sincTable=DivFilterTables::getSincTable(); float s[16]; @@ -971,29 +1072,29 @@ bool DivSample::resampleSinc(double r) { return true; } -bool DivSample::resample(double r, int filter) { +bool DivSample::resample(double sRate, double tRate, int filter) { if (depth!=DIV_SAMPLE_DEPTH_8BIT && depth!=DIV_SAMPLE_DEPTH_16BIT) return false; switch (filter) { case DIV_RESAMPLE_NONE: - return resampleNone(r); + return resampleNone(sRate,tRate); break; case DIV_RESAMPLE_LINEAR: - return resampleLinear(r); + return resampleLinear(sRate,tRate); break; case DIV_RESAMPLE_CUBIC: - return resampleCubic(r); + return resampleCubic(sRate,tRate); break; case DIV_RESAMPLE_BLEP: - return resampleBlep(r); + return resampleBlep(sRate,tRate); break; case DIV_RESAMPLE_SINC: - return resampleSinc(r); + return resampleSinc(sRate,tRate); break; case DIV_RESAMPLE_BEST: - if (r>rate) { - return resampleSinc(r); + if (tRate>sRate) { + return resampleSinc(sRate,tRate); } else { - return resampleBlep(r); + return resampleBlep(sRate,tRate); } break; } @@ -1062,12 +1163,14 @@ void DivSample::render(unsigned int formatMask) { if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_1BIT_DPCM)) { // DPCM if (!initInternal(DIV_SAMPLE_DEPTH_1BIT_DPCM,samples)) return; int accum=63; + int next=63; for (unsigned int i=0; i>9; + next=((unsigned short)(data16[i]^0x8000))>>9; if (next>accum) { dataDPCM[i>>3]|=1<<(i&7); accum++; } else { + dataDPCM[i>>3]&=~(1<<(i&7)); accum--; } if (accum<0) accum=0; @@ -1098,8 +1201,9 @@ void DivSample::render(unsigned int formatMask) { } } if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_BRR)) { // BRR - if (!initInternal(DIV_SAMPLE_DEPTH_BRR,samples)) return; - brrEncode(data16,dataBRR,samples,loop?loopStart:-1,brrEmphasis); + int sampleCount=loop?loopEnd:samples; + if (!initInternal(DIV_SAMPLE_DEPTH_BRR,sampleCount)) return; + brrEncode(data16,dataBRR,sampleCount,loop?loopStart:-1,brrEmphasis); } if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_VOX)) { // VOX if (!initInternal(DIV_SAMPLE_DEPTH_VOX,samples)) return; diff --git a/src/engine/sample.h b/src/engine/sample.h index 76eaee9a2..c2f08ced6 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -188,11 +188,11 @@ struct DivSample { /** * @warning DO NOT USE - internal functions */ - bool resampleNone(double rate); - bool resampleLinear(double rate); - bool resampleCubic(double rate); - bool resampleBlep(double rate); - bool resampleSinc(double rate); + bool resampleNone(double sRate, double tRate); + bool resampleLinear(double sRate, double tRate); + bool resampleCubic(double sRate, double tRate); + bool resampleBlep(double sRate, double tRate); + bool resampleSinc(double sRate, double tRate); /** * save this sample to a file. @@ -201,6 +201,13 @@ struct DivSample { */ bool save(const char* path); + /** + * save this sample to a file (raw). + * @param path a path. + * @return whether saving succeeded or not. + */ + bool saveRaw(const char* path); + /** * @warning DO NOT USE - internal function * initialize sample data. @@ -255,11 +262,19 @@ struct DivSample { /** * change the sample rate. * @warning do not attempt to resample outside of a synchronized block! - * @param rate number of samples. + * @param sRate source rate. + * @param tRate target rate. * @param filter the interpolation filter. * @return whether it was successful. */ - bool resample(double rate, int filter); + bool resample(double sRate, double tRate, int filter); + + /** + * convert sample depth. + * @warning do not attempt to do this outside of a synchronized block! + * @param newDepth the new depth. + */ + void convert(DivSampleDepth newDepth); /** * initialize the rest of sample formats for this sample. diff --git a/src/engine/song.h b/src/engine/song.h index 8b6da13b2..fd2d1e07d 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -130,6 +130,14 @@ enum DivSystem { DIV_SYSTEM_PV1000 }; +enum DivEffectType: unsigned short { + DIV_EFFECT_NULL=0, + DIV_EFFECT_DUMMY, + DIV_EFFECT_EXTERNAL, + DIV_EFFECT_VOLUME, + DIV_EFFECT_FILTER +}; + struct DivGroovePattern { unsigned char val[16]; unsigned char len; @@ -191,6 +199,21 @@ struct DivAssetDir { name(n) {} }; +struct DivEffectStorage { + DivEffectType id; + unsigned short slot, storageVer; + float dryWet; + unsigned char* storage; + size_t storageLen; + DivEffectStorage(): + id(DIV_EFFECT_NULL), + slot(0), + storageVer(0), + dryWet(1.0f), + storage(NULL), + storageLen(0) {} +}; + struct DivSong { // version number used for saving the song. // Furnace will save using the latest possible version, @@ -352,6 +375,7 @@ struct DivSong { bool oldArpStrategy; bool patchbayAuto; bool brokenPortaLegato; + bool brokenFMOff; std::vector ins; std::vector wave; @@ -365,6 +389,8 @@ struct DivSong { std::vector waveDir; std::vector sampleDir; + std::vector effects; + DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsOPLDrums, nullInsQSound; DivWavetable nullWave; DivSample nullSample; @@ -467,7 +493,8 @@ struct DivSong { autoSystem(true), oldArpStrategy(false), patchbayAuto(true), - brokenPortaLegato(false) { + brokenPortaLegato(false), + brokenFMOff(false) { for (int i=0; i,effectVal}}, {0x31, {DIV_CMD_SNES_ECHO_FIR, "31xx: Set echo filter coefficient 1",constVal<1>,effectVal}}, {0x32, {DIV_CMD_SNES_ECHO_FIR, "32xx: Set echo filter coefficient 2",constVal<2>,effectVal}}, @@ -1839,12 +1843,16 @@ void DivEngine::registerSystems() { {"Square 1", "Square 2", "Square 3"}, {"S1", "S2", "S3"}, {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, - {DIV_INS_PV1000, DIV_INS_PV1000, DIV_INS_PV1000} + {DIV_INS_PV1000, DIV_INS_PV1000, DIV_INS_PV1000}, + {}, + { + {0x10, {DIV_CMD_STD_NOISE_MODE, "10xx: Set ring modulation (0: disable, 1: enable)"}} + } ); sysDefs[DIV_SYSTEM_SFX_BEEPER_QUADTONE]=new DivSysDef( "ZX Spectrum Beeper (QuadTone Engine)", NULL, 0xca, 0, 5, false, true, 0, false, 1U<writeC(0x95); w->writeC(streamID); w->writeS(write.val); // sample number - w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0)|(sampleDir[streamID]?0x10:0)); // flags + w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())|(sampleDir[streamID]?0x10:0)); // flags if (sample->isLoopable() && !sampleDir[streamID]) { loopTimer[streamID]=sample->length8; loopSample[streamID]=write.val; @@ -610,7 +610,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(0x95); w->writeC(streamID); w->writeS(pendingFreq[streamID]); // sample number - w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0)|(sampleDir[streamID]?0x10:0)); // flags + w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())|(sampleDir[streamID]?0x10:0)); // flags if (sample->isLoopable() && !sampleDir[streamID]) { loopTimer[streamID]=sample->length8; loopSample[streamID]=pendingFreq[streamID]; @@ -1068,6 +1068,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p bool trailing=false; bool beenOneLoopAlready=false; + bool mayWriteRate=(fmod(curSubSong->hz,1.0)<0.00001 || fmod(curSubSong->hz,1.0)>0.99999); int countDown=MAX(0,trailingTicks)+1; for (int i=0; iwriteI(hasSN); w->writeI(hasOPLL); @@ -2094,6 +2098,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p if (nextTick(false,true)) { if (trailing) beenOneLoopAlready=true; trailing=true; + if (!loop) countDown=0; for (int i=0; i16) { w->writeC(0x61); - w->writeS(totalWait); + w->writeS(delay); } else if (delay>0) { w->writeC(0x70+delay-1); } @@ -2359,6 +2364,9 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p w->writeI(0); w->writeI(0); } + if (mayWriteRate) { + w->writeI(round(curSubSong->hz)); + } w->seek(0x34,SEEK_SET); w->writeI(songOff-0x34); if (version>=0x170) { diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 7c89bbe31..fa020b98a 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -78,11 +78,13 @@ const char* aboutLine[]={ "Dippy", "djtuBIG-MaliceX", "dumbut", + "ElectricKeet", "EpicTyphlosion", "FΛDE", "Forte", "Fragmare", "freq-mod", + "gtr3qq", "iyatemu", "JayBOB18", "Jimmy-DS", diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index de2413e0a..f1b16c639 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -180,6 +180,10 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("behavior changed in 0.6pre4"); } + ImGui::Checkbox("Broken macros in some FM chips after note off",&e->song.brokenFMOff); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.6pre5"); + } ImGui::EndTabItem(); } if (ImGui::BeginTabItem(".mod import")) { diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 2a37c7e8e..72269988a 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -22,13 +22,416 @@ #include "misc/cpp/imgui_stdlib.h" #include "plot_nolerp.h" #include "guiConst.h" +#include "../ta-log.h" #include #include +#include const char* sampleNote[12]={ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; +#define DRAG_SOURCE(_d,_a,_c) \ + if (ImGui::BeginDragDropSource()) { \ + dirToMove=_d; \ + assetToMove=_a; \ + ImGui::SetDragDropPayload(_c,NULL,0,ImGuiCond_Once); \ + ImGui::Button(ICON_FA_ARROWS "##AssetDrag"); \ + ImGui::EndDragDropSource(); \ + } + +#define DRAG_TARGET(_d,_a,_type,_c) \ + if (ImGui::BeginDragDropTarget()) { \ + const ImGuiPayload* dragItem=ImGui::AcceptDragDropPayload(_c); \ + if (dragItem!=NULL) { \ + if (dragItem->IsDataType(_c)) { \ + if (assetToMove==-1) { \ + if (dirToMove!=_d && _a==-1) { \ + e->lockEngine([&]() { \ + DivAssetDir val=_type[dirToMove]; \ + _type.erase(_type.begin()+dirToMove); \ + _type.insert(_type.begin()+_d,val); \ + }); \ + } \ + } else { \ + if (dirToMove!=_d || assetToMove!=_a) { \ + logV("%d/%d -> %d/%d",dirToMove,assetToMove,_d,_a); \ + e->lockEngine([&]() { \ + int val=_type[dirToMove].entries[assetToMove]; \ + _type[dirToMove].entries.erase(_type[dirToMove].entries.begin()+assetToMove); \ + _type[_d].entries.insert((_a<0)?(_type[_d].entries.end()):(_type[_d].entries.begin()+_a),val); \ + }); \ + } \ + } \ + dirToMove=-1; \ + assetToMove=-1; \ + } \ + } \ + ImGui::EndDragDropTarget(); \ + } + +void FurnaceGUI::insListItem(int i, int dir, int asset) { + ImGui::PushID(i); + String name=ICON_FA_CIRCLE_O; + const char* insType="Bug!"; + if (i>=0 && isong.insLen) { + DivInstrument* ins=e->song.ins[i]; + insType=(ins->type>DIV_INS_MAX)?"Unknown":insTypes[ins->type]; + if (ins->type==DIV_INS_N163) insType=settings.c163Name.c_str(); + switch (ins->type) { + case DIV_INS_FM: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FM]); + name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i); + break; + case DIV_INS_STD: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_STD]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; + case DIV_INS_GB: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_GB]); + name=fmt::sprintf(ICON_FA_GAMEPAD "##_INS%d",i); + break; + case DIV_INS_C64: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_C64]); + name=fmt::sprintf(ICON_FA_KEYBOARD_O "##_INS%d",i); + break; + case DIV_INS_AMIGA: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AMIGA]); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); + break; + case DIV_INS_PCE: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PCE]); + name=fmt::sprintf(ICON_FA_ID_BADGE "##_INS%d",i); + break; + case DIV_INS_AY: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; + case DIV_INS_AY8930: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY8930]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; + case DIV_INS_TIA: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_TIA]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; + case DIV_INS_SAA1099: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SAA1099]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; + case DIV_INS_VIC: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VIC]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; + case DIV_INS_PET: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PET]); + name=fmt::sprintf(ICON_FA_SQUARE "##_INS%d",i); + break; + case DIV_INS_VRC6: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; + case DIV_INS_VRC6_SAW: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6_SAW]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; + case DIV_INS_OPLL: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPLL]); + name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i); + break; + case DIV_INS_OPL: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPL]); + name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i); + break; + case DIV_INS_FDS: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FDS]); + name=fmt::sprintf(ICON_FA_FLOPPY_O "##_INS%d",i); + break; + case DIV_INS_VBOY: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VBOY]); + name=fmt::sprintf(ICON_FA_BINOCULARS "##_INS%d",i); + break; + case DIV_INS_N163: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_N163]); + name=fmt::sprintf(ICON_FA_CALCULATOR "##_INS%d",i); + break; + case DIV_INS_SCC: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SCC]); + name=fmt::sprintf(ICON_FA_CALCULATOR "##_INS%d",i); + break; + case DIV_INS_OPZ: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPZ]); + name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i); + break; + case DIV_INS_POKEY: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_POKEY]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; + case DIV_INS_BEEPER: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_BEEPER]); + name=fmt::sprintf(ICON_FA_SQUARE "##_INS%d",i); + break; + case DIV_INS_SWAN: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SWAN]); + name=fmt::sprintf(ICON_FA_GAMEPAD "##_INS%d",i); + break; + case DIV_INS_MIKEY: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MIKEY]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; + case DIV_INS_VERA: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VERA]); + name=fmt::sprintf(ICON_FA_KEYBOARD_O "##_INS%d",i); + break; + case DIV_INS_X1_010: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_X1_010]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; + case DIV_INS_ES5506: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_ES5506]); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); + break; + case DIV_INS_MULTIPCM: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MULTIPCM]); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); + break; + case DIV_INS_SNES: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SNES]); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); + break; + case DIV_INS_SU: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SU]); + name=fmt::sprintf(ICON_FA_MICROCHIP "##_INS%d",i); + break; + case DIV_INS_NAMCO: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_NAMCO]); + name=fmt::sprintf(ICON_FA_PIE_CHART "##_INS%d",i); + break; + case DIV_INS_OPL_DRUMS: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPL_DRUMS]); + name=fmt::sprintf(ICON_FA_COFFEE "##_INS%d",i); + break; + case DIV_INS_OPM: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPM]); + name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i); + break; + case DIV_INS_NES: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_NES]); + name=fmt::sprintf(ICON_FA_GAMEPAD "##_INS%d",i); + break; + case DIV_INS_MSM6258: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MSM6258]); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); + break; + case DIV_INS_MSM6295: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MSM6295]); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); + break; + case DIV_INS_ADPCMA: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_ADPCMA]); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); + break; + case DIV_INS_ADPCMB: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_ADPCMB]); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); + break; + case DIV_INS_SEGAPCM: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SEGAPCM]); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); + break; + case DIV_INS_QSOUND: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_QSOUND]); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); + break; + case DIV_INS_YMZ280B: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_YMZ280B]); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); + break; + case DIV_INS_RF5C68: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_RF5C68]); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); + break; + case DIV_INS_MSM5232: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MSM5232]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; + case DIV_INS_T6W28: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_T6W28]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; + case DIV_INS_K007232: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_K007232]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; + case DIV_INS_GA20: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_GA20]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; + case DIV_INS_POKEMINI: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_POKEMINI]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; + case DIV_INS_SM8521: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SM8521]); + name=fmt::sprintf(ICON_FA_GAMEPAD "##_INS%d",i); + break; + case DIV_INS_PV1000: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PV1000]); + name=fmt::sprintf(ICON_FA_GAMEPAD "##_INS%d",i); + break; + default: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); + name=fmt::sprintf(ICON_FA_QUESTION "##_INS%d",i); + break; + } + } else { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); + } + if (ImGui::Selectable(name.c_str(),(i==-1)?(curIns<0 || curIns>=e->song.insLen):(curIns==i))) { + curIns=i; + wavePreviewInit=true; + updateFMPreview=true; + lastAssetType=0; + if (insListDir) nextWindow=GUI_WINDOW_PATTERN; + } + if (wantScrollList && curIns==i) ImGui::SetScrollHereY(); + if (settings.insFocusesPattern && patternOpen && ImGui::IsItemActivated()) { + if (!insListDir) nextWindow=GUI_WINDOW_PATTERN; + curIns=i; + wavePreviewInit=true; + updateFMPreview=true; + lastAssetType=0; + } + if (ImGui::IsItemHovered() && i>=0 && !mobileUI) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); + ImGui::SetTooltip("%s",insType); + ImGui::PopStyleColor(); + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + insEditOpen=true; + nextWindow=GUI_WINDOW_INS_EDIT; + } + } + if (i>=0) { + if (insListDir) { + DRAG_SOURCE(dir,asset,"FUR_INSDIR"); + DRAG_TARGET(dir,asset,e->song.insDir,"FUR_INSDIR"); + } + + if (ImGui::BeginPopupContextItem("InsRightMenu")) { + curIns=i; + updateFMPreview=true; + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); + if (ImGui::MenuItem("replace...")) { + doAction((curIns>=0 && curIns<(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN); + } + if (ImGui::MenuItem("save")) { + doAction(GUI_ACTION_INS_LIST_SAVE); + } + if (ImGui::MenuItem("save (legacy .fui)")) { + doAction(GUI_ACTION_INS_LIST_SAVE_OLD); + } + if (ImGui::MenuItem("save (.dmp)")) { + doAction(GUI_ACTION_INS_LIST_SAVE_DMP); + } + if (ImGui::MenuItem("delete")) { + doAction(GUI_ACTION_INS_LIST_DELETE); + } + ImGui::PopStyleColor(); + ImGui::EndPopup(); + } + } + if (i>=0) { + if (i<(int)e->song.ins.size()) { + DivInstrument* ins=e->song.ins[i]; + ImGui::SameLine(); + ImGui::Text("%.2X: %s",i,ins->name.c_str()); + } else { + ImGui::SameLine(); + ImGui::Text("%.2X: ",i); + } + } else { + ImGui::SameLine(); + ImGui::Text("- None -"); + } + ImGui::PopID(); + ImGui::PopStyleColor(); +} + +void FurnaceGUI::waveListItem(int i, float* wavePreview, int dir, int asset) { + DivWavetable* wave=e->song.wave[i]; + for (int i=0; ilen; i++) { + wavePreview[i]=wave->data[i]; + } + if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; + if (ImGui::Selectable(fmt::sprintf("%d##_WAVE%d\n",i,i).c_str(),curWave==i)) { + curWave=i; + lastAssetType=1; + } + if (wantScrollList && curWave==i) ImGui::SetScrollHereY(); + if (ImGui::IsItemHovered()) { + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + waveEditOpen=true; + nextWindow=GUI_WINDOW_WAVE_EDIT; + } + } + if (waveListDir || (settings.unifiedDataView && insListDir)) { + DRAG_SOURCE(dir,asset,"FUR_WAVEDIR"); + DRAG_TARGET(dir,asset,e->song.waveDir,"FUR_WAVEDIR"); + } + ImGui::SameLine(); + PlotNoLerp(fmt::sprintf("##_WAVEP%d",i).c_str(),wavePreview,wave->len+1,0,NULL,0,wave->max); +} + +void FurnaceGUI::sampleListItem(int i, int dir, int asset) { + bool memWarning=false; + + DivSample* sample=e->song.sample[i]; + for (int j=0; jsong.systemLen; j++) { + DivDispatch* dispatch=e->getDispatch(j); + if (dispatch==NULL) continue; + + for (int k=0; kgetSampleMemCapacity(k)==0) continue; + if (!dispatch->isSampleLoaded(k,i) && sample->renderOn[k][j]) { + memWarning=true; + break; + } + } + if (memWarning) break; + } + if (memWarning) ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_SAMPLE_CHIP_WARNING]); + if (ImGui::Selectable(fmt::sprintf("%d: %s##_SAM%d",i,sample->name,i).c_str(),curSample==i)) { + curSample=i; + samplePos=0; + updateSampleTex=true; + lastAssetType=2; + } + if (ImGui::IsItemHovered() && !mobileUI) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); + ImGui::SetTooltip("(legacy bank %d: %s)",i/12,sampleNote[i%12]); + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + sampleEditOpen=true; + nextWindow=GUI_WINDOW_SAMPLE_EDIT; + } + ImGui::PopStyleColor(); + } + if (sampleListDir || (settings.unifiedDataView && insListDir)) { + DRAG_SOURCE(dir,asset,"FUR_SDIR"); + DRAG_TARGET(dir,asset,e->song.sampleDir,"FUR_SDIR"); + } + if (memWarning) { + ImGui::SameLine(); + ImGui::Text(ICON_FA_EXCLAMATION_TRIANGLE); + if (ImGui::IsItemHovered() && !mobileUI) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); + ImGui::SetTooltip("out of memory for this sample!"); + ImGui::PopStyleColor(); + } + ImGui::PopStyleColor(); + } + if (wantScrollList && curSample==i) ImGui::SetScrollHereY(); +} + void FurnaceGUI::drawInsList(bool asChild) { if (nextWindow==GUI_WINDOW_INS_LIST) { insListOpen=true; @@ -45,80 +448,99 @@ void FurnaceGUI::drawInsList(bool asChild) { if (began) { if (settings.unifiedDataView) settings.horizontalDataView=0; if (ImGui::Button(ICON_FA_PLUS "##InsAdd")) { - if (!settings.unifiedDataView) doAction(GUI_ACTION_INS_LIST_ADD); + if (settings.unifiedDataView) { + switch (lastAssetType) { + case 0: + doAction(GUI_ACTION_INS_LIST_ADD); + break; + case 1: + doAction(GUI_ACTION_WAVE_LIST_ADD); + break; + case 2: + doAction(GUI_ACTION_SAMPLE_LIST_ADD); + break; + } + } else { + doAction(GUI_ACTION_INS_LIST_ADD); + } } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Add"); } - if (settings.unifiedDataView) { - if (ImGui::BeginPopupContextItem("UnifiedAdd",ImGuiMouseButton_Left)) { - if (ImGui::MenuItem("instrument")) { - doAction(GUI_ACTION_INS_LIST_ADD); - } - if (ImGui::MenuItem("wavetable")) { - doAction(GUI_ACTION_WAVE_LIST_ADD); - } - if (ImGui::MenuItem("sample (create)")) { - doAction(GUI_ACTION_SAMPLE_LIST_ADD); - } - ImGui::EndPopup(); - } - } else { - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - displayInsTypeList=true; - displayInsTypeListMakeInsSample=-1; - } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + makeInsTypeList=e->getPossibleInsTypes(); + displayInsTypeList=true; + displayInsTypeListMakeInsSample=-1; } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FILES_O "##InsClone")) { - if (!settings.unifiedDataView) doAction(GUI_ACTION_INS_LIST_DUPLICATE); + if (settings.unifiedDataView) { + switch (lastAssetType) { + case 0: + doAction(GUI_ACTION_INS_LIST_DUPLICATE); + break; + case 1: + doAction(GUI_ACTION_WAVE_LIST_DUPLICATE); + break; + case 2: + doAction(GUI_ACTION_SAMPLE_LIST_DUPLICATE); + break; + } + } else { + doAction(GUI_ACTION_INS_LIST_DUPLICATE); + } } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Duplicate"); } - if (settings.unifiedDataView) { - if (ImGui::BeginPopupContextItem("UnifiedClone",ImGuiMouseButton_Left)) { - if (ImGui::MenuItem("instrument")) { - doAction(GUI_ACTION_INS_LIST_DUPLICATE); - } - if (ImGui::MenuItem("wavetable")) { - doAction(GUI_ACTION_WAVE_LIST_DUPLICATE); - } - if (ImGui::MenuItem("sample")) { - doAction(GUI_ACTION_SAMPLE_LIST_DUPLICATE); - } - ImGui::EndPopup(); - } - } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FOLDER_OPEN "##InsLoad")) { - if (!settings.unifiedDataView) doAction(GUI_ACTION_INS_LIST_OPEN); + if (settings.unifiedDataView) { + switch (lastAssetType) { + case 0: + doAction(GUI_ACTION_INS_LIST_OPEN); + break; + case 1: + doAction(GUI_ACTION_WAVE_LIST_OPEN); + break; + case 2: + doAction(GUI_ACTION_SAMPLE_LIST_OPEN); + break; + } + } else { + doAction(GUI_ACTION_INS_LIST_OPEN); + } } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Open"); } - if (settings.unifiedDataView) { - if (ImGui::BeginPopupContextItem("UnifiedLoad",ImGuiMouseButton_Left)) { - if (ImGui::MenuItem("instrument")) { - doAction(GUI_ACTION_INS_LIST_OPEN); - } - if (ImGui::MenuItem("instrument (replace...)")) { + if (ImGui::BeginPopupContextItem("InsOpenOpt")) { + if (settings.unifiedDataView) { + if (ImGui::MenuItem("replace instrument...")) { doAction((curIns>=0 && curIns<(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN); } - if (ImGui::MenuItem("wavetable")) { - doAction(GUI_ACTION_WAVE_LIST_OPEN); - } - if (ImGui::MenuItem("sample")) { - doAction(GUI_ACTION_SAMPLE_LIST_OPEN); - } - ImGui::Separator(); - if (ImGui::MenuItem("instrument from TX81Z")) { + if (ImGui::MenuItem("load instrument from TX81Z")) { doAction(GUI_ACTION_TX81Z_REQUEST); } - ImGui::EndPopup(); - } - } else { - if (ImGui::BeginPopupContextItem("InsOpenOpt")) { + + ImGui::Separator(); + + if (ImGui::MenuItem("replace wavetable...")) { + doAction((curWave>=0 && curWave<(int)e->song.wave.size())?GUI_ACTION_WAVE_LIST_OPEN_REPLACE:GUI_ACTION_WAVE_LIST_OPEN); + } + + ImGui::Separator(); + + if (ImGui::MenuItem("replace sample...")) { + doAction((curSample>=0 && curSample<(int)e->song.sample.size())?GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE:GUI_ACTION_SAMPLE_LIST_OPEN); + } + if (ImGui::MenuItem("import raw sample...")) { + doAction(GUI_ACTION_SAMPLE_LIST_OPEN_RAW); + } + if (ImGui::MenuItem("import raw sample (replace)...")) { + doAction((curSample>=0 && curSample<(int)e->song.sample.size())?GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE_RAW:GUI_ACTION_SAMPLE_LIST_OPEN_RAW); + } + } else { if (ImGui::MenuItem("replace...")) { doAction((curIns>=0 && curIns<(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN); } @@ -126,392 +548,247 @@ void FurnaceGUI::drawInsList(bool asChild) { if (ImGui::MenuItem("load from TX81Z")) { doAction(GUI_ACTION_TX81Z_REQUEST); } - ImGui::EndPopup(); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Open (insert; right-click to replace)"); } + ImGui::EndPopup(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Open (insert; right-click to replace)"); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FLOPPY_O "##InsSave")) { - if (!settings.unifiedDataView) doAction(GUI_ACTION_INS_LIST_SAVE); + if (settings.unifiedDataView) { + switch (lastAssetType) { + case 0: + doAction(GUI_ACTION_INS_LIST_SAVE); + break; + case 1: + doAction(GUI_ACTION_WAVE_LIST_SAVE); + break; + case 2: + doAction(GUI_ACTION_SAMPLE_LIST_SAVE); + break; + } + } else { + doAction(GUI_ACTION_INS_LIST_SAVE); + } } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Save"); } - if (settings.unifiedDataView) { - if (ImGui::BeginPopupContextItem("UnifiedSave",ImGuiMouseButton_Left)) { - if (ImGui::MenuItem("instrument")) { - doAction(GUI_ACTION_INS_LIST_SAVE); - } - if (ImGui::MenuItem("instrument (legacy .fui)")) { - doAction(GUI_ACTION_INS_LIST_SAVE_OLD); - } - if (ImGui::MenuItem("instrument (.dmp)")) { + if (ImGui::BeginPopupContextItem("InsSaveFormats",ImGuiMouseButton_Right)) { + if (settings.unifiedDataView) { + if (ImGui::MenuItem("save instrument as .dmp...")) { doAction(GUI_ACTION_INS_LIST_SAVE_DMP); } - if (ImGui::MenuItem("wavetable")) { - doAction(GUI_ACTION_WAVE_LIST_SAVE); - } - if (ImGui::MenuItem("wavetable (.dmw)")) { + + ImGui::Separator(); + + if (ImGui::MenuItem("save wavetable as .dmw...")) { doAction(GUI_ACTION_WAVE_LIST_SAVE_DMW); } - if (ImGui::MenuItem("wavetable (raw)")) { + if (ImGui::MenuItem("save raw wavetable...")) { doAction(GUI_ACTION_WAVE_LIST_SAVE_RAW); } - if (ImGui::MenuItem("sample")) { - doAction(GUI_ACTION_SAMPLE_LIST_SAVE); - } - ImGui::EndPopup(); - } - } else { - if (ImGui::BeginPopupContextItem("InsSaveFormats",ImGuiMouseButton_Right)) { - if (ImGui::MenuItem("save in legacy format...")) { - doAction(GUI_ACTION_INS_LIST_SAVE_OLD); + + ImGui::Separator(); + + if (ImGui::MenuItem("save raw sample...")) { + doAction(GUI_ACTION_SAMPLE_LIST_SAVE_RAW); } + } else { if (ImGui::MenuItem("save as .dmp...")) { doAction(GUI_ACTION_INS_LIST_SAVE_DMP); } - ImGui::EndPopup(); } + ImGui::EndPopup(); + } + ImGui::SameLine(); + pushToggleColors(insListDir); + if (ImGui::Button(ICON_FA_SITEMAP "##DirMode")) { + doAction(GUI_ACTION_INS_LIST_DIR_VIEW); + } + popToggleColors(); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Toggle folders/standard view"); + } + if (!insListDir) { ImGui::SameLine(); if (ImGui::ArrowButton("InsUp",ImGuiDir_Up)) { - doAction(GUI_ACTION_INS_LIST_MOVE_UP); + if (settings.unifiedDataView) { + switch (lastAssetType) { + case 0: + doAction(GUI_ACTION_INS_LIST_MOVE_UP); + break; + case 1: + doAction(GUI_ACTION_WAVE_LIST_MOVE_UP); + break; + case 2: + doAction(GUI_ACTION_SAMPLE_LIST_MOVE_UP); + break; + } + } else { + doAction(GUI_ACTION_INS_LIST_MOVE_UP); + } } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Move up"); } ImGui::SameLine(); if (ImGui::ArrowButton("InsDown",ImGuiDir_Down)) { - doAction(GUI_ACTION_INS_LIST_MOVE_DOWN); + if (settings.unifiedDataView) { + switch (lastAssetType) { + case 0: + doAction(GUI_ACTION_INS_LIST_MOVE_DOWN); + break; + case 1: + doAction(GUI_ACTION_WAVE_LIST_MOVE_DOWN); + break; + case 2: + doAction(GUI_ACTION_SAMPLE_LIST_MOVE_DOWN); + break; + } + } else { + doAction(GUI_ACTION_INS_LIST_MOVE_DOWN); + } } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Move down"); } + } else { + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FOLDER "##InsFolder")) { + folderString=""; + } + if (ImGui::BeginPopupContextItem("NewInsFolder",ImGuiMouseButton_Left)) { + ImGui::InputText("##FolderName",&folderString); + ImGui::SameLine(); + ImGui::BeginDisabled(folderString.empty()); + if (ImGui::Button("Create")) { + if (settings.unifiedDataView) { + switch (lastAssetType) { + case 0: + e->lockEngine([this]() { + e->song.insDir.push_back(DivAssetDir(folderString)); + }); + break; + case 1: + e->lockEngine([this]() { + e->song.waveDir.push_back(DivAssetDir(folderString)); + }); + break; + case 2: + e->lockEngine([this]() { + e->song.sampleDir.push_back(DivAssetDir(folderString)); + }); + break; + } + } else { + e->lockEngine([this]() { + e->song.insDir.push_back(DivAssetDir(folderString)); + }); + } + ImGui::CloseCurrentPopup(); + } + ImGui::EndDisabled(); + ImGui::EndPopup(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("New folder"); + } } ImGui::SameLine(); if (ImGui::Button(ICON_FA_TIMES "##InsDelete")) { - if (!settings.unifiedDataView) doAction(GUI_ACTION_INS_LIST_DELETE); + if (settings.unifiedDataView) { + switch (lastAssetType) { + case 0: + doAction(GUI_ACTION_INS_LIST_DELETE); + break; + case 1: + doAction(GUI_ACTION_WAVE_LIST_DELETE); + break; + case 2: + doAction(GUI_ACTION_SAMPLE_LIST_DELETE); + break; + } + } else { + doAction(GUI_ACTION_INS_LIST_DELETE); + } } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Delete"); } - if (settings.unifiedDataView) { - if (ImGui::BeginPopupContextItem("UnifiedDelete",ImGuiMouseButton_Left)) { - if (ImGui::MenuItem("instrument")) { - doAction(GUI_ACTION_INS_LIST_DELETE); - } - if (ImGui::MenuItem("wavetable")) { - doAction(GUI_ACTION_WAVE_LIST_DELETE); - } - if (ImGui::MenuItem("sample")) { - doAction(GUI_ACTION_SAMPLE_LIST_DELETE); - } - ImGui::EndPopup(); - } - } ImGui::Separator(); int availableRows=ImGui::GetContentRegionAvail().y/ImGui::GetFrameHeight(); if (availableRows<1) availableRows=1; int columns=settings.horizontalDataView?(int)(ceil((double)(e->song.ins.size()+1)/(double)availableRows)):1; if (columns<1) columns=1; if (columns>64) columns=64; + if (insListDir) columns=1; if (ImGui::BeginTable("InsListScroll",columns,(settings.horizontalDataView?ImGuiTableFlags_ScrollX:0)|ImGuiTableFlags_ScrollY)) { if (settings.unifiedDataView) { ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::Text(ICON_FA_TASKS " Instruments"); + if (ImGui::Selectable(ICON_FA_TASKS " Instruments",lastAssetType==0)) { + lastAssetType=0; + } ImGui::Indent(); } - if (settings.horizontalDataView) { + if (settings.horizontalDataView && !insListDir) { ImGui::TableNextRow(); } - int curRow=0; - for (int i=-1; i<(int)e->song.ins.size(); i++) { - ImGui::PushID(i); - String name=ICON_FA_CIRCLE_O; - const char* insType="Bug!"; - if (i>=0) { - DivInstrument* ins=e->song.ins[i]; - insType=(ins->type>DIV_INS_MAX)?"Unknown":insTypes[ins->type]; - if (ins->type==DIV_INS_N163) insType=settings.c163Name.c_str(); - switch (ins->type) { - case DIV_INS_FM: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FM]); - name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i); - break; - case DIV_INS_STD: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_STD]); - name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); - break; - case DIV_INS_GB: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_GB]); - name=fmt::sprintf(ICON_FA_GAMEPAD "##_INS%d",i); - break; - case DIV_INS_C64: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_C64]); - name=fmt::sprintf(ICON_FA_KEYBOARD_O "##_INS%d",i); - break; - case DIV_INS_AMIGA: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AMIGA]); - name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); - break; - case DIV_INS_PCE: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PCE]); - name=fmt::sprintf(ICON_FA_ID_BADGE "##_INS%d",i); - break; - case DIV_INS_AY: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY]); - name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); - break; - case DIV_INS_AY8930: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY8930]); - name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); - break; - case DIV_INS_TIA: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_TIA]); - name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); - break; - case DIV_INS_SAA1099: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SAA1099]); - name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); - break; - case DIV_INS_VIC: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VIC]); - name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); - break; - case DIV_INS_PET: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PET]); - name=fmt::sprintf(ICON_FA_SQUARE "##_INS%d",i); - break; - case DIV_INS_VRC6: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6]); - name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); - break; - case DIV_INS_VRC6_SAW: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6_SAW]); - name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); - break; - case DIV_INS_OPLL: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPLL]); - name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i); - break; - case DIV_INS_OPL: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPL]); - name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i); - break; - case DIV_INS_FDS: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FDS]); - name=fmt::sprintf(ICON_FA_FLOPPY_O "##_INS%d",i); - break; - case DIV_INS_VBOY: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VBOY]); - name=fmt::sprintf(ICON_FA_BINOCULARS "##_INS%d",i); - break; - case DIV_INS_N163: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_N163]); - name=fmt::sprintf(ICON_FA_CALCULATOR "##_INS%d",i); - break; - case DIV_INS_SCC: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SCC]); - name=fmt::sprintf(ICON_FA_CALCULATOR "##_INS%d",i); - break; - case DIV_INS_OPZ: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPZ]); - name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i); - break; - case DIV_INS_POKEY: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_POKEY]); - name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); - break; - case DIV_INS_BEEPER: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_BEEPER]); - name=fmt::sprintf(ICON_FA_SQUARE "##_INS%d",i); - break; - case DIV_INS_SWAN: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SWAN]); - name=fmt::sprintf(ICON_FA_GAMEPAD "##_INS%d",i); - break; - case DIV_INS_MIKEY: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MIKEY]); - name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); - break; - case DIV_INS_VERA: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VERA]); - name=fmt::sprintf(ICON_FA_KEYBOARD_O "##_INS%d",i); - break; - case DIV_INS_X1_010: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_X1_010]); - name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); - break; - case DIV_INS_ES5506: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_ES5506]); - name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); - break; - case DIV_INS_MULTIPCM: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MULTIPCM]); - name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); - break; - case DIV_INS_SNES: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SNES]); - name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); - break; - case DIV_INS_SU: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SU]); - name=fmt::sprintf(ICON_FA_MICROCHIP "##_INS%d",i); - break; - case DIV_INS_NAMCO: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_NAMCO]); - name=fmt::sprintf(ICON_FA_PIE_CHART "##_INS%d",i); - break; - case DIV_INS_OPL_DRUMS: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPL_DRUMS]); - name=fmt::sprintf(ICON_FA_COFFEE "##_INS%d",i); - break; - case DIV_INS_OPM: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPM]); - name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i); - break; - case DIV_INS_NES: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_NES]); - name=fmt::sprintf(ICON_FA_GAMEPAD "##_INS%d",i); - break; - case DIV_INS_MSM6258: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MSM6258]); - name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); - break; - case DIV_INS_MSM6295: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MSM6295]); - name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); - break; - case DIV_INS_ADPCMA: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_ADPCMA]); - name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); - break; - case DIV_INS_ADPCMB: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_ADPCMB]); - name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); - break; - case DIV_INS_SEGAPCM: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SEGAPCM]); - name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); - break; - case DIV_INS_QSOUND: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_QSOUND]); - name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); - break; - case DIV_INS_YMZ280B: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_YMZ280B]); - name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); - break; - case DIV_INS_RF5C68: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_RF5C68]); - name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); - break; - case DIV_INS_MSM5232: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MSM5232]); - name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); - break; - case DIV_INS_T6W28: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_T6W28]); - name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); - break; - case DIV_INS_K007232: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_K007232]); - name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); - break; - case DIV_INS_GA20: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_GA20]); - name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); - break; - case DIV_INS_POKEMINI: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_POKEMINI]); - name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); - break; - case DIV_INS_SM8521: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SM8521]); - name=fmt::sprintf(ICON_FA_GAMEPAD "##_INS%d",i); - break; - case DIV_INS_PV1000: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PV1000]); - name=fmt::sprintf(ICON_FA_GAMEPAD "##_INS%d",i); - break; - default: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); - name=fmt::sprintf(ICON_FA_QUESTION "##_INS%d",i); - break; - } - } else { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); - } - if (!settings.horizontalDataView) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - } else if (curRow==0) { - ImGui::TableNextColumn(); - } - if (ImGui::Selectable(name.c_str(),(i==-1)?(curIns<0 || curIns>=e->song.insLen):(curIns==i))) { - curIns=i; - wavePreviewInit=true; - updateFMPreview=true; - } - if (wantScrollList && curIns==i) ImGui::SetScrollHereY(); - if (settings.insFocusesPattern && patternOpen && ImGui::IsItemActivated()) { - nextWindow=GUI_WINDOW_PATTERN; - curIns=i; - wavePreviewInit=true; - updateFMPreview=true; - } - if (ImGui::IsItemHovered() && i>=0 && !mobileUI) { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); - ImGui::SetTooltip("%s",insType); - ImGui::PopStyleColor(); - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - insEditOpen=true; - nextWindow=GUI_WINDOW_INS_EDIT; - } - } - if (i>=0) { - if (ImGui::BeginPopupContextItem("InsRightMenu")) { - curIns=i; - updateFMPreview=true; - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); - if (ImGui::MenuItem("replace...")) { - doAction((curIns>=0 && curIns<(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN); - } - if (ImGui::MenuItem("save")) { - doAction(GUI_ACTION_INS_LIST_SAVE); - } - if (ImGui::MenuItem("save (legacy .fui)")) { - doAction(GUI_ACTION_INS_LIST_SAVE_OLD); - } - if (ImGui::MenuItem("save (.dmp)")) { - doAction(GUI_ACTION_INS_LIST_SAVE_DMP); + if (insListDir) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + insListItem(-1,-1,-1); + int dirIndex=0; + int dirToDelete=-1; + for (DivAssetDir& i: e->song.insDir) { + String nodeName=fmt::sprintf("%s %s##_ADI%d",i.name.empty()?ICON_FA_FOLDER_O:ICON_FA_FOLDER,i.name.empty()?"":i.name,i.name.empty()?-1:dirIndex); + String popupID=fmt::sprintf("DirRightMenu%d",dirIndex); + bool treeNode=ImGui::TreeNodeEx(nodeName.c_str(),ImGuiTreeNodeFlags_SpanAvailWidth|(i.name.empty()?ImGuiTreeNodeFlags_DefaultOpen:0)); + DRAG_SOURCE(dirIndex,-1,"FUR_INSDIR"); + DRAG_TARGET(dirIndex,-1,e->song.insDir,"FUR_INSDIR"); + if (ImGui::BeginPopupContextItem(popupID.c_str())) { + if (ImGui::MenuItem("rename...")) { + editStr(&i.name); } if (ImGui::MenuItem("delete")) { - doAction(GUI_ACTION_INS_LIST_DELETE); + dirToDelete=dirIndex; } - ImGui::PopStyleColor(); ImGui::EndPopup(); } - } - if (i>=0) { - if (i<(int)e->song.ins.size()) { - DivInstrument* ins=e->song.ins[i]; - ImGui::SameLine(); - ImGui::Text("%.2X: %s",i,ins->name.c_str()); + if (treeNode) { + int assetIndex=0; + for (int j: i.entries) { + insListItem(j,dirIndex,assetIndex); + assetIndex++; + } + ImGui::TreePop(); } - } else { - ImGui::SameLine(); - ImGui::Text("- None -"); + dirIndex++; } - ImGui::PopStyleColor(); - if (settings.horizontalDataView) { - if (++curRow>=availableRows) curRow=0; + if (dirToDelete!=-1) { + e->lockEngine([this,dirToDelete]() { + e->song.insDir.erase(e->song.insDir.begin()+dirToDelete); + e->checkAssetDir(e->song.insDir,e->song.ins.size()); + }); + } + } else { + int curRow=0; + for (int i=-1; i<(int)e->song.ins.size(); i++) { + if (!settings.horizontalDataView) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + } else if (curRow==0) { + ImGui::TableNextColumn(); + } + insListItem(i,-1,-1); + if (settings.horizontalDataView) { + if (++curRow>=availableRows) curRow=0; + } } - ImGui::PopID(); } if (settings.unifiedDataView) { @@ -519,14 +796,18 @@ void FurnaceGUI::drawInsList(bool asChild) { ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::Text(ICON_FA_AREA_CHART " Wavetables"); + if (ImGui::Selectable(ICON_FA_AREA_CHART " Wavetables",lastAssetType==1)) { + lastAssetType=1; + } ImGui::Indent(); actualWaveList(); ImGui::Unindent(); ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::Text(ICON_FA_VOLUME_UP " Samples"); + if (ImGui::Selectable(ICON_FA_VOLUME_UP " Samples",lastAssetType==2)) { + lastAssetType=2; + } ImGui::Indent(); actualSampleList(); ImGui::Unindent(); @@ -607,18 +888,50 @@ void FurnaceGUI::drawWaveList(bool asChild) { } } ImGui::SameLine(); - if (ImGui::ArrowButton("WaveUp",ImGuiDir_Up)) { - doAction(GUI_ACTION_WAVE_LIST_MOVE_UP); + pushToggleColors(waveListDir); + if (ImGui::Button(ICON_FA_SITEMAP "##WaveDirMode")) { + doAction(GUI_ACTION_WAVE_LIST_DIR_VIEW); } + popToggleColors(); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Move up"); + ImGui::SetTooltip("Toggle folders/standard view"); } - ImGui::SameLine(); - if (ImGui::ArrowButton("WaveDown",ImGuiDir_Down)) { - doAction(GUI_ACTION_WAVE_LIST_MOVE_DOWN); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Move down"); + if (!waveListDir) { + ImGui::SameLine(); + if (ImGui::ArrowButton("WaveUp",ImGuiDir_Up)) { + doAction(GUI_ACTION_WAVE_LIST_MOVE_UP); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move up"); + } + ImGui::SameLine(); + if (ImGui::ArrowButton("WaveDown",ImGuiDir_Down)) { + doAction(GUI_ACTION_WAVE_LIST_MOVE_DOWN); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move down"); + } + } else { + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FOLDER "##WaveFolder")) { + folderString=""; + } + if (ImGui::BeginPopupContextItem("NewWaveFolder",ImGuiMouseButton_Left)) { + ImGui::InputText("##FolderName",&folderString); + ImGui::SameLine(); + ImGui::BeginDisabled(folderString.empty()); + if (ImGui::Button("Create")) { + e->lockEngine([this]() { + e->song.waveDir.push_back(DivAssetDir(folderString)); + }); + ImGui::CloseCurrentPopup(); + } + ImGui::EndDisabled(); + ImGui::EndPopup(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("New folder"); + } } ImGui::SameLine(); if (ImGui::Button(ICON_FA_TIMES "##WaveDelete")) { @@ -680,6 +993,10 @@ void FurnaceGUI::drawSampleList(bool asChild) { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Open"); } + if (mobileUI && ImGui::IsItemActive() && CHECK_LONG_HOLD) { + ImGui::OpenPopup("SampleOpenOpt"); + NOTIFY_LONG_HOLD; + } if (ImGui::BeginPopupContextItem("SampleOpenOpt")) { if (ImGui::MenuItem("replace...")) { doAction((curSample>=0 && curSample<(int)e->song.sample.size())?GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE:GUI_ACTION_SAMPLE_LIST_OPEN); @@ -700,19 +1017,61 @@ void FurnaceGUI::drawSampleList(bool asChild) { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Save"); } - ImGui::SameLine(); - if (ImGui::ArrowButton("SampleUp",ImGuiDir_Up)) { - doAction(GUI_ACTION_SAMPLE_LIST_MOVE_UP); + if (mobileUI && ImGui::IsItemActive() && CHECK_LONG_HOLD) { + ImGui::OpenPopup("SampleSaveOpt"); + NOTIFY_LONG_HOLD; } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Move up"); + if (ImGui::BeginPopupContextItem("SampleSaveOpt")) { + if (ImGui::MenuItem("save raw...")) { + doAction(GUI_ACTION_SAMPLE_LIST_SAVE_RAW); + } + ImGui::EndPopup(); } ImGui::SameLine(); - if (ImGui::ArrowButton("SampleDown",ImGuiDir_Down)) { - doAction(GUI_ACTION_SAMPLE_LIST_MOVE_DOWN); + pushToggleColors(sampleListDir); + if (ImGui::Button(ICON_FA_SITEMAP "##SampleDirMode")) { + doAction(GUI_ACTION_SAMPLE_LIST_DIR_VIEW); } + popToggleColors(); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Move down"); + ImGui::SetTooltip("Toggle folders/standard view"); + } + if (!sampleListDir) { + ImGui::SameLine(); + if (ImGui::ArrowButton("SampleUp",ImGuiDir_Up)) { + doAction(GUI_ACTION_SAMPLE_LIST_MOVE_UP); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move up"); + } + ImGui::SameLine(); + if (ImGui::ArrowButton("SampleDown",ImGuiDir_Down)) { + doAction(GUI_ACTION_SAMPLE_LIST_MOVE_DOWN); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move down"); + } + } else { + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FOLDER "##SampleFolder")) { + folderString=""; + } + if (ImGui::BeginPopupContextItem("NewSampleFolder",ImGuiMouseButton_Left)) { + ImGui::InputText("##FolderName",&folderString); + ImGui::SameLine(); + ImGui::BeginDisabled(folderString.empty()); + if (ImGui::Button("Create")) { + e->lockEngine([this]() { + e->song.sampleDir.push_back(DivAssetDir(folderString)); + }); + ImGui::CloseCurrentPopup(); + } + ImGui::EndDisabled(); + ImGui::EndPopup(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("New folder"); + } } ImGui::SameLine(); if (ImGui::Button(ICON_FA_TIMES "##SampleDelete")) { @@ -752,74 +1111,94 @@ void FurnaceGUI::drawSampleList(bool asChild) { void FurnaceGUI::actualWaveList() { float wavePreview[257]; - for (int i=0; i<(int)e->song.wave.size(); i++) { - DivWavetable* wave=e->song.wave[i]; - for (int i=0; ilen; i++) { - wavePreview[i]=wave->data[i]; - } - if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; + + if (waveListDir || (settings.unifiedDataView && insListDir)) { ImGui::TableNextRow(); ImGui::TableNextColumn(); - if (ImGui::Selectable(fmt::sprintf("%d##_WAVE%d\n",i,i).c_str(),curWave==i)) { - curWave=i; - } - if (wantScrollList && curWave==i) ImGui::SetScrollHereY(); - if (ImGui::IsItemHovered()) { - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - waveEditOpen=true; - nextWindow=GUI_WINDOW_WAVE_EDIT; + int dirIndex=0; + int dirToDelete=-1; + for (DivAssetDir& i: e->song.waveDir) { + String nodeName=fmt::sprintf("%s %s##_ADW%d",i.name.empty()?ICON_FA_FOLDER_O:ICON_FA_FOLDER,i.name.empty()?"":i.name,i.name.empty()?-1:dirIndex); + String popupID=fmt::sprintf("DirRightMenu%d",dirIndex); + bool treeNode=ImGui::TreeNodeEx(nodeName.c_str(),ImGuiTreeNodeFlags_SpanAvailWidth|(i.name.empty()?ImGuiTreeNodeFlags_DefaultOpen:0)); + DRAG_SOURCE(dirIndex,-1,"FUR_WAVEDIR"); + DRAG_TARGET(dirIndex,-1,e->song.waveDir,"FUR_WAVEDIR"); + if (ImGui::BeginPopupContextItem(popupID.c_str())) { + if (ImGui::MenuItem("rename...")) { + editStr(&i.name); + } + if (ImGui::MenuItem("delete")) { + dirToDelete=dirIndex; + } + ImGui::EndPopup(); } + if (treeNode) { + int assetIndex=0; + for (int j: i.entries) { + waveListItem(j,wavePreview,dirIndex,assetIndex); + assetIndex++; + } + ImGui::TreePop(); + } + dirIndex++; + } + if (dirToDelete!=-1) { + e->lockEngine([this,dirToDelete]() { + e->song.waveDir.erase(e->song.waveDir.begin()+dirToDelete); + e->checkAssetDir(e->song.waveDir,e->song.wave.size()); + }); + } + } else { + for (int i=0; i<(int)e->song.wave.size(); i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + waveListItem(i,wavePreview,-1,-1); } - ImGui::SameLine(); - PlotNoLerp(fmt::sprintf("##_WAVEP%d",i).c_str(),wavePreview,wave->len+1,0,NULL,0,wave->max); } } void FurnaceGUI::actualSampleList() { - for (int i=0; i<(int)e->song.sample.size(); i++) { - bool memWarning=false; - - DivSample* sample=e->song.sample[i]; + if (sampleListDir || (settings.unifiedDataView && insListDir)) { ImGui::TableNextRow(); ImGui::TableNextColumn(); - for (int j=0; jsong.systemLen; j++) { - DivDispatch* dispatch=e->getDispatch(j); - if (dispatch==NULL) continue; - - for (int k=0; kgetSampleMemCapacity(k)==0) continue; - if (!dispatch->isSampleLoaded(k,i) && sample->renderOn[k][j]) { - memWarning=true; - break; + int dirIndex=0; + int dirToDelete=-1; + for (DivAssetDir& i: e->song.sampleDir) { + String nodeName=fmt::sprintf("%s %s##_ADS%d",i.name.empty()?ICON_FA_FOLDER_O:ICON_FA_FOLDER,i.name.empty()?"":i.name,i.name.empty()?-1:dirIndex); + String popupID=fmt::sprintf("DirRightMenu%d",dirIndex); + bool treeNode=ImGui::TreeNodeEx(nodeName.c_str(),ImGuiTreeNodeFlags_SpanAvailWidth|(i.name.empty()?ImGuiTreeNodeFlags_DefaultOpen:0)); + DRAG_SOURCE(dirIndex,-1,"FUR_SDIR"); + DRAG_TARGET(dirIndex,-1,e->song.sampleDir,"FUR_SDIR"); + if (ImGui::BeginPopupContextItem(popupID.c_str())) { + if (ImGui::MenuItem("rename...")) { + editStr(&i.name); } + if (ImGui::MenuItem("delete")) { + dirToDelete=dirIndex; + } + ImGui::EndPopup(); } - if (memWarning) break; - } - if (memWarning) ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_SAMPLE_CHIP_WARNING]); - if (ImGui::Selectable(fmt::sprintf("%d: %s##_SAM%d",i,sample->name,i).c_str(),curSample==i)) { - curSample=i; - samplePos=0; - updateSampleTex=true; - } - if (ImGui::IsItemHovered() && !mobileUI) { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); - ImGui::SetTooltip("Bank %d: %s",i/12,sampleNote[i%12]); - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - sampleEditOpen=true; - nextWindow=GUI_WINDOW_SAMPLE_EDIT; + if (treeNode) { + int assetIndex=0; + for (int j: i.entries) { + sampleListItem(j,dirIndex,assetIndex); + assetIndex++; + } + ImGui::TreePop(); } - ImGui::PopStyleColor(); + dirIndex++; } - if (memWarning) { - ImGui::SameLine(); - ImGui::Text(ICON_FA_EXCLAMATION_TRIANGLE); - if (ImGui::IsItemHovered() && !mobileUI) { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); - ImGui::SetTooltip("out of memory for this sample!"); - ImGui::PopStyleColor(); - } - ImGui::PopStyleColor(); + if (dirToDelete!=-1) { + e->lockEngine([this,dirToDelete]() { + e->song.sampleDir.erase(e->song.sampleDir.begin()+dirToDelete); + e->checkAssetDir(e->song.sampleDir,e->song.sample.size()); + }); + } + } else { + for (int i=0; i<(int)e->song.sample.size(); i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + sampleListItem(i,-1,-1); } - if (wantScrollList && curSample==i) ImGui::SetScrollHereY(); } } diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index ae1a1dfce..7eb3906aa 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -540,9 +540,23 @@ void FurnaceGUI::drawDebug() { } if (ImGui::TreeNode("Performance")) { double perfFreq=SDL_GetPerformanceFrequency()/1000000.0; + int lastProcTime=(int)e->processTime/1000; + TAAudioDesc& audioGot=e->getAudioDescGot(); + + ImGui::Text("video frame: %.0fµs",ImGui::GetIO().DeltaTime*1000000.0); + ImGui::Text("audio frame: %.0fµs",1000000.0*(double)audioGot.bufsize/(double)audioGot.rate); + ImGui::Separator(); + + ImGui::Text("audio: %dµs",lastProcTime); ImGui::Text("render: %.0fµs",(double)renderTimeDelta/perfFreq); ImGui::Text("layout: %.0fµs",(double)layoutTimeDelta/perfFreq); ImGui::Text("event: %.0fµs",(double)eventTimeDelta/perfFreq); + ImGui::Separator(); + + ImGui::Text("details:"); + for (int i=0; igetTotalChannelCount()-1; + selEndPat.xFine=2+e->curPat[selEndPat.xCoarse].effectCols*2; + selEndPat.y=e->curSubSong->patLen-1; + doCollapse(collapseAmount,SelectionPoint(0,0,0),selEndPat); break; - case GUI_ACTION_PAT_EXPAND_PAT: // TODO + } + case GUI_ACTION_PAT_EXPAND_PAT: { + SelectionPoint selEndPat; + selEndPat.xCoarse=e->getTotalChannelCount()-1; + selEndPat.xFine=2+e->curPat[selEndPat.xCoarse].effectCols*2; + selEndPat.y=e->curSubSong->patLen-1; + doExpand(collapseAmount,SelectionPoint(0,0,0),selEndPat); break; - case GUI_ACTION_PAT_COLLAPSE_SONG: // TODO + } + case GUI_ACTION_PAT_COLLAPSE_SONG: + doCollapseSong(collapseAmount); break; - case GUI_ACTION_PAT_EXPAND_SONG: // TODO + case GUI_ACTION_PAT_EXPAND_SONG: + doExpandSong(collapseAmount); break; case GUI_ACTION_PAT_LATCH: // TODO break; @@ -670,6 +684,9 @@ void FurnaceGUI::doAction(int what) { wavePreviewInit=true; updateFMPreview=true; break; + case GUI_ACTION_INS_LIST_DIR_VIEW: + insListDir=!insListDir; + break; case GUI_ACTION_WAVE_LIST_ADD: curWave=e->addWave(); @@ -743,6 +760,9 @@ void FurnaceGUI::doAction(int what) { if (++curWave>=(int)e->song.wave.size()) curWave=((int)e->song.wave.size())-1; wantScrollList=true; break; + case GUI_ACTION_WAVE_LIST_DIR_VIEW: + waveListDir=!waveListDir; + break; case GUI_ACTION_SAMPLE_LIST_ADD: curSample=e->addSample(); @@ -801,6 +821,9 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_LIST_SAVE: if (curSample>=0 && curSample<(int)e->song.sample.size()) openFileDialog(GUI_FILE_SAMPLE_SAVE); break; + case GUI_ACTION_SAMPLE_LIST_SAVE_RAW: + if (curSample>=0 && curSample<(int)e->song.sample.size()) openFileDialog(GUI_FILE_SAMPLE_SAVE_RAW); + break; case GUI_ACTION_SAMPLE_LIST_MOVE_UP: if (e->moveSampleUp(curSample)) { curSample--; @@ -821,8 +844,8 @@ void FurnaceGUI::doAction(int what) { MARK_MODIFIED; if (curSample>=(int)e->song.sample.size()) { curSample--; - updateSampleTex=true; } + updateSampleTex=true; break; case GUI_ACTION_SAMPLE_LIST_EDIT: sampleEditOpen=true; @@ -843,6 +866,9 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW: e->stopSamplePreview(); break; + case GUI_ACTION_SAMPLE_LIST_DIR_VIEW: + sampleListDir=!sampleListDir; + break; case GUI_ACTION_SAMPLE_SELECT: if (curSample<0 || curSample>=(int)e->song.sample.size()) break; diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 2193ef609..e3e843481 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -463,181 +463,185 @@ void FurnaceGUI::drawMobileControls() { ImGui::Separator(); - if (settings.unifiedDataView) { - drawInsList(true); - } else { - switch (mobScene) { - case GUI_SCENE_PATTERN: - case GUI_SCENE_ORDERS: - case GUI_SCENE_INSTRUMENT: + switch (mobScene) { + case GUI_SCENE_PATTERN: + case GUI_SCENE_ORDERS: + case GUI_SCENE_INSTRUMENT: + drawInsList(true); + break; + case GUI_SCENE_WAVETABLE: + if (settings.unifiedDataView) { drawInsList(true); - break; - case GUI_SCENE_WAVETABLE: + } else { drawWaveList(true); - break; - case GUI_SCENE_SAMPLE: + } + break; + case GUI_SCENE_SAMPLE: + if (settings.unifiedDataView) { + drawInsList(true); + } else { drawSampleList(true); - break; - case GUI_SCENE_SONG: { - if (ImGui::Button("New")) { - mobileMenuOpen=false; - //doAction(GUI_ACTION_NEW); - if (modified) { - showWarning("Unsaved changes! Save changes before creating a new song?",GUI_WARN_NEW); - } else { - displayNew=true; - } - } - ImGui::SameLine(); - if (ImGui::Button("Open")) { - mobileMenuOpen=false; - doAction(GUI_ACTION_OPEN); - } - ImGui::SameLine(); - if (ImGui::Button("Save")) { - mobileMenuOpen=false; - doAction(GUI_ACTION_SAVE); - } - ImGui::SameLine(); - if (ImGui::Button("Save as...")) { - mobileMenuOpen=false; - doAction(GUI_ACTION_SAVE_AS); - } - - ImGui::Button("1.1+ .dmf"); - ImGui::SameLine(); - ImGui::Button("Legacy .dmf"); - ImGui::SameLine(); - if (ImGui::Button("Export Audio")) { - openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); - } - ImGui::SameLine(); - if (ImGui::Button("Export VGM")) { - openFileDialog(GUI_FILE_EXPORT_VGM); - } - - if (ImGui::Button("CmdStream")) { - openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY); - } - ImGui::SameLine(); - if (ImGui::Button("CmdStream Text")) { - openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); - } - - if (ImGui::Button("Restore Backup")) { - mobileMenuOpen=false; - doAction(GUI_ACTION_OPEN_BACKUP); - } - - ImGui::Separator(); - - if (ImGui::BeginTabBar("MobileSong")) { - if (ImGui::BeginTabItem("Song Info")) { - drawSongInfo(true); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Subsongs")) { - drawSubSongs(true); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Speed")) { - drawSpeed(true); - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); - } - break; } - case GUI_SCENE_CHANNELS: - ImGui::Text("Channels here..."); - break; - case GUI_SCENE_CHIPS: - ImGui::Text("Chips here..."); - break; - case GUI_SCENE_MIXER: - ImGui::Text("What the hell..."); - break; - case GUI_SCENE_OTHER: { - if (ImGui::Button("Osc")) { - oscOpen=!oscOpen; + break; + case GUI_SCENE_SONG: { + if (ImGui::Button("New")) { + mobileMenuOpen=false; + //doAction(GUI_ACTION_NEW); + if (modified) { + showWarning("Unsaved changes! Save changes before creating a new song?",GUI_WARN_NEW); + } else { + displayNew=true; } - ImGui::SameLine(); - if (ImGui::Button("ChanOsc")) { - chanOscOpen=!chanOscOpen; - } - ImGui::SameLine(); - if (ImGui::Button("RegView")) { - regViewOpen=!regViewOpen; - } - ImGui::SameLine(); - if (ImGui::Button("Stats")) { - statsOpen=!statsOpen; - } - if (ImGui::Button("Compat Flags")) { - compatFlagsOpen=!compatFlagsOpen; - } - - ImGui::Separator(); - - ImGui::Button("Panic"); - ImGui::SameLine(); - if (ImGui::Button("Settings")) { - mobileMenuOpen=false; - settingsOpen=true; - } - ImGui::SameLine(); - if (ImGui::Button("Log")) { - logOpen=!logOpen; - } - ImGui::SameLine(); - if (ImGui::Button("Debug")) { - debugOpen=!debugOpen; - } - ImGui::SameLine(); - if (ImGui::Button("About")) { - mobileMenuOpen=false; - mobileMenuPos=0.0f; - aboutOpen=true; - } - if (ImGui::Button("Switch to Desktop Mode")) { - toggleMobileUI(!mobileUI); - } - - int numAmiga=0; - for (int i=0; isong.systemLen; i++) { - if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; - } - - if (numAmiga) { - ImGui::Text( - "this is NOT ROM export! only use for making sure the\n" - "Furnace Amiga emulator is working properly by\n" - "comparing it with real Amiga output." - ); - ImGui::Text("Directory"); - ImGui::SameLine(); - ImGui::InputText("##AVDPath",&workingDirROMExport); - if (ImGui::Button("Bake Data")) { - std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); - if (workingDirROMExport.size()>0) { - if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; - } - for (DivROMExportOutput& i: out) { - String path=workingDirROMExport+i.name; - FILE* outFile=ps_fopen(path.c_str(),"wb"); - if (outFile!=NULL) { - fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); - fclose(outFile); - } - i.data->finish(); - delete i.data; - } - showError(fmt::sprintf("Done! Baked %d files.",(int)out.size())); - } - } - - break; } + ImGui::SameLine(); + if (ImGui::Button("Open")) { + mobileMenuOpen=false; + doAction(GUI_ACTION_OPEN); + } + ImGui::SameLine(); + if (ImGui::Button("Save")) { + mobileMenuOpen=false; + doAction(GUI_ACTION_SAVE); + } + ImGui::SameLine(); + if (ImGui::Button("Save as...")) { + mobileMenuOpen=false; + doAction(GUI_ACTION_SAVE_AS); + } + + ImGui::Button("1.1+ .dmf"); + ImGui::SameLine(); + ImGui::Button("Legacy .dmf"); + ImGui::SameLine(); + if (ImGui::Button("Export Audio")) { + openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); + } + ImGui::SameLine(); + if (ImGui::Button("Export VGM")) { + openFileDialog(GUI_FILE_EXPORT_VGM); + } + + if (ImGui::Button("CmdStream")) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY); + } + ImGui::SameLine(); + if (ImGui::Button("CmdStream Text")) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); + } + + if (ImGui::Button("Restore Backup")) { + mobileMenuOpen=false; + doAction(GUI_ACTION_OPEN_BACKUP); + } + + ImGui::Separator(); + + if (ImGui::BeginTabBar("MobileSong")) { + if (ImGui::BeginTabItem("Song Info")) { + drawSongInfo(true); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Subsongs")) { + drawSubSongs(true); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Speed")) { + drawSpeed(true); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + break; + } + case GUI_SCENE_CHANNELS: + ImGui::Text("Channels here..."); + break; + case GUI_SCENE_CHIPS: + ImGui::Text("Chips here..."); + break; + case GUI_SCENE_MIXER: + ImGui::Text("What the hell..."); + break; + case GUI_SCENE_OTHER: { + if (ImGui::Button("Osc")) { + oscOpen=!oscOpen; + } + ImGui::SameLine(); + if (ImGui::Button("ChanOsc")) { + chanOscOpen=!chanOscOpen; + } + ImGui::SameLine(); + if (ImGui::Button("RegView")) { + regViewOpen=!regViewOpen; + } + ImGui::SameLine(); + if (ImGui::Button("Stats")) { + statsOpen=!statsOpen; + } + if (ImGui::Button("Compat Flags")) { + compatFlagsOpen=!compatFlagsOpen; + } + + ImGui::Separator(); + + ImGui::Button("Panic"); + ImGui::SameLine(); + if (ImGui::Button("Settings")) { + mobileMenuOpen=false; + settingsOpen=true; + } + ImGui::SameLine(); + if (ImGui::Button("Log")) { + logOpen=!logOpen; + } + ImGui::SameLine(); + if (ImGui::Button("Debug")) { + debugOpen=!debugOpen; + } + ImGui::SameLine(); + if (ImGui::Button("About")) { + mobileMenuOpen=false; + mobileMenuPos=0.0f; + aboutOpen=true; + } + if (ImGui::Button("Switch to Desktop Mode")) { + toggleMobileUI(!mobileUI); + } + + int numAmiga=0; + for (int i=0; isong.systemLen; i++) { + if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; + } + + if (numAmiga) { + ImGui::Text( + "this is NOT ROM export! only use for making sure the\n" + "Furnace Amiga emulator is working properly by\n" + "comparing it with real Amiga output." + ); + ImGui::Text("Directory"); + ImGui::SameLine(); + ImGui::InputText("##AVDPath",&workingDirROMExport); + if (ImGui::Button("Bake Data")) { + std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); + if (workingDirROMExport.size()>0) { + if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; + } + for (DivROMExportOutput& i: out) { + String path=workingDirROMExport+i.name; + FILE* outFile=ps_fopen(path.c_str(),"wb"); + if (outFile!=NULL) { + fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); + fclose(outFile); + } + i.data->finish(); + delete i.data; + } + showError(fmt::sprintf("Done! Baked %d files.",(int)out.size())); + } + } + + break; } } } diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 77d76da24..1a2b1b11e 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -67,6 +67,9 @@ void FurnaceGUI::prepareUndo(ActionType action) { e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false)->copyOn(oldPat[i]); } break; + case GUI_UNDO_PATTERN_COLLAPSE_SONG: + case GUI_UNDO_PATTERN_EXPAND_SONG: // TODO + break; case GUI_UNDO_REPLACE: // this is handled by doReplace() break; } @@ -130,6 +133,9 @@ void FurnaceGUI::makeUndo(ActionType action) { doPush=true; } break; + case GUI_UNDO_PATTERN_COLLAPSE_SONG: + case GUI_UNDO_PATTERN_EXPAND_SONG: // TODO + break; case GUI_UNDO_REPLACE: // this is handled by doReplace() break; } @@ -839,50 +845,56 @@ void FurnaceGUI::doFlip() { makeUndo(GUI_UNDO_PATTERN_FLIP); } -void FurnaceGUI::doCollapse(int divider) { +void FurnaceGUI::doCollapse(int divider, const SelectionPoint& sStart, const SelectionPoint& sEnd) { + if (divider<2) return; + if (e->curSubSong->patLencurSubSong->chanShow[iCoarse]) continue; DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); - for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsecurPat[iCoarse].effectCols*2 && (iCoarsedata[j][0]; } patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; } - for (int j=0; j<=selEnd.y-selStart.y; j++) { - if (j*divider>=selEnd.y-selStart.y) { + for (int j=0; j<=sEnd.y-sStart.y; j++) { + if (j*divider>=sEnd.y-sStart.y) { if (iFine==0) { - pat->data[j+selStart.y][0]=0; - pat->data[j+selStart.y][1]=0; + pat->data[j+sStart.y][0]=0; + pat->data[j+sStart.y][1]=0; } else { - pat->data[j+selStart.y][iFine+1]=-1; + pat->data[j+sStart.y][iFine+1]=-1; } } else { if (iFine==0) { - pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y][0]; + pat->data[j+sStart.y][0]=patBuffer.data[j*divider+sStart.y][0]; } - pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y][iFine+1]; + pat->data[j+sStart.y][iFine+1]=patBuffer.data[j*divider+sStart.y][iFine+1]; if (iFine==0) { for (int k=1; k=selEnd.y-selStart.y) break; - if (!(pat->data[j+selStart.y][0]==0 && pat->data[j+selStart.y][1]==0)) break; - pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y+k][0]; - pat->data[j+selStart.y][1]=patBuffer.data[j*divider+selStart.y+k][1]; + if ((j*divider+k)>=sEnd.y-sStart.y) break; + if (!(pat->data[j+sStart.y][0]==0 && pat->data[j+sStart.y][1]==0)) break; + pat->data[j+sStart.y][0]=patBuffer.data[j*divider+sStart.y+k][0]; + pat->data[j+sStart.y][1]=patBuffer.data[j*divider+sStart.y+k][1]; } } else { for (int k=1; k=selEnd.y-selStart.y) break; - if (pat->data[j+selStart.y][iFine+1]!=-1) break; - pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y+k][iFine+1]; + if ((j*divider+k)>=sEnd.y-sStart.y) break; + if (pat->data[j+sStart.y][iFine+1]!=-1) break; + pat->data[j+sStart.y][iFine+1]=patBuffer.data[j*divider+sStart.y+k][iFine+1]; } } } @@ -894,41 +906,41 @@ void FurnaceGUI::doCollapse(int divider) { makeUndo(GUI_UNDO_PATTERN_COLLAPSE); } -void FurnaceGUI::doExpand(int multiplier) { - if (multiplier<1) return; +void FurnaceGUI::doExpand(int multiplier, const SelectionPoint& sStart, const SelectionPoint& sEnd) { + if (multiplier<2) return; finishSelection(); prepareUndo(GUI_UNDO_PATTERN_EXPAND); DivPattern patBuffer; - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + int iCoarse=sStart.xCoarse; + int iFine=sStart.xFine; + for (; iCoarse<=sEnd.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 && (iCoarsecurPat[iCoarse].effectCols*2 && (iCoarsedata[j][0]; } patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; } - for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) { - if ((j+selStart.y)>=e->curSubSong->patLen) break; + for (int j=0; j<=(sEnd.y-sStart.y)*multiplier; j++) { + if ((j+sStart.y)>=e->curSubSong->patLen) break; if ((j%multiplier)!=0) { if (iFine==0) { - pat->data[j+selStart.y][0]=0; - pat->data[j+selStart.y][1]=0; + pat->data[j+sStart.y][0]=0; + pat->data[j+sStart.y][1]=0; } else { - pat->data[j+selStart.y][iFine+1]=-1; + pat->data[j+sStart.y][iFine+1]=-1; } continue; } if (iFine==0) { - pat->data[j+selStart.y][0]=patBuffer.data[j/multiplier+selStart.y][0]; + pat->data[j+sStart.y][0]=patBuffer.data[j/multiplier+sStart.y][0]; } - pat->data[j+selStart.y][iFine+1]=patBuffer.data[j/multiplier+selStart.y][iFine+1]; + pat->data[j+sStart.y][iFine+1]=patBuffer.data[j/multiplier+sStart.y][iFine+1]; } } iFine=0; @@ -937,6 +949,162 @@ void FurnaceGUI::doExpand(int multiplier) { makeUndo(GUI_UNDO_PATTERN_EXPAND); } +void FurnaceGUI::doCollapseSong(int divider) { + if (divider<2) return; + finishSelection(); + + UndoStep us; + us.type=GUI_UNDO_PATTERN_COLLAPSE_SONG; + + DivPattern patCopy; + + size_t subSong=e->getCurrentSubSong(); + for (int i=0; igetTotalChannelCount(); i++) { + for (int j=0; jcurPat[i].data[j]==NULL) continue; + + DivPattern* pat=e->curPat[i].getPattern(j,true); + pat->copyOn(&patCopy); + pat->clear(); + for (int k=0; kdata[k/divider][0]==0 && pat->data[k/divider][1]==0)) continue; + } else { + if (pat->data[k/divider][l+1]!=-1) continue; + } + + if (l==0) { + pat->data[k/divider][l]=patCopy.data[k][l]; + } + pat->data[k/divider][l+1]=patCopy.data[k][l+1]; + + if (l>3 && !(l&1)) { // scale effects as needed + switch (pat->data[k/divider][l]) { + case 0x0d: + pat->data[k/divider][l+1]/=divider; + break; + case 0x0f: + pat->data[k/divider][l+1]=CLAMP(pat->data[k/divider][l+1]*divider,1,255); + break; + } + } + } + } + + // put undo + for (int k=0; kdata[k][l]!=patCopy.data[k][l]) { + us.pat.push_back(UndoPatternData(subSong,i,j,k,l,patCopy.data[k][l],pat->data[k][l])); + } + } + } + } + } + // magic + unsigned char* subSongInfoCopy=new unsigned char[1024]; + memcpy(subSongInfoCopy,e->curSubSong,1024); + e->curSubSong->patLen/=divider; + for (int i=0; icurSubSong->speeds.len; i++) { + e->curSubSong->speeds.val[i]=CLAMP(e->curSubSong->speeds.val[i]*divider,1,255); + } + unsigned char* newSubSongInfo=(unsigned char*)e->curSubSong; + for (int i=0; i<1024; i++) { + if (subSongInfoCopy[i]!=newSubSongInfo[i]) { + us.other.push_back(UndoOtherData(GUI_UNDO_TARGET_SUBSONG,subSong,i,subSongInfoCopy[i],newSubSongInfo[i])); + } + } + + if (!us.pat.empty()) { + undoHist.push_back(us); + redoHist.clear(); + if (undoHist.size()>settings.maxUndoSteps) undoHist.pop_front(); + } + + if (e->isPlaying()) e->play(); +} + +void FurnaceGUI::doExpandSong(int multiplier) { + if (multiplier<2) return; + if (e->curSubSong->patLen>(256/multiplier)) { + showError("can't expand any further!"); + return; + } + finishSelection(); + + UndoStep us; + us.type=GUI_UNDO_PATTERN_EXPAND_SONG; + + DivPattern patCopy; + + size_t subSong=e->getCurrentSubSong(); + for (int i=0; igetTotalChannelCount(); i++) { + for (int j=0; jcurPat[i].data[j]==NULL) continue; + + DivPattern* pat=e->curPat[i].getPattern(j,true); + pat->copyOn(&patCopy); + pat->clear(); + for (int k=0; k<(256/multiplier); k++) { + for (int l=0; ldata[k*multiplier][0]==0 && pat->data[k*multiplier][1]==0)) continue; + } else { + if (pat->data[k*multiplier][l+1]!=-1) continue; + } + + if (l==0) { + pat->data[k*multiplier][l]=patCopy.data[k][l]; + } + pat->data[k*multiplier][l+1]=patCopy.data[k][l+1]; + + if (l>3 && !(l&1)) { // scale effects as needed + switch (pat->data[k*multiplier][l]) { + case 0x0d: + pat->data[k*multiplier][l+1]/=multiplier; + break; + case 0x0f: + pat->data[k*multiplier][l+1]=CLAMP(pat->data[k*multiplier][l+1]/multiplier,1,255); + break; + } + } + } + } + + // put undo + for (int k=0; kdata[k][l]!=patCopy.data[k][l]) { + us.pat.push_back(UndoPatternData(subSong,i,j,k,l,patCopy.data[k][l],pat->data[k][l])); + } + } + } + } + } + // magic + unsigned char* subSongInfoCopy=new unsigned char[1024]; + memcpy(subSongInfoCopy,e->curSubSong,1024); + e->curSubSong->patLen*=multiplier; + for (int i=0; icurSubSong->speeds.len; i++) { + e->curSubSong->speeds.val[i]=CLAMP(e->curSubSong->speeds.val[i]/multiplier,1,255); + } + unsigned char* newSubSongInfo=(unsigned char*)e->curSubSong; + for (int i=0; i<1024; i++) { + if (subSongInfoCopy[i]!=newSubSongInfo[i]) { + us.other.push_back(UndoOtherData(GUI_UNDO_TARGET_SUBSONG,subSong,i,subSongInfoCopy[i],newSubSongInfo[i])); + } + } + + if (!us.pat.empty()) { + undoHist.push_back(us); + redoHist.clear(); + if (undoHist.size()>settings.maxUndoSteps) undoHist.pop_front(); + } + + if (e->isPlaying()) e->play(); +} + void FurnaceGUI::doDrag() { int len=dragEnd.xCoarse-dragStart.xCoarse+1; @@ -985,6 +1153,8 @@ void FurnaceGUI::doUndo() { case GUI_UNDO_PATTERN_FLIP: case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: + case GUI_UNDO_PATTERN_COLLAPSE_SONG: + case GUI_UNDO_PATTERN_EXPAND_SONG: case GUI_UNDO_PATTERN_DRAG: case GUI_UNDO_REPLACE: for (UndoPatternData& i: us.pat) { @@ -1005,6 +1175,22 @@ void FurnaceGUI::doUndo() { break; } + bool shallReplay=false; + for (UndoOtherData& i: us.other) { + switch (i.target) { + case GUI_UNDO_TARGET_SONG: + ((unsigned char*)(&e->song))[i.off]=i.oldVal; + shallReplay=true; + break; + case GUI_UNDO_TARGET_SUBSONG: + if (i.subtarget<0 || i.subtarget>=(int)e->song.subsong.size()) break; + ((unsigned char*)(e->song.subsong[i.subtarget]))[i.off]=i.oldVal; + shallReplay=true; + break; + } + } + if (shallReplay && e->isPlaying()) play(); + if (curOrder>=e->curSubSong->ordersLen) { curOrder=e->curSubSong->ordersLen-1; oldOrder=curOrder; @@ -1045,6 +1231,8 @@ void FurnaceGUI::doRedo() { case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: case GUI_UNDO_PATTERN_DRAG: + case GUI_UNDO_PATTERN_COLLAPSE_SONG: + case GUI_UNDO_PATTERN_EXPAND_SONG: case GUI_UNDO_REPLACE: for (UndoPatternData& i: us.pat) { e->changeSongP(i.subSong); @@ -1065,6 +1253,22 @@ void FurnaceGUI::doRedo() { break; } + bool shallReplay=false; + for (UndoOtherData& i: us.other) { + switch (i.target) { + case GUI_UNDO_TARGET_SONG: + ((unsigned char*)(&e->song))[i.off]=i.newVal; + shallReplay=true; + break; + case GUI_UNDO_TARGET_SUBSONG: + if (i.subtarget<0 || i.subtarget>=(int)e->song.subsong.size()) break; + ((unsigned char*)(e->song.subsong[i.subtarget]))[i.off]=i.newVal; + shallReplay=true; + break; + } + } + if (shallReplay && e->isPlaying()) play(); + if (curOrder>=e->curSubSong->ordersLen) { curOrder=e->curSubSong->ordersLen-1; oldOrder=curOrder; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 97cc2de88..614066a11 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1327,6 +1327,41 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { return; } + if (sampleMapWaitingInput) { + if (sampleMapColumn==1) { + // TODO: map? + if (ev.key.keysym.scancode==SDL_SCANCODE_DELETE) { + alterSampleMap(true,-1); + return; + } + try { + int key=noteKeys.at(ev.key.keysym.scancode); + int num=12*curOctave+key; + + if (num<-60) num=-60; // C-(-5) + if (num>119) num=119; // B-9 + + alterSampleMap(true,num); + return; + } catch (std::out_of_range& e) { + } + } else { + // TODO: map? + if (ev.key.keysym.scancode==SDL_SCANCODE_DELETE) { + alterSampleMap(false,-1); + return; + } + try { + int num=valueKeys.at(ev.key.keysym.sym); + if (num<10) { + alterSampleMap(false,num); + return; + } + } catch (std::out_of_range& e) { + } + } + } + // PER-WINDOW KEYS switch (curWindow) { case GUI_WINDOW_PATTERN: @@ -1691,6 +1726,16 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; + case GUI_FILE_SAMPLE_SAVE_RAW: + if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); + hasOpened=fileDialog->openSave( + "Load Raw Sample", + {"all files", "*"}, + ".*", + workingDirSample, + dpiScale + ); + break; case GUI_FILE_EXPORT_AUDIO_ONE: if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); hasOpened=fileDialog->openSave( @@ -1923,8 +1968,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { //ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard; } -#define FURNACE_ZLIB_COMPRESS - int FurnaceGUI::save(String path, int dmfVersion) { SafeWriter* w; logD("saving file..."); @@ -1932,7 +1975,7 @@ int FurnaceGUI::save(String path, int dmfVersion) { if (dmfVersion<24) dmfVersion=24; w=e->saveDMF(dmfVersion); } else { - w=e->saveFur(); + w=e->saveFur(false,settings.newPatternFormat); } if (w==NULL) { lastError=e->getLastError(); @@ -1947,35 +1990,56 @@ int FurnaceGUI::save(String path, int dmfVersion) { w->finish(); return 1; } -#ifdef FURNACE_ZLIB_COMPRESS - unsigned char zbuf[131072]; - int ret; - z_stream zl; - memset(&zl,0,sizeof(z_stream)); - ret=deflateInit(&zl,Z_DEFAULT_COMPRESSION); - if (ret!=Z_OK) { - logE("zlib error!"); - lastError="compression error"; - fclose(outFile); - w->finish(); - return 2; - } - zl.avail_in=w->size(); - zl.next_in=w->getFinalBuf(); - while (zl.avail_in>0) { + if (settings.compress) { + unsigned char zbuf[131072]; + int ret; + z_stream zl; + memset(&zl,0,sizeof(z_stream)); + ret=deflateInit(&zl,Z_DEFAULT_COMPRESSION); + if (ret!=Z_OK) { + logE("zlib error!"); + lastError="compression error"; + fclose(outFile); + w->finish(); + return 2; + } + zl.avail_in=w->size(); + zl.next_in=w->getFinalBuf(); + while (zl.avail_in>0) { + zl.avail_out=131072; + zl.next_out=zbuf; + if ((ret=deflate(&zl,Z_NO_FLUSH))==Z_STREAM_ERROR) { + logE("zlib stream error!"); + lastError="zlib stream error"; + deflateEnd(&zl); + fclose(outFile); + w->finish(); + return 2; + } + size_t amount=131072-zl.avail_out; + if (amount>0) { + if (fwrite(zbuf,1,amount,outFile)!=amount) { + logE("did not write entirely: %s!",strerror(errno)); + lastError=strerror(errno); + deflateEnd(&zl); + fclose(outFile); + w->finish(); + return 1; + } + } + } zl.avail_out=131072; zl.next_out=zbuf; - if ((ret=deflate(&zl,Z_NO_FLUSH))==Z_STREAM_ERROR) { - logE("zlib stream error!"); - lastError="zlib stream error"; + if ((ret=deflate(&zl,Z_FINISH))==Z_STREAM_ERROR) { + logE("zlib finish stream error!"); + lastError="zlib finish stream error"; deflateEnd(&zl); fclose(outFile); w->finish(); return 2; } - size_t amount=131072-zl.avail_out; - if (amount>0) { - if (fwrite(zbuf,1,amount,outFile)!=amount) { + if (131072-zl.avail_out>0) { + if (fwrite(zbuf,1,131072-zl.avail_out,outFile)!=(131072-zl.avail_out)) { logE("did not write entirely: %s!",strerror(errno)); lastError=strerror(errno); deflateEnd(&zl); @@ -1984,37 +2048,16 @@ int FurnaceGUI::save(String path, int dmfVersion) { return 1; } } - } - zl.avail_out=131072; - zl.next_out=zbuf; - if ((ret=deflate(&zl,Z_FINISH))==Z_STREAM_ERROR) { - logE("zlib finish stream error!"); - lastError="zlib finish stream error"; deflateEnd(&zl); - fclose(outFile); - w->finish(); - return 2; - } - if (131072-zl.avail_out>0) { - if (fwrite(zbuf,1,131072-zl.avail_out,outFile)!=(131072-zl.avail_out)) { + } else { + if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { logE("did not write entirely: %s!",strerror(errno)); lastError=strerror(errno); - deflateEnd(&zl); fclose(outFile); w->finish(); return 1; } } - deflateEnd(&zl); -#else - if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { - logE("did not write entirely: %s!",strerror(errno)); - lastError=strerror(errno); - fclose(outFile); - w->finish(); - return 1; - } -#endif fclose(outFile); w->finish(); backupLock.lock(); @@ -2227,6 +2270,11 @@ void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) { displayExporting=true; } +void FurnaceGUI::editStr(String* which) { + editString=which; + displayEditString=true; +} + void FurnaceGUI::showWarning(String what, FurnaceGUIWarnings type) { warnString=what; warnAction=type; @@ -2807,9 +2855,29 @@ void FurnaceGUI::editOptions(bool topMenu) { ImGui::Separator(); if (ImGui::MenuItem("flip selection",BIND_FOR(GUI_ACTION_PAT_FLIP_SELECTION))) doFlip(); - if (ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(2); - if (ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(2); + ImGui::SetNextItemWidth(120.0f*dpiScale); + if (ImGui::InputInt("collapse/expand amount##CollapseAmount",&collapseAmount,1,1)) { + if (collapseAmount<2) collapseAmount=2; + if (collapseAmount>256) collapseAmount=256; + } + if (ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(collapseAmount,selStart,selEnd); + if (ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(collapseAmount,selStart,selEnd); + + if (topMenu) { + ImGui::Separator(); + if (ImGui::MenuItem("collapse pattern",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_PAT))) doAction(GUI_ACTION_PAT_COLLAPSE_PAT); + if (ImGui::MenuItem("expand pattern",BIND_FOR(GUI_ACTION_PAT_EXPAND_PAT))) doAction(GUI_ACTION_PAT_EXPAND_PAT); + } + } + + if (topMenu) { + ImGui::Separator(); + if (ImGui::MenuItem("collapse song",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_SONG))) doAction(GUI_ACTION_PAT_COLLAPSE_SONG); + if (ImGui::MenuItem("expand song",BIND_FOR(GUI_ACTION_PAT_EXPAND_SONG))) doAction(GUI_ACTION_PAT_EXPAND_SONG); + } + + if (!basicMode) { if (topMenu) { ImGui::Separator(); if (ImGui::MenuItem("find/replace",BIND_FOR(GUI_ACTION_WINDOW_FIND),findOpen)) { @@ -2821,16 +2889,6 @@ void FurnaceGUI::editOptions(bool topMenu) { } } } - - /*if (topMenu) { - ImGui::Separator(); - ImGui::MenuItem("collapse pattern",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_PAT)); - ImGui::MenuItem("expand pattern",BIND_FOR(GUI_ACTION_PAT_EXPAND_PAT)); - - ImGui::Separator(); - ImGui::MenuItem("collapse song",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_SONG)); - ImGui::MenuItem("expand song",BIND_FOR(GUI_ACTION_PAT_EXPAND_SONG)); - }*/ } void FurnaceGUI::toggleMobileUI(bool enable, bool force) { @@ -2905,7 +2963,7 @@ int FurnaceGUI::processEvent(SDL_Event* ev) { } #endif if (ev->type==SDL_KEYDOWN) { - if (!ev->key.repeat && latchTarget==0 && !wantCaptureKeyboard && (ev->key.keysym.mod&(~(KMOD_NUM|KMOD_CAPS|KMOD_SCROLL)))==0) { + if (!ev->key.repeat && latchTarget==0 && !wantCaptureKeyboard && !sampleMapWaitingInput && (ev->key.keysym.mod&(~(KMOD_NUM|KMOD_CAPS|KMOD_SCROLL)))==0) { if (settings.notePreviewBehavior==0) return 1; switch (curWindow) { case GUI_WINDOW_SAMPLE_EDIT: @@ -3210,18 +3268,18 @@ void FurnaceGUI::pointMotion(int x, int y, int xrel, int yrel) { // how many pixels should be visible at least at x/y dir #define OOB_PIXELS_SAFETY 25 -bool FurnaceGUI::detectOutOfBoundsWindow() { +bool FurnaceGUI::detectOutOfBoundsWindow(SDL_Rect& failing) { int count=SDL_GetNumVideoDisplays(); if (count<1) { - logW("bounds check: error %s",SDL_GetError()); + logW("bounds check: error: %s",SDL_GetError()); return false; } SDL_Rect rect; for (int i=0; i=scrX); @@ -3233,10 +3291,66 @@ bool FurnaceGUI::detectOutOfBoundsWindow() { } } + failing=rect; return false; } +#define DECLARE_METRIC(_n) \ + int __perfM##_n; + +#define MEASURE_BEGIN(_n) \ + __perfM##_n=SDL_GetPerformanceCounter(); + +#define MEASURE_END(_n) \ + if (perfMetricsLen<64) { \ + perfMetrics[perfMetricsLen++]=FurnaceGUIPerfMetric(#_n,SDL_GetPerformanceCounter()-__perfM##_n); \ + } + +#define MEASURE(_n,_x) \ + MEASURE_BEGIN(_n) \ + _x; \ + MEASURE_END(_n) + bool FurnaceGUI::loop() { + DECLARE_METRIC(calcChanOsc) + DECLARE_METRIC(mobileControls) + DECLARE_METRIC(mobileOrderSel) + DECLARE_METRIC(subSongs) + DECLARE_METRIC(findReplace) + DECLARE_METRIC(spoiler) + DECLARE_METRIC(pattern) + DECLARE_METRIC(editControls) + DECLARE_METRIC(speed) + DECLARE_METRIC(grooves) + DECLARE_METRIC(songInfo) + DECLARE_METRIC(orders) + DECLARE_METRIC(intro) + DECLARE_METRIC(sampleList) + DECLARE_METRIC(sampleEdit) + DECLARE_METRIC(waveList) + DECLARE_METRIC(waveEdit) + DECLARE_METRIC(insList) + DECLARE_METRIC(insEdit) + DECLARE_METRIC(mixer) + DECLARE_METRIC(readOsc) + DECLARE_METRIC(osc) + DECLARE_METRIC(chanOsc) + DECLARE_METRIC(volMeter) + DECLARE_METRIC(settings) + DECLARE_METRIC(debug) + DECLARE_METRIC(stats) + DECLARE_METRIC(compatFlags) + DECLARE_METRIC(piano) + DECLARE_METRIC(notes) + DECLARE_METRIC(channels) + DECLARE_METRIC(patManager) + DECLARE_METRIC(sysManager) + DECLARE_METRIC(clock) + DECLARE_METRIC(regView) + DECLARE_METRIC(log) + DECLARE_METRIC(effectList) + DECLARE_METRIC(popup) + #ifdef IS_MOBILE bool doThreadedInput=true; #else @@ -3258,6 +3372,11 @@ bool FurnaceGUI::loop() { drawHalt=0; if (settings.powerSave) SDL_WaitEventTimeout(NULL,500); } + + memcpy(perfMetricsLast,perfMetrics,64*sizeof(FurnaceGUIPerfMetric)); + perfMetricsLastLen=perfMetricsLen; + perfMetricsLen=0; + eventTimeBegin=SDL_GetPerformanceCounter(); bool updateWindow=false; if (injectBackUp) { @@ -3640,9 +3759,13 @@ bool FurnaceGUI::loop() { }); } + bool fontsFailed=false; + layoutTimeBegin=SDL_GetPerformanceCounter(); - ImGui_ImplSDLRenderer_NewFrame(); + if (!ImGui_ImplSDLRenderer_NewFrame()) { + fontsFailed=true; + } ImGui_ImplSDL2_NewFrame(sdlWin); ImGui::NewFrame(); @@ -3668,6 +3791,7 @@ bool FurnaceGUI::loop() { } } if (ImGui::BeginMenu("open recent")) { + exitDisabledTimer=1; for (int i=0; i<(int)recentFile.size(); i++) { String item=recentFile[i]; if (ImGui::MenuItem(item.c_str())) { @@ -3714,6 +3838,7 @@ bool FurnaceGUI::loop() { } ImGui::Separator(); if (ImGui::BeginMenu("export audio...")) { + exitDisabledTimer=1; if (ImGui::MenuItem("one file")) { openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); } @@ -3732,6 +3857,7 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } if (ImGui::BeginMenu("export VGM...")) { + exitDisabledTimer=1; ImGui::Text("settings:"); if (ImGui::BeginCombo("format version",fmt::sprintf("%d.%.2x",vgmExportVersion>>8,vgmExportVersion&0xff).c_str())) { for (int i=0; i<7; i++) { @@ -3743,11 +3869,11 @@ bool FurnaceGUI::loop() { } ImGui::Checkbox("loop",&vgmExportLoop); if (vgmExportLoop && e->song.loopModality==2) { - ImGui::Text("trailing ticks:"); + ImGui::Text("loop trail:"); if (ImGui::RadioButton("auto-detect",vgmExportTrailingTicks==-1)) { vgmExportTrailingTicks=-1; } - if (ImGui::RadioButton("one loop",vgmExportTrailingTicks==-2)) { + if (ImGui::RadioButton("add one loop",vgmExportTrailingTicks==-2)) { vgmExportTrailingTicks=-2; } if (ImGui::RadioButton("custom",vgmExportTrailingTicks>=0)) { @@ -3820,6 +3946,7 @@ bool FurnaceGUI::loop() { } if (numZSMCompat > 0) { if (ImGui::BeginMenu("export ZSM...")) { + exitDisabledTimer=1; ImGui::Text("Commander X16 Zsound Music File"); if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) { if (zsmExportTickRate<1) zsmExportTickRate=1; @@ -3840,6 +3967,7 @@ bool FurnaceGUI::loop() { } if (numAmiga && settings.iCannotWait) { if (ImGui::BeginMenu("export Amiga validation data...")) { + exitDisabledTimer=1; ImGui::Text( "this is NOT ROM export! only use for making sure the\n" "Furnace Amiga emulator is working properly by\n" @@ -3870,6 +3998,7 @@ bool FurnaceGUI::loop() { } } if (ImGui::BeginMenu("export command stream...")) { + exitDisabledTimer=1; ImGui::Text( "this option exports a text or binary file which\n" "contains a dump of the internal command stream\n" @@ -3887,6 +4016,7 @@ bool FurnaceGUI::loop() { } ImGui::Separator(); if (ImGui::BeginMenu("add chip...")) { + exitDisabledTimer=1; DivSystem picked=systemPicker(); if (picked!=DIV_SYSTEM_NULL) { if (!e->addSystem(picked)) { @@ -3903,6 +4033,7 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } if (ImGui::BeginMenu("configure chip...")) { + exitDisabledTimer=1; for (int i=0; isong.systemLen; i++) { if (ImGui::TreeNode(fmt::sprintf("%d. %s##_SYSP%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { drawSysConf(i,e->song.system[i],e->song.systemFlags[i],true); @@ -3912,6 +4043,7 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } if (ImGui::BeginMenu("change chip...")) { + exitDisabledTimer=1; ImGui::Checkbox("Preserve channel positions",&preserveChanPos); for (int i=0; isong.systemLen; i++) { if (ImGui::BeginMenu(fmt::sprintf("%d. %s##_SYSC%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { @@ -3931,6 +4063,7 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } if (ImGui::BeginMenu("remove chip...")) { + exitDisabledTimer=1; ImGui::Checkbox("Preserve channel positions",&preserveChanPos); for (int i=0; isong.systemLen; i++) { if (ImGui::MenuItem(fmt::sprintf("%d. %s##_SYSR%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { @@ -3947,6 +4080,7 @@ bool FurnaceGUI::loop() { } ImGui::EndMenu(); } + ImGui::BeginDisabled(exitDisabledTimer); ImGui::Separator(); if (ImGui::MenuItem("restore backup",BIND_FOR(GUI_ACTION_OPEN_BACKUP))) { doAction(GUI_ACTION_OPEN_BACKUP); @@ -3959,7 +4093,10 @@ bool FurnaceGUI::loop() { quit=true; } } + ImGui::EndDisabled(); ImGui::EndMenu(); + } else { + exitDisabledTimer=0; } if (ImGui::BeginMenu("edit")) { ImGui::Text("..."); @@ -4146,83 +4283,85 @@ bool FurnaceGUI::loop() { ImGui::EndMainMenuBar(); } - calcChanOsc(); + MEASURE(calcChanOsc,calcChanOsc()); if (mobileUI) { globalWinFlags=ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoBringToFrontOnFocus; //globalWinFlags=ImGuiWindowFlags_NoTitleBar; // scene handling goes here! - drawMobileControls(); + MEASURE(mobileControls,drawMobileControls()); switch (mobScene) { case GUI_SCENE_ORDERS: ordersOpen=true; curWindow=GUI_WINDOW_ORDERS; - drawOrders(); + MEASURE(orders,drawOrders()); break; case GUI_SCENE_INSTRUMENT: insEditOpen=true; curWindow=GUI_WINDOW_INS_EDIT; - drawInsEdit(); - drawPiano(); + MEASURE(insEdit,drawInsEdit()); + MEASURE(piano,drawPiano()); break; case GUI_SCENE_WAVETABLE: waveEditOpen=true; curWindow=GUI_WINDOW_WAVE_EDIT; - drawWaveEdit(); - drawPiano(); + MEASURE(waveEdit,drawWaveEdit()); + MEASURE(piano,drawPiano()); break; case GUI_SCENE_SAMPLE: sampleEditOpen=true; curWindow=GUI_WINDOW_SAMPLE_EDIT; - drawSampleEdit(); - drawPiano(); + MEASURE(sampleEdit,drawSampleEdit()); + MEASURE(piano,drawPiano()); break; case GUI_SCENE_CHANNELS: channelsOpen=true; curWindow=GUI_WINDOW_CHANNELS; - drawChannels(); + MEASURE(channels,drawChannels()); break; case GUI_SCENE_CHIPS: sysManagerOpen=true; curWindow=GUI_WINDOW_SYS_MANAGER; - drawSysManager(); + MEASURE(sysManager,drawSysManager()); break; case GUI_SCENE_MIXER: mixerOpen=true; curWindow=GUI_WINDOW_MIXER; - drawMixer(); + MEASURE(mixer,drawMixer()); break; default: patternOpen=true; curWindow=GUI_WINDOW_PATTERN; - drawPattern(); - drawPiano(); - drawMobileOrderSel(); + MEASURE(pattern,drawPattern()); + MEASURE(piano,drawPiano()); + MEASURE(mobileOrderSel,drawMobileOrderSel()); globalWinFlags=0; - drawFindReplace(); + MEASURE(findReplace,drawFindReplace()); break; } globalWinFlags=0; - drawSettings(); - drawDebug(); - drawLog(); - drawCompatFlags(); - drawStats(); + MEASURE(settings,drawSettings()); + MEASURE(debug,drawDebug()); + MEASURE(log,drawLog()); + MEASURE(compatFlags,drawCompatFlags()); + MEASURE(stats,drawStats()); } else { globalWinFlags=0; ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoWindowMenuButton|ImGuiDockNodeFlags_NoMove|ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0); - drawSubSongs(); - drawFindReplace(); - drawSpoiler(); - drawPattern(); - drawEditControls(); - drawSpeed(); - if (!basicMode) drawGrooves(); - drawSongInfo(); - drawOrders(); + MEASURE(subSongs,drawSubSongs()); + MEASURE(findReplace,drawFindReplace()); + MEASURE(spoiler,drawSpoiler()); + MEASURE(pattern,drawPattern()); + MEASURE(editControls,drawEditControls()); + MEASURE(speed,drawSpeed()); + if (!basicMode) { + MEASURE(grooves,drawGrooves()); + } + MEASURE(songInfo,drawSongInfo()); + MEASURE(orders,drawOrders()); if (introMonOpen) { int totalTicks=e->getTotalTicks(); int totalSeconds=e->getTotalSeconds(); @@ -4234,36 +4373,38 @@ bool FurnaceGUI::loop() { if (e->isPlaying()) monitorPos+=ImGui::GetIO().DeltaTime; } - drawSampleList(); - drawSampleEdit(); - drawWaveList(); - drawWaveEdit(); - drawInsList(); - drawInsEdit(); - drawMixer(); + MEASURE(sampleList,drawSampleList()); + MEASURE(sampleEdit,drawSampleEdit()); + MEASURE(waveList,drawWaveList()); + MEASURE(waveEdit,drawWaveEdit()); + MEASURE(insList,drawInsList()); + MEASURE(insEdit,drawInsEdit()); + MEASURE(mixer,drawMixer()); - readOsc(); + MEASURE(readOsc,readOsc()); - drawOsc(); - drawChanOsc(); - drawVolMeter(); - drawSettings(); - drawDebug(); - drawStats(); - if (!basicMode) drawCompatFlags(); - drawPiano(); - drawNotes(); + MEASURE(osc,drawOsc()); + MEASURE(chanOsc,drawChanOsc()); + MEASURE(volMeter,drawVolMeter()); + MEASURE(settings,drawSettings()); + MEASURE(debug,drawDebug()); + MEASURE(stats,drawStats()); if (!basicMode) { - drawChannels(); + MEASURE(compatFlags,drawCompatFlags()); } - drawPatManager(); + MEASURE(piano,drawPiano()); + MEASURE(notes,drawNotes()); if (!basicMode) { - drawSysManager(); + MEASURE(channels,drawChannels()); } - drawClock(); - drawRegView(); - drawLog(); - drawEffectList(); + MEASURE(patManager,drawPatManager()); + if (!basicMode) { + MEASURE(sysManager,drawSysManager()); + } + MEASURE(clock,drawClock()); + MEASURE(regView,drawRegView()); + MEASURE(log,drawLog()); + MEASURE(effectList,drawEffectList()); } activateTutorial(GUI_TUTORIAL_OVERVIEW); @@ -4343,6 +4484,7 @@ bool FurnaceGUI::loop() { case GUI_FILE_SAMPLE_OPEN_REPLACE: case GUI_FILE_SAMPLE_OPEN_REPLACE_RAW: case GUI_FILE_SAMPLE_SAVE: + case GUI_FILE_SAMPLE_SAVE_RAW: workingDirSample=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_EXPORT_AUDIO_ONE: @@ -4610,7 +4752,16 @@ bool FurnaceGUI::loop() { break; case GUI_FILE_SAMPLE_SAVE: if (curSample>=0 && curSample<(int)e->song.sample.size()) { - e->song.sample[curSample]->save(copyOfName.c_str()); + if (!e->song.sample[curSample]->save(copyOfName.c_str())) { + showError("could not save sample! open Log Viewer for more information."); + } + } + break; + case GUI_FILE_SAMPLE_SAVE_RAW: + if (curSample>=0 && curSample<(int)e->song.sample.size()) { + if (!e->song.sample[curSample]->saveRaw(copyOfName.c_str())) { + showError("could not save sample! open Log Viewer for more information."); + } } break; case GUI_FILE_EXPORT_AUDIO_ONE: @@ -4919,12 +5070,18 @@ bool FurnaceGUI::loop() { ImGui::OpenPopup("New Song"); } + if (displayEditString) { + ImGui::OpenPopup("EditString"); + } + if (nextWindow==GUI_WINDOW_ABOUT) { aboutOpen=true; nextWindow=GUI_WINDOW_NOTHING; } if (aboutOpen) drawAbout(); + MEASURE_BEGIN(popup); + if (ImGui::BeginPopupModal("Rendering...",NULL,ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("Please wait..."); if (ImGui::Button("Abort")) { @@ -4938,7 +5095,7 @@ bool FurnaceGUI::loop() { ImGui::EndPopup(); } - //drawTutorial(); + drawTutorial(); ImVec2 newSongMinSize=mobileUI?ImVec2(canvasW-(portrait?0:(60.0*dpiScale)),canvasH-60.0*dpiScale):ImVec2(400.0f*dpiScale,200.0f*dpiScale); ImVec2 newSongMaxSize=ImVec2(canvasW-((mobileUI && !portrait)?(60.0*dpiScale):0),canvasH-(mobileUI?(60.0*dpiScale):0)); @@ -5456,12 +5613,37 @@ bool FurnaceGUI::loop() { ImGui::EndPopup(); } + if (ImGui::BeginPopup("EditString",ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) { + if (editString==NULL) { + ImGui::Text("Error! No string provided!"); + } else { + if (displayEditString) { + ImGui::SetItemDefaultFocus(); + ImGui::SetKeyboardFocusHere(); + } + ImGui::InputText("##StringVal",editString); + } + displayEditString=false; + ImGui::SameLine(); + if (ImGui::Button("OK") || ImGui::IsKeyPressed(ImGuiKey_Enter,false)) { + editString=NULL; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } else { + editString=NULL; + } + + MEASURE_END(popup); + if (!tutorial.introPlayed || settings.alwaysPlayIntro!=0) { + MEASURE_BEGIN(intro); initialScreenWipe=0; if (settings.alwaysPlayIntro==1) { shortIntro=true; } drawIntro(introPos); + MEASURE_END(intro); } else { introPos=12.0; } @@ -5492,7 +5674,7 @@ bool FurnaceGUI::loop() { } } logD("saving backup..."); - SafeWriter* w=e->saveFur(true); + SafeWriter* w=e->saveFur(true,true); logV("writing file..."); if (w!=NULL) { @@ -5563,6 +5745,8 @@ bool FurnaceGUI::loop() { } } } + + sampleMapWaitingInput=(curWindow==GUI_WINDOW_INS_EDIT && sampleMapFocused); curWindowThreadSafe=curWindow; @@ -5648,6 +5832,10 @@ bool FurnaceGUI::loop() { WAKE_UP; } + if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) { + exitDisabledTimer=0; + } + wheelX=0; wheelY=0; wantScrollList=false; @@ -5660,6 +5848,18 @@ bool FurnaceGUI::loop() { willCommit=false; } + if (fontsFailed) { + showError("it appears I couldn't load these fonts. any setting you can check?"); + logE("couldn't load fonts"); + ImGui::GetIO().Fonts->Clear(); + mainFont=ImGui::GetIO().Fonts->AddFontDefault(); + patFont=mainFont; + ImGui_ImplSDLRenderer_DestroyFontsTexture(); + if (!ImGui::GetIO().Fonts->Build()) { + logE("error again while building font atlas!"); + } + } + if (!editOptsVisible) { latchTarget=0; latchNibble=false; @@ -5734,6 +5934,10 @@ bool FurnaceGUI::init() { basicMode=true; } + insListDir=e->getConfBool("insListDir",false); + waveListDir=e->getConfBool("waveListDir",false); + sampleListDir=e->getConfBool("sampleListDir",false); + tempoView=e->getConfBool("tempoView",true); waveHex=e->getConfBool("waveHex",false); waveSigned=e->getConfBool("waveSigned",false); @@ -5896,10 +6100,23 @@ bool FurnaceGUI::init() { #ifndef IS_MOBILE // if window would spawn out of bounds, force it to be get default position - if (!detectOutOfBoundsWindow()) { + SDL_Rect bounds; + if (!detectOutOfBoundsWindow(bounds)) { scrMax=false; scrX=scrConfX=SDL_WINDOWPOS_CENTERED; scrY=scrConfY=SDL_WINDOWPOS_CENTERED; + + // make sure our window isn't big + /*if (bounds.wsetConf("spoilerOpen",spoilerOpen); e->setConf("basicMode",basicMode); + // commit dir state + e->setConf("insListDir",insListDir); + e->setConf("waveListDir",waveListDir); + e->setConf("sampleListDir",sampleListDir); + // commit last window size e->setConf("lastWindowWidth",scrConfW); e->setConf("lastWindowHeight",scrConfH); @@ -6276,6 +6498,7 @@ FurnaceGUI::FurnaceGUI(): displayPendingRawSample(false), snesFilterHex(false), modTableHex(false), + displayEditString(false), mobileEdit(false), vgmExportVersion(0x171), vgmExportTrailingTicks(-1), @@ -6295,6 +6518,7 @@ FurnaceGUI::FurnaceGUI(): fmPreviewOn(false), fmPreviewPaused(false), fmPreviewOPN(NULL), + editString(NULL), pendingRawSampleDepth(8), pendingRawSampleChannels(1), pendingRawSampleUnsigned(false), @@ -6349,7 +6573,6 @@ FurnaceGUI::FurnaceGUI(): loopEnd(-1), isClipping(0), extraChannelButtons(0), - patNameTarget(-1), newSongCategory(0), latchTarget(0), wheelX(0), @@ -6361,6 +6584,7 @@ FurnaceGUI::FurnaceGUI(): oldBeat(-1), oldBar(-1), curGroove(-1), + exitDisabledTimer(0), soloTimeout(0.0f), exportFadeOut(5.0), editControlsOpen(true), @@ -6399,6 +6623,10 @@ FurnaceGUI::FurnaceGUI(): groovesOpen(false), introMonOpen(false), basicMode(true), + shortIntro(false), + insListDir(false), + waveListDir(false), + sampleListDir(false), clockShowReal(true), clockShowRow(true), clockShowBeat(true), @@ -6416,7 +6644,6 @@ FurnaceGUI::FurnaceGUI(): collapseWindow(false), demandScrollX(false), fancyPattern(false), - wantPatName(false), firstFrame(true), tempoView(true), waveHex(false), @@ -6433,6 +6660,7 @@ FurnaceGUI::FurnaceGUI(): dragMobileMenu(false), dragMobileEditButton(false), wantGrooveListFocus(false), + lastAssetType(0), curWindow(GUI_WINDOW_NOTHING), nextWindow(GUI_WINDOW_NOTHING), curWindowLast(GUI_WINDOW_NOTHING), @@ -6448,6 +6676,7 @@ FurnaceGUI::FurnaceGUI(): wavePreviewLen(32), wavePreviewHeight(255), wavePreviewInit(true), + wavePreviewPaused(false), pgSys(0), pgAddr(0), pgVal(0), @@ -6474,8 +6703,12 @@ FurnaceGUI::FurnaceGUI(): samplePreviewOn(false), samplePreviewKey((SDL_Scancode)0), samplePreviewNote(0), - arpMacroScroll(-12), - pitchMacroScroll(-80), + sampleMapSelStart(-1), + sampleMapSelEnd(-1), + sampleMapDigit(0), + sampleMapColumn(0), + sampleMapFocused(false), + sampleMapWaitingInput(false), macroDragStart(0,0), macroDragAreaSize(0,0), macroDragCTarget(NULL), @@ -6536,15 +6769,19 @@ FurnaceGUI::FurnaceGUI(): eventTimeBegin(0), eventTimeEnd(0), eventTimeDelta(0), + perfMetricsLen(0), chanToMove(-1), sysToMove(-1), sysToDelete(-1), opToMove(-1), + assetToMove(-1), + dirToMove(-1), transposeAmount(0), randomizeMin(0), randomizeMax(255), fadeMin(0), fadeMax(255), + collapseAmount(2), scaleMax(100.0f), fadeMode(false), randomMode(false), @@ -6568,6 +6805,7 @@ FurnaceGUI::FurnaceGUI(): sampleDragMode(false), sampleDrag16(false), sampleZoomAuto(true), + sampleSelTarget(0), sampleDragTarget(NULL), sampleDragStart(0,0), sampleDragAreaSize(0,0), diff --git a/src/gui/gui.h b/src/gui/gui.h index d09bf0fd6..296014e16 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -76,13 +76,34 @@ enum FurnaceGUIColors { GUI_COLOR_BACKGROUND=0, GUI_COLOR_FRAME_BACKGROUND, + GUI_COLOR_FRAME_BACKGROUND_CHILD, + GUI_COLOR_FRAME_BACKGROUND_POPUP, GUI_COLOR_MODAL_BACKDROP, GUI_COLOR_HEADER, GUI_COLOR_TEXT, GUI_COLOR_ACCENT_PRIMARY, GUI_COLOR_ACCENT_SECONDARY, + GUI_COLOR_TITLE_INACTIVE, + GUI_COLOR_TITLE_COLLAPSED, + GUI_COLOR_MENU_BAR, GUI_COLOR_BORDER, GUI_COLOR_BORDER_SHADOW, + GUI_COLOR_SCROLL_BACKGROUND, + GUI_COLOR_SCROLL, + GUI_COLOR_SCROLL_HOVER, + GUI_COLOR_SCROLL_ACTIVE, + GUI_COLOR_SEPARATOR, + GUI_COLOR_SEPARATOR_HOVER, + GUI_COLOR_SEPARATOR_ACTIVE, + GUI_COLOR_DOCKING_PREVIEW, + GUI_COLOR_DOCKING_EMPTY, + GUI_COLOR_TABLE_HEADER, + GUI_COLOR_TABLE_BORDER_HARD, + GUI_COLOR_TABLE_BORDER_SOFT, + GUI_COLOR_DRAG_DROP_TARGET, + GUI_COLOR_NAV_HIGHLIGHT, + GUI_COLOR_NAV_WIN_HIGHLIGHT, + GUI_COLOR_NAV_WIN_BACKDROP, GUI_COLOR_TOGGLE_OFF, GUI_COLOR_TOGGLE_ON, GUI_COLOR_EDITING, @@ -365,6 +386,7 @@ enum FurnaceGUIFileDialogs { GUI_FILE_SAMPLE_OPEN_REPLACE, GUI_FILE_SAMPLE_OPEN_REPLACE_RAW, GUI_FILE_SAMPLE_SAVE, + GUI_FILE_SAMPLE_SAVE_RAW, GUI_FILE_EXPORT_AUDIO_ONE, GUI_FILE_EXPORT_AUDIO_PER_SYS, GUI_FILE_EXPORT_AUDIO_PER_CHANNEL, @@ -565,6 +587,7 @@ enum FurnaceGUIActions { GUI_ACTION_INS_LIST_EDIT, GUI_ACTION_INS_LIST_UP, GUI_ACTION_INS_LIST_DOWN, + GUI_ACTION_INS_LIST_DIR_VIEW, GUI_ACTION_INS_LIST_MAX, GUI_ACTION_WAVE_LIST_MIN, @@ -581,6 +604,7 @@ enum FurnaceGUIActions { GUI_ACTION_WAVE_LIST_EDIT, GUI_ACTION_WAVE_LIST_UP, GUI_ACTION_WAVE_LIST_DOWN, + GUI_ACTION_WAVE_LIST_DIR_VIEW, GUI_ACTION_WAVE_LIST_MAX, GUI_ACTION_SAMPLE_LIST_MIN, @@ -591,6 +615,7 @@ enum FurnaceGUIActions { GUI_ACTION_SAMPLE_LIST_OPEN_RAW, GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE_RAW, GUI_ACTION_SAMPLE_LIST_SAVE, + GUI_ACTION_SAMPLE_LIST_SAVE_RAW, GUI_ACTION_SAMPLE_LIST_MOVE_UP, GUI_ACTION_SAMPLE_LIST_MOVE_DOWN, GUI_ACTION_SAMPLE_LIST_DELETE, @@ -599,6 +624,7 @@ enum FurnaceGUIActions { GUI_ACTION_SAMPLE_LIST_DOWN, GUI_ACTION_SAMPLE_LIST_PREVIEW, GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW, + GUI_ACTION_SAMPLE_LIST_DIR_VIEW, GUI_ACTION_SAMPLE_LIST_MAX, GUI_ACTION_SAMPLE_MIN, @@ -721,6 +747,8 @@ enum NoteCtrl { struct SelectionPoint { int xCoarse, xFine; int y; + SelectionPoint(int xc, int xf, int yp): + xCoarse(xc), xFine(xf), y(yp) {} SelectionPoint(): xCoarse(0), xFine(0), y(0) {} }; @@ -742,10 +770,17 @@ enum ActionType { GUI_UNDO_PATTERN_FLIP, GUI_UNDO_PATTERN_COLLAPSE, GUI_UNDO_PATTERN_EXPAND, + GUI_UNDO_PATTERN_COLLAPSE_SONG, + GUI_UNDO_PATTERN_EXPAND_SONG, GUI_UNDO_PATTERN_DRAG, GUI_UNDO_REPLACE }; +enum UndoOtherTarget { + GUI_UNDO_TARGET_SONG, + GUI_UNDO_TARGET_SUBSONG +}; + struct UndoPatternData { int subSong, chan, pat, row, col; short oldVal, newVal; @@ -770,6 +805,19 @@ struct UndoOrderData { newVal(v2) {} }; +struct UndoOtherData { + UndoOtherTarget target; + int subtarget; + size_t off; + unsigned char oldVal, newVal; + UndoOtherData(UndoOtherTarget t, int st, size_t o, unsigned char v1, unsigned char v2): + target(t), + subtarget(st), + off(o), + oldVal(v1), + newVal(v2) {} +}; + struct UndoStep { ActionType type; SelectionPoint cursor, selStart, selEnd; @@ -779,6 +827,7 @@ struct UndoStep { int oldPatLen, newPatLen; std::vector ord; std::vector pat; + std::vector other; }; // -1 = any @@ -1153,6 +1202,17 @@ struct FurnaceGUIImage { ch(0) {} }; +struct FurnaceGUIPerfMetric { + const char* name; + int elapsed; + FurnaceGUIPerfMetric(const char* n, int t): + name(n), + elapsed(t) {} + FurnaceGUIPerfMetric(): + name(NULL), + elapsed(0) {} +}; + class FurnaceGUI { DivEngine* e; @@ -1171,6 +1231,7 @@ class FurnaceGUI { String workingDirLayout, workingDirROM, workingDirTest; String mmlString[32]; String mmlStringW, mmlStringSNES, grooveString, grooveListString, mmlStringModTable; + String folderString; std::vector sysSearchResults; std::vector newSongSearchResults; @@ -1183,7 +1244,7 @@ class FurnaceGUI { bool portrait, injectBackUp, mobileMenuOpen; bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly; - bool displayPendingIns, pendingInsSingle, displayPendingRawSample, snesFilterHex, modTableHex; + bool displayPendingIns, pendingInsSingle, displayPendingRawSample, snesFilterHex, modTableHex, displayEditString; bool mobileEdit; bool willExport[DIV_MAX_CHIPS]; int vgmExportVersion; @@ -1201,6 +1262,7 @@ class FurnaceGUI { short fmPreview[FM_PREVIEW_SIZE]; bool updateFMPreview, fmPreviewOn, fmPreviewPaused; void* fmPreviewOPN; + String* editString; String pendingRawSample; int pendingRawSampleDepth, pendingRawSampleChannels; @@ -1366,8 +1428,10 @@ class FurnaceGUI { int channelFont; int channelTextCenter; int midiOutClock; + int midiOutTime; int midiOutProgramChange; int midiOutMode; + int midiOutTimeRate; int maxRecentFile; int centerPattern; int ordersCursor; @@ -1381,6 +1445,8 @@ class FurnaceGUI { int alwaysPlayIntro; int iCannotWait; int orderButtonPos; + int compress; + int newPatternFormat; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -1509,8 +1575,10 @@ class FurnaceGUI { channelFont(1), channelTextCenter(1), midiOutClock(0), + midiOutTime(0), midiOutProgramChange(0), midiOutMode(1), + midiOutTimeRate(0), maxRecentFile(10), centerPattern(0), ordersCursor(1), @@ -1524,6 +1592,8 @@ class FurnaceGUI { alwaysPlayIntro(0), iCannotWait(0), orderButtonPos(2), + compress(1), + newPatternFormat(1), maxUndoSteps(100), mainFontPath(""), patFontPath(""), @@ -1543,12 +1613,12 @@ class FurnaceGUI { struct Tutorial { int userComesFrom; bool introPlayed; - bool welcome; + bool protoWelcome; bool taken[GUI_TUTORIAL_MAX]; Tutorial(): userComesFrom(0), introPlayed(false), - welcome(false) { + protoWelcome(false) { memset(taken,0,GUI_TUTORIAL_MAX*sizeof(bool)); } } tutorial; @@ -1558,9 +1628,9 @@ class FurnaceGUI { DivInstrument* prevInsData; int curIns, curWave, curSample, curOctave, curOrder, prevIns, oldRow, oldOrder, oldOrder1, editStep, exportLoops, soloChan,orderEditMode, orderCursor; - int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, patNameTarget, newSongCategory, latchTarget; + int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, newSongCategory, latchTarget; int wheelX, wheelY, dragSourceX, dragSourceXFine, dragSourceY, dragDestinationX, dragDestinationXFine, dragDestinationY, oldBeat, oldBar; - int curGroove; + int curGroove, exitDisabledTimer; float soloTimeout; double exportFadeOut; @@ -1574,14 +1644,16 @@ class FurnaceGUI { bool groovesOpen, introMonOpen; bool basicMode, shortIntro; + bool insListDir, waveListDir, sampleListDir; bool clockShowReal, clockShowRow, clockShowBeat, clockShowMetro, clockShowTime; float clockMetroTick[16]; 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, waveSigned, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble; + bool collapseWindow, demandScrollX, fancyPattern, firstFrame, tempoView, waveHex, waveSigned, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble; bool keepLoopAlive, keepGrooveAlive, orderScrollLocked, orderScrollTolerance, dragMobileMenu, dragMobileEditButton, wantGrooveListFocus; + unsigned char lastAssetType; FurnaceGUIWindows curWindow, nextWindow, curWindowLast; std::atomic curWindowThreadSafe; float peak[DIV_MAX_OUTPUTS]; @@ -1599,7 +1671,7 @@ class FurnaceGUI { DivWaveSynth wavePreview; int wavePreviewLen, wavePreviewHeight; - bool wavePreviewInit; + bool wavePreviewInit, wavePreviewPaused; // bit 31: ctrl // bit 30: reserved for SDL scancode mask @@ -1680,8 +1752,11 @@ class FurnaceGUI { std::vector pressedPoints; std::vector releasedPoints; - int arpMacroScroll; - int pitchMacroScroll; + int sampleMapSelStart; + int sampleMapSelEnd; + int sampleMapDigit; + int sampleMapColumn; + bool sampleMapFocused, sampleMapWaitingInput; ImVec2 macroDragStart; ImVec2 macroDragAreaSize; @@ -1735,9 +1810,16 @@ class FurnaceGUI { int renderTimeBegin, renderTimeEnd, renderTimeDelta; int eventTimeBegin, eventTimeEnd, eventTimeDelta; + FurnaceGUIPerfMetric perfMetrics[64]; + int perfMetricsLen; + + FurnaceGUIPerfMetric perfMetricsLast[64]; + int perfMetricsLastLen; + std::map images; int chanToMove, sysToMove, sysToDelete, opToMove; + int assetToMove, dirToMove; ImVec2 patWindowPos, patWindowSize; @@ -1746,7 +1828,7 @@ class FurnaceGUI { ImVec2 noteCellSize, insCellSize, volCellSize, effectCellSize, effectValCellSize; SelectionPoint sel1, sel2; int dummyRows, demandX; - int transposeAmount, randomizeMin, randomizeMax, fadeMin, fadeMax; + int transposeAmount, randomizeMin, randomizeMax, fadeMin, fadeMax, collapseAmount; float scaleMax; bool fadeMode, randomMode, haveHitBounds, pendingStepUpdate; @@ -1768,6 +1850,9 @@ class FurnaceGUI { int sampleSelStart, sampleSelEnd; bool sampleInfo, sampleCompatRate; bool sampleDragActive, sampleDragMode, sampleDrag16, sampleZoomAuto; + // 0: start + // 1: end + unsigned char sampleSelTarget; void* sampleDragTarget; ImVec2 sampleDragStart; ImVec2 sampleDragAreaSize; @@ -1936,12 +2021,17 @@ class FurnaceGUI { void drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float availableWidth, int index); void drawMacros(std::vector& macros, FurnaceGUIMacroEditState& state); + void alterSampleMap(bool isNote, int val); void drawOrderButtons(); void actualWaveList(); void actualSampleList(); + void insListItem(int index, int dir, int asset); + void waveListItem(int index, float* wavePreview, int dir, int asset); + void sampleListItem(int index, int dir, int asset); + void toggleMobileUI(bool enable, bool force=false); void pushToggleColors(bool status); @@ -2043,8 +2133,10 @@ class FurnaceGUI { void doScale(float top); void doRandomize(int bottom, int top, bool mode); void doFlip(); - void doCollapse(int divider); - void doExpand(int multiplier); + void doCollapse(int divider, const SelectionPoint& sStart, const SelectionPoint& sEnd); + void doExpand(int multiplier, const SelectionPoint& sStart, const SelectionPoint& sEnd); + void doCollapseSong(int divider); + void doExpandSong(int multiplier); void doUndo(); void doRedo(); void doFind(); @@ -2100,6 +2192,7 @@ class FurnaceGUI { const char* getSystemName(DivSystem which); public: + void editStr(String* which); void showWarning(String what, FurnaceGUIWarnings type); void showError(String what); String getLastError(); @@ -2113,7 +2206,7 @@ class FurnaceGUI { void runBackupThread(); void pushPartBlend(); void popPartBlend(); - bool detectOutOfBoundsWindow(); + bool detectOutOfBoundsWindow(SDL_Rect& failing); int processEvent(SDL_Event* ev); bool loop(); bool finish(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index f747dbbe5..2989ae728 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -610,6 +610,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("INS_LIST_EDIT", "Edit", FURKMOD_SHIFT|SDLK_RETURN), D("INS_LIST_UP", "Cursor up", SDLK_UP), D("INS_LIST_DOWN", "Cursor down", SDLK_DOWN), + D("INS_LIST_DIR_VIEW", "Toggle folders/standard view", FURKMOD_CMD|SDLK_v), D("INS_LIST_MAX", "", NOT_AN_ACTION), D("WAVE_LIST_MIN", "---Wavetable list", NOT_AN_ACTION), @@ -626,6 +627,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WAVE_LIST_EDIT", "Edit", FURKMOD_SHIFT|SDLK_RETURN), D("WAVE_LIST_UP", "Cursor up", SDLK_UP), D("WAVE_LIST_DOWN", "Cursor down", SDLK_DOWN), + D("WAVE_LIST_DIR_VIEW", "Toggle folders/standard view", FURKMOD_CMD|SDLK_v), D("WAVE_LIST_MAX", "", NOT_AN_ACTION), D("SAMPLE_LIST_MIN", "---Sample list", NOT_AN_ACTION), @@ -636,6 +638,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("SAMPLE_LIST_OPEN_RAW", "Import raw data", 0), D("SAMPLE_LIST_OPEN_REPLACE_RAW", "Import raw data (replace current)", 0), D("SAMPLE_LIST_SAVE", "Save", 0), + D("SAMPLE_LIST_SAVE_RAW", "Save (raw)", 0), D("SAMPLE_LIST_MOVE_UP", "Move up", FURKMOD_SHIFT|SDLK_UP), D("SAMPLE_LIST_MOVE_DOWN", "Move down", FURKMOD_SHIFT|SDLK_DOWN), D("SAMPLE_LIST_DELETE", "Delete", 0), @@ -644,6 +647,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("SAMPLE_LIST_DOWN", "Cursor down", SDLK_DOWN), D("SAMPLE_LIST_PREVIEW", "Preview", 0), D("SAMPLE_LIST_STOP_PREVIEW", "Stop preview", 0), + D("SAMPLE_LIST_DIR_VIEW", "Toggle folders/standard view", FURKMOD_CMD|SDLK_v), D("SAMPLE_LIST_MAX", "", NOT_AN_ACTION), D("SAMPLE_MIN", "---Sample editor", NOT_AN_ACTION), @@ -706,13 +710,34 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_BACKGROUND,"Background",ImVec4(0.1f,0.1f,0.1f,1.0f)), D(GUI_COLOR_FRAME_BACKGROUND,"",ImVec4(0.0f,0.0f,0.0f,0.85f)), + D(GUI_COLOR_FRAME_BACKGROUND_CHILD,"",ImVec4(0.0f,0.0f,0.0f,0.0f)), + D(GUI_COLOR_FRAME_BACKGROUND_POPUP,"",ImVec4(0.08f,0.08f,0.08f,0.94f)), D(GUI_COLOR_MODAL_BACKDROP,"",ImVec4(0.0f,0.0f,0.0f,0.55f)), D(GUI_COLOR_HEADER,"",ImVec4(0.2f,0.2f,0.2f,1.0f)), D(GUI_COLOR_TEXT,"",ImVec4(1.0f,1.0f,1.0f,1.0f)), D(GUI_COLOR_ACCENT_PRIMARY,"",ImVec4(0.06f,0.53f,0.98f,1.0f)), D(GUI_COLOR_ACCENT_SECONDARY,"",ImVec4(0.26f,0.59f,0.98f,1.0f)), + D(GUI_COLOR_TITLE_INACTIVE,"",ImVec4(0.04f,0.04f,0.04f,1.0f)), + D(GUI_COLOR_TITLE_COLLAPSED,"",ImVec4(0.0f,0.0f,0.0f,0.51f)), + D(GUI_COLOR_MENU_BAR,"",ImVec4(0.14f,0.14f,0.14f,1.0f)), D(GUI_COLOR_BORDER,"",ImVec4(0.43f,0.43f,0.5f,0.5f)), D(GUI_COLOR_BORDER_SHADOW,"",ImVec4(0.0f,0.0f,0.0f,0.0f)), + D(GUI_COLOR_SCROLL_BACKGROUND,"",ImVec4(0.02f,0.02f,0.02f,0.33f)), + D(GUI_COLOR_SCROLL,"",ImVec4(0.31f,0.31f,0.31f,1.0f)), + D(GUI_COLOR_SCROLL_HOVER,"",ImVec4(0.41f,0.41f,0.41f,1.0f)), + D(GUI_COLOR_SCROLL_ACTIVE,"",ImVec4(0.51f,0.51f,0.51f,1.0f)), + D(GUI_COLOR_SEPARATOR,"",ImVec4(0.43f,0.43f,0.5f,0.5f)), + D(GUI_COLOR_SEPARATOR_HOVER,"",ImVec4(0.1f,0.4f,0.75f,0.78f)), + D(GUI_COLOR_SEPARATOR_ACTIVE,"",ImVec4(0.1f,0.4f,0.75f,1.0f)), + D(GUI_COLOR_DOCKING_PREVIEW,"",ImVec4(0.26f,0.59f,0.98f,0.7f)), + D(GUI_COLOR_DOCKING_EMPTY,"",ImVec4(0.2f,0.2f,0.2f,1.0f)), + D(GUI_COLOR_TABLE_HEADER,"",ImVec4(0.19f,0.19f,0.2f,1.0f)), + D(GUI_COLOR_TABLE_BORDER_HARD,"",ImVec4(0.31f,0.31f,0.35f,1.0f)), + D(GUI_COLOR_TABLE_BORDER_SOFT,"",ImVec4(0.23f,0.23f,0.25f,1.0f)), + D(GUI_COLOR_DRAG_DROP_TARGET,"",ImVec4(1.0f,1.0f,0.0f,0.9f)), + D(GUI_COLOR_NAV_HIGHLIGHT,"",ImVec4(0.26f,0.59f,0.98f,1.0f)), + D(GUI_COLOR_NAV_WIN_HIGHLIGHT,"",ImVec4(1.0f,1.0f,1.0f,0.7f)), + D(GUI_COLOR_NAV_WIN_BACKDROP,"",ImVec4(0.8f,0.8f,0.8f,0.2f)), D(GUI_COLOR_TOGGLE_OFF,"",ImVec4(0.2f,0.2f,0.2f,1.0f)), D(GUI_COLOR_TOGGLE_ON,"",ImVec4(0.2f,0.6f,0.2f,1.0f)), D(GUI_COLOR_EDITING,"",ImVec4(0.2f,0.1f,0.1f,1.0f)), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index f2d97ac04..e02f39695 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1464,7 +1464,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (!i.isBitfield) { if (settings.oldMacroVSlider) { ImGui::SameLine(0.0f); - if (ImGui::VSliderInt("IMacroVScroll",ImVec2(20.0f*dpiScale,i.height*dpiScale),&i.macro->vScroll,0,(i.max-i.min)-i.macro->vZoom,"")) { + if (ImGui::VSliderInt("IMacroVScroll",ImVec2(20.0f*dpiScale,i.height*dpiScale),&i.macro->vScroll,0,(i.max-i.min)-i.macro->vZoom,"",ImGuiSliderFlags_NoInput)) { if (i.macro->vScroll<0) i.macro->vScroll=0; if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; } @@ -1592,7 +1592,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MAAR",&i.macro->val[2],0,255)) { PARAMETER if (i.macro->val[2]<0) i.macro->val[2]=0; if (i.macro->val[2]>255) i.macro->val[2]=255; - } + } rightClickable ImGui::TableNextColumn(); ImGui::Text("Sustain"); @@ -1601,7 +1601,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MASL",&i.macro->val[5],0,255)) { PARAMETER if (i.macro->val[5]<0) i.macro->val[5]=0; if (i.macro->val[5]>255) i.macro->val[5]=255; - } + } rightClickable ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -1611,7 +1611,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MAHT",&i.macro->val[3],0,255)) { PARAMETER if (i.macro->val[3]<0) i.macro->val[3]=0; if (i.macro->val[3]>255) i.macro->val[3]=255; - } + } rightClickable ImGui::TableNextColumn(); ImGui::Text("SusTime"); @@ -1620,7 +1620,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MAST",&i.macro->val[6],0,255)) { PARAMETER if (i.macro->val[6]<0) i.macro->val[6]=0; if (i.macro->val[6]>255) i.macro->val[6]=255; - } + } rightClickable ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -1630,7 +1630,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MADR",&i.macro->val[4],0,255)) { PARAMETER if (i.macro->val[4]<0) i.macro->val[4]=0; if (i.macro->val[4]>255) i.macro->val[4]=255; - } + } rightClickable ImGui::TableNextColumn(); ImGui::Text("SusDecay"); @@ -1639,7 +1639,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MASR",&i.macro->val[7],0,255)) { PARAMETER if (i.macro->val[7]<0) i.macro->val[7]=0; if (i.macro->val[7]>255) i.macro->val[7]=255; - } + } rightClickable ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -1652,7 +1652,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MARR",&i.macro->val[8],0,255)) { PARAMETER if (i.macro->val[8]<0) i.macro->val[8]=0; if (i.macro->val[8]>255) i.macro->val[8]=255; - } + } rightClickable ImGui::EndTable(); } @@ -1695,7 +1695,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MLSpeed",&i.macro->val[11],0,255)) { PARAMETER if (i.macro->val[11]<0) i.macro->val[11]=0; if (i.macro->val[11]>255) i.macro->val[11]=255; - } + } rightClickable ImGui::TableNextColumn(); ImGui::Text("Phase"); @@ -1704,7 +1704,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MLPhase",&i.macro->val[13],0,1023)) { PARAMETER if (i.macro->val[13]<0) i.macro->val[13]=0; if (i.macro->val[13]>1023) i.macro->val[13]=1023; - } + } rightClickable ImGui::TableNextColumn(); ImGui::Text("Shape"); @@ -1713,7 +1713,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MLShape",&i.macro->val[12],0,2,macroLFOShapes[i.macro->val[12]&3])) { PARAMETER if (i.macro->val[12]<0) i.macro->val[12]=0; if (i.macro->val[12]>2) i.macro->val[12]=2; - } + } rightClickable ImGui::EndTable(); } @@ -2023,6 +2023,59 @@ void FurnaceGUI::drawMacros(std::vector& macros, FurnaceGUI } } +void FurnaceGUI::alterSampleMap(bool isNote, int val) { + if (curIns<0 || curIns>=(int)e->song.ins.size()) return; + DivInstrument* ins=e->song.ins[curIns]; + int sampleMapMin=sampleMapSelStart; + int sampleMapMax=sampleMapSelEnd; + if (sampleMapMin>sampleMapMax) { + sampleMapMin^=sampleMapMax; + sampleMapMax^=sampleMapMin; + sampleMapMin^=sampleMapMax; + } + + for (int i=sampleMapMin; i<=sampleMapMax; i++) { + if (i<0 || i>=120) continue; + + if (sampleMapColumn==1 && isNote) { + ins->amiga.noteMap[i].freq=val; + } else if (sampleMapColumn==0 && !isNote) { + if (val<0) { + ins->amiga.noteMap[i].map=-1; + } else if (sampleMapDigit>0) { + ins->amiga.noteMap[i].map*=10; + ins->amiga.noteMap[i].map+=val; + } else { + ins->amiga.noteMap[i].map=val; + } + if (ins->amiga.noteMap[i].map>=(int)e->song.sample.size()) { + ins->amiga.noteMap[i].map=((int)e->song.sample.size())-1; + } + } + } + + bool advance=false; + if (sampleMapColumn==1 && isNote) { + advance=true; + } else if (sampleMapColumn==0 && !isNote) { + int digits=1; + if (e->song.sample.size()>=10) digits=2; + if (e->song.sample.size()>=100) digits=3; + if (++sampleMapDigit>=digits) { + sampleMapDigit=0; + advance=true; + } + } + + if (advance && sampleMapMin==sampleMapMax) { + sampleMapSelStart++; + if (sampleMapSelStart>119) sampleMapSelStart=119; + sampleMapSelEnd=sampleMapSelStart; + } + + MARK_MODIFIED; +} + #define DRUM_FREQ(name,db,df,prop) \ ImGui::TableNextRow(); \ ImGui::TableNextColumn(); \ @@ -2786,37 +2839,37 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); op.ar&=maxArDr; CENTER_VSLIDER; - P(CWVSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); + P(CWVSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); rightClickable ImGui::TableNextColumn(); op.dr&=maxArDr; CENTER_VSLIDER; - P(CWVSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); + P(CWVSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); rightClickable if (settings.susPosition==0) { ImGui::TableNextColumn(); op.sl&=15; CENTER_VSLIDER; - P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { ImGui::TableNextColumn(); op.d2r&=31; CENTER_VSLIDER; - P(CWVSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); + P(CWVSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); rightClickable } ImGui::TableNextColumn(); op.rr&=15; CENTER_VSLIDER; - P(CWVSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); + P(CWVSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); rightClickable if (settings.susPosition==1) { ImGui::TableNextColumn(); op.sl&=15; CENTER_VSLIDER; - P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable } ImGui::TableNextColumn(); @@ -2825,38 +2878,38 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); op.tl&=maxTl; CENTER_VSLIDER; - P(CWVSliderScalar("##TL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); + P(CWVSliderScalar("##TL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); rightClickable ImGui::TableNextColumn(); CENTER_VSLIDER; if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { - P(CWVSliderScalar("##RS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); + P(CWVSliderScalar("##RS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); rightClickable } else { int ksl=ins->type==DIV_INS_OPLL?op.ksl:kslMap[op.ksl&3]; if (CWVSliderInt("##KSL",ImVec2(20.0f*dpiScale,sliderHeight),&ksl,0,3)) { op.ksl=(ins->type==DIV_INS_OPLL?ksl:kslMap[ksl&3]); PARAMETER; - } + } rightClickable } if (ins->type==DIV_INS_OPZ) { ImGui::TableNextColumn(); CENTER_VSLIDER; - P(CWVSliderScalar("##EGS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); + P(CWVSliderScalar("##EGS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); rightClickable ImGui::TableNextColumn(); CENTER_VSLIDER; - P(CWVSliderScalar("##REV",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dam,&_ZERO,&_SEVEN)); + P(CWVSliderScalar("##REV",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dam,&_ZERO,&_SEVEN)); rightClickable } ImGui::TableNextColumn(); CENTER_VSLIDER; - P(CWVSliderScalar("##MULT",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##MULT",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); rightClickable if (ins->type==DIV_INS_OPZ) { ImGui::TableNextColumn(); CENTER_VSLIDER; - P(CWVSliderScalar("##FINE",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dvb,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##FINE",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dvb,&_ZERO,&_FIFTEEN)); rightClickable } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { @@ -2867,7 +2920,7 @@ void FurnaceGUI::drawInsEdit() { if (detune<-3) detune=-3; if (detune>7) detune=7; op.dt=detuneUnmap[settings.unsignedDetune?1:0][detune+3]; - } + } rightClickable if (ins->type!=DIV_INS_FM) { ImGui::TableNextColumn(); @@ -3116,19 +3169,19 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); op.ar&=maxArDr; - P(CWVSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); + P(CWVSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); rightClickable ImGui::SameLine(); op.dr&=maxArDr; float textX_DR=ImGui::GetCursorPosX(); - P(CWVSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); + P(CWVSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); rightClickable 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)); + P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable } float textX_D2R=0.0f; @@ -3136,19 +3189,19 @@ void FurnaceGUI::drawInsEdit() { ImGui::SameLine(); op.d2r&=31; textX_D2R=ImGui::GetCursorPosX(); - P(CWVSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); + P(CWVSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); rightClickable } ImGui::SameLine(); op.rr&=15; float textX_RR=ImGui::GetCursorPosX(); - P(CWVSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); + P(CWVSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); rightClickable 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)); + P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable } ImVec2 prevCurPos=ImGui::GetCursorPos(); @@ -3447,7 +3500,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); op.tl&=maxTl; - P(CWVSliderScalar("##TL",ImVec2(ImGui::GetFrameHeight(),sliderHeight-((ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM)?(ImGui::GetFrameHeightWithSpacing()+ImGui::CalcTextSize(FM_SHORT_NAME(FM_AM)).y+ImGui::GetStyle().ItemSpacing.y):0.0f)),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); + P(CWVSliderScalar("##TL",ImVec2(ImGui::GetFrameHeight(),sliderHeight-((ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM)?(ImGui::GetFrameHeightWithSpacing()+ImGui::CalcTextSize(FM_SHORT_NAME(FM_AM)).y+ImGui::GetStyle().ItemSpacing.y):0.0f)),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); rightClickable if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM) { CENTER_TEXT(FM_SHORT_NAME(FM_AM)); @@ -4246,13 +4299,13 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); - P(CWVSliderScalar("##Attack",sliderSize,ImGuiDataType_U8,&ins->c64.a,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Attack",sliderSize,ImGuiDataType_U8,&ins->c64.a,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Decay",sliderSize,ImGuiDataType_U8,&ins->c64.d,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Decay",sliderSize,ImGuiDataType_U8,&ins->c64.d,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Sustain",sliderSize,ImGuiDataType_U8,&ins->c64.s,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Sustain",sliderSize,ImGuiDataType_U8,&ins->c64.s,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Release",sliderSize,ImGuiDataType_U8,&ins->c64.r,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Release",sliderSize,ImGuiDataType_U8,&ins->c64.r,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); drawFMEnv(0,16-ins->c64.a,16-ins->c64.d,15-ins->c64.r,15-ins->c64.r,15-ins->c64.s,0,0,0,15,16,15,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); @@ -4341,6 +4394,7 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_GA20) { if (ImGui::BeginTabItem((ins->type==DIV_INS_SU)?"Sound Unit":"Sample")) { String sName; + bool wannaOpenSMPopup=false; if (ins->amiga.initSample<0 || ins->amiga.initSample>=e->song.sampleLen) { sName="none selected"; } else { @@ -4405,71 +4459,191 @@ void FurnaceGUI::drawInsEdit() { ImGui::BeginDisabled(ins->amiga.useWave); P(ImGui::Checkbox("Use sample map",&ins->amiga.useNoteMap)); if (ins->amiga.useNoteMap) { - if (ImGui::BeginTable("NoteMap",3,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) sampleMapFocused=false; + if (curWindowLast!=GUI_WINDOW_INS_EDIT) sampleMapFocused=false; + if (!sampleMapFocused) sampleMapDigit=0; + if (ImGui::BeginTable("NoteMap",4,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupScrollFreeze(0,1); ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableNextColumn(); ImGui::TableNextColumn(); - ImGui::Text("Sample"); + ImGui::Text("#"); ImGui::TableNextColumn(); - ImGui::Text("Note"); + ImGui::Text("note"); + ImGui::TableNextColumn(); + ImGui::Text("sample name"); + int sampleMapMin=sampleMapSelStart; + int sampleMapMax=sampleMapSelEnd; + if (sampleMapMin>sampleMapMax) { + sampleMapMin^=sampleMapMax; + sampleMapMax^=sampleMapMin; + sampleMapMin^=sampleMapMax; + } + + ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(ImGuiCol_HeaderHovered)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive,ImGui::GetColorU32(ImGuiCol_HeaderHovered)); for (int i=0; i<120; i++) { DivInstrumentAmiga::SampleMap& sampleMap=ins->amiga.noteMap[i]; ImGui::TableNextRow(); - ImGui::PushID(fmt::sprintf("NM_%d",i).c_str()); ImGui::TableNextColumn(); + ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(ImGuiCol_TableHeaderBg)); ImGui::Text("%s",noteNames[60+i]); ImGui::TableNextColumn(); if (sampleMap.map<0 || sampleMap.map>=e->song.sampleLen) { - sName="-- empty --"; + sName=fmt::sprintf("---##SM%d",i); sampleMap.map=-1; } else { - sName=e->song.sample[sampleMap.map]->name; + sName=fmt::sprintf("%3d##SM%d",sampleMap.map,i); } - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("##SM",sName.c_str())) { - String id; - if (ImGui::Selectable("-- empty --",sampleMap.map==-1)) { PARAMETER - sampleMap.map=-1; + ImGui::PushFont(patFont); + ImGui::SetNextItemWidth(ImGui::CalcTextSize("00000").x); + ImGui::Selectable(sName.c_str(),(sampleMapWaitingInput && sampleMapColumn==0 && i>=sampleMapMin && i<=sampleMapMax)); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + sampleMapFocused=true; + sampleMapColumn=0; + sampleMapDigit=0; + sampleMapSelStart=i; + sampleMapSelEnd=i; + + sampleMapMin=sampleMapSelStart; + sampleMapMax=sampleMapSelEnd; + if (sampleMapMin>sampleMapMax) { + sampleMapMin^=sampleMapMax; + sampleMapMax^=sampleMapMin; + sampleMapMin^=sampleMapMax; } - for (int j=0; jsong.sampleLen; j++) { - id=fmt::sprintf("%d: %s",j,e->song.sample[j]->name); - if (ImGui::Selectable(id.c_str(),sampleMap.map==j)) { PARAMETER - sampleMap.map=j; + ImGui::InhibitInertialScroll(); + } + if (sampleMapFocused && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { + sampleMapSelEnd=i; + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + if (sampleMapSelStart==sampleMapSelEnd) { + sampleMapFocused=true; + sampleMapColumn=0; + sampleMapDigit=0; + sampleMapSelStart=i; + sampleMapSelEnd=i; + + sampleMapMin=sampleMapSelStart; + sampleMapMax=sampleMapSelEnd; + if (sampleMapMin>sampleMapMax) { + sampleMapMin^=sampleMapMax; + sampleMapMax^=sampleMapMin; + sampleMapMin^=sampleMapMax; } } - ImGui::EndCombo(); + if (sampleMapFocused) { + wannaOpenSMPopup=true; + } } + ImGui::PopFont(); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - const char* nName="???"; + sName="???"; if ((sampleMap.freq+60)>0 && (sampleMap.freq+60)<180) { - nName=noteNames[sampleMap.freq+60]; + sName=noteNames[sampleMap.freq+60]; } - if (ImGui::BeginCombo("##SN",nName)) { - for (int j=0; j<180; j++) { - const char* nName2="???"; - nName2=noteNames[j]; - if (ImGui::Selectable(nName2,(sampleMap.freq+60)==j)) { - sampleMap.freq=j-60; + sName+=fmt::sprintf("##SN%d",i); + ImGui::PushFont(patFont); + ImGui::SetNextItemWidth(ImGui::CalcTextSize("00000").x); + ImGui::Selectable(sName.c_str(),(sampleMapWaitingInput && sampleMapColumn==1 && i>=sampleMapMin && i<=sampleMapMax)); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + sampleMapFocused=true; + sampleMapColumn=1; + sampleMapDigit=0; + sampleMapSelStart=i; + sampleMapSelEnd=i; + + sampleMapMin=sampleMapSelStart; + sampleMapMax=sampleMapSelEnd; + if (sampleMapMin>sampleMapMax) { + sampleMapMin^=sampleMapMax; + sampleMapMax^=sampleMapMin; + sampleMapMin^=sampleMapMax; + } + ImGui::InhibitInertialScroll(); + } + if (sampleMapFocused && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { + sampleMapSelEnd=i; + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + if (sampleMapSelStart==sampleMapSelEnd) { + sampleMapFocused=true; + sampleMapColumn=1; + sampleMapDigit=0; + sampleMapSelStart=i; + sampleMapSelEnd=i; + + sampleMapMin=sampleMapSelStart; + sampleMapMax=sampleMapSelEnd; + if (sampleMapMin>sampleMapMax) { + sampleMapMin^=sampleMapMax; + sampleMapMax^=sampleMapMin; + sampleMapMin^=sampleMapMax; } } - ImGui::EndCombo(); + if (sampleMapFocused) { + wannaOpenSMPopup=true; + } } + ImGui::PopFont(); - ImGui::PopID(); + ImGui::TableNextColumn(); + if (sampleMap.map>=0 && sampleMap.mapsong.sampleLen) { + ImGui::TextUnformatted(e->song.sample[sampleMap.map]->name.c_str()); + } } + ImGui::PopStyleColor(2); ImGui::EndTable(); } + } else { + sampleMapFocused=false; } ImGui::EndDisabled(); + if (wannaOpenSMPopup) { + ImGui::OpenPopup("SampleMapUtils"); + } + if (ImGui::BeginPopup("SampleMapUtils",ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) { + if (sampleMapSelStart==sampleMapSelEnd && sampleMapSelStart>=0 && sampleMapSelStart<120) { + if (ImGui::MenuItem("set entire map to this note")) { + if (sampleMapSelStart>=0 && sampleMapSelStart<120) { + for (int i=0; i<120; i++) { + if (i==sampleMapSelStart) continue; + ins->amiga.noteMap[i].freq=ins->amiga.noteMap[sampleMapSelStart].freq; + } + } + } + if (ImGui::MenuItem("set entire map to this sample")) { + if (sampleMapSelStart>=0 && sampleMapSelStart<120) { + for (int i=0; i<120; i++) { + if (i==sampleMapSelStart) continue; + ins->amiga.noteMap[i].map=ins->amiga.noteMap[sampleMapSelStart].map; + } + } + } + } + if (ImGui::MenuItem("reset notes")) { + for (int i=0; i<120; i++) { + ins->amiga.noteMap[i].freq=i; + } + } + if (ImGui::MenuItem("clear map samples")) { + for (int i=0; i<120; i++) { + ins->amiga.noteMap[i].map=-1; + } + } + ImGui::EndPopup(); + } ImGui::EndTabItem(); + } else { + sampleMapFocused=false; } } if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem(settings.c163Name.c_str())) { @@ -4625,7 +4799,7 @@ void FurnaceGUI::drawInsEdit() { // filter ImGui::TableNextRow(); ImGui::TableNextColumn(); - P(CWSliderScalar("Filter 4,3 Mode",ImGuiDataType_U8,&ins->es5506.filter.mode,&_ZERO,&_THREE,es5506FilterModes[ins->es5506.filter.mode&3])); rightClickable + P(CWSliderScalar("Filter Mode",ImGuiDataType_U8,&ins->es5506.filter.mode,&_ZERO,&_THREE,es5506FilterModes[ins->es5506.filter.mode&3])); ImGui::TableNextRow(); ImGui::TableNextColumn(); P(CWSliderScalar("Filter K1",ImGuiDataType_U16,&ins->es5506.filter.k1,&_ZERO,&_SIXTY_FIVE_THOUSAND_FIVE_HUNDRED_THIRTY_FIVE)); rightClickable @@ -4709,17 +4883,17 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); - P(CWVSliderScalar("##Attack Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.ar,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Attack Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.ar,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Decay 1 Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.d1r,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Decay 1 Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.d1r,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Decay Level",sliderSize,ImGuiDataType_U8,&ins->multipcm.dl,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Decay Level",sliderSize,ImGuiDataType_U8,&ins->multipcm.dl,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Decay 2 Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.d2r,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Decay 2 Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.d2r,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Release Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.rr,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Release Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.rr,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Rate Correction",sliderSize,ImGuiDataType_U8,&ins->multipcm.rc,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Rate Correction",sliderSize,ImGuiDataType_U8,&ins->multipcm.rc,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); drawFMEnv(0,ins->multipcm.ar,ins->multipcm.d1r,ins->multipcm.d2r,ins->multipcm.rr,ins->multipcm.dl,0,0,0,127,15,15,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); ImGui::EndTable(); @@ -4777,17 +4951,17 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); - P(CWVSliderScalar("##Attack",sliderSize,ImGuiDataType_U8,&ins->snes.a,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Attack",sliderSize,ImGuiDataType_U8,&ins->snes.a,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Decay",sliderSize,ImGuiDataType_U8,&ins->snes.d,&_ZERO,&_SEVEN)); + P(CWVSliderScalar("##Decay",sliderSize,ImGuiDataType_U8,&ins->snes.d,&_ZERO,&_SEVEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Sustain",sliderSize,ImGuiDataType_U8,&ins->snes.s,&_ZERO,&_SEVEN)); + P(CWVSliderScalar("##Sustain",sliderSize,ImGuiDataType_U8,&ins->snes.s,&_ZERO,&_SEVEN)); rightClickable if (ins->snes.sus) { ImGui::TableNextColumn(); - P(CWVSliderScalar("##Decay2",sliderSize,ImGuiDataType_U8,&ins->snes.d2,&_ZERO,&_THIRTY_ONE)); + P(CWVSliderScalar("##Decay2",sliderSize,ImGuiDataType_U8,&ins->snes.d2,&_ZERO,&_THIRTY_ONE)); rightClickable } ImGui::TableNextColumn(); - P(CWVSliderScalar("##Release",sliderSize,ImGuiDataType_U8,&ins->snes.r,&_ZERO,&_THIRTY_ONE)); + P(CWVSliderScalar("##Release",sliderSize,ImGuiDataType_U8,&ins->snes.r,&_ZERO,&_THIRTY_ONE)); rightClickable ImGui::TableNextColumn(); drawFMEnv(0,ins->snes.a+1,1+ins->snes.d*2,ins->snes.sus?ins->snes.d2:ins->snes.r,ins->snes.sus?ins->snes.r:31,(14-ins->snes.s*2),(ins->snes.r==0 || (ins->snes.sus && ins->snes.d2==0)),0,0,7,16,31,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); @@ -4849,7 +5023,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); unsigned char gainMax=(ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT)?127:31; if (ins->snes.gain>gainMax) ins->snes.gain=gainMax; - P(CWVSliderScalar("##Gain",sliderSize,ImGuiDataType_U8,&ins->snes.gain,&_ZERO,&gainMax)); + P(CWVSliderScalar("##Gain",sliderSize,ImGuiDataType_U8,&ins->snes.gain,&_ZERO,&gainMax)); rightClickable ImGui::TableNextColumn(); ImGui::Text("Envelope goes here..."); @@ -4979,7 +5153,9 @@ void FurnaceGUI::drawInsEdit() { wavePreview2[i]=wave2->data[i]; } } - if (ins->ws.enabled) wavePreview.tick(true); + if (ins->ws.enabled && (!wavePreviewPaused || wavePreviewInit)) { + wavePreview.tick(true); + } for (int i=0; idata[i]>wavePreviewHeight) { wavePreview3[i]=wavePreviewHeight; @@ -5025,9 +5201,43 @@ void FurnaceGUI::drawInsEdit() { } } ImGui::TableNextColumn(); + if (ImGui::Button(wavePreviewPaused?(ICON_FA_PLAY "##WSPause"):(ICON_FA_PAUSE "##WSPause"))) { + wavePreviewPaused=!wavePreviewPaused; + } + if (ImGui::IsItemHovered()) { + if (wavePreviewPaused) { + ImGui::SetTooltip("Resume preview"); + } else { + ImGui::SetTooltip("Pause preview"); + } + } + ImGui::SameLine(); if (ImGui::Button(ICON_FA_REPEAT "##WSRestart")) { wavePreviewInit=true; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Restart preview"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UPLOAD "##WSCopy")) { + curWave=e->addWave(); + if (curWave==-1) { + showError("too many wavetables!"); + } else { + wantScrollList=true; + MARK_MODIFIED; + RESET_WAVE_MACRO_ZOOM; + nextWindow=GUI_WINDOW_WAVE_EDIT; + + DivWavetable* copyWave=e->song.wave[curWave]; + copyWave->len=wavePreviewLen; + copyWave->max=wavePreviewHeight; + memcpy(copyWave->data,wavePreview.output,256*sizeof(int)); + } + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Copy to new wavetable"); + } ImGui::SameLine(); ImGui::Text("(%d×%d)",wavePreviewLen,wavePreviewHeight+1); ImGui::EndTable(); diff --git a/src/gui/osc.cpp b/src/gui/osc.cpp index e5bc09e4b..7f783ddee 100644 --- a/src/gui/osc.cpp +++ b/src/gui/osc.cpp @@ -100,7 +100,7 @@ void FurnaceGUI::drawOsc() { if (ImGui::VSliderFloat("##OscZoom",ImVec2(20.0f*dpiScale,ImGui::GetContentRegionAvail().y),&oscZoom,0.5,2.0)) { if (oscZoom<0.5) oscZoom=0.5; if (oscZoom>2.0) oscZoom=2.0; - } + } rightClickable if (ImGui::IsItemHovered()) { ImGui::SetTooltip("zoom: %.2fx (%.1fdB)",oscZoom,20.0*log10(oscZoom*2.0)); } @@ -111,7 +111,7 @@ void FurnaceGUI::drawOsc() { if (ImGui::VSliderFloat("##OscWinSize",ImVec2(20.0f*dpiScale,ImGui::GetContentRegionAvail().y),&oscWindowSize,5.0,100.0)) { if (oscWindowSize<5.0) oscWindowSize=5.0; if (oscWindowSize>100.0) oscWindowSize=100.0; - } + } rightClickable if (ImGui::IsItemHovered()) { ImGui::SetTooltip("window size: %.1fms",oscWindowSize); } diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 33de4d4d3..554984043 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -834,28 +834,9 @@ void FurnaceGUI::drawPattern() { if (extraChannelButtons==2) { DivPattern* pat=e->curPat[i].getPattern(e->curOrders->ord[i][ord],true); ImGui::PushFont(mainFont); - if (patNameTarget==i) { - snprintf(chanID,2048,"##PatNameI%d_%d",i,ord); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(8.0f*dpiScale)); - ImGui::SetCursorPosX(ImGui::GetCursorPosX()+4.0f*dpiScale); - ImGui::InputText(chanID,&pat->name); - if (wantPatName) { - wantPatName=false; - ImGui::SetItemDefaultFocus(); - ImGui::SetKeyboardFocusHere(-1); - } else { - if (!ImGui::IsItemActive()) { - patNameTarget=-1; - } - } - } else { - snprintf(chanID,2048," %s##PatName%d",pat->name.c_str(),i); - if (ImGui::Selectable(chanID,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+1.0f*dpiScale))) { - patNameTarget=i; - wantPatName=true; - snprintf(chanID,2048,"##PatNameI%d_%d",i,ord); - ImGui::SetActiveID(ImGui::GetID(chanID),ImGui::GetCurrentWindow()); - } + snprintf(chanID,2048," %s##PatName%d",pat->name.c_str(),i); + if (ImGui::Selectable(chanID,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+1.0f*dpiScale))) { + editStr(&pat->name); } ImGui::PopFont(); } else if (extraChannelButtons==1) { diff --git a/src/gui/piano.cpp b/src/gui/piano.cpp index fe451c3f7..baae316d1 100644 --- a/src/gui/piano.cpp +++ b/src/gui/piano.cpp @@ -415,10 +415,14 @@ void FurnaceGUI::drawPiano() { e->previewSample(curSample,note); break; default: - e->synchronized([this,note]() { - e->autoNoteOn(-1,curIns,note); - }); - if (edit && curWindow!=GUI_WINDOW_INS_LIST && curWindow!=GUI_WINDOW_INS_EDIT) noteInput(note,0); + if (sampleMapWaitingInput) { + alterSampleMap(true,note); + } else { + e->synchronized([this,note]() { + e->autoNoteOn(-1,curIns,note); + }); + if (edit && curWindow!=GUI_WINDOW_INS_LIST && curWindow!=GUI_WINDOW_INS_EDIT) noteInput(note,0); + } break; } } diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 3a8fae4b1..9d0fcc19b 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -133,6 +133,12 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Open"); } + if (ImGui::BeginPopupContextItem("SampleEOpenOpt")) { + if (ImGui::MenuItem("import raw...")) { + doAction((curSample>=0 && curSample<(int)e->song.sample.size())?GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE_RAW:GUI_ACTION_SAMPLE_LIST_OPEN_RAW); + } + ImGui::EndPopup(); + } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FLOPPY_O "##SESave")) { doAction(GUI_ACTION_SAMPLE_LIST_SAVE); @@ -140,6 +146,12 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Save"); } + if (ImGui::BeginPopupContextItem("SampleESaveOpt")) { + if (ImGui::MenuItem("save raw...")) { + doAction(GUI_ACTION_SAMPLE_LIST_SAVE_RAW); + } + ImGui::EndPopup(); + } ImGui::SameLine(); @@ -176,6 +188,8 @@ void FurnaceGUI::drawSampleEdit() { for (int i=0; irate:sample->centerRate; if (ImGui::BeginTable("SampleProps",(selColumns>1)?4:3,ImGuiTableFlags_SizingStretchSame|ImGuiTableFlags_BordersV|ImGuiTableFlags_BordersOuterH)) { ImGui::TableNextRow(ImGuiTableRowFlags_Headers); @@ -201,16 +215,21 @@ void FurnaceGUI::drawSampleEdit() { } popToggleColors(); ImGui::TableNextColumn(); - bool doLoop=(sample->isLoopable()); + bool doLoop=(sample->loop); if (ImGui::Checkbox("Loop",&doLoop)) { MARK_MODIFIED if (doLoop) { sample->loop=true; - sample->loopStart=0; - sample->loopEnd=sample->samples; + if (sample->loopStart<0) { + sample->loopStart=0; + } + if (sample->loopEnd<0) { + sample->loopEnd=sample->samples; + } } else { sample->loop=false; + /* sample->loopStart=-1; - sample->loopEnd=sample->samples; + sample->loopEnd=sample->samples;*/ } updateSampleTex=true; if (e->getSampleFormatMask()&(1U<prepareUndo(true); e->lockEngine([this,sample,i]() { - sample->render(); - sample->depth=(DivSampleDepth)i; + sample->convert((DivSampleDepth)i); e->renderSamples(); }); updateSampleTex=true; @@ -274,7 +292,6 @@ void FurnaceGUI::drawSampleEdit() { } } - int targetRate=sampleCompatRate?sample->rate:sample->centerRate; int sampleNote=round(64.0+(128.0*12.0*log((double)targetRate/8363.0)/log(2.0))); int sampleNoteCoarse=60+(sampleNote>>7); int sampleNoteFine=(sampleNote&127)-64; @@ -608,7 +625,7 @@ void FurnaceGUI::drawSampleEdit() { ImGui::SameLine(); ImGui::Button(ICON_FA_EXPAND "##SResample"); if (ImGui::IsItemClicked()) { - resampleTarget=sample->rate; + resampleTarget=targetRate; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Resample"); @@ -629,23 +646,23 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::SameLine(); if (ImGui::Button("==")) { - resampleTarget=sample->rate; + resampleTarget=targetRate; } ImGui::SameLine(); if (ImGui::Button("2.0x")) { resampleTarget*=2.0; } - double factor=resampleTarget/(double)sample->rate; + double factor=resampleTarget/(double)targetRate; if (ImGui::InputDouble("Factor",&factor,0.125,0.5,"%g")) { - resampleTarget=(double)sample->rate*factor; + resampleTarget=(double)targetRate*factor; if (resampleTarget<0) resampleTarget=0; if (resampleTarget>96000) resampleTarget=96000; } ImGui::Combo("Filter",&resampleStrat,resampleStrats,6); if (ImGui::Button("Resample")) { sample->prepareUndo(true); - e->lockEngine([this,sample]() { - if (!sample->resample(resampleTarget,resampleStrat)) { + e->lockEngine([this,sample,targetRate]() { + if (!sample->resample(targetRate,resampleTarget,resampleStrat)) { showError("couldn't resample! make sure your sample is 8 or 16-bit."); } e->renderSamples(); @@ -658,7 +675,7 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::EndPopup(); } else { - resampleTarget=sample->rate; + resampleTarget=targetRate; } ImGui::SameLine(); ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); @@ -1111,23 +1128,26 @@ void FurnaceGUI::drawSampleEdit() { if (sampleTex!=NULL) { if (updateSampleTex) { - unsigned int* data=NULL; + unsigned int* dataT=NULL; int pitch=0; logD("updating sample texture."); - if (SDL_LockTexture(sampleTex,NULL,(void**)&data,&pitch)!=0) { + if (SDL_LockTexture(sampleTex,NULL,(void**)&dataT,&pitch)!=0) { logE("error while locking sample texture! %s",SDL_GetError()); } else { + unsigned int* data=new unsigned int[sampleTexW*sampleTexH]; + ImU32 bgColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_BG]); ImU32 bgColorLoop=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_LOOP]); ImU32 lineColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_FG]); ImU32 centerLineColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_CENTER]); + int ij=0; for (int i=0; iisLoopable() && (scaledPos>=sample->loopStart && scaledPos<=sample->loopEnd)) { - data[i*availX+j]=bgColorLoop; + data[ij++]=bgColorLoop; } else { - data[i*availX+j]=bgColor; + data[ij++]=bgColor; } } } @@ -1143,11 +1163,15 @@ void FurnaceGUI::drawSampleEdit() { for (unsigned int i=0; i<(unsigned int)availX; i++) { if (xCoarse>=sample->samples) break; int y1, y2; + int candMin=INT_MAX; + int candMax=INT_MIN; int totalAdvance=0; if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { - y1=((unsigned char)sample->data8[xCoarse]^0x80)*availY/256; + if (candMin>sample->data8[xCoarse]) candMin=sample->data8[xCoarse]; + if (candMaxdata8[xCoarse]) candMax=sample->data8[xCoarse]; } else { - y1=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; + if (candMin>sample->data16[xCoarse]) candMin=sample->data16[xCoarse]; + if (candMaxdata16[xCoarse]) candMax=sample->data16[xCoarse]; } xFine+=xAdvanceFine; if (xFine>=16777216) { @@ -1157,27 +1181,44 @@ void FurnaceGUI::drawSampleEdit() { totalAdvance+=xAdvanceCoarse; if (xCoarse>=sample->samples) break; do { + if (xCoarse>=sample->samples) break; if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { - y2=((unsigned char)sample->data8[xCoarse]^0x80)*availY/256; + if (candMin>sample->data8[xCoarse]) candMin=sample->data8[xCoarse]; + if (candMaxdata8[xCoarse]) candMax=sample->data8[xCoarse]; } else { - y2=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; - } - if (y1>y2) { - y2^=y1; - y1^=y2; - y2^=y1; - } - if (y1<0) y1=0; - if (y1>=availY) y1=availY-1; - if (y2<0) y2=0; - if (y2>=availY) y2=availY-1; - for (int j=y1; j<=y2; j++) { - data[i+availX*(availY-j-1)]=lineColor; + if (candMin>sample->data16[xCoarse]) candMin=sample->data16[xCoarse]; + if (candMaxdata16[xCoarse]) candMax=sample->data16[xCoarse]; } if (totalAdvance>0) xCoarse++; } while ((totalAdvance--)>0); + if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { + y1=(((unsigned char)candMin^0x80)*availY)>>8; + y2=(((unsigned char)candMax^0x80)*availY)>>8; + } else { + y1=(((unsigned short)candMin^0x8000)*availY)>>16; + y2=(((unsigned short)candMax^0x8000)*availY)>>16; + } + if (y1>y2) { + y2^=y1; + y1^=y2; + y2^=y1; + } + if (y1<0) y1=0; + if (y1>=availY) y1=availY-1; + if (y2<0) y2=0; + if (y2>=availY) y2=availY-1; + + const int s1=i+availX*(availY-y1-1); + const int s2=i+availX*(availY-y2-1); + + for (int j=s2; j<=s1; j+=availX) { + data[j]=lineColor; + } } + + memcpy(dataT,data,sampleTexW*sampleTexH*sizeof(unsigned int)); SDL_UnlockTexture(sampleTex); + delete[] data; } updateSampleTex=false; } @@ -1188,6 +1229,31 @@ void FurnaceGUI::drawSampleEdit() { ImVec2 rectMax=ImGui::GetItemRectMax(); ImVec2 rectSize=ImGui::GetItemRectSize(); + unsigned char selectTarget=255; + + if (ImGui::IsItemHovered()) { + int start=sampleSelStart; + int end=sampleSelEnd; + if (start>end) { + start^=end; + end^=start; + start^=end; + } + ImVec2 p1=rectMin; + p1.x+=(start-samplePos)/sampleZoom; + + ImVec2 p2=ImVec2(rectMin.x+(end-samplePos)/sampleZoom,rectMax.y); + + ImVec2 mousePos=ImGui::GetMousePos(); + if (p1.x>=rectMin.x && p1.x<=rectMax.x && fabs(mousePos.x-p1.x)<2.0*dpiScale) { + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); + selectTarget=0; + } else if (p2.x>=rectMin.x && p2.x<=rectMax.x && fabs(mousePos.x-p2.x)<2.0*dpiScale) { + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); + selectTarget=1; + } + } + if (ImGui::IsItemClicked()) { nextWindow=GUI_WINDOW_SAMPLE_EDIT; if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { @@ -1214,9 +1280,23 @@ void FurnaceGUI::drawSampleEdit() { } sampleDragLen=sample->samples; sampleDragActive=true; - sampleSelStart=-1; - sampleSelEnd=-1; - if (sampleDragMode) sample->prepareUndo(true); + if (!sampleDragMode) { + switch (selectTarget) { + case 0: + sampleSelStart^=sampleSelEnd; + sampleSelEnd^=sampleSelStart; + sampleSelStart^=sampleSelEnd; + break; + case 1: + break; + default: + sampleSelStart=-1; + sampleSelEnd=-1; + break; + } + } else { + sample->prepareUndo(true); + } processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); } } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 9e291a43f..e2a30e35a 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -276,6 +276,8 @@ void FurnaceGUI::drawSettings() { ImVec2 setWindowSize=ImVec2(canvasW,canvasH); ImGui::SetNextWindowPos(setWindowPos); ImGui::SetNextWindowSize(setWindowSize); + } else { + ImGui::SetNextWindowSizeConstraints(ImVec2(200.0f*dpiScale,100.0f*dpiScale),ImVec2(canvasW,canvasH)); } if (ImGui::Begin("Settings",&settingsOpen,ImGuiWindowFlags_NoDocking|globalWinFlags)) { if (!settingsOpen) { @@ -652,6 +654,22 @@ void FurnaceGUI::drawSettings() { settings.saveUnusedPatterns=saveUnusedPatternsB; } + bool compressB=settings.compress; + if (ImGui::Checkbox("Compress when saving",&compressB)) { + settings.compress=compressB; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("use zlib to compress saved songs."); + } + + bool newPatternFormatB=settings.newPatternFormat; + if (ImGui::Checkbox("Use new pattern format when saving",&newPatternFormatB)) { + settings.newPatternFormat=newPatternFormatB; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("use a packed format which saves space when saving songs.\ndisable if you need compatibility with older Furnace and/or tools\nwhich do not support this format."); + } + bool cursorFollowsOrderB=settings.cursorFollowsOrder; if (ImGui::Checkbox("Cursor follows current order when moving it",&cursorFollowsOrderB)) { settings.cursorFollowsOrder=cursorFollowsOrderB; @@ -1144,14 +1162,38 @@ void FurnaceGUI::drawSettings() { settings.midiOutMode=2; }*/ + bool midiOutProgramChangeB=settings.midiOutProgramChange; + if (ImGui::Checkbox("Send Program Change",&midiOutProgramChangeB)) { + settings.midiOutProgramChange=midiOutProgramChangeB; + } + bool midiOutClockB=settings.midiOutClock; if (ImGui::Checkbox("Send MIDI clock",&midiOutClockB)) { settings.midiOutClock=midiOutClockB; } - bool midiOutProgramChangeB=settings.midiOutProgramChange; - if (ImGui::Checkbox("Send Program Change",&midiOutProgramChangeB)) { - settings.midiOutProgramChange=midiOutProgramChangeB; + bool midiOutTimeB=settings.midiOutTime; + if (ImGui::Checkbox("Send MIDI timecode",&midiOutTimeB)) { + settings.midiOutTime=midiOutTimeB; + } + + if (settings.midiOutTime) { + ImGui::Text("Timecode frame rate:"); + if (ImGui::RadioButton("Closest to Tick Rate",settings.midiOutTimeRate==0)) { + settings.midiOutTimeRate=0; + } + if (ImGui::RadioButton("Film (24fps)",settings.midiOutTimeRate==1)) { + settings.midiOutTimeRate=1; + } + if (ImGui::RadioButton("PAL (25fps)",settings.midiOutTimeRate==2)) { + settings.midiOutTimeRate=2; + } + if (ImGui::RadioButton("NTSC drop (29.97fps)",settings.midiOutTimeRate==3)) { + settings.midiOutTimeRate=3; + } + if (ImGui::RadioButton("NTSC non-drop (30fps)",settings.midiOutTimeRate==4)) { + settings.midiOutTimeRate=4; + } } ImGui::TreePop(); @@ -1809,13 +1851,33 @@ void FurnaceGUI::drawSettings() { } UI_COLOR_CONFIG(GUI_COLOR_BACKGROUND,"Background"); UI_COLOR_CONFIG(GUI_COLOR_FRAME_BACKGROUND,"Window background"); + UI_COLOR_CONFIG(GUI_COLOR_FRAME_BACKGROUND_CHILD,"Sub-window background"); + UI_COLOR_CONFIG(GUI_COLOR_FRAME_BACKGROUND_POPUP,"Pop-up background"); UI_COLOR_CONFIG(GUI_COLOR_MODAL_BACKDROP,"Modal backdrop"); UI_COLOR_CONFIG(GUI_COLOR_HEADER,"Header"); UI_COLOR_CONFIG(GUI_COLOR_TEXT,"Text"); UI_COLOR_CONFIG(GUI_COLOR_ACCENT_PRIMARY,"Primary"); UI_COLOR_CONFIG(GUI_COLOR_ACCENT_SECONDARY,"Secondary"); + UI_COLOR_CONFIG(GUI_COLOR_TITLE_INACTIVE,"Title bar (inactive)"); + UI_COLOR_CONFIG(GUI_COLOR_TITLE_COLLAPSED,"Title bar (collapsed)"); + UI_COLOR_CONFIG(GUI_COLOR_MENU_BAR,"Menu bar"); UI_COLOR_CONFIG(GUI_COLOR_BORDER,"Border"); UI_COLOR_CONFIG(GUI_COLOR_BORDER_SHADOW,"Border shadow"); + UI_COLOR_CONFIG(GUI_COLOR_SCROLL,"Scroll bar"); + UI_COLOR_CONFIG(GUI_COLOR_SCROLL_HOVER,"Scroll bar (hovered)"); + UI_COLOR_CONFIG(GUI_COLOR_SCROLL_ACTIVE,"Scroll bar (clicked)"); + UI_COLOR_CONFIG(GUI_COLOR_SCROLL_BACKGROUND,"Scroll bar background"); + UI_COLOR_CONFIG(GUI_COLOR_SEPARATOR,"Separator"); + UI_COLOR_CONFIG(GUI_COLOR_SEPARATOR_HOVER,"Separator (hover)"); + UI_COLOR_CONFIG(GUI_COLOR_SEPARATOR_ACTIVE,"Separator (active)"); + UI_COLOR_CONFIG(GUI_COLOR_DOCKING_PREVIEW,"Docking preview"); + UI_COLOR_CONFIG(GUI_COLOR_DOCKING_EMPTY,"Docking empty"); + UI_COLOR_CONFIG(GUI_COLOR_TABLE_HEADER,"Table header"); + UI_COLOR_CONFIG(GUI_COLOR_TABLE_BORDER_HARD,"Table border (hard)"); + UI_COLOR_CONFIG(GUI_COLOR_TABLE_BORDER_SOFT,"Table border (soft)"); + UI_COLOR_CONFIG(GUI_COLOR_DRAG_DROP_TARGET,"Drag and drop target"); + UI_COLOR_CONFIG(GUI_COLOR_NAV_WIN_HIGHLIGHT,"Window switcher (highlight)"); + UI_COLOR_CONFIG(GUI_COLOR_NAV_WIN_BACKDROP,"Window switcher backdrop"); UI_COLOR_CONFIG(GUI_COLOR_TOGGLE_ON,"Toggle on"); UI_COLOR_CONFIG(GUI_COLOR_TOGGLE_OFF,"Toggle off"); UI_COLOR_CONFIG(GUI_COLOR_EDITING,"Editing"); @@ -2302,10 +2364,12 @@ void FurnaceGUI::drawSettings() { UI_KEYBIND_CONFIG(GUI_ACTION_PAT_FLIP_SELECTION); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COLLAPSE_ROWS); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_EXPAND_ROWS); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COLLAPSE_PAT); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_EXPAND_PAT); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COLLAPSE_SONG); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_EXPAND_SONG); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_LATCH); - // TODO: collapse/expand pattern and song - KEYBIND_CONFIG_END; ImGui::TreePop(); } @@ -2323,6 +2387,7 @@ void FurnaceGUI::drawSettings() { UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_EDIT); UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_UP); UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_DOWN); + UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_DIR_VIEW); KEYBIND_CONFIG_END; ImGui::TreePop(); @@ -2340,6 +2405,7 @@ void FurnaceGUI::drawSettings() { UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_EDIT); UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_UP); UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_DOWN); + UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_DIR_VIEW); KEYBIND_CONFIG_END; ImGui::TreePop(); @@ -2359,6 +2425,7 @@ void FurnaceGUI::drawSettings() { UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_DOWN); UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_PREVIEW); UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_DIR_VIEW); KEYBIND_CONFIG_END; ImGui::TreePop(); @@ -2625,8 +2692,10 @@ void FurnaceGUI::syncSettings() { settings.channelTextCenter=e->getConfInt("channelTextCenter",1); settings.maxRecentFile=e->getConfInt("maxRecentFile",10); settings.midiOutClock=e->getConfInt("midiOutClock",0); + settings.midiOutTime=e->getConfInt("midiOutTime",0); settings.midiOutProgramChange=e->getConfInt("midiOutProgramChange",0); settings.midiOutMode=e->getConfInt("midiOutMode",1); + settings.midiOutTimeRate=e->getConfInt("midiOutTimeRate",0); settings.centerPattern=e->getConfInt("centerPattern",0); settings.ordersCursor=e->getConfInt("ordersCursor",1); settings.persistFadeOut=e->getConfInt("persistFadeOut",1); @@ -2640,6 +2709,8 @@ void FurnaceGUI::syncSettings() { settings.cursorFollowsOrder=e->getConfInt("cursorFollowsOrder",1); settings.iCannotWait=e->getConfInt("iCannotWait",0); settings.orderButtonPos=e->getConfInt("orderButtonPos",2); + settings.compress=e->getConfInt("compress",1); + settings.newPatternFormat=e->getConfInt("newPatternFormat",1); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2745,8 +2816,10 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.channelTextCenter,0,1); clampSetting(settings.maxRecentFile,0,30); clampSetting(settings.midiOutClock,0,1); + clampSetting(settings.midiOutTime,0,1); clampSetting(settings.midiOutProgramChange,0,1); clampSetting(settings.midiOutMode,0,2); + clampSetting(settings.midiOutTimeRate,0,4); clampSetting(settings.centerPattern,0,1); clampSetting(settings.ordersCursor,0,1); clampSetting(settings.persistFadeOut,0,1); @@ -2758,6 +2831,8 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.cursorFollowsOrder,0,1); clampSetting(settings.iCannotWait,0,1); clampSetting(settings.orderButtonPos,0,2); + clampSetting(settings.compress,0,1); + clampSetting(settings.newPatternFormat,0,1); if (settings.exportLoops<0.0) settings.exportLoops=0.0; if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0; @@ -2956,8 +3031,10 @@ void FurnaceGUI::commitSettings() { e->setConf("channelTextCenter",settings.channelTextCenter); e->setConf("maxRecentFile",settings.maxRecentFile); e->setConf("midiOutClock",settings.midiOutClock); + e->setConf("midiOutTime",settings.midiOutTime); e->setConf("midiOutProgramChange",settings.midiOutProgramChange); e->setConf("midiOutMode",settings.midiOutMode); + e->setConf("midiOutTimeRate",settings.midiOutTimeRate); e->setConf("centerPattern",settings.centerPattern); e->setConf("ordersCursor",settings.ordersCursor); e->setConf("persistFadeOut",settings.persistFadeOut); @@ -2971,6 +3048,8 @@ void FurnaceGUI::commitSettings() { e->setConf("cursorFollowsOrder",settings.cursorFollowsOrder); e->setConf("iCannotWait",settings.iCannotWait); e->setConf("orderButtonPos",settings.orderButtonPos); + e->setConf("compress",settings.compress); + e->setConf("newPatternFormat",settings.newPatternFormat); // colors for (int i=0; iSetFileStyle(IGFD_FileStyleByExtension,".ttc",uiColors[GUI_COLOR_FILE_FONT],ICON_FA_FONT); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".mod",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fc13",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fc14",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fc",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".smod",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ftm",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".tfi",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 77bc6c726..07d395826 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -310,6 +310,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo case DIV_SYSTEM_GB: { int chipType=flags.getInt("chipType",0); bool noAntiClick=flags.getBool("noAntiClick",false); + bool invertWave=flags.getBool("invertWave",true); bool enoughAlready=flags.getBool("enoughAlready",false); if (ImGui::Checkbox("Disable anti-click",&noAntiClick)) { @@ -332,6 +333,26 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo chipType=3; altered=true; } + ImGui::Text("Wave channel orientation:"); + if (chipType==3) { + if (ImGui::RadioButton("Normal",!invertWave)) { + invertWave=false; + altered=true; + } + if (ImGui::RadioButton("Inverted",invertWave)) { + invertWave=true; + altered=true; + } + } else { + if (ImGui::RadioButton("Exact data (inverted)",!invertWave)) { + invertWave=false; + altered=true; + } + if (ImGui::RadioButton("Exact output (normal)",invertWave)) { + invertWave=true; + altered=true; + } + } if (ImGui::Checkbox("Pretty please one more compat flag when I use arpeggio and my sound length",&enoughAlready)) { altered=true; } @@ -340,6 +361,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo e->lockSave([&]() { flags.set("chipType",chipType); flags.set("noAntiClick",noAntiClick); + flags.set("invertWave",invertWave); flags.set("enoughAlready",enoughAlready); }); } @@ -434,6 +456,9 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo case DIV_SYSTEM_FDS: case DIV_SYSTEM_MMC5: { int clockSel=flags.getInt("clockSel",0); + bool dpcmMode=flags.getBool("dpcmMode",true); + + ImGui::Text("Clock rate:"); if (ImGui::RadioButton("NTSC (1.79MHz)",clockSel==0)) { clockSel=0; @@ -448,9 +473,21 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo altered=true; } + ImGui::Text("DPCM channel mode:"); + + if (ImGui::RadioButton("DPCM (muffled samples; low CPU usage)",dpcmMode)) { + dpcmMode=true; + altered=true; + } + if (ImGui::RadioButton("PCM (crisp samples; high CPU usage)",!dpcmMode)) { + dpcmMode=false; + altered=true; + } + if (altered) { e->lockSave([&]() { flags.set("clockSel",clockSel); + flags.set("dpcmMode",dpcmMode); }); } break; @@ -1770,6 +1807,21 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo } break; } + case DIV_SYSTEM_SEGAPCM: + case DIV_SYSTEM_SEGAPCM_COMPAT: { + bool oldSlides=flags.getBool("oldSlides",false); + + if (ImGui::Checkbox("Legacy slides and pitch (compatibility)",&oldSlides)) { + altered=true; + } + + if (altered) { + e->lockSave([&]() { + flags.set("oldSlides",oldSlides); + }); + } + break; + } case DIV_SYSTEM_SM8521:/* { bool noAntiClick=flags.getBool("noAntiClick",false); diff --git a/src/gui/tutorial.cpp b/src/gui/tutorial.cpp index c3dad67bd..71ad3ca40 100644 --- a/src/gui/tutorial.cpp +++ b/src/gui/tutorial.cpp @@ -223,26 +223,28 @@ void FurnaceGUI::initTutorial() { void FurnaceGUI::syncTutorial() { // tutorial.userComesFrom=e->getConfInt("tutUserComesFrom",0); tutorial.introPlayed=e->getConfBool("tutIntroPlayed",false); -// tutorial.welcome=e->getConfBool("tutWelcome",false); + tutorial.protoWelcome=e->getConfBool("tutProtoWelcome2",false); } void FurnaceGUI::commitTutorial() { // e->setConf("tutUserComesFrom",tutorial.userComesFrom); e->setConf("tutIntroPlayed",tutorial.introPlayed); -// e->setConf("tutWelcome",tutorial.welcome); + e->setConf("tutProtoWelcome2",tutorial.protoWelcome); } void FurnaceGUI::activateTutorial(FurnaceGUITutorials which) { - if (tutorial.welcome && !tutorial.taken[which] && !ImGui::IsPopupOpen((const char*)NULL,ImGuiPopupFlags_AnyPopupId|ImGuiPopupFlags_AnyPopupLevel) && curTutorial==-1 && introPos>=10.0) { + /* + if (tutorial.protoWelcome && !tutorial.taken[which] && !ImGui::IsPopupOpen((const char*)NULL,ImGuiPopupFlags_AnyPopupId|ImGuiPopupFlags_AnyPopupLevel) && curTutorial==-1 && introPos>=10.0) { logV("activating tutorial %d.",which); curTutorial=which; curTutorialStep=0; } + */ } void FurnaceGUI::drawTutorial() { // welcome - if (!tutorial.welcome) { + if (!tutorial.protoWelcome) { ImGui::OpenPopup("Welcome"); } if (ImGui::BeginPopupModal("Welcome",NULL,ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoTitleBar)) { @@ -253,7 +255,35 @@ void FurnaceGUI::drawTutorial() { ImGui::Text("welcome to Furnace, the biggest open-source chiptune tracker!"); - ImGui::TextWrapped("get ready for the tutorial, which will teach you how to use it."); + ImGui::TextWrapped( + "did I say that 0.6pre5 will have a tutorial? well, it doesn't...\n" + "the reason is because 0.6pre5 fixes a critical bug which may cause config loss in some machines.\n" + "furthermore, it dramatically improves the backup system. couldn't put this version on hold anymore." + ); + + ImGui::Separator(); + + ImGui::TextWrapped("here are some tips to get you started:"); + + ImGui::TextWrapped( + "- add an instrument by clicking on + in Instruments\n" + "- click on the pattern view to focus it\n" + "- channel columns have the following, in this order: note, instrument, volume and effects\n" + "- hit space bar while on the pattern to toggle Edit Mode\n" + "- click on the pattern or use arrow keys to move the cursor\n" + "- values (instrument, volume, effects and effect values) are in hexadecimal\n" + "- hit enter to play/stop the song\n" + "- extend the song by adding more orders in the Orders window\n" + "- click on the Orders matrix to change the patterns of a channel (left click increases; right click decreases)" + ); + + ImGui::TextWrapped( + "if you need help, you may:\n" + "- read the (incomplete) manual: https://github.com/tildearrow/furnace/blob/master/papers/doc/README.md\n" + "- ask for help in Discussions (https://github.com/tildearrow/furnace/discussions) or the Furnace Discord (https://discord.gg/EfrwT2wq7z)" + ); + + ImGui::Separator(); ImGui::TextWrapped( "there are two interface modes: Basic, and Advanced.\n" @@ -265,13 +295,13 @@ void FurnaceGUI::drawTutorial() { if (ImGui::Button("Start in Basic Mode")) { basicMode=true; - tutorial.welcome=true; + tutorial.protoWelcome=true; commitTutorial(); ImGui::CloseCurrentPopup(); } if (ImGui::Button("Start in Advanced Mode")) { basicMode=false; - tutorial.welcome=true; + tutorial.protoWelcome=true; commitTutorial(); ImGui::CloseCurrentPopup(); } diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 0530a2d27..dce89bc96 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -598,7 +598,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (CWSliderFloat("##WGDuty",&waveGenDuty,0.0f,1.0f)) { doGenerateWave(); - } + } rightClickable ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -607,7 +607,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (CWSliderInt("##WGExp",&waveGenPower,1,8)) { doGenerateWave(); - } + } rightClickable ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -616,7 +616,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (CWSliderFloat("##WGXOR",&waveGenInvertPoint,0.0f,1.0f)) { doGenerateWave(); - } + } rightClickable ImGui::EndTable(); } @@ -636,7 +636,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (CWSliderFloat("##WGAmp",&waveGenAmp[i],-1.0f,1.0f)) { doGenerateWave(); - } + } rightClickable if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) { waveGenAmp[i]=0.0f; doGenerateWave(); @@ -647,7 +647,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (CWSliderFloat("##WGPhase",&waveGenPhase[i],0.0f,1.0f)) { doGenerateWave(); - } + } rightClickable if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) { waveGenPhase[i]=0.0f; doGenerateWave(); @@ -690,7 +690,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::PushID(i); if (CWSliderFloat("##WGTL",&waveGenTL[i],0.0f,1.0f)) { doGenerateWave(); - } + } rightClickable ImGui::PopID(); ImGui::TableNextColumn(); @@ -698,7 +698,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::PushID(i); if (CWSliderInt("##WGMULT",&waveGenMult[i],1,16)) { doGenerateWave(); - } + } rightClickable ImGui::PopID(); ImGui::TableNextColumn(); @@ -706,7 +706,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::PushID(i); if (CWSliderInt("##WGFB",&waveGenFB[i],0,7)) { doGenerateWave(); - } + } rightClickable ImGui::PopID(); } diff --git a/src/main.cpp b/src/main.cpp index 8ba66f7e7..574c1dd7a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -59,6 +59,7 @@ String zsmOutName; String cmdOutName; int loops=1; int benchMode=0; +int subsong=-1; DivAudioExportModes outMode=DIV_EXPORT_MODE_ONE; #ifdef HAVE_GUI @@ -237,6 +238,21 @@ TAParamResult pLoops(String val) { return TA_PARAM_SUCCESS; } +TAParamResult pSubSong(String val) { + try { + int v=std::stoi(val); + if (v<0) { + logE("sub-song shall be 0 or higher."); + return TA_PARAM_ERROR; + } + subsong=v; + } catch (std::exception& e) { + logE("sub-song shall be a number."); + return TA_PARAM_ERROR; + } + return TA_PARAM_SUCCESS; +} + TAParamResult pOutMode(String val) { if (val=="one") { outMode=DIV_EXPORT_MODE_ONE; @@ -312,6 +328,7 @@ void initParams() { params.push_back(TAParam("c","console",false,pConsole,"","enable console mode")); params.push_back(TAParam("l","loops",true,pLoops,"","set number of loops (-1 means loop forever)")); + params.push_back(TAParam("s","subsong",true,pSubSong,"","set sub-song")); params.push_back(TAParam("o","outmode",true,pOutMode,"one|persys|perchan","set file output mode")); params.push_back(TAParam("B","benchmark",true,pBenchmark,"render|seek","run performance test")); @@ -569,6 +586,10 @@ int main(int argc, char** argv) { return 0; } + if (subsong!=-1) { + e.changeSongP(subsong); + } + if (consoleMode) { bool cliSuccess=false; cli.bindEngine(&e);