diff --git a/.gitignore b/.gitignore index 566a3c3f..25bd6d0e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ linuxbuild/ test/songs/ test/delta/ test/result/ +test/assert_delta android/.gradle/ android/app/build/ android/app/.cxx/ diff --git a/CMakeLists.txt b/CMakeLists.txt index ff7db42e..6e213dcb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -386,7 +386,8 @@ src/engine/platform/sound/c64_fp/WaveformCalculator.cpp src/engine/platform/sound/c64_fp/WaveformGenerator.cpp src/engine/platform/sound/c64_fp/resample/SincResampler.cpp -src/engine/platform/sound/tia/TIASnd.cpp +src/engine/platform/sound/tia/AudioChannel.cpp +src/engine/platform/sound/tia/Audio.cpp src/engine/platform/sound/ymfm/ymfm_adpcm.cpp src/engine/platform/sound/ymfm/ymfm_opm.cpp diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c6931ce8..853020f9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,7 +22,7 @@ bug fixes, improvements and several other things accepted. the coding style is described here: -- indentation: two spaces +- indentation: two spaces. **strictly** spaces. do NOT use tabs. - modified 1TBS style: - no spaces in function calls - spaces between arguments in function declarations diff --git a/README.md b/README.md index d2308dd7..7efe2c2a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ 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 check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage). @@ -74,7 +74,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a - loads .dmf modules from all versions (beta 1 to 1.1.3) - saves .dmf modules - both modern and legacy - Furnace doubles as a module downgrader - - loads .dmp instruments and .dmw wavetables as well + - loads/saves .dmp instruments and .dmw wavetables as well - clean-room design (guesswork and ABX tests only, no decompilation involved) - bug/quirk implementation for increased playback accuracy through compatibility flags - VGM export @@ -103,7 +103,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a - built-in visualizer in pattern view - open-source under GPLv2 or later. -*** +--- # quick references - **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z). @@ -119,7 +119,7 @@ some people have provided packages for Unix/Unix-like distributions. here's a li - **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 [![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml) @@ -228,7 +228,7 @@ 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.** -*** +--- # frequently asked questions > woah! 50 sound chips?! I can't believe it! @@ -274,7 +274,7 @@ the DefleMask format has several limitations. save in Furnace song format instea right click on the channel name. -*** +--- # footnotes copyright (C) 2021-2022 tildearrow and contributors. diff --git a/TODO.md b/TODO.md index cba0b7c6..859ad68c 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,7 @@ -# to-do for 0.6pre1.5-0.6pre2 +# to-do for 0.6pre1.5 -- volume commands should work on Game Boy - stereo separation control for AY -- "paste with instrument" \ No newline at end of file +- "paste with instrument" +- FM operator muting +- FM operator swap +- bug fixes diff --git a/android/app/build.gradle b/android/app/build.gradle index cb4b3c59..92a79e4d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -15,8 +15,8 @@ android { } minSdkVersion 21 targetSdkVersion 26 - versionCode 93 - versionName "0.6pre1" + versionCode 113 + versionName "dev113" 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 5f7a06ef..b77cf32e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ @@ -83,13 +83,17 @@ - diff --git a/demos/Bullet_Hell.fur b/demos/Bullet_Hell.fur index 48c50f67..dfd2a042 100644 Binary files a/demos/Bullet_Hell.fur and b/demos/Bullet_Hell.fur differ diff --git a/demos/Cafe - 010 Editor 2.0crk.fur b/demos/Cafe - 010 Editor 2.0crk.fur new file mode 100644 index 00000000..339e7238 Binary files /dev/null and b/demos/Cafe - 010 Editor 2.0crk.fur differ diff --git a/demos/ChaosTune.fur b/demos/ChaosTune.fur new file mode 100644 index 00000000..b75fbf9c Binary files /dev/null and b/demos/ChaosTune.fur differ diff --git a/demos/FEDMS.fur b/demos/FEDMS.fur new file mode 100644 index 00000000..4c521dad Binary files /dev/null and b/demos/FEDMS.fur differ diff --git a/demos/GEN Equinox Intro.fur b/demos/GEN Equinox Intro.fur new file mode 100644 index 00000000..04a5bb05 Binary files /dev/null and b/demos/GEN Equinox Intro.fur differ diff --git a/demos/her11_veraedit.fur b/demos/her11_veraedit.fur new file mode 100644 index 00000000..1cfabac1 Binary files /dev/null and b/demos/her11_veraedit.fur differ diff --git a/demos/hope_for_the_dream.fur b/demos/hope_for_the_dream.fur new file mode 100644 index 00000000..77a3fed2 Binary files /dev/null and b/demos/hope_for_the_dream.fur differ diff --git a/demos/iji_tor.fur b/demos/iji_tor.fur new file mode 100644 index 00000000..84a6ac5b Binary files /dev/null and b/demos/iji_tor.fur differ diff --git a/demos/lunacommdemo.fur b/demos/lunacommdemo.fur new file mode 100644 index 00000000..43531b8b Binary files /dev/null and b/demos/lunacommdemo.fur differ diff --git a/extern/Nuked-OPN2 b/extern/Nuked-OPN2 index 64704a44..b0e9de0f 160000 --- a/extern/Nuked-OPN2 +++ b/extern/Nuked-OPN2 @@ -1 +1 @@ -Subproject commit 64704a443f8f6c1906ba26297092ea70fa1d45d7 +Subproject commit b0e9de0f816943ad3820ddfefa0fff276d659250 diff --git a/extern/opm/opm.c b/extern/opm/opm.c index 79f6414c..aafce483 100644 --- a/extern/opm/opm.c +++ b/extern/opm/opm.c @@ -1,5 +1,5 @@ /* Nuked OPM - * Copyright (C) 2020 Nuke.YKT + * Copyright (C) 2022 Nuke.YKT * * This file is part of Nuked OPM. * @@ -21,7 +21,7 @@ * siliconpr0n.org(digshadow, John McMaster): * YM2151 and other FM chip decaps and die shots. * - * version: 0.9.2 beta + * version: 0.9.3 beta */ #include #include @@ -651,7 +651,7 @@ static inline void OPM_EnvelopePhase4(opm_t *chip) chip->eg_instantattack = chip->eg_ratemax[1] && (kon || !chip->eg_ratemax[1]); eg_off = (chip->eg_level[slot] & 0x3f0) == 0x3f0; - slreach = (chip->eg_level[slot] >> 5) == chip->eg_sl[1]; + slreach = (chip->eg_level[slot] >> 4) == (chip->eg_sl[1] << 1); eg_zero = chip->eg_level[slot] == 0; chip->eg_mute = eg_off && chip->eg_state[slot] != eg_num_attack && !kon; diff --git a/instruments/FM/guitar/Banjo (Muted).opni b/instruments/FM/guitar/Banjo (Muted).opni new file mode 100644 index 00000000..1bf6e88f Binary files /dev/null and b/instruments/FM/guitar/Banjo (Muted).opni differ diff --git a/instruments/FM/guitar/Banjo.opni b/instruments/FM/guitar/Banjo.opni new file mode 100644 index 00000000..bfe78989 Binary files /dev/null and b/instruments/FM/guitar/Banjo.opni differ diff --git a/instruments/FM/guitar/Koto.opni b/instruments/FM/guitar/Koto.opni new file mode 100644 index 00000000..9bac79ba Binary files /dev/null and b/instruments/FM/guitar/Koto.opni differ diff --git a/instruments/FM/guitar/Oud.opni b/instruments/FM/guitar/Oud.opni new file mode 100644 index 00000000..029a0c2a Binary files /dev/null and b/instruments/FM/guitar/Oud.opni differ diff --git a/instruments/FM/guitar/Shamisen (Regular Pluck).opni b/instruments/FM/guitar/Shamisen (Regular Pluck).opni new file mode 100644 index 00000000..c25acca1 Binary files /dev/null and b/instruments/FM/guitar/Shamisen (Regular Pluck).opni differ diff --git a/instruments/FM/guitar/Shamisen (Tsugaru Slap).opni b/instruments/FM/guitar/Shamisen (Tsugaru Slap).opni new file mode 100644 index 00000000..ed9c9497 Binary files /dev/null and b/instruments/FM/guitar/Shamisen (Tsugaru Slap).opni differ diff --git a/instruments/FM/guitar/Sitar.opni b/instruments/FM/guitar/Sitar.opni new file mode 100644 index 00000000..3427fe2a Binary files /dev/null and b/instruments/FM/guitar/Sitar.opni differ diff --git a/instruments/FM/guitar/Tamboura (Bass Sitar).opni b/instruments/FM/guitar/Tamboura (Bass Sitar).opni new file mode 100644 index 00000000..e6514b73 Binary files /dev/null and b/instruments/FM/guitar/Tamboura (Bass Sitar).opni differ diff --git a/instruments/FM/keys/brickblock369 Harpsichord.dmp b/instruments/FM/keys/brickblock369 Harpsichord.dmp index a2aba1f2..0f5ea587 100644 Binary files a/instruments/FM/keys/brickblock369 Harpsichord.dmp and b/instruments/FM/keys/brickblock369 Harpsichord.dmp differ diff --git a/instruments/FM/percussion/Kalimba.fui b/instruments/FM/percussion/Kalimba.fui new file mode 100644 index 00000000..128deca3 Binary files /dev/null and b/instruments/FM/percussion/Kalimba.fui differ diff --git a/instruments/other/(SMS) 2-Arp Chord High.dmp b/instruments/other/(SMS) 2-Arp Chord High.dmp index 93401736..845babb2 100644 Binary files a/instruments/other/(SMS) 2-Arp Chord High.dmp and b/instruments/other/(SMS) 2-Arp Chord High.dmp differ diff --git a/instruments/other/(SMS) 2-Arp Major Low.dmp b/instruments/other/(SMS) 2-Arp Major Low.dmp index 532c2456..f65623f0 100644 Binary files a/instruments/other/(SMS) 2-Arp Major Low.dmp and b/instruments/other/(SMS) 2-Arp Major Low.dmp differ diff --git a/instruments/other/(SMS) 2-Arp Minor Low.dmp b/instruments/other/(SMS) 2-Arp Minor Low.dmp index 608e5483..5345dd45 100644 Binary files a/instruments/other/(SMS) 2-Arp Minor Low.dmp and b/instruments/other/(SMS) 2-Arp Minor Low.dmp differ diff --git a/instruments/other/(SMS) 3-Arp High.dmp b/instruments/other/(SMS) 3-Arp High.dmp index a5ebf975..d0fab8e5 100644 Binary files a/instruments/other/(SMS) 3-Arp High.dmp and b/instruments/other/(SMS) 3-Arp High.dmp differ diff --git a/instruments/other/(SMS) 3-Arp Major.dmp b/instruments/other/(SMS) 3-Arp Major.dmp index 3d284983..d9f9e3a7 100644 Binary files a/instruments/other/(SMS) 3-Arp Major.dmp and b/instruments/other/(SMS) 3-Arp Major.dmp differ diff --git a/instruments/other/(SMS) 3-Arp Minor.dmp b/instruments/other/(SMS) 3-Arp Minor.dmp index a28275fb..f36e80e9 100644 Binary files a/instruments/other/(SMS) 3-Arp Minor.dmp and b/instruments/other/(SMS) 3-Arp Minor.dmp differ diff --git a/instruments/other/(SMS) Arp Snare.dmp b/instruments/other/(SMS) Arp Snare.dmp index fa5734d4..71359542 100644 Binary files a/instruments/other/(SMS) Arp Snare.dmp and b/instruments/other/(SMS) Arp Snare.dmp differ diff --git a/instruments/other/(SMS) Attack.dmp b/instruments/other/(SMS) Attack.dmp index 0be93062..ac5d24ff 100644 Binary files a/instruments/other/(SMS) Attack.dmp and b/instruments/other/(SMS) Attack.dmp differ diff --git a/instruments/other/(SMS) Buzz Noise.dmp b/instruments/other/(SMS) Buzz Noise.dmp index 8b7dfd97..d1bc3921 100644 Binary files a/instruments/other/(SMS) Buzz Noise.dmp and b/instruments/other/(SMS) Buzz Noise.dmp differ diff --git a/instruments/other/(SMS) Crash.dmp b/instruments/other/(SMS) Crash.dmp index f9855bde..6f05ff80 100644 Binary files a/instruments/other/(SMS) Crash.dmp and b/instruments/other/(SMS) Crash.dmp differ diff --git a/instruments/other/(SMS) Decay Noise.dmp b/instruments/other/(SMS) Decay Noise.dmp index 9a829f44..65e45cf8 100644 Binary files a/instruments/other/(SMS) Decay Noise.dmp and b/instruments/other/(SMS) Decay Noise.dmp differ diff --git a/instruments/other/(SMS) Decay.dmp b/instruments/other/(SMS) Decay.dmp index 7f42d6ec..8c2688d1 100644 Binary files a/instruments/other/(SMS) Decay.dmp and b/instruments/other/(SMS) Decay.dmp differ diff --git a/instruments/other/(SMS) Down Slider.dmp b/instruments/other/(SMS) Down Slider.dmp index 5d0b6b27..cc13d5fa 100644 Binary files a/instruments/other/(SMS) Down Slider.dmp and b/instruments/other/(SMS) Down Slider.dmp differ diff --git a/instruments/other/(SMS) Hi-Hat & Note.dmp b/instruments/other/(SMS) Hi-Hat & Note.dmp index 3f92dc68..cff02382 100644 Binary files a/instruments/other/(SMS) Hi-Hat & Note.dmp and b/instruments/other/(SMS) Hi-Hat & Note.dmp differ diff --git a/instruments/other/(SMS) Hi-Hat Closed.dmp b/instruments/other/(SMS) Hi-Hat Closed.dmp index 24f92bf2..5e4e60a8 100644 Binary files a/instruments/other/(SMS) Hi-Hat Closed.dmp and b/instruments/other/(SMS) Hi-Hat Closed.dmp differ diff --git a/instruments/other/(SMS) Hi-Hat Open.dmp b/instruments/other/(SMS) Hi-Hat Open.dmp index 6726e12d..ae6e88e7 100644 Binary files a/instruments/other/(SMS) Hi-Hat Open.dmp and b/instruments/other/(SMS) Hi-Hat Open.dmp differ diff --git a/instruments/other/(SMS) Kick Noise.dmp b/instruments/other/(SMS) Kick Noise.dmp index 8f2d3cbf..b76fb886 100644 Binary files a/instruments/other/(SMS) Kick Noise.dmp and b/instruments/other/(SMS) Kick Noise.dmp differ diff --git a/instruments/other/(SMS) Multi Slider.dmp b/instruments/other/(SMS) Multi Slider.dmp index abd3094a..4f2bd3fc 100644 Binary files a/instruments/other/(SMS) Multi Slider.dmp and b/instruments/other/(SMS) Multi Slider.dmp differ diff --git a/instruments/other/(SMS) Obvious Crash.dmp b/instruments/other/(SMS) Obvious Crash.dmp index 54c7387e..0f54a85e 100644 Binary files a/instruments/other/(SMS) Obvious Crash.dmp and b/instruments/other/(SMS) Obvious Crash.dmp differ diff --git a/instruments/other/(SMS) Record Scratch Down.dmp b/instruments/other/(SMS) Record Scratch Down.dmp index 8e28c418..b3f4b07f 100644 Binary files a/instruments/other/(SMS) Record Scratch Down.dmp and b/instruments/other/(SMS) Record Scratch Down.dmp differ diff --git a/instruments/other/(SMS) Record Scratch Up.dmp b/instruments/other/(SMS) Record Scratch Up.dmp index b17b654e..a710de04 100644 Binary files a/instruments/other/(SMS) Record Scratch Up.dmp and b/instruments/other/(SMS) Record Scratch Up.dmp differ diff --git a/instruments/other/(SMS) Retrig.dmp b/instruments/other/(SMS) Retrig.dmp index 65c77610..b2822b8c 100644 Binary files a/instruments/other/(SMS) Retrig.dmp and b/instruments/other/(SMS) Retrig.dmp differ diff --git a/instruments/other/(SMS) Ride.dmp b/instruments/other/(SMS) Ride.dmp index 01070ff7..45c4b5fe 100644 Binary files a/instruments/other/(SMS) Ride.dmp and b/instruments/other/(SMS) Ride.dmp differ diff --git a/instruments/other/(SMS) Snare.dmp b/instruments/other/(SMS) Snare.dmp index c0e32b21..d624b92a 100644 Binary files a/instruments/other/(SMS) Snare.dmp and b/instruments/other/(SMS) Snare.dmp differ diff --git a/instruments/other/(SMS) Splash.dmp b/instruments/other/(SMS) Splash.dmp index e758a67b..f3296072 100644 Binary files a/instruments/other/(SMS) Splash.dmp and b/instruments/other/(SMS) Splash.dmp differ diff --git a/instruments/other/(SMS) Thump & Note.dmp b/instruments/other/(SMS) Thump & Note.dmp index 41fb6d51..3dccb56c 100644 Binary files a/instruments/other/(SMS) Thump & Note.dmp and b/instruments/other/(SMS) Thump & Note.dmp differ diff --git a/instruments/other/(SMS) Tim Follin 6-Arp Fast Major.dmp b/instruments/other/(SMS) Tim Follin 6-Arp Fast Major.dmp index e306203c..6440474c 100644 Binary files a/instruments/other/(SMS) Tim Follin 6-Arp Fast Major.dmp and b/instruments/other/(SMS) Tim Follin 6-Arp Fast Major.dmp differ diff --git a/instruments/other/(SMS) Tim Follin 6-Arp Fast Minor.dmp b/instruments/other/(SMS) Tim Follin 6-Arp Fast Minor.dmp index a845fc1c..105421b6 100644 Binary files a/instruments/other/(SMS) Tim Follin 6-Arp Fast Minor.dmp and b/instruments/other/(SMS) Tim Follin 6-Arp Fast Minor.dmp differ diff --git a/instruments/other/(SMS) Tim Follin 6-Arp Slow Major.dmp b/instruments/other/(SMS) Tim Follin 6-Arp Slow Major.dmp index 9536150c..11eeafb4 100644 Binary files a/instruments/other/(SMS) Tim Follin 6-Arp Slow Major.dmp and b/instruments/other/(SMS) Tim Follin 6-Arp Slow Major.dmp differ diff --git a/instruments/other/(SMS) Tim Follin 6-Arp Slow Minor.dmp b/instruments/other/(SMS) Tim Follin 6-Arp Slow Minor.dmp index 166a1cb3..3cbc3f38 100644 Binary files a/instruments/other/(SMS) Tim Follin 6-Arp Slow Minor.dmp and b/instruments/other/(SMS) Tim Follin 6-Arp Slow Minor.dmp differ diff --git a/instruments/other/(SMS) Tim Follin Lead.dmp b/instruments/other/(SMS) Tim Follin Lead.dmp index 08a3a081..69ab7d40 100644 Binary files a/instruments/other/(SMS) Tim Follin Lead.dmp and b/instruments/other/(SMS) Tim Follin Lead.dmp differ diff --git a/instruments/other/(SMS) Tom A.dmp b/instruments/other/(SMS) Tom A.dmp index 8976d0cf..813de521 100644 Binary files a/instruments/other/(SMS) Tom A.dmp and b/instruments/other/(SMS) Tom A.dmp differ diff --git a/instruments/other/(SMS) Tom B.dmp b/instruments/other/(SMS) Tom B.dmp index 664021f6..e06cccb8 100644 Binary files a/instruments/other/(SMS) Tom B.dmp and b/instruments/other/(SMS) Tom B.dmp differ diff --git a/instruments/other/(SMS) Up Slider.dmp b/instruments/other/(SMS) Up Slider.dmp index 765f156e..b57cfffe 100644 Binary files a/instruments/other/(SMS) Up Slider.dmp and b/instruments/other/(SMS) Up Slider.dmp differ diff --git a/instruments/other/(SMS) Variable.dmp b/instruments/other/(SMS) Variable.dmp index 7ed3209f..16a5c0f7 100644 Binary files a/instruments/other/(SMS) Variable.dmp and b/instruments/other/(SMS) Variable.dmp differ diff --git a/instruments/other/(SMS) Whistle.dmp b/instruments/other/(SMS) Whistle.dmp index 0fd9127c..1ed679bb 100644 Binary files a/instruments/other/(SMS) Whistle.dmp and b/instruments/other/(SMS) Whistle.dmp differ diff --git a/instruments/other/2A03 Noise Hi-Hat Closed.fui b/instruments/other/2A03 Noise Hi-Hat Closed.fui new file mode 100644 index 00000000..a24dff83 Binary files /dev/null and b/instruments/other/2A03 Noise Hi-Hat Closed.fui differ diff --git a/instruments/other/2A03 Noise Hi-Hat Open.fui b/instruments/other/2A03 Noise Hi-Hat Open.fui new file mode 100644 index 00000000..26889b0a Binary files /dev/null and b/instruments/other/2A03 Noise Hi-Hat Open.fui differ diff --git a/instruments/other/2A03 Noise Kick.fui b/instruments/other/2A03 Noise Kick.fui new file mode 100644 index 00000000..7cf815e1 Binary files /dev/null and b/instruments/other/2A03 Noise Kick.fui differ diff --git a/instruments/other/2A03 Noise Snare.fui b/instruments/other/2A03 Noise Snare.fui new file mode 100644 index 00000000..0bcd5dac Binary files /dev/null and b/instruments/other/2A03 Noise Snare.fui differ diff --git a/instruments/other/2A03 Triangle Kick+Bass.fui b/instruments/other/2A03 Triangle Kick+Bass.fui new file mode 100644 index 00000000..9beaea86 Binary files /dev/null and b/instruments/other/2A03 Triangle Kick+Bass.fui differ diff --git a/instruments/other/2A03 Triangle Kick.fui b/instruments/other/2A03 Triangle Kick.fui new file mode 100644 index 00000000..f2263010 Binary files /dev/null and b/instruments/other/2A03 Triangle Kick.fui differ diff --git a/instruments/other/2A03 Triangle Snare+Bass.fui b/instruments/other/2A03 Triangle Snare+Bass.fui new file mode 100644 index 00000000..e91831c8 Binary files /dev/null and b/instruments/other/2A03 Triangle Snare+Bass.fui differ diff --git a/instruments/other/2A03 Triangle Snare.fui b/instruments/other/2A03 Triangle Snare.fui new file mode 100644 index 00000000..7ed4b8ac Binary files /dev/null and b/instruments/other/2A03 Triangle Snare.fui differ diff --git a/instruments/other/AY kick.fui b/instruments/other/AY kick.fui new file mode 100644 index 00000000..111f1da7 Binary files /dev/null and b/instruments/other/AY kick.fui differ diff --git a/instruments/other/AY snare.fui b/instruments/other/AY snare.fui new file mode 100644 index 00000000..b8c03a2d Binary files /dev/null and b/instruments/other/AY snare.fui differ diff --git a/papers/doc/6-sample/README.md b/papers/doc/6-sample/README.md index 59613da2..4fbb6c91 100644 --- a/papers/doc/6-sample/README.md +++ b/papers/doc/6-sample/README.md @@ -64,7 +64,3 @@ In there, you can modify certain data pertaining to your sample, such as the: - 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. - -# tips - -if you have a sample you wanna use that is about 44100 or anything over 32000Hz, downsample the sample to 32000Hz so that the pitch of the sample in Furnace stays like the original audio file, diff --git a/papers/doc/7-systems/soundunit.md b/papers/doc/7-systems/soundunit.md index 8c3863b9..34cc16b4 100644 --- a/papers/doc/7-systems/soundunit.md +++ b/papers/doc/7-systems/soundunit.md @@ -1,5 +1,5 @@ # tildearrow Sound Unit -This is a fantasy sound chip, used in the specs2 fantasy computer designed by tildearrow. It includes native support for sample playback, but with only 8KB of sample data. Since 0.6pre1, this sound chip is no longer hidden by default and can be accessed through the module creation screen and can be added or removed. +This is a fantasy sound chip, used in the specs2 fantasy computer designed by tildearrow. It includes native support for sample playback, but with only 8KB or 64KB of sample data, depending on the configuration used. Since 0.6pre1, this sound chip is no longer hidden by default and can be accessed through the module creation screen and can be added or removed. # effects diff --git a/papers/format.md b/papers/format.md index def8cba1..da06488f 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,6 +32,8 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: +- 114: Furnace dev114 +- 113: Furnace dev113 - 112: Furnace dev112 - 111: Furnace dev111 - 110: Furnace dev110 @@ -254,6 +256,7 @@ size | description | - 0xc3: OPN CSM - 10 channels | - 0xc4: PC-98 CSM - 20 channels | - 0xc5: YM2610B CSM - 20 channels + | - 0xc6: MSM5232 - 8 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - 0xfd: Dummy System - 8 channels @@ -338,7 +341,8 @@ size | description 1 | broken initial position of porta after arp (>=101) or reserved 1 | SN periods under 8 are treated as 1 (>=108) or reserved 1 | cut/delay effect policy (>=110) or reserved - 5 | reserved + 1 | 0B/0D effect treatment (>=113) or reserved + 4 | reserved --- | **virtual tempo data** 2 | virtual tempo numerator of first song (>=96) or reserved 2 | virtual tempo denominator of first song (>=96) or reserved @@ -494,7 +498,8 @@ size | description 1 | vib 1 | ws 1 | ksr - 12 | reserved + 1 | operator enabled (>=114) or reserved + 11 | reserved --- | **Game Boy instrument data** 1 | volume 1 | direction diff --git a/src/engine/config.cpp b/src/engine/config.cpp index 862cd4c0..38cb2b04 100644 --- a/src/engine/config.cpp +++ b/src/engine/config.cpp @@ -239,6 +239,10 @@ void DivEngine::setConf(String key, double value) { conf[key]=fmt::sprintf("%f",value); } +void DivEngine::setConf(String key, const char* value) { + conf[key]=String(value); +} + void DivEngine::setConf(String key, String value) { conf[key]=value; } diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a39d88f8..12f76a51 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -147,37 +147,68 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) { int nextOrder=-1; int nextRow=0; int effectVal=0; + int lastSuspectedLoopEnd=-1; DivPattern* pat[DIV_MAX_CHANS]; + unsigned char wsWalked[8192]; + memset(wsWalked,0,8192); for (int i=0; iordersLen; i++) { for (int j=0; jord[j][i],false); } + if (i>lastSuspectedLoopEnd) { + lastSuspectedLoopEnd=i; + } for (int j=nextRow; jpatLen; j++) { nextRow=0; + bool changingOrder=false; + bool jumpingOrder=false; + if (wsWalked[((i<<5)+(j>>3))&8191]&(1<<(j&7))) { + loopOrder=i; + loopRow=j; + loopEnd=lastSuspectedLoopEnd; + return; + } for (int k=0; kdata[j][5+(l<<1)]; if (effectVal<0) effectVal=0; if (pat[k]->data[j][4+(l<<1)]==0x0d) { - if (nextOrder==-1 && (iordersLen-1 || !song.ignoreJumpAtEnd)) { - nextOrder=i+1; - nextRow=effectVal; + if (song.jumpTreatment==2) { + if ((iordersLen-1 || !song.ignoreJumpAtEnd)) { + nextOrder=i+1; + nextRow=effectVal; + jumpingOrder=true; + } + } else if (song.jumpTreatment==1) { + if (nextOrder==-1 && (iordersLen-1 || !song.ignoreJumpAtEnd)) { + nextOrder=i+1; + nextRow=effectVal; + jumpingOrder=true; + } + } else { + if ((iordersLen-1 || !song.ignoreJumpAtEnd)) { + if (!changingOrder) { + nextOrder=i+1; + } + jumpingOrder=true; + nextRow=effectVal; + } } } else if (pat[k]->data[j][4+(l<<1)]==0x0b) { - if (nextOrder==-1) { + if (nextOrder==-1 || song.jumpTreatment==0) { nextOrder=effectVal; - nextRow=0; + if (song.jumpTreatment==1 || song.jumpTreatment==2 || !jumpingOrder) { + nextRow=0; + } + changingOrder=true; } } } } + + wsWalked[((i<<5)+(j>>3))&8191]|=1<<(j&7); + if (nextOrder!=-1) { - if (nextOrder<=i) { - loopOrder=nextOrder; - loopRow=nextRow; - loopEnd=i; - return; - } i=nextOrder-1; nextOrder=-1; break; @@ -1659,6 +1690,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { speedAB=false; playing=true; skipping=true; + memset(walked,0,8192); for (int i=0; isetSkipRegisterWrites(true); while (playing && curOrderquit(); if (output->midiIn) { if (output->midiIn->isDeviceOpen()) { @@ -3709,7 +3742,9 @@ bool DivEngine::deinitAudioBackend() { output->quitMidi(); delete output; output=NULL; - //audioEngine=DIV_AUDIO_NULL; + if (dueToSwitchMaster) { + audioEngine=DIV_AUDIO_NULL; + } } return true; } diff --git a/src/engine/engine.h b/src/engine/engine.h index c7dcc847..cb7df1dc 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -46,9 +46,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev112" -#define DIV_ENGINE_VERSION 112 - +#define DIV_VERSION "dev114" +#define DIV_ENGINE_VERSION 114 // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 @@ -358,6 +357,8 @@ class DivEngine { double exportFadeOut; std::map conf; std::deque pendingNotes; + // bitfield + unsigned char walked[8192]; bool isMuted[DIV_MAX_CHANS]; std::mutex isBusy, saveLock; String configPath; @@ -453,7 +454,7 @@ class DivEngine { int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret); bool initAudioBackend(); - bool deinitAudioBackend(); + bool deinitAudioBackend(bool dueToSwitchMaster=false); void registerSystems(); void initSongWithDesc(const int* description); @@ -545,6 +546,7 @@ class DivEngine { void setConf(String key, int value); void setConf(String key, float value); void setConf(String key, double value); + void setConf(String key, const char* value); void setConf(String key, String value); // calculate base frequency/period @@ -1074,6 +1076,7 @@ class DivEngine { memset(reversePitchTable,0,4096*sizeof(int)); memset(pitchTable,0,4096*sizeof(int)); memset(sysDefs,0,256*sizeof(void*)); + memset(walked,0,8192); for (int i=0; i<256; i++) { sysFileMapFur[i]=DIV_SYSTEM_NULL; diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 7dc98a27..6f6828ed 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -179,6 +179,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.brokenPortaArp=false; ds.snNoLowPeriods=true; ds.delayBehavior=0; + ds.jumpTreatment=2; // 1.1 compat flags if (ds.version>24) { @@ -1081,6 +1082,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<110) { ds.delayBehavior=1; } + if (ds.version<113) { + ds.jumpTreatment=1; + } ds.isDMF=false; reader.readS(); // reserved @@ -1503,7 +1507,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } else { reader.readC(); } - for (int i=0; i<5; i++) { + if (ds.version>=113) { + ds.jumpTreatment=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<4; i++) { reader.readC(); } } @@ -3751,7 +3760,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeC(song.brokenPortaArp); w->writeC(song.snNoLowPeriods); w->writeC(song.delayBehavior); - for (int i=0; i<5; i++) { + w->writeC(song.jumpTreatment); + for (int i=0; i<4; i++) { w->writeC(0); } diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index 62c3a1d8..83361130 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -150,9 +150,13 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St ins->type=DIV_INS_FM; logD("instrument type is Arcade"); break; + case 9: // Neo Geo + ins->type=DIV_INS_FM; + logD("instrument type is Neo Geo"); + break; default: logD("instrument type is unknown"); - lastError="unknown instrument type!"; + lastError=fmt::sprintf("unknown instrument type %d!",sys); delete ins; return; break; @@ -171,11 +175,21 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St mode=reader.readC(); logD("instrument mode is %d",mode); if (mode==0) { - if (version<11) { - ins->type=DIV_INS_STD; + if (ins->type==DIV_INS_FM) { + if (sys==9) { + ins->type=DIV_INS_AY; + } else { + ins->type=DIV_INS_STD; + } } } else { - ins->type=DIV_INS_FM; + if (sys==3 || sys==6) { + ins->type=DIV_INS_OPLL; + } else if (sys==1) { + ins->type=DIV_INS_OPL; + } else { + ins->type=DIV_INS_FM; + } } } else { ins->type=DIV_INS_FM; @@ -232,12 +246,23 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St ins->fm.op[j].dvb=reader.readC(); ins->fm.op[j].dam=reader.readC(); } else { - ins->fm.op[j].rs=reader.readC(); - ins->fm.op[j].dt=reader.readC(); - ins->fm.op[j].dt2=ins->fm.op[j].dt>>4; - ins->fm.op[j].dt&=15; - ins->fm.op[j].d2r=reader.readC(); - ins->fm.op[j].ssgEnv=reader.readC(); + if (sys==3 || sys==6) { // OPLL/VRC7 + ins->fm.op[j].ksr=reader.readC()?1:0; + ins->fm.op[j].vib=reader.readC(); + if (j==0) { + ins->fm.opllPreset=ins->fm.op[j].vib>>4; + } + ins->fm.op[j].vib=ins->fm.op[j].vib?1:0; + ins->fm.op[j].ksl=reader.readC()?1:0; + ins->fm.op[j].ssgEnv=reader.readC(); + } else { + ins->fm.op[j].rs=reader.readC(); + ins->fm.op[j].dt=reader.readC(); + ins->fm.op[j].dt2=ins->fm.op[j].dt>>4; + ins->fm.op[j].dt&=15; + ins->fm.op[j].d2r=reader.readC(); + ins->fm.op[j].ssgEnv=reader.readC(); + } } } } else { // STD @@ -247,6 +272,9 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St if (version>5) { for (int i=0; istd.volMacro.len; i++) { ins->std.volMacro.val[i]=reader.readI(); + if (ins->std.volMacro.val[i]>15 && sys==6) { // FDS + ins->type=DIV_INS_FDS; + } } } else { for (int i=0; istd.volMacro.len; i++) { @@ -805,7 +833,7 @@ void DivEngine::loadOPNI(SafeReader& reader, std::vector& ret, S op.mult = dtMul & 0xF; op.dt = ((dtMul >> 4) & 0x7); - op.tl = totalLevel & 0x3F; + op.tl = totalLevel & 0x7F; op.rs = ((arRateScale >> 6) & 0x3); op.ar = arRateScale & 0x1F; op.dr = drAmpEnable & 0x1F; @@ -1643,7 +1671,7 @@ void DivEngine::loadWOPN(SafeReader& reader, std::vector& ret, S total += (op.mult = dtMul & 0xF); total += (op.dt = ((dtMul >> 4) & 0x7)); - total += (op.tl = totalLevel & 0x3F); + total += (op.tl = totalLevel & 0x7F); total += (op.rs = ((arRateScale >> 6) & 0x3)); total += (op.ar = arRateScale & 0x1F); total += (op.dr = drAmpEnable & 0x1F); diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 3d5de69d..85d40e84 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -71,8 +71,10 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(op.ws); w->writeC(op.ksr); + w->writeC(op.enable); + // reserved - for (int k=0; k<12; k++) { + for (int k=0; k<11; k++) { w->writeC(0); } } @@ -716,8 +718,14 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { op.ws=reader.readC(); op.ksr=reader.readC(); + if (version>=114) { + op.enable=reader.readC(); + } else { + reader.readC(); + } + // reserved - for (int k=0; k<12; k++) reader.readC(); + for (int k=0; k<11; k++) reader.readC(); } // GB diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index cca54d28..6e925c9a 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -67,7 +67,7 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si w.addrOrVal=true; } } - + OPM_Clock(&fm,NULL,NULL,NULL,NULL); OPM_Clock(&fm,NULL,NULL,NULL,NULL); OPM_Clock(&fm,NULL,NULL,NULL,NULL); @@ -77,13 +77,13 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si for (int i=0; i<8; i++) { oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]; } - + if (o[0]<-32768) o[0]=-32768; if (o[0]>32767) o[0]=32767; if (o[1]<-32768) o[1]=-32768; if (o[1]>32767) o[1]=32767; - + bufL[h]=o[0]; bufR[h]=o[1]; } @@ -106,7 +106,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz delay=1; } } - + fm_ymfm->generate(&out_ymfm); for (int i=0; i<8; i++) { @@ -120,7 +120,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz os[1]=out_ymfm.data[1]; if (os[1]<-32768) os[1]=-32768; if (os[1]>32767) os[1]=32767; - + bufL[h]=os[0]; bufR[h]=os[1]; } @@ -255,6 +255,10 @@ void DivPlatformArcade::tick(bool sysTick) { chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3)); } + if (chan[i].std.ex4.had && chan[i].active) { + chan[i].opMask=chan[i].std.ex4.val&15; + chan[i].opMaskChanged=true; + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -347,8 +351,9 @@ void DivPlatformArcade::tick(bool sysTick) { immWrite(i+0x30,chan[i].freq<<2); chan[i].freqChanged=false; } - if (chan[i].keyOn) { - immWrite(0x08,0x78|i); + if (chan[i].keyOn || chan[i].opMaskChanged) { + immWrite(0x08,(chan[i].opMask<<3)|i); + chan[i].opMaskChanged=false; chan[i].keyOn=false; } } @@ -370,6 +375,11 @@ int DivPlatformArcade::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; + chan[c.chan].opMask= + (chan[c.chan].state.op[0].enable?1:0)| + (chan[c.chan].state.op[2].enable?2:0)| + (chan[c.chan].state.op[1].enable?4:0)| + (chan[c.chan].state.op[3].enable?8:0); } chan[c.chan].macroInit(ins); @@ -502,6 +512,12 @@ int DivPlatformArcade::dispatch(DivCommand c) { break; } case DIV_CMD_FM_LFO: { + if(c.value==0) { + rWrite(0x01,0x02); + } + else { + rWrite(0x01,0x00); + } rWrite(0x18,c.value); break; } @@ -825,6 +841,8 @@ void DivPlatformArcade::reset() { pmDepth=0x7f; //rWrite(0x18,0x10); + immWrite(0x01,0x02); // LFO Off + immWrite(0x18,0x00); // LFO Freq Off immWrite(0x19,amDepth); immWrite(0x19,0x80|pmDepth); //rWrite(0x1b,0x00); diff --git a/src/engine/platform/arcade.h b/src/engine/platform/arcade.h index 93aa490d..2a9c2b40 100644 --- a/src/engine/platform/arcade.h +++ b/src/engine/platform/arcade.h @@ -43,9 +43,9 @@ class DivPlatformArcade: public DivPlatformOPM { int freq, baseFreq, pitch, pitch2, note; int ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset, opMaskChanged; int vol, outVol; - unsigned char chVolL, chVolR; + unsigned char chVolL, chVolR, opMask; void macroInit(DivInstrument* which) { std.init(which); pitch2=0; @@ -68,10 +68,12 @@ class DivPlatformArcade: public DivPlatformOPM { portaPause(false), furnacePCM(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(0), chVolL(127), - chVolR(127) {} + chVolR(127), + opMask(15) {} }; Channel chan[8]; DivDispatchOscBuffer* oscBuf[8]; diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 6ae10d0c..2a9c2ef3 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -347,6 +347,10 @@ void DivPlatformGenesis::tick(bool sysTick) { chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } + if (chan[i].std.ex4.had && chan[i].active) { + chan[i].opMask=chan[i].std.ex4.val&15; + chan[i].opMaskChanged=true; + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -479,8 +483,9 @@ void DivPlatformGenesis::tick(bool sysTick) { } chan[i].freqChanged=false; } - if (chan[i].keyOn) { - if (i<6) immWrite(0x28,0xf0|konOffs[i]); + if (chan[i].keyOn || chan[i].opMaskChanged) { + if (i<6) immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); + chan[i].opMaskChanged=false; chan[i].keyOn=false; } } @@ -591,6 +596,11 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; + chan[c.chan].opMask= + (chan[c.chan].state.op[0].enable?1:0)| + (chan[c.chan].state.op[2].enable?2:0)| + (chan[c.chan].state.op[1].enable?4:0)| + (chan[c.chan].state.op[3].enable?8:0); } chan[c.chan].macroInit(ins); diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index 5e61fe67..8588b21d 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -45,9 +45,9 @@ class DivPlatformGenesis: public DivPlatformOPN { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, note; int ins; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset, opMaskChanged; int vol, outVol; - unsigned char pan; + unsigned char pan, opMask; bool dacMode; int dacPeriod; @@ -82,9 +82,11 @@ class DivPlatformGenesis: public DivPlatformOPN { furnaceDac(false), inPorta(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(0), pan(3), + opMask(15), dacMode(false), dacPeriod(0), dacRate(0), diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index dfd7d514..a2df33c4 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -25,6 +25,7 @@ #define CHIP_DIVIDER fmDivBase #define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6]))) +#define IS_EXTCH_MUTED (isOpMuted[0] && isOpMuted[1] && isOpMuted[2] && isOpMuted[3]) int DivPlatformGenesisExt::dispatch(DivCommand c) { if (c.chan<2) { @@ -69,10 +70,11 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { rWrite(baseAddr+0x70,op.d2r&31); rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); rWrite(baseAddr+0x90,op.ssgEnv&15); + opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? rWrite(chanOffs[2]+0xb0,(chan[2].state.alg&7)|(chan[2].state.fb<<3)); - rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); + rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); } opChan[ch].insChanged=false; @@ -123,7 +125,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { opChan[i].pan=opChan[ch].pan; } } - rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); + rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); break; } case DIV_CMD_PITCH: { @@ -397,6 +399,8 @@ void DivPlatformGenesisExt::muteChannel(int ch, bool mute) { rWrite(baseAddr+0x40,op.tl); immWrite(baseAddr+0x40,op.tl); } + + rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); } static int opChanOffsL[4]={ @@ -412,7 +416,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -459,10 +463,12 @@ void DivPlatformGenesisExt::tick(bool sysTick) { immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); } - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn) { writeNoteOn=true; - writeMask|=1<<(4+i); + if (opChan[i].mask) { + writeMask|=1<<(4+i); + } opChan[i].keyOn=false; } } @@ -544,7 +550,11 @@ void DivPlatformGenesisExt::forceIns() { rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (i==2) { + rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[0].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } else { + rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } if (chan[i].active) { chan[i].keyOn=true; chan[i].freqChanged=true; diff --git a/src/engine/platform/genesisext.h b/src/engine/platform/genesisext.h index 07e0d5cc..d4dd93e7 100644 --- a/src/engine/platform/genesisext.h +++ b/src/engine/platform/genesisext.h @@ -27,7 +27,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask; int vol; unsigned char pan; OpChannel(): @@ -46,6 +46,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis { keyOff(false), portaPause(false), inPorta(false), + mask(true), vol(0), pan(3) {} }; diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 7da2e0a1..bc985b42 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -642,6 +642,9 @@ void DivPlatformN163::setFlags(unsigned int flags) { for (int i=0; i<8; i++) { oscBuf[i]->rate=rate/(initChanMax+1); } + + // needed to make sure changing channel count won't trigger glitches + reset(); } int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 11fb81a4..fee3c50b 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -249,7 +249,9 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_ if (os[1]>32767) os[1]=32767; bufL[h]=os[0]; - bufR[h]=os[1]; + if (oplType==3 || oplType==759) { + bufR[h]=os[1]; + } } } @@ -1531,7 +1533,7 @@ void DivPlatformOPL::reset() { } bool DivPlatformOPL::isStereo() { - return true; + return (oplType==3 || oplType==759); } bool DivPlatformOPL::keyOffAffectsArp(int ch) { diff --git a/src/engine/platform/sound/tia/Audio.cpp b/src/engine/platform/sound/tia/Audio.cpp new file mode 100644 index 00000000..662bd7fd --- /dev/null +++ b/src/engine/platform/sound/tia/Audio.cpp @@ -0,0 +1,143 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#define _USE_MATH_DEFINES +#include "Audio.h" + +#include + +namespace { + constexpr double R_MAX = 30.; + constexpr double R = 1.; + + short mixingTableEntry(unsigned char v, unsigned char vMax) + { + return static_cast( + floor(0x7fff * double(v) / double(vMax) * (R_MAX + R * double(vMax)) / (R_MAX + R * double(v))) + ); + } +} + +namespace TIA { + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Audio::Audio() +{ + for (unsigned char i = 0; i <= 0x1e; ++i) myMixingTableSum[i] = mixingTableEntry(i, 0x1e); + for (unsigned char i = 0; i <= 0x0f; ++i) myMixingTableIndividual[i] = mixingTableEntry(i, 0x0f); + + reset(false); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::reset(bool st) +{ + myCounter = 0; + mySampleIndex = 0; + stereo = st; + + myCurrentSample[0]=0; + myCurrentSample[1]=0; + + myChannelOut[0]=0; + myChannelOut[1]=0; + + myChannel0.reset(); + myChannel1.reset(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::tick() +{ + switch (myCounter) { + case 9: + case 81: + myChannel0.phase0(); + myChannel1.phase0(); + + break; + + case 37: + case 149: + phase1(); + break; + } + + if (++myCounter == 228) myCounter = 0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::write(unsigned char addr, unsigned char val) { + switch (addr&0x3f) { + case 0x15: + myChannel0.audc(val); + break; + case 0x16: + myChannel1.audc(val); + break; + case 0x17: + myChannel0.audf(val); + break; + case 0x18: + myChannel1.audf(val); + break; + case 0x19: + myChannel0.audv(val); + break; + case 0x1a: + myChannel1.audv(val); + break; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::phase1() +{ + unsigned char sample0 = myChannel0.phase1(); + unsigned char sample1 = myChannel1.phase1(); + + addSample(sample0, sample1); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::addSample(unsigned char sample0, unsigned char sample1) +{ + if(stereo) { + myCurrentSample[0] = myMixingTableIndividual[sample0]; + myCurrentSample[1] = myMixingTableIndividual[sample1]; + } + else { + myCurrentSample[0] = myMixingTableSum[sample0 + sample1]; + } + + myChannelOut[0] = myMixingTableIndividual[sample0]; + myChannelOut[1] = myMixingTableIndividual[sample1]; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioChannel& Audio::channel0() +{ + return myChannel0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioChannel& Audio::channel1() +{ + return myChannel1; +} + +} \ No newline at end of file diff --git a/src/engine/platform/sound/tia/Audio.h b/src/engine/platform/sound/tia/Audio.h new file mode 100644 index 00000000..4a782bb3 --- /dev/null +++ b/src/engine/platform/sound/tia/Audio.h @@ -0,0 +1,72 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#ifndef TIA_AUDIO_HXX +#define TIA_AUDIO_HXX + +#include "AudioChannel.h" +#include + +namespace TIA { + class Audio + { + public: + Audio(); + + void reset(bool stereo); + + void tick(); + + void write(unsigned char addr, unsigned char val); + + AudioChannel& channel0(); + + AudioChannel& channel1(); + + short myCurrentSample[2]; + short myChannelOut[2]; + + private: + void phase1(); + void addSample(unsigned char sample0, unsigned char sample1); + + private: + unsigned char myCounter{0}; + + AudioChannel myChannel0; + AudioChannel myChannel1; + + bool stereo; + + std::array myMixingTableSum; + std::array myMixingTableIndividual; + + unsigned int mySampleIndex{0}; + #ifdef GUI_SUPPORT + bool myRewindMode{false}; + mutable ByteArray mySamples; + #endif + + private: + Audio(const Audio&) = delete; + Audio(Audio&&) = delete; + Audio& operator=(const Audio&) = delete; + Audio& operator=(Audio&&) = delete; + }; +} + +#endif // TIA_AUDIO_HXX diff --git a/src/engine/platform/sound/tia/AudioChannel.cpp b/src/engine/platform/sound/tia/AudioChannel.cpp new file mode 100644 index 00000000..c780c172 --- /dev/null +++ b/src/engine/platform/sound/tia/AudioChannel.cpp @@ -0,0 +1,140 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#include "AudioChannel.h" + +namespace TIA { + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::reset() +{ + myAudc = myAudv = myAudf = 0; + myClockEnable = myNoiseFeedback = myNoiseCounterBit4 = myPulseCounterHold = false; + myDivCounter = myPulseCounter = myNoiseCounter = 0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::phase0() +{ + if (myClockEnable) { + myNoiseCounterBit4 = myNoiseCounter & 0x01; + + switch (myAudc & 0x03) { + case 0x00: + case 0x01: + myPulseCounterHold = false; + break; + + case 0x02: + myPulseCounterHold = (myNoiseCounter & 0x1e) != 0x02; + break; + + case 0x03: + myPulseCounterHold = !myNoiseCounterBit4; + break; + } + + switch (myAudc & 0x03) { + case 0x00: + myNoiseFeedback = + ((myPulseCounter ^ myNoiseCounter) & 0x01) || + !(myNoiseCounter || (myPulseCounter != 0x0a)) || + !(myAudc & 0x0c); + + break; + + default: + myNoiseFeedback = + (((myNoiseCounter & 0x04) ? 1 : 0) ^ (myNoiseCounter & 0x01)) || + myNoiseCounter == 0; + + break; + } + } + + myClockEnable = myDivCounter == myAudf; + + if (myDivCounter == myAudf || myDivCounter == 0x1f) { + myDivCounter = 0; + } else { + ++myDivCounter; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +unsigned char AudioChannel::phase1() +{ + if (myClockEnable) { + bool pulseFeedback = false; + switch (myAudc >> 2) { + case 0x00: + pulseFeedback = + (((myPulseCounter & 0x02) ? 1 : 0) ^ (myPulseCounter & 0x01)) && + (myPulseCounter != 0x0a) && + (myAudc & 0x03); + + break; + + case 0x01: + pulseFeedback = !(myPulseCounter & 0x08); + break; + + case 0x02: + pulseFeedback = !myNoiseCounterBit4; + break; + + case 0x03: + pulseFeedback = !((myPulseCounter & 0x02) || !(myPulseCounter & 0x0e)); + break; + } + + myNoiseCounter >>= 1; + if (myNoiseFeedback) { + myNoiseCounter |= 0x10; + } + + if (!myPulseCounterHold) { + myPulseCounter = ~(myPulseCounter >> 1) & 0x07; + + if (pulseFeedback) { + myPulseCounter |= 0x08; + } + } + } + + return (myPulseCounter & 0x01) * myAudv; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::audc(unsigned char value) +{ + myAudc = value & 0x0f; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::audv(unsigned char value) +{ + myAudv = value & 0x0f; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::audf(unsigned char value) +{ + myAudf = value & 0x1f; +} + +} \ No newline at end of file diff --git a/src/engine/platform/sound/tia/AudioChannel.h b/src/engine/platform/sound/tia/AudioChannel.h new file mode 100644 index 00000000..942e101a --- /dev/null +++ b/src/engine/platform/sound/tia/AudioChannel.h @@ -0,0 +1,61 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#ifndef TIA_AUDIO_CHANNEL_HXX +#define TIA_AUDIO_CHANNEL_HXX + +namespace TIA { + class AudioChannel + { + public: + AudioChannel() = default; + + void reset(); + + void phase0(); + + unsigned char phase1(); + + void audc(unsigned char value); + + void audf(unsigned char value); + + void audv(unsigned char value); + + private: + unsigned char myAudc{0}; + unsigned char myAudv{0}; + unsigned char myAudf{0}; + + bool myClockEnable{false}; + bool myNoiseFeedback{false}; + bool myNoiseCounterBit4{false}; + bool myPulseCounterHold{false}; + + unsigned char myDivCounter{0}; + unsigned char myPulseCounter{0}; + unsigned char myNoiseCounter{0}; + + private: + AudioChannel(const AudioChannel&) = delete; + AudioChannel(AudioChannel&&) = delete; + AudioChannel& operator=(const AudioChannel&) = delete; + AudioChannel& operator=(AudioChannel&&) = delete; + }; +} + +#endif // TIA_AUDIO_CHANNEL_HXX diff --git a/src/engine/platform/sound/tia/TIASnd.cpp b/src/engine/platform/sound/tia/TIASnd.cpp deleted file mode 100644 index e2d0568c..00000000 --- a/src/engine/platform/sound/tia/TIASnd.cpp +++ /dev/null @@ -1,377 +0,0 @@ -//============================================================================ -// -// SSSS tt lll lll -// SS SS tt ll ll -// SS tttttt eeee ll ll aaaa -// SSSS tt ee ee ll ll aa -// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" -// SS SS tt ee ll ll aa aa -// SSSS ttt eeeee llll llll aaaaa -// -// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony -// and the Stella Team -// -// See the file "License.txt" for information on usage and redistribution of -// this file, and for a DISCLAIMER OF ALL WARRANTIES. -// -// $Id: TIASnd.cxx 3239 2015-12-29 19:22:46Z stephena $ -//============================================================================ - -#include "TIATables.h" -#include "TIASnd.h" - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TIASound::TIASound(int outputFrequency) - : myChannelMode(Hardware2Stereo), - myOutputFrequency(outputFrequency), - myVolumePercentage(100) -{ - reset(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::reset() -{ - // Fill the polynomials - polyInit(Bit4, 4, 4, 3); - polyInit(Bit5, 5, 5, 3); - polyInit(Bit9, 9, 9, 5); - - // Initialize instance variables - for(int chan = 0; chan <= 1; ++chan) - { - myVolume[chan] = 0; - myDivNCnt[chan] = 0; - myDivNMax[chan] = 0; - myDiv3Cnt[chan] = 3; - myAUDC[chan] = 0; - myAUDF[chan] = 0; - myAUDV[chan] = 0; - myP4[chan] = 0; - myP5[chan] = 0; - myP9[chan] = 0; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::outputFrequency(int freq) -{ - myOutputFrequency = freq; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -std::string TIASound::channels(unsigned int hardware, bool stereo) -{ - if(hardware == 1) - myChannelMode = Hardware1; - else - myChannelMode = stereo ? Hardware2Stereo : Hardware2Mono; - - switch(myChannelMode) - { - case Hardware1: return "Hardware1"; - case Hardware2Mono: return "Hardware2Mono"; - case Hardware2Stereo: return "Hardware2Stereo"; - default: return ""; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::set(unsigned short address, unsigned char value) -{ - int chan = ~address & 0x1; - switch(address) - { - case TIARegister::AUDC0: - case TIARegister::AUDC1: - myAUDC[chan] = value & 0x0f; - break; - - case TIARegister::AUDF0: - case TIARegister::AUDF1: - myAUDF[chan] = value & 0x1f; - break; - - case TIARegister::AUDV0: - case TIARegister::AUDV1: - myAUDV[chan] = (value & 0x0f) << AUDV_SHIFT; - break; - - default: - return; - } - - unsigned short newVal = 0; - - // An AUDC value of 0 is a special case - if (myAUDC[chan] == SET_TO_1 || myAUDC[chan] == POLY5_POLY5) - { - // Indicate the clock is zero so no processing will occur, - // and set the output to the selected volume - newVal = 0; - myVolume[chan] = (myAUDV[chan] * myVolumePercentage) / 100; - } - else - { - // Otherwise calculate the 'divide by N' value - newVal = myAUDF[chan] + 1; - - // If bits 2 & 3 are set, then multiply the 'div by n' count by 3 - if((myAUDC[chan] & DIV3_MASK) == DIV3_MASK && myAUDC[chan] != POLY5_DIV3) - newVal *= 3; - } - - // Only reset those channels that have changed - if(newVal != myDivNMax[chan]) - { - // Reset the divide by n counters - myDivNMax[chan] = newVal; - - // If the channel is now volume only or was volume only, - // reset the counter (otherwise let it complete the previous) - if ((myDivNCnt[chan] == 0) || (newVal == 0)) - myDivNCnt[chan] = newVal; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -unsigned char TIASound::get(unsigned short address) const -{ - switch(address) - { - case TIARegister::AUDC0: return myAUDC[0]; - case TIARegister::AUDC1: return myAUDC[1]; - case TIARegister::AUDF0: return myAUDF[0]; - case TIARegister::AUDF1: return myAUDF[1]; - case TIARegister::AUDV0: return myAUDV[0] >> AUDV_SHIFT; - case TIARegister::AUDV1: return myAUDV[1] >> AUDV_SHIFT; - default: return 0; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::volume(unsigned int percent) -{ - if(percent <= 100) - myVolumePercentage = percent; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::process(short* buffer, unsigned int samples, DivDispatchOscBuffer** oscBuf) -{ - // Make temporary local copy - unsigned char audc0 = myAUDC[0], audc1 = myAUDC[1]; - unsigned char p5_0 = myP5[0], p5_1 = myP5[1]; - unsigned char div_n_cnt0 = myDivNCnt[0], div_n_cnt1 = myDivNCnt[1]; - short v0 = myVolume[0], v1 = myVolume[1]; - - // Take external volume into account - short audv0 = (myAUDV[0] * myVolumePercentage) / 100, - audv1 = (myAUDV[1] * myVolumePercentage) / 100; - - // Loop until the sample buffer is full - while(samples > 0) - { - // Process channel 0 - if (div_n_cnt0 > 1) - { - div_n_cnt0--; - } - else if (div_n_cnt0 == 1) - { - int prev_bit5 = Bit5[p5_0]; - div_n_cnt0 = myDivNMax[0]; - - // The P5 counter has multiple uses, so we increment it here - p5_0++; - if (p5_0 == POLY5_SIZE) - p5_0 = 0; - - // Check clock modifier for clock tick - if ((audc0 & 0x02) == 0 || - ((audc0 & 0x01) == 0 && Div31[p5_0]) || - ((audc0 & 0x01) == 1 && Bit5[p5_0]) || - ((audc0 & 0x0f) == POLY5_DIV3 && Bit5[p5_0] != prev_bit5)) - { - if (audc0 & 0x04) // Pure modified clock selected - { - if ((audc0 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode - { - if ( Bit5[p5_0] != prev_bit5 ) - { - myDiv3Cnt[0]--; - if ( !myDiv3Cnt[0] ) - { - myDiv3Cnt[0] = 3; - v0 = v0 ? 0 : audv0; - } - } - } - else - { - // If the output was set turn it off, else turn it on - v0 = v0 ? 0 : audv0; - } - } - else if (audc0 & 0x08) // Check for p5/p9 - { - if (audc0 == POLY9) // Check for poly9 - { - // Increase the poly9 counter - myP9[0]++; - if (myP9[0] == POLY9_SIZE) - myP9[0] = 0; - - v0 = Bit9[myP9[0]] ? audv0 : 0; - } - else if ( audc0 & 0x02 ) - { - v0 = (v0 || audc0 & 0x01) ? 0 : audv0; - } - else // Must be poly5 - { - v0 = Bit5[p5_0] ? audv0 : 0; - } - } - else // Poly4 is the only remaining option - { - // Increase the poly4 counter - myP4[0]++; - if (myP4[0] == POLY4_SIZE) - myP4[0] = 0; - - v0 = Bit4[myP4[0]] ? audv0 : 0; - } - } - } - - // Process channel 1 - if (div_n_cnt1 > 1) - { - div_n_cnt1--; - } - else if (div_n_cnt1 == 1) - { - int prev_bit5 = Bit5[p5_1]; - - div_n_cnt1 = myDivNMax[1]; - - // The P5 counter has multiple uses, so we increment it here - p5_1++; - if (p5_1 == POLY5_SIZE) - p5_1 = 0; - - // Check clock modifier for clock tick - if ((audc1 & 0x02) == 0 || - ((audc1 & 0x01) == 0 && Div31[p5_1]) || - ((audc1 & 0x01) == 1 && Bit5[p5_1]) || - ((audc1 & 0x0f) == POLY5_DIV3 && Bit5[p5_1] != prev_bit5)) - { - if (audc1 & 0x04) // Pure modified clock selected - { - if ((audc1 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode - { - if ( Bit5[p5_1] != prev_bit5 ) - { - myDiv3Cnt[1]--; - if ( ! myDiv3Cnt[1] ) - { - myDiv3Cnt[1] = 3; - v1 = v1 ? 0 : audv1; - } - } - } - else - { - // If the output was set turn it off, else turn it on - v1 = v1 ? 0 : audv1; - } - } - else if (audc1 & 0x08) // Check for p5/p9 - { - if (audc1 == POLY9) // Check for poly9 - { - // Increase the poly9 counter - myP9[1]++; - if (myP9[1] == POLY9_SIZE) - myP9[1] = 0; - - v1 = Bit9[myP9[1]] ? audv1 : 0; - } - else if ( audc1 & 0x02 ) - { - v1 = (v1 || audc1 & 0x01) ? 0 : audv1; - } - else // Must be poly5 - { - v1 = Bit5[p5_1] ? audv1 : 0; - } - } - else // Poly4 is the only remaining option - { - // Increase the poly4 counter - myP4[1]++; - if (myP4[1] == POLY4_SIZE) - myP4[1] = 0; - - v1 = Bit4[myP4[1]] ? audv1 : 0; - } - } - } - - short byte = v0 + v1; - switch(myChannelMode) - { - case Hardware2Mono: // mono sampling with 2 hardware channels - *(buffer++) = byte; - *(buffer++) = byte; - samples--; - break; - - case Hardware2Stereo: // stereo sampling with 2 hardware channels - *(buffer++) = v0; - *(buffer++) = v1; - samples--; - break; - - case Hardware1: // mono/stereo sampling with only 1 hardware channel - *(buffer++) = (v0 + v1) >> 1; - samples--; - break; - } - - if (oscBuf!=NULL) { - oscBuf[0]->data[oscBuf[0]->needle++]=v0; - oscBuf[1]->data[oscBuf[1]->needle++]=v1; - } - } - - // Save for next round - myP5[0] = p5_0; - myP5[1] = p5_1; - myVolume[0] = v0; - myVolume[1] = v1; - myDivNCnt[0] = div_n_cnt0; - myDivNCnt[1] = div_n_cnt1; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::polyInit(unsigned char* poly, int size, int f0, int f1) -{ - int mask = (1 << size) - 1, x = mask; - - for(int i = 0; i < mask; i++) - { - int bit0 = ( ( size - f0 ) ? ( x >> ( size - f0 ) ) : x ) & 0x01; - int bit1 = ( ( size - f1 ) ? ( x >> ( size - f1 ) ) : x ) & 0x01; - poly[i] = x & 1; - // calculate next bit - x = ( x >> 1 ) | ( ( bit0 ^ bit1 ) << ( size - 1) ); - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -const unsigned char TIASound::Div31[POLY5_SIZE] = { - 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; diff --git a/src/engine/platform/sound/tia/TIASnd.h b/src/engine/platform/sound/tia/TIASnd.h deleted file mode 100644 index 78459426..00000000 --- a/src/engine/platform/sound/tia/TIASnd.h +++ /dev/null @@ -1,186 +0,0 @@ -//============================================================================ -// -// SSSS tt lll lll -// SS SS tt ll ll -// SS tttttt eeee ll ll aaaa -// SSSS tt ee ee ll ll aa -// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" -// SS SS tt ee ll ll aa aa -// SSSS ttt eeeee llll llll aaaaa -// -// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony -// and the Stella Team -// -// See the file "License.txt" for information on usage and redistribution of -// this file, and for a DISCLAIMER OF ALL WARRANTIES. -// -// $Id: TIASnd.hxx 3239 2015-12-29 19:22:46Z stephena $ -//============================================================================ - -#ifndef TIASOUND_HXX -#define TIASOUND_HXX - -#include -#include "../../../dispatch.h" - -/** - This class implements a fairly accurate emulation of the TIA sound - hardware. This class uses code/ideas from z26 and MESS. - - Currently, the sound generation routines work at 31400Hz only. - Resampling can be done by passing in a different output frequency. - - @author Bradford W. Mott, Stephen Anthony, z26 and MESS teams - @version $Id: TIASnd.hxx 3239 2015-12-29 19:22:46Z stephena $ -*/ -class TIASound -{ - public: - /** - Create a new TIA Sound object using the specified output frequency - */ - TIASound(int outputFrequency = 31400); - - public: - /** - Reset the sound emulation to its power-on state - */ - void reset(); - - /** - Set the frequency output samples should be generated at - */ - void outputFrequency(int freq); - - /** - Selects the number of audio channels per sample. There are two factors - to consider: hardware capability and desired mixing. - - @param hardware The number of channels supported by the sound system - @param stereo Whether to output the internal sound signals into 1 - or 2 channels - - @return Status of the channel configuration used - */ - std::string channels(unsigned int hardware, bool stereo); - - public: - /** - Sets the specified sound register to the given value - - @param address Register address - @param value Value to store in the register - */ - void set(unsigned short address, unsigned char value); - - /** - Gets the specified sound register's value - - @param address Register address - */ - unsigned char get(unsigned short address) const; - - /** - Create sound samples based on the current sound register settings - in the specified buffer. NOTE: If channels is set to stereo then - the buffer will need to be twice as long as the number of samples. - - @param buffer The location to store generated samples - @param samples The number of samples to generate - */ - void process(short* buffer, unsigned int samples, DivDispatchOscBuffer** oscBuf=NULL); - - /** - Set the volume of the samples created (0-100) - */ - void volume(unsigned int percent); - - private: - void polyInit(unsigned char* poly, int size, int f0, int f1); - - private: - // Definitions for AUDCx (15, 16) - enum AUDCxRegister - { - SET_TO_1 = 0x00, // 0000 - POLY4 = 0x01, // 0001 - DIV31_POLY4 = 0x02, // 0010 - POLY5_POLY4 = 0x03, // 0011 - PURE1 = 0x04, // 0100 - PURE2 = 0x05, // 0101 - DIV31_PURE = 0x06, // 0110 - POLY5_2 = 0x07, // 0111 - POLY9 = 0x08, // 1000 - POLY5 = 0x09, // 1001 - DIV31_POLY5 = 0x0a, // 1010 - POLY5_POLY5 = 0x0b, // 1011 - DIV3_PURE = 0x0c, // 1100 - DIV3_PURE2 = 0x0d, // 1101 - DIV93_PURE = 0x0e, // 1110 - POLY5_DIV3 = 0x0f // 1111 - }; - - enum { - POLY4_SIZE = 0x000f, - POLY5_SIZE = 0x001f, - POLY9_SIZE = 0x01ff, - DIV3_MASK = 0x0c, - AUDV_SHIFT = 10 // shift 2 positions for AUDV, - // then another 8 for 16-bit sound - }; - - enum ChannelMode { - Hardware2Mono, // mono sampling with 2 hardware channels - Hardware2Stereo, // stereo sampling with 2 hardware channels - Hardware1 // mono/stereo sampling with only 1 hardware channel - }; - - private: - // Structures to hold the 6 tia sound control bytes - unsigned char myAUDC[2]; // AUDCx (15, 16) - unsigned char myAUDF[2]; // AUDFx (17, 18) - short myAUDV[2]; // AUDVx (19, 1A) - - short myVolume[2]; // Last output volume for each channel - - unsigned char myP4[2]; // Position pointer for the 4-bit POLY array - unsigned char myP5[2]; // Position pointer for the 5-bit POLY array - unsigned short myP9[2]; // Position pointer for the 9-bit POLY array - - unsigned char myDivNCnt[2]; // Divide by n counter. one for each channel - unsigned char myDivNMax[2]; // Divide by n maximum, one for each channel - unsigned char myDiv3Cnt[2]; // Div 3 counter, used for POLY5_DIV3 mode - - ChannelMode myChannelMode; - int myOutputFrequency; - unsigned int myVolumePercentage; - - /* - Initialize the bit patterns for the polynomials (at runtime). - - The 4bit and 5bit patterns are the identical ones used in the tia chip. - Though the patterns could be packed with 8 bits per byte, using only a - single bit per byte keeps the math simple, which is important for - efficient processing. - */ - unsigned char Bit4[POLY4_SIZE]; - unsigned char Bit5[POLY5_SIZE]; - unsigned char Bit9[POLY9_SIZE]; - - /* - The 'Div by 31' counter is treated as another polynomial because of - the way it operates. It does not have a 50% duty cycle, but instead - has a 13:18 ratio (of course, 13+18 = 31). This could also be - implemented by using counters. - */ - static const unsigned char Div31[POLY5_SIZE]; - - private: - // Following constructors and assignment operators not supported - TIASound(const TIASound&) = delete; - TIASound(TIASound&&) = delete; - TIASound& operator=(const TIASound&) = delete; - TIASound& operator=(TIASound&&) = delete; -}; - -#endif diff --git a/src/engine/platform/sound/tia/TIATables.h b/src/engine/platform/sound/tia/TIATables.h deleted file mode 100644 index 782a24de..00000000 --- a/src/engine/platform/sound/tia/TIATables.h +++ /dev/null @@ -1,219 +0,0 @@ -//============================================================================ -// -// SSSS tt lll lll -// SS SS tt ll ll -// SS tttttt eeee ll ll aaaa -// SSSS tt ee ee ll ll aa -// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" -// SS SS tt ee ll ll aa aa -// SSSS ttt eeeee llll llll aaaaa -// -// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony -// and the Stella Team -// -// See the file "License.txt" for information on usage and redistribution of -// this file, and for a DISCLAIMER OF ALL WARRANTIES. -// -// $Id: TIATables.hxx 3239 2015-12-29 19:22:46Z stephena $ -//============================================================================ - -#ifndef TIA_TABLES_HXX -#define TIA_TABLES_HXX - -enum TIABit { - P0Bit = 0x01, // Bit for Player 0 - M0Bit = 0x02, // Bit for Missle 0 - P1Bit = 0x04, // Bit for Player 1 - M1Bit = 0x08, // Bit for Missle 1 - BLBit = 0x10, // Bit for Ball - PFBit = 0x20, // Bit for Playfield - ScoreBit = 0x40, // Bit for Playfield score mode - PriorityBit = 0x80 // Bit for Playfield priority -}; - -enum TIAColor { - BKColor = 0, // Color index for Background - PFColor = 1, // Color index for Playfield - P0Color = 2, // Color index for Player 0 - P1Color = 3, // Color index for Player 1 - M0Color = 4, // Color index for Missle 0 - M1Color = 5, // Color index for Missle 1 - BLColor = 6, // Color index for Ball - HBLANKColor = 7 // Color index for HMove blank area -}; - -enum CollisionBit -{ - Cx_M0P1 = 1 << 0, // Missle0 - Player1 collision - Cx_M0P0 = 1 << 1, // Missle0 - Player0 collision - Cx_M1P0 = 1 << 2, // Missle1 - Player0 collision - Cx_M1P1 = 1 << 3, // Missle1 - Player1 collision - Cx_P0PF = 1 << 4, // Player0 - Playfield collision - Cx_P0BL = 1 << 5, // Player0 - Ball collision - Cx_P1PF = 1 << 6, // Player1 - Playfield collision - Cx_P1BL = 1 << 7, // Player1 - Ball collision - Cx_M0PF = 1 << 8, // Missle0 - Playfield collision - Cx_M0BL = 1 << 9, // Missle0 - Ball collision - Cx_M1PF = 1 << 10, // Missle1 - Playfield collision - Cx_M1BL = 1 << 11, // Missle1 - Ball collision - Cx_BLPF = 1 << 12, // Ball - Playfield collision - Cx_P0P1 = 1 << 13, // Player0 - Player1 collision - Cx_M0M1 = 1 << 14 // Missle0 - Missle1 collision -}; - -// TIA Write/Read register names -enum TIARegister { - VSYNC = 0x00, // Write: vertical sync set-clear (D1) - VBLANK = 0x01, // Write: vertical blank set-clear (D7-6,D1) - WSYNC = 0x02, // Write: wait for leading edge of hrz. blank (strobe) - RSYNC = 0x03, // Write: reset hrz. sync counter (strobe) - NUSIZ0 = 0x04, // Write: number-size player-missle 0 (D5-0) - NUSIZ1 = 0x05, // Write: number-size player-missle 1 (D5-0) - COLUP0 = 0x06, // Write: color-lum player 0 (D7-1) - COLUP1 = 0x07, // Write: color-lum player 1 (D7-1) - COLUPF = 0x08, // Write: color-lum playfield (D7-1) - COLUBK = 0x09, // Write: color-lum background (D7-1) - CTRLPF = 0x0a, // Write: cntrl playfield ballsize & coll. (D5-4,D2-0) - REFP0 = 0x0b, // Write: reflect player 0 (D3) - REFP1 = 0x0c, // Write: reflect player 1 (D3) - PF0 = 0x0d, // Write: playfield register byte 0 (D7-4) - PF1 = 0x0e, // Write: playfield register byte 1 (D7-0) - PF2 = 0x0f, // Write: playfield register byte 2 (D7-0) - RESP0 = 0x10, // Write: reset player 0 (strobe) - RESP1 = 0x11, // Write: reset player 1 (strobe) - RESM0 = 0x12, // Write: reset missle 0 (strobe) - RESM1 = 0x13, // Write: reset missle 1 (strobe) - RESBL = 0x14, // Write: reset ball (strobe) - AUDC0 = 0x15, // Write: audio control 0 (D3-0) - AUDC1 = 0x16, // Write: audio control 1 (D4-0) - AUDF0 = 0x17, // Write: audio frequency 0 (D4-0) - AUDF1 = 0x18, // Write: audio frequency 1 (D3-0) - AUDV0 = 0x19, // Write: audio volume 0 (D3-0) - AUDV1 = 0x1a, // Write: audio volume 1 (D3-0) - GRP0 = 0x1b, // Write: graphics player 0 (D7-0) - GRP1 = 0x1c, // Write: graphics player 1 (D7-0) - ENAM0 = 0x1d, // Write: graphics (enable) missle 0 (D1) - ENAM1 = 0x1e, // Write: graphics (enable) missle 1 (D1) - ENABL = 0x1f, // Write: graphics (enable) ball (D1) - HMP0 = 0x20, // Write: horizontal motion player 0 (D7-4) - HMP1 = 0x21, // Write: horizontal motion player 1 (D7-4) - HMM0 = 0x22, // Write: horizontal motion missle 0 (D7-4) - HMM1 = 0x23, // Write: horizontal motion missle 1 (D7-4) - HMBL = 0x24, // Write: horizontal motion ball (D7-4) - VDELP0 = 0x25, // Write: vertical delay player 0 (D0) - VDELP1 = 0x26, // Write: vertical delay player 1 (D0) - VDELBL = 0x27, // Write: vertical delay ball (D0) - RESMP0 = 0x28, // Write: reset missle 0 to player 0 (D1) - RESMP1 = 0x29, // Write: reset missle 1 to player 1 (D1) - HMOVE = 0x2a, // Write: apply horizontal motion (strobe) - HMCLR = 0x2b, // Write: clear horizontal motion registers (strobe) - CXCLR = 0x2c, // Write: clear collision latches (strobe) - - CXM0P = 0x00, // Read collision: D7=(M0,P1); D6=(M0,P0) - CXM1P = 0x01, // Read collision: D7=(M1,P0); D6=(M1,P1) - CXP0FB = 0x02, // Read collision: D7=(P0,PF); D6=(P0,BL) - CXP1FB = 0x03, // Read collision: D7=(P1,PF); D6=(P1,BL) - CXM0FB = 0x04, // Read collision: D7=(M0,PF); D6=(M0,BL) - CXM1FB = 0x05, // Read collision: D7=(M1,PF); D6=(M1,BL) - CXBLPF = 0x06, // Read collision: D7=(BL,PF); D6=(unused) - CXPPMM = 0x07, // Read collision: D7=(P0,P1); D6=(M0,M1) - INPT0 = 0x08, // Read pot port: D7 - INPT1 = 0x09, // Read pot port: D7 - INPT2 = 0x0a, // Read pot port: D7 - INPT3 = 0x0b, // Read pot port: D7 - INPT4 = 0x0c, // Read P1 joystick trigger: D7 - INPT5 = 0x0d // Read P2 joystick trigger: D7 -}; - -/** - The TIA class uses some static tables that aren't dependent on the actual - TIA state. For code organization, it's better to place that functionality - here. - - @author Stephen Anthony - @version $Id: TIATables.hxx 3239 2015-12-29 19:22:46Z stephena $ -*/ -class TIATables -{ - public: - /** - Compute all static tables used by the TIA - */ - static void computeAllTables(); - - // Player mask table - // [suppress mode][nusiz][pixel] - static unsigned char PxMask[2][8][320]; - - // Missle mask table (entries are true or false) - // [number][size][pixel] - // There are actually only 4 possible size combinations on a real system - // The fifth size is used for simulating the starfield effect in - // Cosmic Ark and Stay Frosty - static unsigned char MxMask[8][5][320]; - - // Ball mask table (entries are true or false) - // [size][pixel] - static unsigned char BLMask[4][320]; - - // Playfield mask table for reflected and non-reflected playfields - // [reflect, pixel] - static unsigned int PFMask[2][160]; - - // A mask table which can be used when an object is disabled - static unsigned char DisabledMask[640]; - - // Used to set the collision register to the correct value - static unsigned short CollisionMask[64]; - - // Indicates the update delay associated with poking at a TIA address - static const short PokeDelay[64]; - -#if 0 - // Used to convert value written in a motion register into - // its internal representation - static const int CompleteMotion[76][16]; -#endif - - // Indicates if HMOVE blanks should occur for the corresponding cycle - static const bool HMOVEBlankEnableCycles[76]; - - // Used to reflect a players graphics - static unsigned char GRPReflect[256]; - - // Indicates if player is being reset during delay, display or other times - // [nusiz][old pixel][new pixel] - static signed char PxPosResetWhen[8][160][160]; - - private: - // Compute the collision decode table - static void buildCollisionMaskTable(); - - // Compute the player mask table - static void buildPxMaskTable(); - - // Compute the missle mask table - static void buildMxMaskTable(); - - // Compute the ball mask table - static void buildBLMaskTable(); - - // Compute playfield mask table - static void buildPFMaskTable(); - - // Compute the player reflect table - static void buildGRPReflectTable(); - - // Compute the player position reset when table - static void buildPxPosResetWhenTable(); - - private: - // Following constructors and assignment operators not supported - TIATables() = delete; - TIATables(const TIATables&) = delete; - TIATables(TIATables&&) = delete; - TIATables& operator=(const TIATables&) = delete; - TIATables& operator=(TIATables&&) = delete; -}; - -#endif diff --git a/src/engine/platform/sound/ymfm/ymfm_opz.cpp b/src/engine/platform/sound/ymfm/ymfm_opz.cpp index 94123bf6..37c6a5fc 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opz.cpp +++ b/src/engine/platform/sound/ymfm/ymfm_opz.cpp @@ -292,6 +292,10 @@ bool opz_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint3 // note from tildearrow: // - are you kidding? I have to write to this "load preset" register before keying on? + // another note from tildearrow: + // - see https://github.com/110-kenichi/ymfm/blob/main/src/ymfm_opz.cpp + // - is 0x08 the actual key on register just like OPM? + // - if so then what's bit 5? if ((index & 0xf8) == 0x20 /*&& bitfield(index, 0, 3) == bitfield(m_regdata[0x08], 0, 3)*/) { channel = bitfield(index, 0, 3); diff --git a/src/engine/platform/tia.cpp b/src/engine/platform/tia.cpp index 82d03270..796c9448 100644 --- a/src/engine/platform/tia.cpp +++ b/src/engine/platform/tia.cpp @@ -22,7 +22,7 @@ #include #include -#define rWrite(a,v) if (!skipRegisterWrites) {tia.set(a,v); regPool[((a)-0x15)&0x0f]=v; if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {tia.write(a,v); regPool[((a)-0x15)&0x0f]=v; if (dumpWrites) {addWrite(a,v);} } const char* regCheatSheetTIA[]={ "AUDC0", "15", @@ -39,7 +39,22 @@ const char** DivPlatformTIA::getRegisterSheet() { } void DivPlatformTIA::acquire(short* bufL, short* bufR, size_t start, size_t len) { - tia.process(bufL+start,len,oscBuf); + for (size_t h=start; h>1; + } else { + bufL[h]=tia.myCurrentSample[0]; + } + if (++chanOscCounter>=114) { + chanOscCounter=0; + oscBuf[0]->data[oscBuf[0]->needle++]=tia.myChannelOut[0]; + oscBuf[1]->data[oscBuf[1]->needle++]=tia.myChannelOut[1]; + } + } } unsigned char DivPlatformTIA::dealWithFreq(unsigned char shape, int base, int pitch) { @@ -300,7 +315,7 @@ int DivPlatformTIA::getRegisterPoolSize() { } void DivPlatformTIA::reset() { - tia.reset(); + tia.reset(mixingType); memset(regPool,0,16); for (int i=0; i<2; i++) { chan[i]=DivPlatformTIA::Channel(); @@ -309,8 +324,12 @@ void DivPlatformTIA::reset() { } } +float DivPlatformTIA::getPostAmp() { + return 0.5f; +} + bool DivPlatformTIA::isStereo() { - return false; + return (mixingType==2); } bool DivPlatformTIA::keyOffAffectsArp(int ch) { @@ -333,25 +352,28 @@ void DivPlatformTIA::poke(std::vector& wlist) { void DivPlatformTIA::setFlags(unsigned int flags) { if (flags&1) { - rate=31250; + rate=COLOR_PAL*4.0/5.0; } else { - rate=31468; + rate=COLOR_NTSC; } chipClock=rate; + mixingType=(flags>>1)&3; for (int i=0; i<2; i++) { - oscBuf[i]->rate=rate; + oscBuf[i]->rate=rate/114; } + tia.reset(mixingType); } int DivPlatformTIA::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; skipRegisterWrites=false; + mixingType=0; + chanOscCounter=0; for (int i=0; i<2; i++) { isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - tia.channels(1,false); setFlags(flags); reset(); return 2; diff --git a/src/engine/platform/tia.h b/src/engine/platform/tia.h index 16817536..b838e068 100644 --- a/src/engine/platform/tia.h +++ b/src/engine/platform/tia.h @@ -22,7 +22,7 @@ #include "../dispatch.h" #include "../macroInt.h" #include -#include "sound/tia/TIASnd.h" +#include "sound/tia/Audio.h" class DivPlatformTIA: public DivDispatch { protected: @@ -42,7 +42,9 @@ class DivPlatformTIA: public DivDispatch { Channel chan[2]; DivDispatchOscBuffer* oscBuf[2]; bool isMuted[2]; - TIASound tia; + unsigned char mixingType; + unsigned char chanOscCounter; + TIA::Audio tia; unsigned char regPool[16]; friend void putDispatchChan(void*,int,int); @@ -61,6 +63,7 @@ class DivPlatformTIA: public DivDispatch { void tick(bool sysTick=true); void muteChannel(int ch, bool mute); void setFlags(unsigned int flags); + float getPostAmp(); bool isStereo(); bool keyOffAffectsArp(int ch); void notifyInsDeletion(void* ins); diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index 0fe0265e..1c6470fc 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -906,6 +906,7 @@ void DivPlatformTX81Z::reset() { pmDepth=0x7f; //rWrite(0x18,0x10); + immWrite(0x18,0x00); // LFO Freq Off immWrite(0x19,amDepth); immWrite(0x19,0x80|pmDepth); //rWrite(0x1b,0x00); diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index e8cfb99d..db8a718a 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -214,7 +214,7 @@ void DivPlatformVRC6::tick(bool sysTick) { if (chan[i].freq<0) chan[i].freq=0; if (chan[i].keyOff) { chWrite(i,2,0); - } else { + } else if (chan[i].active) { chWrite(i,1,chan[i].freq&0xff); chWrite(i,2,0x80|((chan[i].freq>>8)&0xf)); } diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 178ef05b..1c348800 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -273,6 +273,10 @@ void DivPlatformYM2203::tick(bool sysTick) { chan[i].state.fb=chan[i].std.fb.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); } + if (chan[i].std.ex4.had && chan[i].active) { + chan[i].opMask=chan[i].std.ex4.val&15; + chan[i].opMaskChanged=true; + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -385,8 +389,9 @@ void DivPlatformYM2203::tick(bool sysTick) { immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); chan[i].freqChanged=false; } - if (chan[i].keyOn) { - immWrite(0x28,0xf0|konOffs[i]); + if (chan[i].keyOn || chan[i].opMaskChanged) { + immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); + chan[i].opMaskChanged=false; chan[i].keyOn=false; } } @@ -409,6 +414,11 @@ int DivPlatformYM2203::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; + chan[c.chan].opMask= + (chan[c.chan].state.op[0].enable?1:0)| + (chan[c.chan].state.op[2].enable?2:0)| + (chan[c.chan].state.op[1].enable?4:0)| + (chan[c.chan].state.op[3].enable?8:0); } for (int i=0; i<4; i++) { diff --git a/src/engine/platform/ym2203.h b/src/engine/platform/ym2203.h index 0395c9d0..75660365 100644 --- a/src/engine/platform/ym2203.h +++ b/src/engine/platform/ym2203.h @@ -43,9 +43,9 @@ class DivPlatformYM2203: public DivPlatformOPN { DivInstrumentFM state; unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; - unsigned char psgMode, autoEnvNum, autoEnvDen; + unsigned char psgMode, autoEnvNum, autoEnvDen, opMask; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged; int vol, outVol; int sample; DivMacroInt std; @@ -66,6 +66,7 @@ class DivPlatformYM2203: public DivPlatformOPN { psgMode(1), autoEnvNum(0), autoEnvDen(0), + opMask(15), active(false), insChanged(true), freqChanged(false), @@ -75,6 +76,7 @@ class DivPlatformYM2203: public DivPlatformOPN { inPorta(false), furnacePCM(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(15), sample(-1) {} diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index 3ff24eb7..c7080d43 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -59,6 +59,7 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) { rWrite(baseAddr+0x70,op.d2r&31); rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); rWrite(baseAddr+0x90,op.ssgEnv&15); + opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); @@ -358,7 +359,7 @@ void DivPlatformYM2203Ext::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -395,10 +396,12 @@ void DivPlatformYM2203Ext::tick(bool sysTick) { immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); } - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn) { writeNoteOn=true; - writeMask|=1<<(4+i); + if (opChan[i].mask) { + writeMask|=1<<(4+i); + } opChan[i].keyOn=false; } } diff --git a/src/engine/platform/ym2203ext.h b/src/engine/platform/ym2203ext.h index 1a398d1a..d25ca45d 100644 --- a/src/engine/platform/ym2203ext.h +++ b/src/engine/platform/ym2203ext.h @@ -27,12 +27,12 @@ class DivPlatformYM2203Ext: public DivPlatformYM2203 { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask; int vol; unsigned char pan; // UGLY OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), - inPorta(false), vol(0), pan(3) {} + inPorta(false), mask(true), vol(0), pan(3) {} }; OpChannel opChan[4]; bool isOpMuted[4]; diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 1a3366b9..702d5b39 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -441,6 +441,10 @@ void DivPlatformYM2608::tick(bool sysTick) { chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } + if (chan[i].std.ex4.had && chan[i].active) { + chan[i].opMask=chan[i].std.ex4.val&15; + chan[i].opMaskChanged=true; + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -641,8 +645,9 @@ void DivPlatformYM2608::tick(bool sysTick) { immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); chan[i].freqChanged=false; } - if (chan[i].keyOn) { - immWrite(0x28,0xf0|konOffs[i]); + if (chan[i].keyOn || chan[i].opMaskChanged) { + immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); + chan[i].opMaskChanged=false; chan[i].keyOn=false; } } @@ -766,6 +771,11 @@ int DivPlatformYM2608::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; + chan[c.chan].opMask= + (chan[c.chan].state.op[0].enable?1:0)| + (chan[c.chan].state.op[2].enable?2:0)| + (chan[c.chan].state.op[1].enable?4:0)| + (chan[c.chan].state.op[3].enable?8:0); } for (int i=0; i<4; i++) { diff --git a/src/engine/platform/ym2608.h b/src/engine/platform/ym2608.h index c647d23f..8e2c4e40 100644 --- a/src/engine/platform/ym2608.h +++ b/src/engine/platform/ym2608.h @@ -48,9 +48,9 @@ class DivPlatformYM2608: public DivPlatformOPN { DivInstrumentFM state; unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; - unsigned char psgMode, autoEnvNum, autoEnvDen; + unsigned char psgMode, autoEnvNum, autoEnvDen, opMask; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged; int vol, outVol; int sample; unsigned char pan; @@ -73,6 +73,7 @@ class DivPlatformYM2608: public DivPlatformOPN { psgMode(1), autoEnvNum(0), autoEnvDen(0), + opMask(15), active(false), insChanged(true), freqChanged(false), @@ -82,6 +83,7 @@ class DivPlatformYM2608: public DivPlatformOPN { inPorta(false), furnacePCM(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(15), sample(-1), diff --git a/src/engine/platform/ym2608ext.cpp b/src/engine/platform/ym2608ext.cpp index 63503ccc..c6d7e03b 100644 --- a/src/engine/platform/ym2608ext.cpp +++ b/src/engine/platform/ym2608ext.cpp @@ -59,6 +59,7 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) { rWrite(baseAddr+0x70,op.d2r&31); rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); rWrite(baseAddr+0x90,op.ssgEnv&15); + opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); @@ -358,7 +359,7 @@ void DivPlatformYM2608Ext::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -395,10 +396,12 @@ void DivPlatformYM2608Ext::tick(bool sysTick) { immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); } - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn) { writeNoteOn=true; - writeMask|=1<<(4+i); + if (opChan[i].mask) { + writeMask|=1<<(4+i); + } opChan[i].keyOn=false; } } diff --git a/src/engine/platform/ym2608ext.h b/src/engine/platform/ym2608ext.h index bc3d4f99..21c8a35c 100644 --- a/src/engine/platform/ym2608ext.h +++ b/src/engine/platform/ym2608ext.h @@ -27,12 +27,12 @@ class DivPlatformYM2608Ext: public DivPlatformYM2608 { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask; int vol; unsigned char pan; // UGLY OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), - inPorta(false), vol(0), pan(3) {} + inPorta(false), mask(true), vol(0), pan(3) {} }; OpChannel opChan[4]; bool isOpMuted[4]; diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 143ecb18..903993ef 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -378,6 +378,10 @@ void DivPlatformYM2610::tick(bool sysTick) { chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } + if (chan[i].std.ex4.had && chan[i].active) { + chan[i].opMask=chan[i].std.ex4.val&15; + chan[i].opMaskChanged=true; + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -581,8 +585,9 @@ void DivPlatformYM2610::tick(bool sysTick) { immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); chan[i].freqChanged=false; } - if (chan[i].keyOn) { - immWrite(0x28,0xf0|konOffs[i]); + if (chan[i].keyOn || chan[i].opMaskChanged) { + immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); + chan[i].opMaskChanged=false; chan[i].keyOn=false; } } @@ -749,6 +754,11 @@ int DivPlatformYM2610::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; + chan[c.chan].opMask= + (chan[c.chan].state.op[0].enable?1:0)| + (chan[c.chan].state.op[2].enable?2:0)| + (chan[c.chan].state.op[1].enable?4:0)| + (chan[c.chan].state.op[3].enable?8:0); } for (int i=0; i<4; i++) { diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 6ae245e7..8d7a7009 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -441,6 +441,10 @@ void DivPlatformYM2610B::tick(bool sysTick) { chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } + if (chan[i].std.ex4.had && chan[i].active) { + chan[i].opMask=chan[i].std.ex4.val&15; + chan[i].opMaskChanged=true; + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -643,8 +647,9 @@ void DivPlatformYM2610B::tick(bool sysTick) { immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); chan[i].freqChanged=false; } - if (chan[i].keyOn) { - immWrite(0x28,0xf0|konOffs[i]); + if (chan[i].keyOn || chan[i].opMaskChanged) { + immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); + chan[i].opMaskChanged=false; chan[i].keyOn=false; } } @@ -811,6 +816,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; + chan[c.chan].opMask= + (chan[c.chan].state.op[0].enable?1:0)| + (chan[c.chan].state.op[2].enable?2:0)| + (chan[c.chan].state.op[1].enable?4:0)| + (chan[c.chan].state.op[3].enable?8:0); } for (int i=0; i<4; i++) { diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index 9f6e528f..5360aa3e 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -55,6 +55,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { rWrite(baseAddr+0x70,op.d2r&31); rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); rWrite(baseAddr+0x90,op.ssgEnv&15); + opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? rWrite(chanOffs[extChanOffs]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); @@ -354,7 +355,7 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -391,10 +392,12 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); } - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn) { writeNoteOn=true; - writeMask|=1<<(4+i); + if (opChan[i].mask) { + writeMask|=1<<(4+i); + } opChan[i].keyOn=false; } } diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index ca385f3d..a2a6c8d1 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -55,6 +55,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { rWrite(baseAddr+0x70,op.d2r&31); rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); rWrite(baseAddr+0x90,op.ssgEnv&15); + opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? rWrite(chanOffs[extChanOffs]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); @@ -354,7 +355,7 @@ void DivPlatformYM2610Ext::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -391,10 +392,12 @@ void DivPlatformYM2610Ext::tick(bool sysTick) { immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); } - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn) { writeNoteOn=true; - writeMask|=1<<(4+i); + if (opChan[i].mask) { + writeMask|=1<<(4+i); + } opChan[i].keyOn=false; } } diff --git a/src/engine/platform/ym2610shared.h b/src/engine/platform/ym2610shared.h index 2fee30c7..6c06eb06 100644 --- a/src/engine/platform/ym2610shared.h +++ b/src/engine/platform/ym2610shared.h @@ -52,10 +52,10 @@ template class DivPlatformYM2610Base: public DivPlatformOPN { int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; unsigned char psgMode, autoEnvNum, autoEnvDen; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged; int vol, outVol; int sample; - unsigned char pan; + unsigned char pan, opMask; int macroVolMul; DivMacroInt std; void macroInit(DivInstrument* which) { @@ -84,10 +84,12 @@ template class DivPlatformYM2610Base: public DivPlatformOPN { inPorta(false), furnacePCM(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(15), sample(-1), pan(3), + opMask(15), macroVolMul(255) {} }; @@ -96,7 +98,7 @@ template class DivPlatformYM2610Base: public DivPlatformOPN { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask; int vol; unsigned char pan; // UGLY @@ -116,6 +118,7 @@ template class DivPlatformYM2610Base: public DivPlatformOPN { keyOff(false), portaPause(false), inPorta(false), + mask(true), vol(0), pan(3) {} }; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 10f544d7..5d6a9d0a 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -31,7 +31,9 @@ void DivEngine::nextOrder() { curRow=0; if (repeatPattern) return; if (++curOrder>=curSubSong->ordersLen) { + logV("end of orders reached"); endOfSong=true; + memset(walked,0,8192); curOrder=0; } } @@ -351,15 +353,31 @@ void DivEngine::processRow(int i, bool afterDelay) { if (effectVal>0) speed2=effectVal; break; case 0x0b: // change order - if (changeOrd==-1) { + if (changeOrd==-1 || song.jumpTreatment==0) { changeOrd=effectVal; - changePos=0; + if (song.jumpTreatment==1 || song.jumpTreatment==2) { + changePos=0; + } } break; case 0x0d: // next order - if (changeOrd<0 && (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) { - changeOrd=-2; - changePos=effectVal; + if (song.jumpTreatment==2) { + if ((curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) { + changeOrd=-2; + changePos=effectVal; + } + } else if (song.jumpTreatment==1) { + if (changeOrd<0 && (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) { + changeOrd=-2; + changePos=effectVal; + } + } else { + if (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd) { + if (changeOrd<0) { + changeOrd=-2; + } + changePos=effectVal; + } } break; case 0xed: // delay @@ -915,18 +933,23 @@ void DivEngine::nextRow() { processRow(i,false); } + walked[((curOrder<<5)+(curRow>>3))&8191]|=1<<(curRow&7); + if (changeOrd!=-1) { if (repeatPattern) { curRow=0; changeOrd=-1; } else { curRow=changePos; + changePos=0; if (changeOrd==-2) changeOrd=curOrder+1; - if (changeOrd<=curOrder) endOfSong=true; + // old loop detection routine + //if (changeOrd<=curOrder) endOfSong=true; curOrder=changeOrd; if (curOrder>=curSubSong->ordersLen) { curOrder=0; endOfSong=true; + memset(walked,0,8192); } changeOrd=-1; } @@ -936,6 +959,13 @@ void DivEngine::nextRow() { if (haltOn==DIV_HALT_PATTERN) halted=true; } + // new loop detection routine + if (!endOfSong && walked[((curOrder<<5)+(curRow>>3))&8191]&(1<<(curRow&7))) { + logV("loop reached"); + endOfSong=true; + memset(walked,0,8192); + } + if (song.brokenSpeedSel) { if ((curSubSong->patLen&1) && curOrder&1) { ticks=((curRow&1)?speed2:speed1)*(curSubSong->timeBase+1); diff --git a/src/engine/song.h b/src/engine/song.h index ac49832e..493fc412 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -468,6 +468,11 @@ struct DivSong { // 1: broken (don't allow value higher than speed) // 2: lax (allow value higher than speed) unsigned char delayBehavior; + // 0B/0D treatment + // 0: normal (0B/0D accepted) + // 1: old Furnace (first one accepted) + // 2: DefleMask (0D takes priority over 0B) + unsigned char jumpTreatment; bool properNoiseLayout; bool waveDutyIsVol; bool resetMacroOnPorta; @@ -571,6 +576,7 @@ struct DivSong { pitchSlideSpeed(4), loopModality(2), delayBehavior(2), + jumpTreatment(0), properNoiseLayout(true), waveDutyIsVol(false), resetMacroOnPorta(false), diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 39cb1192..b763ab97 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -82,13 +82,15 @@ const char* aboutLine[]={ "Laggy", "LovelyA72", "LunaMoth", + "Lunathir", "LVintageNerd", "Mahbod Karamoozian", "Miker", "nicco1690", "NikonTeen", - "psdominator", + "psxdominator", "Raijin", + "SnugglyBun", "SuperJet Spade", "TheDuccinator", "theloredev", @@ -102,7 +104,7 @@ const char* aboutLine[]={ "fd", "GENATARi", "host12prog", - "lunathir", + "Lunathir", "plane", "TheEssem", "", diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index 74d493e7..63571dca 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -213,6 +213,26 @@ void FurnaceGUI::drawCompatFlags() { ImGui::SetTooltip("no checks (like FamiTracker)"); } + ImGui::Text("Simultaneous jump (0B+0D) treatment:"); + if (ImGui::RadioButton("Normal",e->song.jumpTreatment==0)) { + e->song.jumpTreatment=0; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("accept 0B+0D to jump to a specific row of an order"); + } + if (ImGui::RadioButton("Old Furnace",e->song.jumpTreatment==1)) { + e->song.jumpTreatment=1; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("only accept the first jump effect"); + } + if (ImGui::RadioButton("DefleMask",e->song.jumpTreatment==2)) { + e->song.jumpTreatment=2; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("only accept 0Dxx"); + } + ImGui::Separator(); ImGui::TextWrapped("the following flags are for compatibility with older Furnace versions."); diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 76c6da9b..e25f504d 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -29,14 +29,20 @@ const char* sampleNote[12]={ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; -void FurnaceGUI::drawInsList() { +void FurnaceGUI::drawInsList(bool asChild) { if (nextWindow==GUI_WINDOW_INS_LIST) { insListOpen=true; ImGui::SetNextWindowFocus(); nextWindow=GUI_WINDOW_NOTHING; } - if (!insListOpen) return; - if (ImGui::Begin("Instruments",&insListOpen,globalWinFlags)) { + if (!insListOpen && !asChild) return; + bool began=false; + if (asChild) { + began=ImGui::BeginChild("Instruments"); + } else { + began=ImGui::Begin("Instruments",&insListOpen,globalWinFlags); + } + if (began) { if (settings.unifiedDataView) settings.horizontalDataView=0; if (ImGui::Button(ICON_FA_PLUS "##InsAdd")) { if (!settings.unifiedDataView) doAction(GUI_ACTION_INS_LIST_ADD); @@ -121,16 +127,30 @@ void FurnaceGUI::drawInsList() { if (ImGui::MenuItem("instrument")) { doAction(GUI_ACTION_INS_LIST_SAVE); } + if (ImGui::MenuItem("instrument (.dmp)")) { + doAction(GUI_ACTION_INS_LIST_SAVE_DMP); + } if (ImGui::MenuItem("wavetable")) { doAction(GUI_ACTION_WAVE_LIST_SAVE); } + if (ImGui::MenuItem("wavetable (.dmw)")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_DMW); + } + if (ImGui::MenuItem("wavetable (raw)")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_RAW); + } if (ImGui::MenuItem("sample")) { doAction(GUI_ACTION_SAMPLE_LIST_SAVE); } ImGui::EndPopup(); } - } - if (!settings.unifiedDataView) { + } else { + if (ImGui::BeginPopupContextItem("InsSaveFormats",ImGuiMouseButton_Right)) { + if (ImGui::MenuItem("save as .dmp...")) { + doAction(GUI_ACTION_INS_LIST_SAVE_DMP); + } + ImGui::EndPopup(); + } ImGui::SameLine(); if (ImGui::ArrowButton("InsUp",ImGuiDir_Up)) { doAction(GUI_ACTION_INS_LIST_MOVE_UP); @@ -391,6 +411,9 @@ void FurnaceGUI::drawInsList() { if (ImGui::MenuItem("save")) { doAction(GUI_ACTION_INS_LIST_SAVE); } + if (ImGui::MenuItem("save (.dmp)")) { + doAction(GUI_ACTION_INS_LIST_SAVE_DMP); + } if (ImGui::MenuItem("delete")) { doAction(GUI_ACTION_INS_LIST_DELETE); } @@ -424,11 +447,15 @@ void FurnaceGUI::drawInsList() { ImGui::EndTable(); } } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_LIST; - ImGui::End(); + if (asChild) { + ImGui::EndChild(); + } else { + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_LIST; + ImGui::End(); + } } -void FurnaceGUI::drawWaveList() { +void FurnaceGUI::drawWaveList(bool asChild) { if (nextWindow==GUI_WINDOW_WAVE_LIST) { waveListOpen=true; if (settings.unifiedDataView) { @@ -439,8 +466,14 @@ void FurnaceGUI::drawWaveList() { nextWindow=GUI_WINDOW_NOTHING; } if (settings.unifiedDataView) return; - if (!waveListOpen) return; - if (ImGui::Begin("Wavetables",&waveListOpen,globalWinFlags)) { + if (!waveListOpen && !asChild) return; + bool began=false; + if (asChild) { + began=ImGui::BeginChild("Wavetables"); + } else { + began=ImGui::Begin("Wavetables",&waveListOpen,globalWinFlags); + } + if (began) { if (ImGui::Button(ICON_FA_PLUS "##WaveAdd")) { doAction(GUI_ACTION_WAVE_LIST_ADD); } @@ -462,6 +495,17 @@ void FurnaceGUI::drawWaveList() { if (ImGui::Button(ICON_FA_FLOPPY_O "##WaveSave")) { doAction(GUI_ACTION_WAVE_LIST_SAVE); } + if (!settings.unifiedDataView) { + if (ImGui::BeginPopupContextItem("WaveSaveFormats",ImGuiMouseButton_Right)) { + if (ImGui::MenuItem("save as .dmw...")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_DMW); + } + if (ImGui::MenuItem("save raw...")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_RAW); + } + ImGui::EndPopup(); + } + } ImGui::SameLine(); if (ImGui::ArrowButton("WaveUp",ImGuiDir_Up)) { doAction(GUI_ACTION_WAVE_LIST_MOVE_UP); @@ -480,11 +524,15 @@ void FurnaceGUI::drawWaveList() { ImGui::EndTable(); } } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_LIST; - ImGui::End(); + if (asChild) { + ImGui::EndChild(); + } else { + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_LIST; + ImGui::End(); + } } -void FurnaceGUI::drawSampleList() { +void FurnaceGUI::drawSampleList(bool asChild) { if (nextWindow==GUI_WINDOW_SAMPLE_LIST) { sampleListOpen=true; if (settings.unifiedDataView) { @@ -495,8 +543,14 @@ void FurnaceGUI::drawSampleList() { nextWindow=GUI_WINDOW_NOTHING; } if (settings.unifiedDataView) return; - if (!sampleListOpen) return; - if (ImGui::Begin("Samples",&sampleListOpen,globalWinFlags)) { + if (!sampleListOpen && !asChild) return; + bool began=false; + if (asChild) { + began=ImGui::BeginChild("Samples"); + } else { + began=ImGui::Begin("Samples",&sampleListOpen,globalWinFlags); + } + if (began) { if (ImGui::Button(ICON_FA_FILE "##SampleAdd")) { doAction(GUI_ACTION_SAMPLE_LIST_ADD); } @@ -552,8 +606,12 @@ void FurnaceGUI::drawSampleList() { } ImGui::Unindent(); } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_LIST; - ImGui::End(); + if (asChild) { + ImGui::EndChild(); + } else { + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_LIST; + ImGui::End(); + } } void FurnaceGUI::actualWaveList() { diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index ba6fc74e..93f02a24 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -599,6 +599,9 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_INS_LIST_SAVE: if (curIns>=0 && curIns<(int)e->song.ins.size()) openFileDialog(GUI_FILE_INS_SAVE); break; + case GUI_ACTION_INS_LIST_SAVE_DMP: + if (curIns>=0 && curIns<(int)e->song.ins.size()) openFileDialog(GUI_FILE_INS_SAVE_DMP); + break; case GUI_ACTION_INS_LIST_MOVE_UP: if (e->moveInsUp(curIns)) { curIns--; @@ -666,6 +669,12 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_WAVE_LIST_SAVE: if (curWave>=0 && curWave<(int)e->song.wave.size()) openFileDialog(GUI_FILE_WAVE_SAVE); break; + case GUI_ACTION_WAVE_LIST_SAVE_DMW: + if (curWave>=0 && curWave<(int)e->song.wave.size()) openFileDialog(GUI_FILE_WAVE_SAVE_DMW); + break; + case GUI_ACTION_WAVE_LIST_SAVE_RAW: + if (curWave>=0 && curWave<(int)e->song.wave.size()) openFileDialog(GUI_FILE_WAVE_SAVE_RAW); + break; case GUI_ACTION_WAVE_LIST_MOVE_UP: if (e->moveWaveUp(curWave)) { curWave--; diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 912bd1c0..1d0e2a25 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -22,54 +22,242 @@ #include void FurnaceGUI::drawMobileControls() { + float timeScale=1.0f/(60.0f*ImGui::GetIO().DeltaTime); + if (mobileMenuOpen) { + if (mobileMenuPos<0.999f) { + WAKE_UP; + mobileMenuPos+=MIN(0.1,(1.0-mobileMenuPos)*0.65)*timeScale; + } else { + mobileMenuPos=1.0f; + } + } else { + if (mobileMenuPos>0.001f) { + WAKE_UP; + mobileMenuPos-=MIN(0.1,mobileMenuPos*0.65)*timeScale; + } else { + mobileMenuPos=0.0f; + } + } + ImGui::SetNextWindowPos(portrait?ImVec2(0.0f,((1.0-mobileMenuPos*0.65)*scrH*dpiScale)-(0.16*scrW*dpiScale)):ImVec2(0.5*scrW*dpiScale*mobileMenuPos,0.0f)); + ImGui::SetNextWindowSize(portrait?ImVec2(scrW*dpiScale,0.16*scrW*dpiScale):ImVec2(0.16*scrH*dpiScale,scrH*dpiScale)); if (ImGui::Begin("Mobile Controls",NULL,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) { - float availX=ImGui::GetContentRegionAvail().x; - ImVec2 buttonSize=ImVec2(availX,availX); + float avail=portrait?ImGui::GetContentRegionAvail().y:ImGui::GetContentRegionAvail().x; + ImVec2 buttonSize=ImVec2(avail,avail); - if (ImGui::Button(ICON_FA_CHEVRON_RIGHT "##MobileMenu",buttonSize)) { + const char* mobButtonName=ICON_FA_CHEVRON_RIGHT "##MobileMenu"; + if (portrait) mobButtonName=ICON_FA_CHEVRON_UP "##MobileMenu"; + if (mobileMenuOpen) { + if (portrait) { + mobButtonName=ICON_FA_CHEVRON_DOWN "##MobileMenu"; + } else { + mobButtonName=ICON_FA_CHEVRON_LEFT "##MobileMenu"; + } + } + if (ImGui::Button(mobButtonName,buttonSize)) { + mobileMenuOpen=!mobileMenuOpen; } - ImGui::Separator(); + if (!portrait) ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); + pushToggleColors(e->isPlaying()); + if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_PLAY "##Play",buttonSize)) { play(); } - ImGui::PopStyleColor(); + popToggleColors(); + if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_STOP "##Stop",buttonSize)) { stop(); } + if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne",buttonSize)) { e->stepOne(cursor.y); pendingStepUpdate=true; } bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); + pushToggleColors(repeatPattern); + if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern",buttonSize)) { e->setRepeatPattern(!repeatPattern); } - ImGui::PopStyleColor(); + popToggleColors(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); + pushToggleColors(edit); + if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_CIRCLE "##Edit",buttonSize)) { edit=!edit; } - ImGui::PopStyleColor(); + popToggleColors(); bool metro=e->getMetronome(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); + pushToggleColors(metro); + if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_BELL_O "##Metronome",buttonSize)) { e->setMetronome(!metro); } - ImGui::PopStyleColor(); - - if (ImGui::Button("Get me out of here")) { - toggleMobileUI(false); - } + popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); + + ImGui::SetNextWindowPos(portrait?ImVec2(0.0f,((1.0-mobileMenuPos*0.65)*scrH*dpiScale)):ImVec2(0.5*scrW*dpiScale*(mobileMenuPos-1.0),0.0f)); + ImGui::SetNextWindowSize(portrait?ImVec2(scrW*dpiScale,0.65*scrH*dpiScale):ImVec2(0.5*scrW*dpiScale,scrH*dpiScale)); + if (ImGui::Begin("Mobile Menu",NULL,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) { + if (ImGui::BeginTable("SceneSel",5)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,1.0f); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,1.0f); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,1.0f); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,1.0f); + ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,1.0f); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImVec2 buttonSize=ImGui::GetContentRegionAvail(); + buttonSize.y=30.0f*dpiScale; + + if (ImGui::Button("Pattern",buttonSize)) { + mobScene=GUI_SCENE_PATTERN; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Orders",buttonSize)) { + mobScene=GUI_SCENE_ORDERS; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Ins",buttonSize)) { + mobScene=GUI_SCENE_INSTRUMENT; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Wave",buttonSize)) { + mobScene=GUI_SCENE_WAVETABLE; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Sample",buttonSize)) { + mobScene=GUI_SCENE_SAMPLE; + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Button("Song",buttonSize)) { + mobScene=GUI_SCENE_SONG; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Channels",buttonSize)) { + mobScene=GUI_SCENE_CHANNELS; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Chips",buttonSize)) { + mobScene=GUI_SCENE_CHIPS; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Other",buttonSize)) { + mobScene=GUI_SCENE_OTHER; + } + ImGui::EndTable(); + } + + ImGui::Separator(); + + if (settings.unifiedDataView) { + drawInsList(true); + } else { + switch (mobScene) { + case GUI_SCENE_PATTERN: + case GUI_SCENE_ORDERS: + case GUI_SCENE_INSTRUMENT: + drawInsList(true); + break; + case GUI_SCENE_WAVETABLE: + drawWaveList(true); + break; + case GUI_SCENE_SAMPLE: + 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(); + ImGui::Button("Export Audio"); + ImGui::SameLine(); + ImGui::Button("Export VGM"); + + ImGui::Button("CmdStream"); + + ImGui::Separator(); + + ImGui::Text("Song info here..."); + break; + } + case GUI_SCENE_CHANNELS: + ImGui::Text("Channels here..."); + break; + case GUI_SCENE_CHIPS: + ImGui::Text("Chips here..."); + 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; + } + + ImGui::Separator(); + + ImGui::Button("Panic"); + ImGui::SameLine(); + if (ImGui::Button("Settings")) { + mobileMenuOpen=false; + } + ImGui::SameLine(); + if (ImGui::Button("About")) { + mobileMenuOpen=false; + mobileMenuPos=0.0f; + aboutOpen=true; + } + if (ImGui::Button("Switch to Desktop Mode")) { + toggleMobileUI(!mobileUI); + } + break; + } + } + } + } + ImGui::End(); } void FurnaceGUI::drawEditControls() { @@ -118,11 +306,11 @@ void FurnaceGUI::drawEditControls() { ImGui::EndTable(); } - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); + pushToggleColors(e->isPlaying()); if (ImGui::Button(ICON_FA_PLAY "##Play")) { play(); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); if (ImGui::Button(ICON_FA_STOP "##Stop")) { stop(); @@ -152,12 +340,12 @@ void FurnaceGUI::drawEditControls() { } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly)); + pushToggleColors(noteInputPoly); if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) { noteInputPoly=!noteInputPoly; e->setAutoNotePoly(noteInputPoly); } - ImGui::PopStyleColor(); + popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); @@ -168,11 +356,11 @@ void FurnaceGUI::drawEditControls() { stop(); } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); + pushToggleColors(e->isPlaying()); if (ImGui::Button(ICON_FA_PLAY "##Play")) { play(); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { e->stepOne(cursor.y); @@ -181,26 +369,26 @@ void FurnaceGUI::drawEditControls() { ImGui::SameLine(); bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); + pushToggleColors(repeatPattern); if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { e->setRepeatPattern(!repeatPattern); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); + pushToggleColors(edit); if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { edit=!edit; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); bool metro=e->getMetronome(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); + pushToggleColors(metro); if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { e->setMetronome(!metro); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); ImGui::Text("Octave"); @@ -237,12 +425,12 @@ void FurnaceGUI::drawEditControls() { unimportant(ImGui::Checkbox("Pattern",&followPattern)); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly)); + pushToggleColors(noteInputPoly); if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) { noteInputPoly=!noteInputPoly; e->setAutoNotePoly(noteInputPoly); } - ImGui::PopStyleColor(); + popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); @@ -250,11 +438,11 @@ void FurnaceGUI::drawEditControls() { case 2: // compact vertical if (ImGui::Begin("Play/Edit Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) { ImVec2 buttonSize=ImVec2(ImGui::GetContentRegionAvail().x,0.0f); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); + pushToggleColors(e->isPlaying()); if (ImGui::Button(ICON_FA_PLAY "##Play",buttonSize)) { play(); } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::Button(ICON_FA_STOP "##Stop",buttonSize)) { stop(); } @@ -264,24 +452,24 @@ void FurnaceGUI::drawEditControls() { } bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); + pushToggleColors(repeatPattern); if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern",buttonSize)) { e->setRepeatPattern(!repeatPattern); } - ImGui::PopStyleColor(); + popToggleColors(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); + pushToggleColors(edit); if (ImGui::Button(ICON_FA_CIRCLE "##Edit",buttonSize)) { edit=!edit; } - ImGui::PopStyleColor(); + popToggleColors(); bool metro=e->getMetronome(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); + pushToggleColors(metro); if (ImGui::Button(ICON_FA_BELL_O "##Metronome",buttonSize)) { e->setMetronome(!metro); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::Text("Oct."); float avail=ImGui::GetContentRegionAvail().x; @@ -308,23 +496,23 @@ void FurnaceGUI::drawEditControls() { } ImGui::Text("Foll."); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(followOrders)); + pushToggleColors(followOrders); if (ImGui::Button("Ord##FollowOrders",buttonSize)) { handleUnimportant followOrders=!followOrders; } - ImGui::PopStyleColor(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(followPattern)); + popToggleColors(); + pushToggleColors(followPattern); if (ImGui::Button("Pat##FollowPattern",buttonSize)) { handleUnimportant followPattern=!followPattern; } - ImGui::PopStyleColor(); + popToggleColors(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly)); + pushToggleColors(noteInputPoly); if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) { noteInputPoly=!noteInputPoly; e->setAutoNotePoly(noteInputPoly); } - ImGui::PopStyleColor(); + popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); @@ -332,11 +520,11 @@ void FurnaceGUI::drawEditControls() { case 3: // split if (ImGui::Begin("Play Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) { if (e->isPlaying()) { - ImGui::PushStyleColor(ImGuiCol_Button,uiColors[GUI_COLOR_TOGGLE_ON]); + pushToggleColors(true); if (ImGui::Button(ICON_FA_STOP "##Stop")) { stop(); } - ImGui::PopStyleColor(); + popToggleColors(); } else { if (ImGui::Button(ICON_FA_PLAY "##Play")) { play(oldRow); @@ -359,35 +547,35 @@ void FurnaceGUI::drawEditControls() { } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); + pushToggleColors(edit); if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { edit=!edit; } - ImGui::PopStyleColor(); + popToggleColors(); bool metro=e->getMetronome(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); + pushToggleColors(metro); if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { e->setMetronome(!metro); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); + pushToggleColors(repeatPattern); if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { e->setRepeatPattern(!repeatPattern); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly)); + pushToggleColors(noteInputPoly); if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) { noteInputPoly=!noteInputPoly; e->setAutoNotePoly(noteInputPoly); } - ImGui::PopStyleColor(); + popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 391a39f6..201c9be2 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -431,7 +431,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { int startOff=-1; bool invalidData=false; if (data.size()<2) return; - if (data[0]!=fmt::sprintf("org.tildearrow.furnace - Pattern Data (%d)",DIV_ENGINE_VERSION)) return; + if (data[0].find("org.tildearrow.furnace - Pattern Data")!=0) return; if (sscanf(data[1].c_str(),"%d",&startOff)!=1) return; if (startOff<0) return; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 86c063e9..5153e1f6 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -17,6 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +// I hate you clangd extension! +// how about you DON'T insert random headers before this freaking important +// define!!!!!! #define _USE_MATH_DEFINES #include "gui.h" #include "util.h" @@ -225,7 +228,7 @@ void FurnaceGUI::encodeMMLStr(String& target, int* macro, int macroLen, int macr } } -void FurnaceGUI::decodeMMLStrW(String& source, int* macro, int& macroLen, int macroMax, bool hex) { +void FurnaceGUI::decodeMMLStrW(String& source, int* macro, int& macroLen, int macroMin, int macroMax, bool hex) { int buf=0; bool negaBuf=false; bool hasVal=false; @@ -261,9 +264,9 @@ void FurnaceGUI::decodeMMLStrW(String& source, int* macro, int& macroLen, int ma case ' ': if (hasVal) { hasVal=false; - negaBuf=false; macro[macroLen]=negaBuf?-buf:buf; - if (macro[macroLen]<0) macro[macroLen]=0; + negaBuf=false; + if (macro[macroLen]macroMax) macro[macroLen]=macroMax; macroLen++; buf=0; @@ -274,9 +277,9 @@ void FurnaceGUI::decodeMMLStrW(String& source, int* macro, int& macroLen, int ma } if (hasVal && macroLen<256) { hasVal=false; - negaBuf=false; macro[macroLen]=negaBuf?-buf:buf; - if (macro[macroLen]<0) macro[macroLen]=0; + negaBuf=false; + if (macro[macroLen]macroMax) macro[macroLen]=macroMax; macroLen++; buf=0; @@ -533,6 +536,7 @@ void FurnaceGUI::setFileName(String name) { } #endif updateWindowTitle(); + pushRecentFile(curFileName); } void FurnaceGUI::updateWindowTitle() { @@ -883,7 +887,7 @@ void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode, bool autoNote) { void FurnaceGUI::noteInput(int num, int key, int vol) { DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true); - + prepareUndo(GUI_UNDO_PATTERN_EDIT); if (key==100) { // note off @@ -1151,6 +1155,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { e->lockSave([this,num]() { e->curOrders->ord[orderCursor][curOrder]=((e->curOrders->ord[orderCursor][curOrder]<<4)|num); }); + MARK_MODIFIED; if (orderEditMode==2 || orderEditMode==3) { curNibble=!curNibble; if (!curNibble) { @@ -1258,9 +1263,18 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openSave( "Save File", - {"Furnace song", "*.fur", - "DefleMask 1.1.3 module", "*.dmf"}, - "Furnace song{.fur},DefleMask 1.1.3 module{.dmf}", + {"Furnace song", "*.fur"}, + "Furnace song{.fur}", + workingDirSong, + dpiScale + ); + break; + case GUI_FILE_SAVE_DMF: + if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); + hasOpened=fileDialog->openSave( + "Save File", + {"DefleMask 1.1.3 module", "*.dmf"}, + "DefleMask 1.1.3 module{.dmf}", workingDirSong, dpiScale ); @@ -1335,9 +1349,18 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); hasOpened=fileDialog->openSave( "Save Instrument", - {"Furnace instrument", "*.fui", - "DefleMask preset", "*.dmp"}, - "Furnace instrument{.fui},DefleMask preset{.dmp}", + {"Furnace instrument", "*.fui"}, + "Furnace instrument{.fui}", + workingDirIns, + dpiScale + ); + break; + case GUI_FILE_INS_SAVE_DMP: + if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); + hasOpened=fileDialog->openSave( + "Save Instrument", + {"DefleMask preset", "*.dmp"}, + "DefleMask preset{.dmp}", workingDirIns, dpiScale ); @@ -1358,10 +1381,28 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); hasOpened=fileDialog->openSave( "Save Wavetable", - {"Furnace wavetable", ".fuw", - "DefleMask wavetable", ".dmw", - "raw data", ".raw"}, - "Furnace wavetable{.fuw},DefleMask wavetable{.dmw},raw data{.raw}", + {"Furnace wavetable", ".fuw"}, + "Furnace wavetable{.fuw}", + workingDirWave, + dpiScale + ); + break; + case GUI_FILE_WAVE_SAVE_DMW: + if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); + hasOpened=fileDialog->openSave( + "Save Wavetable", + {"DefleMask wavetable", ".dmw"}, + "DefleMask wavetable{.dmw}", + workingDirWave, + dpiScale + ); + break; + case GUI_FILE_WAVE_SAVE_RAW: + if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); + hasOpened=fileDialog->openSave( + "Save Wavetable", + {"raw data", ".raw"}, + "raw data{.raw}", workingDirWave, dpiScale ); @@ -1684,6 +1725,7 @@ int FurnaceGUI::save(String path, int dmfVersion) { if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } + pushRecentFile(path); return 0; } @@ -1761,9 +1803,26 @@ int FurnaceGUI::load(String path) { if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } + pushRecentFile(path); return 0; } +void FurnaceGUI::pushRecentFile(String path) { + if (path.empty()) return; + if (path==backupPath) return; + for (int i=0; i<(int)recentFile.size(); i++) { + if (recentFile[i]==path) { + recentFile.erase(recentFile.begin()+i); + i--; + } + } + recentFile.push_front(path); + + while (!recentFile.empty() && (int)recentFile.size()>settings.maxRecentFile) { + recentFile.pop_back(); + } +} + void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) { e->saveAudio(path.c_str(),exportLoops+1,mode,exportFadeOut); displayExporting=true; @@ -2138,7 +2197,7 @@ void FurnaceGUI::editOptions(bool topMenu) { snprintf(id,63,"%.2x##LatchFX",data); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[fxColors[data]]); } - + if (ImGui::Selectable(id,latchTarget==3,ImGuiSelectableFlags_DontClosePopups)) { latchTarget=3; latchNibble=false; @@ -2211,7 +2270,7 @@ void FurnaceGUI::editOptions(bool topMenu) { doTranspose(transposeAmount,opMaskTransposeValue); ImGui::CloseCurrentPopup(); } - + ImGui::Separator(); if (ImGui::MenuItem("interpolate",BIND_FOR(GUI_ACTION_PAT_INTERPOLATE))) doInterpolate(); if (ImGui::BeginMenu("change instrument...")) { @@ -2338,15 +2397,44 @@ void FurnaceGUI::toggleMobileUI(bool enable, bool force) { if (mobileUI!=enable || force) { if (!mobileUI && enable) { ImGui::SaveIniSettingsToDisk(finalLayoutPath); - } + } mobileUI=enable; if (mobileUI) { ImGui::GetIO().IniFilename=NULL; } else { - ImGui::GetIO().IniFilename=finalLayoutPath; + ImGui::GetIO().IniFilename=NULL; ImGui::LoadIniSettingsFromDisk(finalLayoutPath); } - } + } +} + +void FurnaceGUI::pushToggleColors(bool status) { + ImVec4 toggleColor=status?uiColors[GUI_COLOR_TOGGLE_ON]:uiColors[GUI_COLOR_TOGGLE_OFF]; + ImGui::PushStyleColor(ImGuiCol_Button,toggleColor); + if (settings.guiColorsBase) { + toggleColor.x*=0.8f; + toggleColor.y*=0.8f; + toggleColor.z*=0.8f; + } else { + toggleColor.x=CLAMP(toggleColor.x*1.3f,0.0f,1.0f); + toggleColor.y=CLAMP(toggleColor.y*1.3f,0.0f,1.0f); + toggleColor.z=CLAMP(toggleColor.z*1.3f,0.0f,1.0f); + } + ImGui::PushStyleColor(ImGuiCol_ButtonHovered,toggleColor); + if (settings.guiColorsBase) { + toggleColor.x*=0.8f; + toggleColor.y*=0.8f; + toggleColor.z*=0.8f; + } else { + toggleColor.x=CLAMP(toggleColor.x*1.5f,0.0f,1.0f); + toggleColor.y=CLAMP(toggleColor.y*1.5f,0.0f,1.0f); + toggleColor.z=CLAMP(toggleColor.z*1.5f,0.0f,1.0f); + } + ImGui::PushStyleColor(ImGuiCol_ButtonActive,toggleColor); +} + +void FurnaceGUI::popToggleColors() { + ImGui::PopStyleColor(3); } int _processEvent(void* instance, SDL_Event* event) { @@ -2504,12 +2592,15 @@ void FurnaceGUI::processPoint(SDL_Event& ev) { TouchPoint* point=NULL; FIND_POINT(point,ev.tfinger.fingerId); if (point!=NULL) { + float prevX=point->x; + float prevY=point->y; point->x=ev.tfinger.x*scrW*dpiScale; point->y=ev.tfinger.y*scrH*dpiScale; point->z=ev.tfinger.pressure; if (point->id==0) { ImGui::GetIO().AddMousePosEvent(point->x,point->y); + pointMotion(point->x,point->y,point->x-prevX,point->y-prevY); } } break; @@ -2530,6 +2621,7 @@ void FurnaceGUI::processPoint(SDL_Event& ev) { if (newPoint.id==0) { ImGui::GetIO().AddMousePosEvent(newPoint.x,newPoint.y); ImGui::GetIO().AddMouseButtonEvent(ImGuiMouseButton_Left,true); + pointDown(newPoint.x,newPoint.y,0); } break; } @@ -2537,13 +2629,15 @@ void FurnaceGUI::processPoint(SDL_Event& ev) { for (size_t i=0; irenderSamplesP(); + } else { + if (sampleSelStart>sampleSelEnd) { + sampleSelStart^=sampleSelEnd; + sampleSelEnd^=sampleSelStart; + sampleSelStart^=sampleSelEnd; + } + } + } + sampleDragActive=false; + if (selecting) { + if (!selectingFull) cursor=selEnd; + finishSelection(); + demandScrollX=true; + if (cursor.xCoarse==selStart.xCoarse && cursor.xFine==selStart.xFine && cursor.y==selStart.y && + cursor.xCoarse==selEnd.xCoarse && cursor.xFine==selEnd.xFine && cursor.y==selEnd.y) { + if (!settings.cursorMoveNoScroll) { + updateScroll(cursor.y); + } + } + } +} + +void FurnaceGUI::pointMotion(int x, int y, int xrel, int yrel) { + if (selecting) { + // detect whether we have to scroll + if (ypatWindowPos.y+patWindowSize.y-2.0f*dpiScale) { + addScroll(1); + } + } + if (macroDragActive || macroLoopDragActive || waveDragActive || sampleDragActive) { + int distance=fabs((double)xrel); + if (distance<1) distance=1; + float start=x-xrel; + float end=x; + float startY=y-yrel; + float endY=y; + for (int i=0; i<=distance; i++) { + float fraction=(float)i/(float)distance; + float x=start+(end-start)*fraction; + float y=startY+(endY-startY)*fraction; + processDrags(x,y); + } + } +} + +// how many pixels should be visible at least at x/y dir +#define OOB_PIXELS_SAFETY 25 + +bool FurnaceGUI::detectOutOfBoundsWindow() { + int count=SDL_GetNumVideoDisplays(); + if (count<1) { + logW("bounds check: error %s",SDL_GetError()); + return false; + } + + SDL_Rect rect; + for (int i=0; i=scrX); + bool ybound=((rect.y+OOB_PIXELS_SAFETY)<=(scrY+scrH)) && ((rect.y+rect.h-OOB_PIXELS_SAFETY)>=scrY); + logD("bounds check: display %d is at %dx%dx%dx%d: %s%s",i,rect.x+OOB_PIXELS_SAFETY,rect.y+OOB_PIXELS_SAFETY,rect.x+rect.w-OOB_PIXELS_SAFETY,rect.y+rect.h-OOB_PIXELS_SAFETY,xbound?"x":"",ybound?"y":""); + + if (xbound && ybound) { + return true; + } + } + + return false; +} + bool FurnaceGUI::loop() { bool doThreadedInput=!settings.noThreadedInput; if (doThreadedInput) { @@ -2571,6 +2772,7 @@ bool FurnaceGUI::loop() { if (settings.powerSave) SDL_WaitEventTimeout(NULL,500); } eventTimeBegin=SDL_GetPerformanceCounter(); + bool updateWindow=false; while (SDL_PollEvent(&ev)) { WAKE_UP; ImGui_ImplSDL2_ProcessEvent(&ev); @@ -2588,80 +2790,14 @@ bool FurnaceGUI::loop() { motionXrel*=dpiScale; motionYrel*=dpiScale; #endif - if (selecting) { - // detect whether we have to scroll - if (motionYpatWindowPos.y+patWindowSize.y-2.0f*dpiScale) { - addScroll(1); - } - } - if (macroDragActive || macroLoopDragActive || waveDragActive || sampleDragActive) { - int distance=fabs((double)motionXrel); - if (distance<1) distance=1; - float start=motionX-motionXrel; - float end=motionX; - float startY=motionY-motionYrel; - float endY=motionY; - for (int i=0; i<=distance; i++) { - float fraction=(float)i/(float)distance; - float x=start+(end-start)*fraction; - float y=startY+(endY-startY)*fraction; - processDrags(x,y); - } - } + pointMotion(motionX,motionY,motionXrel,motionYrel); break; } case SDL_MOUSEBUTTONUP: - if (macroDragActive || macroLoopDragActive || waveDragActive || (sampleDragActive && sampleDragMode)) { - MARK_MODIFIED; - } - if (macroDragActive && macroDragLineMode && !macroDragMouseMoved) { - displayMacroMenu=true; - } - macroDragActive=false; - macroDragBitMode=false; - macroDragInitialValue=false; - macroDragInitialValueSet=false; - macroDragLastX=-1; - macroDragLastY=-1; - macroLoopDragActive=false; - waveDragActive=false; - if (sampleDragActive) { - logD("stopping sample drag"); - if (sampleDragMode) { - e->renderSamplesP(); - } else { - if (sampleSelStart>sampleSelEnd) { - sampleSelStart^=sampleSelEnd; - sampleSelEnd^=sampleSelStart; - sampleSelStart^=sampleSelEnd; - } - } - } - sampleDragActive=false; - if (selecting) { - if (!selectingFull) cursor=selEnd; - finishSelection(); - demandScrollX=true; - if (cursor.xCoarse==selStart.xCoarse && cursor.xFine==selStart.xFine && cursor.y==selStart.y && - cursor.xCoarse==selEnd.xCoarse && cursor.xFine==selEnd.xFine && cursor.y==selEnd.y) { - if (!settings.cursorMoveNoScroll) { - updateScroll(cursor.y); - } - } - } + pointUp(ev.button.x,ev.button.y,ev.button.button); break; case SDL_MOUSEBUTTONDOWN: - aboutOpen=false; - if (bindSetActive) { - bindSetActive=false; - bindSetPending=false; - actionKeys[bindSetTarget]=bindSetPrevValue; - bindSetTarget=0; - bindSetPrevValue=0; - } + pointDown(ev.button.x,ev.button.y,ev.button.button); break; case SDL_MOUSEWHEEL: wheelX+=ev.wheel.x; @@ -2677,6 +2813,22 @@ bool FurnaceGUI::loop() { scrW=ev.window.data1/dpiScale; scrH=ev.window.data2/dpiScale; #endif + portrait=(scrW0) { + showError(fmt::sprintf("Error while loading file! (%s)",lastError)); + } + } + } + } + if (recentFile.empty()) { + ImGui::Text("nothing here yet"); + } + ImGui::EndMenu(); + } ImGui::Separator(); if (ImGui::MenuItem("save",BIND_FOR(GUI_ACTION_SAVE))) { if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { @@ -2944,7 +3127,10 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("save as...",BIND_FOR(GUI_ACTION_SAVE_AS))) { openFileDialog(GUI_FILE_SAVE); } - if (ImGui::MenuItem("save as .dmf (1.0/legacy)...",BIND_FOR(GUI_ACTION_SAVE_AS))) { + if (ImGui::MenuItem("save as .dmf (1.1.3+)...")) { + openFileDialog(GUI_FILE_SAVE_DMF); + } + if (ImGui::MenuItem("save as .dmf (1.0/legacy)...")) { openFileDialog(GUI_FILE_SAVE_DMF_LEGACY); } ImGui::Separator(); @@ -3128,6 +3314,11 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("reset layout")) { showWarning("Are you sure you want to reset the workspace layout?",GUI_WARN_RESET_LAYOUT); } +#ifdef IS_MOBILE + if (ImGui::MenuItem("switch to mobile view")) { + toggleMobileUI(!mobileUI); + } +#endif if (ImGui::MenuItem("settings...",BIND_FOR(GUI_ACTION_WINDOW_SETTINGS))) { syncSettings(); settingsOpen=true; @@ -3166,7 +3357,7 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("log viewer",BIND_FOR(GUI_ACTION_WINDOW_LOG),logOpen)) logOpen=!logOpen; if (ImGui::MenuItem("statistics",BIND_FOR(GUI_ACTION_WINDOW_STATS),statsOpen)) statsOpen=!statsOpen; if (spoilerOpen) if (ImGui::MenuItem("spoiler",NULL,spoilerOpen)) spoilerOpen=!spoilerOpen; - + ImGui::EndMenu(); } if (ImGui::BeginMenu("help")) { @@ -3250,9 +3441,41 @@ bool FurnaceGUI::loop() { if (mobileUI) { globalWinFlags=ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoBringToFrontOnFocus; + //globalWinFlags=ImGuiWindowFlags_NoTitleBar; + // scene handling goes here! + pianoOpen=true; drawMobileControls(); - drawPattern(); - drawPiano(); + switch (mobScene) { + case GUI_SCENE_ORDERS: + ordersOpen=true; + curWindow=GUI_WINDOW_ORDERS; + drawOrders(); + break; + case GUI_SCENE_INSTRUMENT: + insEditOpen=true; + curWindow=GUI_WINDOW_INS_EDIT; + drawInsEdit(); + drawPiano(); + break; + case GUI_SCENE_WAVETABLE: + waveEditOpen=true; + curWindow=GUI_WINDOW_WAVE_EDIT; + drawWaveEdit(); + drawPiano(); + break; + case GUI_SCENE_SAMPLE: + sampleEditOpen=true; + curWindow=GUI_WINDOW_SAMPLE_EDIT; + drawSampleEdit(); + drawPiano(); + break; + default: + patternOpen=true; + curWindow=GUI_WINDOW_PATTERN; + drawPattern(); + drawPiano(); + break; + } } else { globalWinFlags=0; ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoWindowMenuButton|ImGuiDockNodeFlags_NoMove|ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0); @@ -3295,6 +3518,13 @@ bool FurnaceGUI::loop() { if (firstFrame) { firstFrame=false; +#ifdef IS_MOBILE + SDL_GetWindowSize(sdlWin,&scrW,&scrH); + scrW/=dpiScale; + scrH/=dpiScale; + portrait=(scrWgetPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_INS_OPEN: case GUI_FILE_INS_OPEN_REPLACE: case GUI_FILE_INS_SAVE: + case GUI_FILE_INS_SAVE_DMP: workingDirIns=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_WAVE_OPEN: case GUI_FILE_WAVE_OPEN_REPLACE: case GUI_FILE_WAVE_SAVE: + case GUI_FILE_WAVE_SAVE_DMW: + case GUI_FILE_WAVE_SAVE_RAW: workingDirWave=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_SAMPLE_OPEN: @@ -3409,9 +3643,10 @@ bool FurnaceGUI::loop() { } if (fileName!="") { if (curFileDialog==GUI_FILE_SAVE) { - // we can't tell whether the user chose .dmf or .fur in the system file picker - const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace song")?".fur":".dmf"; - checkExtensionDual(".fur",".dmf",fallbackExt); + checkExtension(".fur"); + } + if (curFileDialog==GUI_FILE_SAVE_DMF) { + checkExtension(".dmf"); } if (curFileDialog==GUI_FILE_SAVE_DMF_LEGACY) { checkExtension(".dmf"); @@ -3423,21 +3658,19 @@ bool FurnaceGUI::loop() { checkExtension(".wav"); } if (curFileDialog==GUI_FILE_INS_SAVE) { - // we can't tell whether the user chose .fui or .dmp in the system file picker - const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace instrument")?".fui":".dmp"; - checkExtensionDual(".fui",".dmp",fallbackExt); + checkExtension(".fui"); + } + if (curFileDialog==GUI_FILE_INS_SAVE_DMP) { + checkExtension(".dmp"); } if (curFileDialog==GUI_FILE_WAVE_SAVE) { - // same thing here - const char* fallbackExt=".fuw"; - if (!settings.sysFileDialog) { - if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="raw data") { - fallbackExt=".raw"; - } else if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="DefleMask wavetable") { - fallbackExt=".dmw"; - } - } - checkExtensionTriple(".fuw",".dmw",".raw",fallbackExt); + checkExtension(".fuw"); + } + if (curFileDialog==GUI_FILE_WAVE_SAVE_DMW) { + checkExtension(".dmw"); + } + if (curFileDialog==GUI_FILE_WAVE_SAVE_RAW) { + checkExtension(".raw"); } if (curFileDialog==GUI_FILE_EXPORT_VGM) { checkExtension(".vgm"); @@ -3465,21 +3698,10 @@ bool FurnaceGUI::loop() { break; case GUI_FILE_SAVE: { logD("saving: %s",copyOfName.c_str()); - String lowerCase=fileName; - for (char& i: lowerCase) { - if (i>='A' && i<='Z') i+='a'-'A'; - } bool saveWasSuccessful=true; - if ((lowerCase.size()<4 || lowerCase.rfind(".dmf")!=lowerCase.size()-4)) { - if (save(copyOfName,0)>0) { - showError(fmt::sprintf("Error while saving file! (%s)",lastError)); - saveWasSuccessful=false; - } - } else { - if (save(copyOfName,26)>0) { - showError(fmt::sprintf("Error while saving file! (%s)",lastError)); - saveWasSuccessful=false; - } + if (save(copyOfName,0)>0) { + showError(fmt::sprintf("Error while saving file! (%s)",lastError)); + saveWasSuccessful=false; } if (saveWasSuccessful && postWarnAction!=GUI_WARN_GENERIC) { switch (postWarnAction) { @@ -3512,6 +3734,12 @@ bool FurnaceGUI::loop() { } break; } + case GUI_FILE_SAVE_DMF: + logD("saving: %s",copyOfName.c_str()); + if (save(copyOfName,26)>0) { + showError(fmt::sprintf("Error while saving file! (%s)",lastError)); + } + break; case GUI_FILE_SAVE_DMF_LEGACY: logD("saving: %s",copyOfName.c_str()); if (save(copyOfName,24)>0) { @@ -3520,34 +3748,29 @@ bool FurnaceGUI::loop() { break; case GUI_FILE_INS_SAVE: if (curIns>=0 && curIns<(int)e->song.ins.size()) { - String lowerCase=fileName; - for (char& i: lowerCase) { - if (i>='A' && i<='Z') i+='a'-'A'; - } - if ((lowerCase.size()<4 || lowerCase.rfind(".dmp")!=lowerCase.size()-4)) { - e->song.ins[curIns]->save(copyOfName.c_str()); - } else { - if (!e->song.ins[curIns]->saveDMP(copyOfName.c_str())) { - showError("error while saving instrument! make sure your instrument is compatible."); - } + e->song.ins[curIns]->save(copyOfName.c_str()); + } + break; + case GUI_FILE_INS_SAVE_DMP: + if (curIns>=0 && curIns<(int)e->song.ins.size()) { + if (!e->song.ins[curIns]->saveDMP(copyOfName.c_str())) { + showError("error while saving instrument! make sure your instrument is compatible."); } } break; case GUI_FILE_WAVE_SAVE: if (curWave>=0 && curWave<(int)e->song.wave.size()) { - String lowerCase=fileName; - for (char& i: lowerCase) { - if (i>='A' && i<='Z') i+='a'-'A'; - } - if (lowerCase.size()<4) { - e->song.wave[curWave]->save(copyOfName.c_str()); - } else if (lowerCase.rfind(".dmw")==lowerCase.size()-4) { - e->song.wave[curWave]->saveDMW(copyOfName.c_str()); - } else if (lowerCase.rfind(".raw")==lowerCase.size()-4) { - e->song.wave[curWave]->saveRaw(copyOfName.c_str()); - } else { - e->song.wave[curWave]->save(copyOfName.c_str()); - } + e->song.wave[curWave]->save(copyOfName.c_str()); + } + break; + case GUI_FILE_WAVE_SAVE_DMW: + if (curWave>=0 && curWave<(int)e->song.wave.size()) { + e->song.wave[curWave]->saveDMW(copyOfName.c_str()); + } + break; + case GUI_FILE_WAVE_SAVE_RAW: + if (curWave>=0 && curWave<(int)e->song.wave.size()) { + e->song.wave[curWave]->saveRaw(copyOfName.c_str()); } break; case GUI_FILE_SAMPLE_OPEN: { @@ -4314,7 +4537,7 @@ bool FurnaceGUI::loop() { } logD("saving backup..."); SafeWriter* w=e->saveFur(true); - + if (w!=NULL) { FILE* outFile=ps_fopen(backupPath.c_str(),"wb"); if (outFile!=NULL) { @@ -4427,6 +4650,7 @@ bool FurnaceGUI::init() { tempoView=e->getConfBool("tempoView",true); waveHex=e->getConfBool("waveHex",false); + waveSigned=e->getConfBool("waveSigned",false); waveGenVisible=e->getConfBool("waveGenVisible",false); waveEditStyle=e->getConfInt("waveEditStyle",0); lockLayout=e->getConfBool("lockLayout",false); @@ -4474,6 +4698,13 @@ bool FurnaceGUI::init() { syncSettings(); + for (int i=0; igetConfString(fmt::sprintf("recentFile%d",i),""); + if (!r.empty()) { + recentFile.push_back(r); + } + } + if (settings.dpiScale>=0.5f) { dpiScale=settings.dpiScale; } @@ -4487,10 +4718,22 @@ bool FurnaceGUI::init() { SDL_Surface* icon=SDL_CreateRGBSurfaceFrom(furIcon,256,256,32,256*4,0xff,0xff00,0xff0000,0xff000000); #endif - scrW=e->getConfInt("lastWindowWidth",1280); - scrH=e->getConfInt("lastWindowHeight",800); +#ifdef IS_MOBILE + scrW=960; + scrH=540; + scrX=0; + scrY=0; +#else + scrW=scrConfW=e->getConfInt("lastWindowWidth",1280); + scrH=scrConfH=e->getConfInt("lastWindowHeight",800); + scrX=scrConfX=e->getConfInt("lastWindowX",SDL_WINDOWPOS_CENTERED); + scrY=scrConfY=e->getConfInt("lastWindowY",SDL_WINDOWPOS_CENTERED); + scrMax=e->getConfBool("lastWindowMax",false); +#endif + portrait=(scrWdisplaySize.w/dpiScale) scrW=(displaySize.w/dpiScale)-32; if (scrH>displaySize.h/dpiScale) scrH=(displaySize.h/dpiScale)-32; + portrait=(scrWsetConf("spoilerOpen",spoilerOpen); // commit last window size - e->setConf("lastWindowWidth",scrW); - e->setConf("lastWindowHeight",scrH); + e->setConf("lastWindowWidth",scrConfW); + e->setConf("lastWindowHeight",scrConfH); + e->setConf("lastWindowX",settings.saveWindowPos?scrConfX:(int)SDL_WINDOWPOS_CENTERED); + e->setConf("lastWindowY",settings.saveWindowPos?scrConfY:(int)SDL_WINDOWPOS_CENTERED); + e->setConf("lastWindowMax",scrMax); e->setConf("tempoView",tempoView); e->setConf("waveHex",waveHex); + e->setConf("waveSigned",waveSigned); e->setConf("waveGenVisible",waveGenVisible); e->setConf("waveEditStyle",waveEditStyle); e->setConf("lockLayout",lockLayout); @@ -4716,6 +4978,16 @@ bool FurnaceGUI::finish() { e->setConf("chanOscUseGrad",chanOscUseGrad); e->setConf("chanOscGrad",chanOscGrad.toString()); + // commit recent files + for (int i=0; i<30; i++) { + String key=fmt::sprintf("recentFile%d",i); + if (i>=settings.maxRecentFile || i>=(int)recentFile.size()) { + e->setConf(key,""); + } else { + e->setConf(key,recentFile[i]); + } + } + for (int i=0; i hell this->min=macroMin; this->max=macroMax; @@ -987,8 +1006,10 @@ class FurnaceGUI { std::vector sysSearchResults; std::vector newSongSearchResults; + std::deque recentFile; bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints; + bool portrait, mobileMenuOpen; bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly; bool displayPendingIns, pendingInsSingle, displayPendingRawSample; @@ -997,6 +1018,7 @@ class FurnaceGUI { int drawHalt; int macroPointSize; int waveEditStyle; + float mobileMenuPos; const int* curSysSection; String pendingRawSample; @@ -1008,10 +1030,13 @@ class FurnaceGUI { FurnaceGUIFileDialogs curFileDialog; FurnaceGUIWarnings warnAction; FurnaceGUIWarnings postWarnAction; + FurnaceGUIMobileScenes mobScene; FurnaceGUIFileDialog* fileDialog; - int scrW, scrH; + int scrW, scrH, scrConfW, scrConfH; + int scrX, scrY, scrConfX, scrConfY; + bool scrMax; double dpiScale; @@ -1145,6 +1170,7 @@ class FurnaceGUI { int dragMovesSelection; int unsignedDetune; int noThreadedInput; + int saveWindowPos; int clampSamples; int saveUnusedPatterns; int channelColors; @@ -1152,6 +1178,7 @@ class FurnaceGUI { int channelStyle; int channelVolStyle; int channelFeedbackStyle; + int maxRecentFile; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -1272,6 +1299,7 @@ class FurnaceGUI { channelStyle(0), channelVolStyle(0), channelFeedbackStyle(1), + maxRecentFile(10), maxUndoSteps(100), mainFontPath(""), patFontPath(""), @@ -1305,7 +1333,7 @@ class FurnaceGUI { 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, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble; + bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, waveSigned, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble; FurnaceGUIWindows curWindow, nextWindow, curWindowLast; float peak[2]; float patChanX[DIV_MAX_CHANS+1]; @@ -1426,6 +1454,7 @@ class FurnaceGUI { FurnaceGUIMacroDesc lastMacroDesc; int macroOffX, macroOffY; float macroScaleX, macroScaleY; + int macroRandMin, macroRandMax; ImVec2 macroLoopDragStart; ImVec2 macroLoopDragAreaSize; @@ -1449,10 +1478,10 @@ class FurnaceGUI { int renderTimeBegin, renderTimeEnd, renderTimeDelta; int eventTimeBegin, eventTimeEnd, eventTimeDelta; - int chanToMove, sysToMove, sysToDelete; + int chanToMove, sysToMove, sysToDelete, opToMove; ImVec2 patWindowPos, patWindowSize; - + // pattern view specific ImVec2 fourChars, threeChars, twoChars; ImVec2 noteCellSize, insCellSize, volCellSize, effectCellSize, effectValCellSize; @@ -1528,7 +1557,7 @@ class FurnaceGUI { // visualizer float keyHit[DIV_MAX_CHANS]; int lastIns[DIV_MAX_CHANS]; - + // log window bool followLog; @@ -1553,7 +1582,9 @@ class FurnaceGUI { float waveGenPhase[16]; float waveGenTL[4]; int waveGenMult[4]; - float waveGenFB[4]; + int waveGenFB[4]; + int waveGenScaleX, waveGenScaleY, waveGenOffsetX, waveGenOffsetY, waveGenSmooth; + float waveGenAmplify; bool waveGenFMCon1[4]; bool waveGenFMCon2[3]; bool waveGenFMCon3[2]; @@ -1592,16 +1623,19 @@ class FurnaceGUI { void toggleMobileUI(bool enable, bool force=false); + void pushToggleColors(bool status); + void popToggleColors(); + void drawMobileControls(); void drawEditControls(); void drawSongInfo(); void drawOrders(); void drawPattern(); - void drawInsList(); + void drawInsList(bool asChild=false); void drawInsEdit(); - void drawWaveList(); + void drawWaveList(bool asChild=false); void drawWaveEdit(); - void drawSampleList(); + void drawSampleList(bool asChild=false); void drawSampleEdit(); void drawMixer(); void drawOsc(); @@ -1700,9 +1734,14 @@ class FurnaceGUI { void keyDown(SDL_Event& ev); void keyUp(SDL_Event& ev); + void pointDown(int x, int y, int button); + void pointUp(int x, int y, int button); + void pointMotion(int x, int y, int xrel, int yrel); + void openFileDialog(FurnaceGUIFileDialogs type); int save(String path, int dmfVersion); int load(String path); + void pushRecentFile(String path); void exportAudio(String path, DivAudioExportModes mode); bool parseSysEx(unsigned char* data, size_t len); @@ -1712,7 +1751,7 @@ class FurnaceGUI { void encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel, bool hex=false, bool bit30=false); void decodeMMLStr(String& source, int* macro, unsigned char& macroLen, unsigned char& macroLoop, int macroMin, int macroMax, unsigned char& macroRel, bool bit30=false); - void decodeMMLStrW(String& source, int* macro, int& macroLen, int macroMax, bool hex=false); + void decodeMMLStrW(String& source, int* macro, int& macroLen, int macroMin, int macroMax, bool hex=false); String encodeKeyMap(std::map& map); void decodeKeyMap(std::map& map, String source); @@ -1732,6 +1771,7 @@ class FurnaceGUI { void runBackupThread(); void pushPartBlend(); void popPartBlend(); + bool detectOutOfBoundsWindow(); int processEvent(SDL_Event* ev); bool loop(); bool finish(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index b5e98c0e..0deac391 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -585,6 +585,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("INS_LIST_OPEN", "Open", 0), D("INS_LIST_OPEN_REPLACE", "Open (replace current)", 0), D("INS_LIST_SAVE", "Save", 0), + D("INS_LIST_SAVE_DMP", "Save (.dmp)", 0), D("INS_LIST_MOVE_UP", "Move up", FURKMOD_SHIFT|SDLK_UP), D("INS_LIST_MOVE_DOWN", "Move down", FURKMOD_SHIFT|SDLK_DOWN), D("INS_LIST_DELETE", "Delete", 0), @@ -599,6 +600,8 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WAVE_LIST_OPEN", "Open", 0), D("WAVE_LIST_OPEN_REPLACE", "Open (replace current)", 0), D("WAVE_LIST_SAVE", "Save", 0), + D("WAVE_LIST_SAVE_DMW", "Save (.dmw)", 0), + D("WAVE_LIST_SAVE_RAW", "Save (raw)", 0), D("WAVE_LIST_MOVE_UP", "Move up", FURKMOD_SHIFT|SDLK_UP), D("WAVE_LIST_MOVE_DOWN", "Move down", FURKMOD_SHIFT|SDLK_DOWN), D("WAVE_LIST_DELETE", "Delete", 0), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 02cf405a..2c350e23 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -316,27 +316,31 @@ const char* macroRelativeMode="Relative"; const char* macroQSoundMode="QSound"; const char* macroDummyMode="Bug"; -String macroHoverNote(int id, float val) { - if (val<-60 || val>=120) return "???"; - return fmt::sprintf("%d: %s",id,noteNames[(int)val+60]); +String macroHoverNote(int id, float val, void* u) { + int* macroVal=(int*)u; + if ((macroVal[id]&0xc0000000)==0x40000000 || (macroVal[id]&0xc0000000)==0x80000000) { + if (val<-60 || val>=120) return "???"; + return fmt::sprintf("%d: %s",id,noteNames[(int)val+60]); + } + return fmt::sprintf("%d: %d",id,(int)val); } -String macroHover(int id, float val) { +String macroHover(int id, float val, void* u) { return fmt::sprintf("%d: %d",id,val); } -String macroHoverLoop(int id, float val) { +String macroHoverLoop(int id, float val, void* u) { if (val>1) return "Release"; if (val>0) return "Loop"; return ""; } -String macroHoverBit30(int id, float val) { +String macroHoverBit30(int id, float val, void* u) { if (val>0) return "Fixed"; return "Relative"; } -String macroHoverES5506FilterMode(int id, float val) { +String macroHoverES5506FilterMode(int id, float val, void* u) { String mode="???"; switch (((int)val)&3) { case 0: @@ -357,7 +361,7 @@ String macroHoverES5506FilterMode(int id, float val) { return fmt::sprintf("%d: %s",id,mode); } -String macroLFOWaves(int id, float val) { +String macroLFOWaves(int id, float val, void* u) { switch (((int)val)&3) { case 0: return "Saw"; @@ -1355,7 +1359,7 @@ void FurnaceGUI::drawMacros(std::vector& macros) { if (i.isBitfield) { PlotBitfield("##IMacro",asInt,totalFit,0,i.bitfieldBits,i.max,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),doHighlight); } else { - PlotCustom("##IMacro",asFloat,totalFit,macroDragScroll,NULL,i.min+i.macro->vScroll,i.min+i.macro->vScroll+i.macro->vZoom,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),i.color,i.macro->len-macroDragScroll,i.hoverFunc,i.blockMode,i.macro->open?genericGuide:NULL,doHighlight); + PlotCustom("##IMacro",asFloat,totalFit,macroDragScroll,NULL,i.min+i.macro->vScroll,i.min+i.macro->vScroll+i.macro->vZoom,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),i.color,i.macro->len-macroDragScroll,i.hoverFunc,i.hoverFuncUser,i.blockMode,i.macro->open?genericGuide:NULL,doHighlight); } if (i.macro->open && (ImGui::IsItemClicked(ImGuiMouseButton_Left) || ImGui::IsItemClicked(ImGuiMouseButton_Right))) { macroDragStart=ImGui::GetItemRectMin(); @@ -1553,6 +1557,49 @@ void FurnaceGUI::drawMacros(std::vector& macros) { #define CENTER_TEXT_20(text) \ ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(20.0f*dpiScale-ImGui::CalcTextSize(text).x)); +#define OP_DRAG_POINT \ + if (ImGui::Button(ICON_FA_ARROWS)) { \ + } \ + if (ImGui::BeginDragDropSource()) { \ + opToMove=i; \ + ImGui::SetDragDropPayload("FUR_OP",NULL,0,ImGuiCond_Once); \ + ImGui::Button(ICON_FA_ARROWS "##SysDrag"); \ + ImGui::SameLine(); \ + if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { \ + ImGui::Text("(copying)"); \ + } else { \ + ImGui::Text("(swapping)"); \ + } \ + ImGui::EndDragDropSource(); \ + } else if (ImGui::IsItemHovered()) { \ + ImGui::SetTooltip("- drag to swap operator\n- shift-drag to copy operator"); \ + } \ + if (ImGui::BeginDragDropTarget()) { \ + const ImGuiPayload* dragItem=ImGui::AcceptDragDropPayload("FUR_OP"); \ + if (dragItem!=NULL) { \ + if (dragItem->IsDataType("FUR_OP")) { \ + if (opToMove!=i && opToMove>=0) { \ + int destOp=(opCount==4 && ins->type!=DIV_INS_OPL_DRUMS)?opOrder[i]:i; \ + int sourceOp=(opCount==4 && ins->type!=DIV_INS_OPL_DRUMS)?opOrder[opToMove]:opToMove; \ + if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { \ + e->lockEngine([ins,destOp,sourceOp]() { \ + ins->fm.op[destOp]=ins->fm.op[sourceOp]; \ + }); \ + } else { \ + e->lockEngine([ins,destOp,sourceOp]() { \ + DivInstrumentFM::Operator origOp=ins->fm.op[sourceOp]; \ + ins->fm.op[sourceOp]=ins->fm.op[destOp]; \ + ins->fm.op[destOp]=origOp; \ + }); \ + } \ + PARAMETER; \ + } \ + opToMove=-1; \ + } \ + } \ + ImGui::EndDragDropTarget(); \ + } + void FurnaceGUI::drawInsEdit() { if (nextWindow==GUI_WINDOW_INS_EDIT) { insEditOpen=true; @@ -1560,7 +1607,14 @@ void FurnaceGUI::drawInsEdit() { nextWindow=GUI_WINDOW_NOTHING; } if (!insEditOpen) return; - ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + if (mobileUI) { + patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f)); + patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f))); + ImGui::SetNextWindowPos(patWindowPos); + ImGui::SetNextWindowSize(patWindowSize); + } else { + ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + } if (ImGui::Begin("Instrument Editor",&insEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) { if (curIns<0 || curIns>=(int)e->song.ins.size()) { ImGui::Text("no instrument selected"); @@ -1608,6 +1662,12 @@ void FurnaceGUI::drawInsEdit() { if (ImGui::Button(ICON_FA_FLOPPY_O "##IESave")) { doAction(GUI_ACTION_INS_LIST_SAVE); } + if (ImGui::BeginPopupContextItem("InsSaveFormats",ImGuiMouseButton_Right)) { + if (ImGui::MenuItem("save as .dmp...")) { + doAction(GUI_ACTION_INS_LIST_SAVE_DMP); + } + ImGui::EndPopup(); + } ImGui::TableNextColumn(); ImGui::Text("Type"); @@ -1691,6 +1751,7 @@ void FurnaceGUI::drawInsEdit() { int opCount=4; if (ins->type==DIV_INS_OPLL) opCount=2; if (ins->type==DIV_INS_OPL) opCount=(ins->fm.ops==4)?4:2; + bool opsAreMutable=(ins->type==DIV_INS_FM); if (ImGui::BeginTabItem("FM")) { if (ImGui::BeginTable("fmDetails",3,ImGuiTableFlags_SizingStretchSame)) { @@ -2045,17 +2106,31 @@ void FurnaceGUI::drawInsEdit() { if (i==0) sliderHeight=(ImGui::GetContentRegionAvail().y/opCount)-ImGui::GetStyle().ItemSpacing.y; ImGui::PushID(fmt::sprintf("op%d",i).c_str()); + String opNameLabel; if (ins->type==DIV_INS_OPL_DRUMS) { - ImGui::Text("%s",oplDrumNames[i]); + opNameLabel=fmt::sprintf("%s",oplDrumNames[i]); } else if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) { if (i==1) { - ImGui::Text("Kick"); + opNameLabel="Kick"; } else { - ImGui::Text("Env"); + opNameLabel="Env"; } } else { - ImGui::Text("OP%d",i+1); + opNameLabel=fmt::sprintf("OP%d",i+1); } + if (opsAreMutable) { + pushToggleColors(op.enable); + if (ImGui::Button(opNameLabel.c_str())) { + op.enable=!op.enable; + PARAMETER; + } + popToggleColors(); + } else { + ImGui::TextUnformatted(opNameLabel.c_str()); + } + + // drag point + OP_DRAG_POINT; int maxTl=127; if (ins->type==DIV_INS_OPLL) { @@ -2339,8 +2414,20 @@ void FurnaceGUI::drawInsEdit() { } else { snprintf(tempID,1024,"Operator %d",i+1); } - CENTER_TEXT(tempID); - ImGui::TextUnformatted(tempID); + float nextCursorPosX=ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(tempID).x-(opsAreMutable?(ImGui::GetStyle().FramePadding.x*2.0f):0.0f)); + OP_DRAG_POINT; + ImGui::SameLine(); + ImGui::SetCursorPosX(nextCursorPosX); + if (opsAreMutable) { + pushToggleColors(op.enable); + if (ImGui::Button(tempID)) { + op.enable=!op.enable; + PARAMETER; + } + popToggleColors(); + } else { + ImGui::TextUnformatted(tempID); + } float sliderHeight=200.0f*dpiScale; float waveWidth=140.0*dpiScale; @@ -2772,16 +2859,29 @@ void FurnaceGUI::drawInsEdit() { } ImGui::Dummy(ImVec2(dpiScale,dpiScale)); + String opNameLabel; + OP_DRAG_POINT; + ImGui::SameLine(); if (ins->type==DIV_INS_OPL_DRUMS) { - ImGui::Text("%s",oplDrumNames[i]); + opNameLabel=fmt::sprintf("%s",oplDrumNames[i]); } else if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) { if (i==1) { - ImGui::Text("Envelope 2 (kick only)"); + opNameLabel="Envelope 2 (kick only)"; } else { - ImGui::Text("Envelope"); + opNameLabel="Envelope"; } } else { - ImGui::Text("OP%d",i+1); + opNameLabel=fmt::sprintf("OP%d",i+1); + } + if (opsAreMutable) { + pushToggleColors(op.enable); + if (ImGui::Button(opNameLabel.c_str())) { + op.enable=!op.enable; + PARAMETER; + } + popToggleColors(); + } else { + ImGui::TextUnformatted(opNameLabel.c_str()); } ImGui::SameLine(); @@ -3015,7 +3115,14 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("PM Depth",&ins->std.ex2Macro,0,127,128,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("LFO Speed",&ins->std.ex3Macro,0,255,128,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("LFO Shape",&ins->std.waveMacro,0,3,48,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,macroLFOWaves)); + } + if (ins->type==DIV_INS_FM) { macroList.push_back(FurnaceGUIMacroDesc("OpMask",&ins->std.ex4Macro,0,4,128,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,fmOperatorBits)); + } else if (ins->type==DIV_INS_OPZ) { + macroList.push_back(FurnaceGUIMacroDesc("AM Depth 2",&ins->std.ex5Macro,0,127,128,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("PM Depth 2",&ins->std.ex6Macro,0,127,128,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("LFO2 Speed",&ins->std.ex7Macro,0,255,128,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("LFO2 Shape",&ins->std.ex8Macro,0,3,48,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,macroLFOWaves)); } drawMacros(macroList); ImGui::EndTabItem(); @@ -3346,29 +3453,29 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_C64) if (ImGui::BeginTabItem("C64")) { ImGui::Text("Waveform"); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.triOn)); + pushToggleColors(ins->c64.triOn); if (ImGui::Button("tri")) { PARAMETER ins->c64.triOn=!ins->c64.triOn; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.sawOn)); + pushToggleColors(ins->c64.sawOn); if (ImGui::Button("saw")) { PARAMETER ins->c64.sawOn=!ins->c64.sawOn; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.pulseOn)); + pushToggleColors(ins->c64.pulseOn); if (ImGui::Button("pulse")) { PARAMETER ins->c64.pulseOn=!ins->c64.pulseOn; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.noiseOn)); + pushToggleColors(ins->c64.noiseOn); if (ImGui::Button("noise")) { PARAMETER ins->c64.noiseOn=!ins->c64.noiseOn; } - ImGui::PopStyleColor(); + popToggleColors(); ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale); @@ -3430,29 +3537,29 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("Filter Mode"); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.lp)); + pushToggleColors(ins->c64.lp); if (ImGui::Button("low")) { PARAMETER ins->c64.lp=!ins->c64.lp; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.bp)); + pushToggleColors(ins->c64.bp); if (ImGui::Button("band")) { PARAMETER ins->c64.bp=!ins->c64.bp; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.hp)); + pushToggleColors(ins->c64.hp); if (ImGui::Button("high")) { PARAMETER ins->c64.hp=!ins->c64.hp; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.ch3off)); + pushToggleColors(ins->c64.ch3off); if (ImGui::Button("ch3off")) { PARAMETER ins->c64.ch3off=!ins->c64.ch3off; } - ImGui::PopStyleColor(); + popToggleColors(); P(ImGui::Checkbox("Volume Macro is Cutoff Macro",&ins->c64.volIsCutoff)); P(ImGui::Checkbox("Absolute Cutoff Macro",&ins->c64.filterIsAbs)); @@ -3633,7 +3740,7 @@ void FurnaceGUI::drawInsEdit() { modTable[i]=ins->fds.modTable[i]; } ImVec2 modTableSize=ImVec2(ImGui::GetContentRegionAvail().x,96.0f*dpiScale); - PlotCustom("ModTable",modTable,32,0,NULL,-4,3,modTableSize,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,true); + PlotCustom("ModTable",modTable,32,0,NULL,-4,3,modTableSize,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,NULL,true); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { macroDragStart=ImGui::GetItemRectMin(); macroDragAreaSize=modTableSize; @@ -4297,7 +4404,7 @@ void FurnaceGUI::drawInsEdit() { if (volMax>0) { macroList.push_back(FurnaceGUIMacroDesc(volumeLabel,&ins->std.volMacro,volMin,volMax,160,uiColors[GUI_COLOR_MACRO_VOLUME])); } - macroList.push_back(FurnaceGUIMacroDesc("Arpeggio",&ins->std.arpMacro,-120,120,160,uiColors[GUI_COLOR_MACRO_PITCH],true,NULL,NULL,false,NULL,0,true)); + macroList.push_back(FurnaceGUIMacroDesc("Arpeggio",&ins->std.arpMacro,-120,120,160,uiColors[GUI_COLOR_MACRO_PITCH],true,NULL,macroHoverNote,false,NULL,0,true,ins->std.arpMacro.val)); if (dutyMax>0) { if (ins->type==DIV_INS_MIKEY) { macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,mikeyFeedbackBits)); @@ -4546,6 +4653,28 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndMenu(); } + if (ImGui::BeginMenu("randomize...")) { + if (macroRandMinlastMacroDesc.max) macroRandMin=lastMacroDesc.max; + if (macroRandMaxlastMacroDesc.max) macroRandMax=lastMacroDesc.max; + ImGui::InputInt("Min",¯oRandMin,1,10); + ImGui::InputInt("Max",¯oRandMax,1,10); + if (ImGui::Button("randomize")) { + for (int i=0; ilen; i++) { + int val=0; + if (macroRandMax<=macroRandMin) { + val=macroRandMin; + } else { + val=macroRandMin+(rand()%(macroRandMax-macroRandMin+1)); + } + lastMacroDesc.macro->val[i]=val; + } + + ImGui::CloseCurrentPopup(); + } + ImGui::EndMenu(); + } ImGui::EndPopup(); } diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 254c53be..ec65101d 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -372,10 +372,17 @@ void FurnaceGUI::drawPattern() { sel2.xFine^=sel1.xFine; } ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0.0f,0.0f)); + if (mobileUI) { + patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f)); + patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f))); + ImGui::SetNextWindowPos(patWindowPos); + ImGui::SetNextWindowSize(patWindowSize); + } if (ImGui::Begin("Pattern",&patternOpen,globalWinFlags|(settings.avoidRaisingPattern?ImGuiWindowFlags_NoBringToFrontOnFocus:0))) { - //ImGui::SetWindowSize(ImVec2(scrW*dpiScale,scrH*dpiScale)); - patWindowPos=ImGui::GetWindowPos(); - patWindowSize=ImGui::GetWindowSize(); + if (!mobileUI) { + patWindowPos=ImGui::GetWindowPos(); + patWindowSize=ImGui::GetWindowSize(); + } //char id[32]; ImGui::PushFont(patFont); int ord=oldOrder; diff --git a/src/gui/piano.cpp b/src/gui/piano.cpp index 0e0063bb..082cc65e 100644 --- a/src/gui/piano.cpp +++ b/src/gui/piano.cpp @@ -52,27 +52,28 @@ void FurnaceGUI::drawPiano() { nextWindow=GUI_WINDOW_NOTHING; } if (!pianoOpen) return; + if (mobileUI) { + ImGui::SetNextWindowPos(ImVec2(patWindowPos.x,patWindowPos.y+patWindowSize.y)); + ImGui::SetNextWindowSize(portrait?ImVec2(scrW*dpiScale,0.4*scrW*dpiScale):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),0.3*scrH*dpiScale)); + } if (ImGui::Begin("Piano",&pianoOpen,((pianoOptions)?0:ImGuiWindowFlags_NoTitleBar)|ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) { bool oldPianoKeyPressed[180]; memcpy(oldPianoKeyPressed,pianoKeyPressed,180*sizeof(bool)); memset(pianoKeyPressed,0,180*sizeof(bool)); - if (ImGui::BeginTable("PianoLayout",(pianoOptions?2:1)+((pianoInputPadMode==1 && cursor.xFine>0)?1:0),ImGuiTableFlags_BordersInnerV)) { + if (ImGui::BeginTable("PianoLayout",((pianoOptions && (!mobileUI || !portrait))?2:1),ImGuiTableFlags_BordersInnerV)) { int& off=(e->isPlaying() || pianoSharePosition)?pianoOffset:pianoOffsetEdit; int& oct=(e->isPlaying() || pianoSharePosition)?pianoOctaves:pianoOctavesEdit; bool view=(pianoView==2)?(!e->isPlaying()):pianoView; - if (pianoOptions) { + if (pianoOptions && (!mobileUI || !portrait)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); } - if (pianoInputPadMode==1 && cursor.xFine>0) { - ImGui::TableSetupColumn("c0s",ImGuiTableColumnFlags_WidthStretch,2.0f); - } ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,1.0f); ImGui::TableNextRow(); if (pianoOptions) { ImGui::TableNextColumn(); - float optionSizeY=ImGui::GetContentRegionAvail().y*0.5-ImGui::GetStyle().ItemSpacing.y; - ImVec2 optionSize=ImVec2(1.2f*optionSizeY,optionSizeY); + float optionSizeY=ImGui::GetContentRegionAvail().y*((mobileUI && portrait)?0.3:0.5)-ImGui::GetStyle().ItemSpacing.y; + ImVec2 optionSize=ImVec2((mobileUI && portrait)?((ImGui::GetContentRegionAvail().x-ImGui::GetStyle().ItemSpacing.x*5.0f)/6.0f):(1.2f*optionSizeY),optionSizeY); if (pianoOptionsSet) { if (ImGui::Button("OFF##PianoNOff",optionSize)) { if (edit) noteInput(0,100); @@ -122,6 +123,10 @@ void FurnaceGUI::drawPiano() { ImGui::EndPopup(); } + if (mobileUI && portrait) { + ImGui::SameLine(); + } + if (pianoOptionsSet) { if (ImGui::Button("REL##PianoNMRel",optionSize)) { if (edit) noteInput(0,102); @@ -148,6 +153,10 @@ void FurnaceGUI::drawPiano() { } } + if (mobileUI && portrait) { + ImGui::TableNextRow(); + } + ImGui::TableNextColumn(); if (pianoInputPadMode==1 && cursor.xFine>0) { ImVec2 buttonSize=ImGui::GetContentRegionAvail(); @@ -195,192 +204,192 @@ void FurnaceGUI::drawPiano() { ImGui::EndTable(); } - ImGui::TableNextColumn(); - } - ImGuiWindow* window=ImGui::GetCurrentWindow(); - ImVec2 size=ImGui::GetContentRegionAvail(); - ImDrawList* dl=ImGui::GetWindowDrawList(); + } else { + ImGuiWindow* window=ImGui::GetCurrentWindow(); + ImVec2 size=ImGui::GetContentRegionAvail(); + ImDrawList* dl=ImGui::GetWindowDrawList(); - ImVec2 minArea=window->DC.CursorPos; - ImVec2 maxArea=ImVec2( - minArea.x+size.x, - minArea.y+size.y - ); - ImRect rect=ImRect(minArea,maxArea); + ImVec2 minArea=window->DC.CursorPos; + ImVec2 maxArea=ImVec2( + minArea.x+size.x, + minArea.y+size.y + ); + ImRect rect=ImRect(minArea,maxArea); - // render piano - //ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y); - if (ImGui::ItemAdd(rect,ImGui::GetID("pianoDisplay"))) { - ImGui::ItemHoverable(rect,ImGui::GetID("pianoDisplay")); - if (view) { - int notes=oct*12; - // evaluate input - for (TouchPoint& i: activePoints) { - if (rect.Contains(ImVec2(i.x,i.y))) { - int note=(((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*notes)+12*off; - if (note<0) continue; - if (note>=180) continue; - pianoKeyPressed[note]=true; - } - } - - for (int i=0; i=180) continue; - float pkh=pianoKeyHit[note]; - ImVec4 color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM]; - if (pianoKeyPressed[note]) { - color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE]; - } else { - ImVec4 colorHit=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT]; - color.x+=(colorHit.x-color.x)*pkh; - color.y+=(colorHit.y-color.y)*pkh; - color.z+=(colorHit.z-color.z)*pkh; - color.w+=(colorHit.w-color.w)*pkh; - } - ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/notes,0.0f)); - ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/notes,1.0f)); - p1.x-=dpiScale; - dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color)); - if ((i%12)==0) { - String label=fmt::sprintf("%d",(note-60)/12); - ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f)); - ImVec2 labelSize=ImGui::CalcTextSize(label.c_str()); - pText.x-=labelSize.x*0.5f; - pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y; - dl->AddText(pText,0xff404040,label.c_str()); - } - } - } else { - int bottomNotes=7*oct; - // evaluate input - for (TouchPoint& i: activePoints) { - if (rect.Contains(ImVec2(i.x,i.y))) { - // top - int o=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*oct; - ImVec2 op0=ImLerp(rect.Min,rect.Max,ImVec2((float)o/oct,0.0f)); - ImVec2 op1=ImLerp(rect.Min,rect.Max,ImVec2((float)(o+1)/oct,1.0f)); - bool foundTopKey=false; - - for (int j=0; j<5; j++) { - int note=topKeyNotes[j]+12*(o+off); + // render piano + //ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID("pianoDisplay"))) { + ImGui::ItemHoverable(rect,ImGui::GetID("pianoDisplay")); + if (view) { + int notes=oct*12; + // evaluate input + for (TouchPoint& i: activePoints) { + if (rect.Contains(ImVec2(i.x,i.y))) { + int note=(((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*notes)+12*off; if (note<0) continue; if (note>=180) continue; - ImRect keyRect=ImRect( - ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f)), - ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f)) - ); - if (keyRect.Contains(ImVec2(i.x,i.y))) { - pianoKeyPressed[note]=true; - foundTopKey=true; - break; - } + pianoKeyPressed[note]=true; } - if (foundTopKey) continue; - - // bottom - int n=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*bottomNotes; - int note=bottomKeyNotes[n%7]+12*((n/7)+off); - if (note<0) continue; - if (note>=180) continue; - pianoKeyPressed[note]=true; - } - } - - for (int i=0; i=180) continue; - - float pkh=pianoKeyHit[note]; - ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM]; - if (pianoKeyPressed[note]) { - color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE]; - } else { - ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT]; - color.x+=(colorHit.x-color.x)*pkh; - color.y+=(colorHit.y-color.y)*pkh; - color.z+=(colorHit.z-color.z)*pkh; - color.w+=(colorHit.w-color.w)*pkh; } - ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/bottomNotes,0.0f)); - ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/bottomNotes,1.0f)); - p1.x-=dpiScale; - - dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color)); - if ((i%7)==0) { - String label=fmt::sprintf("%d",(note-60)/12); - ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f)); - ImVec2 labelSize=ImGui::CalcTextSize(label.c_str()); - pText.x-=labelSize.x*0.5f; - pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y; - dl->AddText(pText,0xff404040,label.c_str()); - } - } - - for (int i=0; i=180) continue; float pkh=pianoKeyHit[note]; - ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_TOP]; + ImVec4 color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM]; if (pianoKeyPressed[note]) { - color=uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE]; + color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE]; } else { - ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]; + ImVec4 colorHit=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT]; color.x+=(colorHit.x-color.x)*pkh; color.y+=(colorHit.y-color.y)*pkh; color.z+=(colorHit.z-color.z)*pkh; color.w+=(colorHit.w-color.w)*pkh; } - ImVec2 p0=ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f)); - ImVec2 p1=ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f)); - dl->AddRectFilled(p0,p1,ImGui::GetColorU32(uiColors[GUI_COLOR_PIANO_BACKGROUND])); - p0.x+=dpiScale; + ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/notes,0.0f)); + ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/notes,1.0f)); p1.x-=dpiScale; - p1.y-=dpiScale; dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color)); + if ((i%12)==0) { + String label=fmt::sprintf("%d",(note-60)/12); + ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f)); + ImVec2 labelSize=ImGui::CalcTextSize(label.c_str()); + pText.x-=labelSize.x*0.5f; + pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y; + dl->AddText(pText,0xff404040,label.c_str()); + } + } + } else { + int bottomNotes=7*oct; + // evaluate input + for (TouchPoint& i: activePoints) { + if (rect.Contains(ImVec2(i.x,i.y))) { + // top + int o=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*oct; + ImVec2 op0=ImLerp(rect.Min,rect.Max,ImVec2((float)o/oct,0.0f)); + ImVec2 op1=ImLerp(rect.Min,rect.Max,ImVec2((float)(o+1)/oct,1.0f)); + bool foundTopKey=false; + + for (int j=0; j<5; j++) { + int note=topKeyNotes[j]+12*(o+off); + if (note<0) continue; + if (note>=180) continue; + ImRect keyRect=ImRect( + ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f)), + ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f)) + ); + if (keyRect.Contains(ImVec2(i.x,i.y))) { + pianoKeyPressed[note]=true; + foundTopKey=true; + break; + } + } + if (foundTopKey) continue; + + // bottom + int n=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*bottomNotes; + int note=bottomKeyNotes[n%7]+12*((n/7)+off); + if (note<0) continue; + if (note>=180) continue; + pianoKeyPressed[note]=true; + } + } + + for (int i=0; i=180) continue; + + float pkh=pianoKeyHit[note]; + ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM]; + if (pianoKeyPressed[note]) { + color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE]; + } else { + ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT]; + color.x+=(colorHit.x-color.x)*pkh; + color.y+=(colorHit.y-color.y)*pkh; + color.z+=(colorHit.z-color.z)*pkh; + color.w+=(colorHit.w-color.w)*pkh; + } + + ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/bottomNotes,0.0f)); + ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/bottomNotes,1.0f)); + p1.x-=dpiScale; + + dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color)); + if ((i%7)==0) { + String label=fmt::sprintf("%d",(note-60)/12); + ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f)); + ImVec2 labelSize=ImGui::CalcTextSize(label.c_str()); + pText.x-=labelSize.x*0.5f; + pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y; + dl->AddText(pText,0xff404040,label.c_str()); + } + } + + for (int i=0; i=180) continue; + float pkh=pianoKeyHit[note]; + ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_TOP]; + if (pianoKeyPressed[note]) { + color=uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE]; + } else { + ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]; + color.x+=(colorHit.x-color.x)*pkh; + color.y+=(colorHit.y-color.y)*pkh; + color.z+=(colorHit.z-color.z)*pkh; + color.w+=(colorHit.w-color.w)*pkh; + } + ImVec2 p0=ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f)); + ImVec2 p1=ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f)); + dl->AddRectFilled(p0,p1,ImGui::GetColorU32(uiColors[GUI_COLOR_PIANO_BACKGROUND])); + p0.x+=dpiScale; + p1.x-=dpiScale; + p1.y-=dpiScale; + dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color)); + } + } + } + + const float reduction=ImGui::GetIO().DeltaTime*60.0f*0.12; + for (int i=0; i<180; i++) { + pianoKeyHit[i]-=reduction; + if (pianoKeyHit[i]<0) pianoKeyHit[i]=0; + } + } + + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + pianoOptions=!pianoOptions; + } + + // first check released keys + for (int i=0; i<180; i++) { + int note=i-60; + if (!pianoKeyPressed[i]) { + if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) { + e->synchronized([this,note]() { + e->autoNoteOff(-1,note); + }); } } } - - const float reduction=ImGui::GetIO().DeltaTime*60.0f*0.12; + // then pressed ones for (int i=0; i<180; i++) { - pianoKeyHit[i]-=reduction; - if (pianoKeyHit[i]<0) pianoKeyHit[i]=0; - } - } - - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - pianoOptions=!pianoOptions; - } - - // first check released keys - for (int i=0; i<180; i++) { - int note=i-60; - if (!pianoKeyPressed[i]) { - if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) { - e->synchronized([this,note]() { - e->autoNoteOff(-1,note); - }); - } - } - } - // then pressed ones - for (int i=0; i<180; i++) { - int note=i-60; - if (pianoKeyPressed[i]) { - if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) { - e->synchronized([this,note]() { - e->autoNoteOn(-1,curIns,note); - }); - if (edit) noteInput(note,0); + int note=i-60; + if (pianoKeyPressed[i]) { + if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) { + e->synchronized([this,note]() { + e->autoNoteOn(-1,curIns,note); + }); + if (edit) noteInput(note,0); + } } } } diff --git a/src/gui/plot_nolerp.cpp b/src/gui/plot_nolerp.cpp index d802cbc5..b75bda4f 100644 --- a/src/gui/plot_nolerp.cpp +++ b/src/gui/plot_nolerp.cpp @@ -293,7 +293,7 @@ void PlotBitfield(const char* label, const int* values, int values_count, int va PlotBitfieldEx(label, &Plot_IntArrayGetter, (void*)&data, values_count, values_offset, overlay_text, bits, graph_size, values_highlight, highlightColor); } -int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_display_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float), bool blockMode, std::string (*guideFunc)(float), const bool* values_highlight, ImVec4 highlightColor) +int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_display_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float,void*), void* hoverFuncUser, bool blockMode, std::string (*guideFunc)(float), const bool* values_highlight, ImVec4 highlightColor) { ImGuiContext& g = *GImGui; ImGuiWindow* window = ImGui::GetCurrentWindow(); @@ -359,7 +359,7 @@ int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_gett const float v0 = values_getter(data, (v_idx) % values_count); const float v1 = values_getter(data, (v_idx + 1) % values_count); if (hoverFunc) { - std::string hoverText=hoverFunc(v_idx+values_display_offset,v0); + std::string hoverText=hoverFunc(v_idx+values_display_offset,v0,hoverFuncUser); if (!hoverText.empty()) { ImGui::SetTooltip("%s",hoverText.c_str()); } @@ -459,8 +459,8 @@ int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_gett return idx_hovered; } -void PlotCustom(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float), bool blockMode, std::string (*guideFunc)(float), const bool* values_highlight, ImVec4 highlightColor) +void PlotCustom(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float,void*), void* hoverFuncUser, bool blockMode, std::string (*guideFunc)(float), const bool* values_highlight, ImVec4 highlightColor) { FurnacePlotArrayGetterData data(values, stride); - PlotCustomEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size, color, highlight, hoverFunc, blockMode, guideFunc, values_highlight, highlightColor); -} \ No newline at end of file + PlotCustomEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size, color, highlight, hoverFunc, hoverFuncUser, blockMode, guideFunc, values_highlight, highlightColor); +} diff --git a/src/gui/plot_nolerp.h b/src/gui/plot_nolerp.h index b353e6f8..48332b33 100644 --- a/src/gui/plot_nolerp.h +++ b/src/gui/plot_nolerp.h @@ -22,4 +22,4 @@ void PlotNoLerp(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float)); void PlotBitfield(const char* label, const int* values, int values_count, int values_offset = 0, const char** overlay_text = NULL, int bits = 8, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float), const bool* values_highlight = NULL, ImVec4 highlightColor = ImVec4(1.0f,1.0f,1.0f,1.0f)); -void PlotCustom(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float), ImVec4 fgColor = ImVec4(1.0f,1.0f,1.0f,1.0f), int highlight = 0, std::string (*hoverFunc)(int,float) = NULL, bool blockMode=false, std::string (*guideFunc)(float) = NULL, const bool* values_highlight = NULL, ImVec4 highlightColor = ImVec4(1.0f,1.0f,1.0f,1.0f)); \ No newline at end of file +void PlotCustom(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float), ImVec4 fgColor = ImVec4(1.0f,1.0f,1.0f,1.0f), int highlight = 0, std::string (*hoverFunc)(int,float,void*) = NULL, void* hoverFuncUser=NULL, bool blockMode=false, std::string (*guideFunc)(float) = NULL, const bool* values_highlight = NULL, ImVec4 highlightColor = ImVec4(1.0f,1.0f,1.0f,1.0f)); diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 9fc5141e..e08fab60 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -35,6 +35,12 @@ void FurnaceGUI::drawSampleEdit() { nextWindow=GUI_WINDOW_NOTHING; } if (!sampleEditOpen) return; + if (mobileUI) { + patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f)); + patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f))); + ImGui::SetNextWindowPos(patWindowPos); + ImGui::SetNextWindowSize(patWindowSize); + } if (ImGui::Begin("Sample Editor",&sampleEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) { if (curSample<0 || curSample>=(int)e->song.sample.size()) { ImGui::Text("no sample selected"); @@ -171,20 +177,20 @@ void FurnaceGUI::drawSampleEdit() { ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode)); + pushToggleColors(!sampleDragMode); if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { sampleDragMode=false; } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Select"); } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(sampleDragMode)); + pushToggleColors(sampleDragMode); if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) { sampleDragMode=true; } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Draw"); } @@ -726,20 +732,20 @@ void FurnaceGUI::drawSampleEdit() { ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode)); + pushToggleColors(!sampleDragMode); if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { sampleDragMode=false; } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Select"); } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(sampleDragMode)); + pushToggleColors(sampleDragMode); if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) { sampleDragMode=true; } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Draw"); } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 893f881f..f5965c3a 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -264,7 +264,7 @@ void FurnaceGUI::drawSettings() { } ImGui::Separator(); - + ImGui::Text("Initial system:"); ImGui::SameLine(); if (ImGui::Button("Current system")) { @@ -374,7 +374,7 @@ void FurnaceGUI::drawSettings() { } rightClickable ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(50.0f*dpiScale)); CWSliderScalar("Panning",ImGuiDataType_S8,&settings.initialSys[i+2],&_MINUS_ONE_HUNDRED_TWENTY_SEVEN,&_ONE_HUNDRED_TWENTY_SEVEN); rightClickable - + // oh please MSVC don't cry if (ImGui::TreeNode("Configure")) { drawSysConf(-1,(DivSystem)settings.initialSys[i],(unsigned int&)settings.initialSys[i+3],false); @@ -457,7 +457,7 @@ void FurnaceGUI::drawSettings() { if (ImGui::Checkbox("Double click selects entire column",&doubleClickColumnB)) { settings.doubleClickColumn=doubleClickColumnB; } - + bool allowEditDockingB=settings.allowEditDocking; if (ImGui::Checkbox("Allow docking editors",&allowEditDockingB)) { settings.allowEditDocking=allowEditDockingB; @@ -514,6 +514,14 @@ void FurnaceGUI::drawSettings() { ImGui::SetTooltip("threaded input processes key presses for note preview on a separate thread (on supported platforms), which reduces latency.\nhowever, crashes have been reported when threaded input is on. enable this option if that is the case."); } + bool saveWindowPosB=settings.saveWindowPos; + if (ImGui::Checkbox("Remember window position",&saveWindowPosB)) { + settings.saveWindowPos=saveWindowPosB; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("remembers the window's last position on startup."); + } + bool blankInsB=settings.blankIns; if (ImGui::Checkbox("New instruments are blank",&blankInsB)) { settings.blankIns=blankInsB; @@ -644,7 +652,7 @@ void FurnaceGUI::drawSettings() { BUFFER_SIZE_SELECTABLE(2048); ImGui::EndCombo(); } - + ImGui::Text("Quality"); ImGui::SameLine(); ImGui::Combo("##Quality",&settings.audioQuality,audioQualities,2); @@ -702,7 +710,7 @@ void FurnaceGUI::drawSettings() { } if (hasToReloadMidi) { - midiMap.read(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); + midiMap.read(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); midiMap.compile(); } @@ -1122,6 +1130,13 @@ void FurnaceGUI::drawSettings() { ImGui::Separator(); + if (ImGui::InputInt("Number of recent files",&settings.maxRecentFile)) { + if (settings.maxRecentFile<0) settings.maxRecentFile=0; + if (settings.maxRecentFile>30) settings.maxRecentFile=30; + } + + ImGui::Separator(); + ImGui::Text("Pattern view labels:"); ImGui::InputTextWithHint("Note off (3-char)","OFF",&settings.noteOffLabel); ImGui::InputTextWithHint("Note release (3-char)","===",&settings.noteRelLabel); @@ -1379,7 +1394,7 @@ void FurnaceGUI::drawSettings() { if (ImGui::Checkbox("Unsigned FM detune values",&unsignedDetuneB)) { settings.unsignedDetune=unsignedDetuneB; } - + // sorry. temporarily disabled until ImGui has a way to add separators in tables arbitrarily. /*bool sysSeparatorsB=settings.sysSeparators; if (ImGui::Checkbox("Add separators between systems in Orders",&sysSeparatorsB)) { @@ -1588,12 +1603,12 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_FM_SECONDARY_MOD,"Mod. accent (secondary)"); UI_COLOR_CONFIG(GUI_COLOR_FM_BORDER_MOD,"Mod. border"); UI_COLOR_CONFIG(GUI_COLOR_FM_BORDER_SHADOW_MOD,"Mod. border shadow"); - + UI_COLOR_CONFIG(GUI_COLOR_FM_PRIMARY_CAR,"Car. accent (primary"); UI_COLOR_CONFIG(GUI_COLOR_FM_SECONDARY_CAR,"Car. accent (secondary)"); UI_COLOR_CONFIG(GUI_COLOR_FM_BORDER_CAR,"Car. border"); UI_COLOR_CONFIG(GUI_COLOR_FM_BORDER_SHADOW_CAR,"Car. border shadow"); - + ImGui::TreePop(); } if (ImGui::TreeNode("Macro Editor")) { @@ -1961,7 +1976,7 @@ void FurnaceGUI::drawSettings() { UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COLLAPSE_ROWS); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_EXPAND_ROWS); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_LATCH); - + // TODO: collapse/expand pattern and song KEYBIND_CONFIG_END; @@ -2260,6 +2275,7 @@ void FurnaceGUI::syncSettings() { settings.dragMovesSelection=e->getConfInt("dragMovesSelection",2); settings.unsignedDetune=e->getConfInt("unsignedDetune",0); settings.noThreadedInput=e->getConfInt("noThreadedInput",0); + settings.saveWindowPos=e->getConfInt("saveWindowPos",1); settings.initialSysName=e->getConfString("initialSysName",""); settings.clampSamples=e->getConfInt("clampSamples",0); settings.noteOffLabel=e->getConfString("noteOffLabel","OFF"); @@ -2273,6 +2289,7 @@ void FurnaceGUI::syncSettings() { settings.channelStyle=e->getConfInt("channelStyle",0); settings.channelVolStyle=e->getConfInt("channelVolStyle",0); settings.channelFeedbackStyle=e->getConfInt("channelFeedbackStyle",1); + settings.maxRecentFile=e->getConfInt("maxRecentFile",10); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2363,6 +2380,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.dragMovesSelection,0,2); clampSetting(settings.unsignedDetune,0,1); clampSetting(settings.noThreadedInput,0,1); + clampSetting(settings.saveWindowPos,0,1); clampSetting(settings.clampSamples,0,1); clampSetting(settings.saveUnusedPatterns,0,1); clampSetting(settings.channelColors,0,2); @@ -2370,6 +2388,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.channelStyle,0,5); clampSetting(settings.channelVolStyle,0,3); clampSetting(settings.channelFeedbackStyle,0,3); + clampSetting(settings.maxRecentFile,0,30); settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys","")); if (settings.initialSys.size()<4) { @@ -2394,7 +2413,7 @@ void FurnaceGUI::syncSettings() { parseKeybinds(); - midiMap.read(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); + midiMap.read(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); midiMap.compile(); e->setMidiDirect(midiMap.directChannel); @@ -2402,7 +2421,7 @@ void FurnaceGUI::syncSettings() { } void FurnaceGUI::commitSettings() { - bool sampleROMsChanged = settings.yrw801Path!=e->getConfString("yrw801Path","") || + bool sampleROMsChanged=settings.yrw801Path!=e->getConfString("yrw801Path","") || settings.tg100Path!=e->getConfString("tg100Path","") || settings.mu5Path!=e->getConfString("mu5Path",""); @@ -2511,6 +2530,7 @@ void FurnaceGUI::commitSettings() { e->setConf("dragMovesSelection",settings.dragMovesSelection); e->setConf("unsignedDetune",settings.unsignedDetune); e->setConf("noThreadedInput",settings.noThreadedInput); + e->setConf("saveWindowPos",settings.saveWindowPos); e->setConf("clampSamples",settings.clampSamples); e->setConf("noteOffLabel",settings.noteOffLabel); e->setConf("noteRelLabel",settings.noteRelLabel); @@ -2523,6 +2543,7 @@ void FurnaceGUI::commitSettings() { e->setConf("channelStyle",settings.channelStyle); e->setConf("channelVolStyle",settings.channelVolStyle); e->setConf("channelFeedbackStyle",settings.channelFeedbackStyle); + e->setConf("maxRecentFile",settings.maxRecentFile); // colors for (int i=0; isaveConf(); + while (!recentFile.empty() && (int)recentFile.size()>settings.maxRecentFile) { + recentFile.pop_back(); + } + if (sampleROMsChanged) { if (e->loadSampleROMs()) { showError(e->getLastError()); @@ -3189,7 +3214,7 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { if ((iconFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(iconFont_compressed_data,iconFont_compressed_size,e->getConfInt("iconSize",16)*dpiScale,&fc,fontRangeIcon))==NULL) { logE("could not load icon font!"); } - + if (settings.mainFontSize==settings.patFontSize && settings.patFont<5 && builtinFontM[settings.patFont]==builtinFont[settings.mainFont]) { logD("using main font for pat font."); patFont=mainFont; @@ -3223,7 +3248,7 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { } } } - + if ((bigFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_plexSans_compressed_data,font_plexSans_compressed_size,40*dpiScale))==NULL) { logE("could not load big UI font!"); } diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index a6bbcb92..ba3ded1a 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -402,6 +402,24 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } break; } + case DIV_SYSTEM_TIA: { + ImGui::Text("Mixing mode:"); + if (ImGui::RadioButton("Mono",(flags&6)==0)) { + copyOfFlags=(flags&(~6)); + } + if (ImGui::RadioButton("Mono (no distortion)",(flags&6)==2)) { + copyOfFlags=(flags&(~6))|2; + } + if (ImGui::RadioButton("Stereo",(flags&6)==4)) { + copyOfFlags=(flags&(~6))|4; + } + + sysPal=flags&1; + if (ImGui::Checkbox("PAL",&sysPal)) { + copyOfFlags=(flags&(~1))|(unsigned int)sysPal; + } + break; + } case DIV_SYSTEM_PCSPKR: { ImGui::Text("Speaker type:"); if (ImGui::RadioButton("Unfiltered",(flags&3)==0)) { diff --git a/src/gui/sysEx.cpp b/src/gui/sysEx.cpp index 5b2b6657..f8849b56 100644 --- a/src/gui/sysEx.cpp +++ b/src/gui/sysEx.cpp @@ -1,6 +1,17 @@ #include "gui.h" #include "../ta-log.h" +// table taken from https://nornand.hatenablog.com/entry/2020/11/21/201911 +// Yamaha why didn't you just use 0-127 as it should be? +const unsigned char tlTable[100]={ + 127, 122, 118, 114, 110, 107, 104, 102, 100, 98, 96, 94, 92, 90, 88, 86, 85, 84, 82, 81, + // desde aquí la tabla consiste de valores que bajan de 1 en 1 + 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, + 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, + 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, + 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 +}; + bool FurnaceGUI::parseSysEx(unsigned char* data, size_t len) { SafeReader reader(data,len); @@ -137,7 +148,7 @@ bool FurnaceGUI::parseSysEx(unsigned char* data, size_t len) { op.sl=15-reader.readC(); reader.readC(); // LS - ignore op.am=(reader.readC()&0x40)?1:0; - op.tl=3+((99-reader.readC())*124)/99; + op.tl=tlTable[reader.readC()%100]; unsigned char freq=reader.readC(); logV("OP%d freq: %d",i,freq); op.mult=freq>>2; diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 7e453ef5..87f5ad38 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -32,6 +32,26 @@ const char* waveGenBaseShapes[4]={ "Pulse" }; +const float multFactors[17]={ + M_PI, + 2*M_PI, + 4*M_PI, + 6*M_PI, + 8*M_PI, + 10*M_PI, + 12*M_PI, + 14*M_PI, + 16*M_PI, + 18*M_PI, + 20*M_PI, + 22*M_PI, + 24*M_PI, + 26*M_PI, + 28*M_PI, + 30*M_PI, + 32*M_PI, +}; + void FurnaceGUI::doGenerateWave() { float finalResult[256]; if (curWave<0 || curWave>=(int)e->song.wave.size()) return; @@ -42,7 +62,37 @@ void FurnaceGUI::doGenerateWave() { if (wave->len<2) return; if (waveGenFM) { + float s0fb0=0; + float s0fb1=0; + float s1fb0=0; + float s1fb1=0; + float s2fb0=0; + float s2fb1=0; + float s3fb0=0; + float s3fb1=0; + for (int i=0; ilen; i++) { + float pos=(float)i/(float)wave->len; + float s0=sin((pos+(waveGenFB[0]?((s0fb0+s0fb1)*pow(2.0f,waveGenFB[0]-8)):0.0f))*multFactors[waveGenMult[0]])*waveGenTL[0]; + s0fb0=s0fb1; + s0fb1=s0; + + float s1=sin((pos+(waveGenFB[1]?((s1fb0+s1fb1)*pow(2.0f,waveGenFB[1]-8)):0.0f)+(waveGenFMCon1[0]?s0:0.0f))*multFactors[waveGenMult[1]])*waveGenTL[1]; + s1fb0=s1fb1; + s1fb1=s1; + float s2=sin((pos+(waveGenFB[2]?((s2fb0+s2fb1)*pow(2.0f,waveGenFB[2]-8)):0.0f)+(waveGenFMCon1[1]?s0:0.0f)+(waveGenFMCon2[0]?s1:0.0f))*multFactors[waveGenMult[2]])*waveGenTL[2]; + s2fb0=s2fb1; + s2fb1=s2; + + float s3=sin((pos+(waveGenFB[3]?((s3fb0+s3fb1)*pow(2.0f,waveGenFB[3]-8)):0.0f)+(waveGenFMCon1[2]?s0:0.0f)+(waveGenFMCon2[1]?s1:0.0f)+(waveGenFMCon3[0]?s2:0.0f))*multFactors[waveGenMult[3]])*waveGenTL[3]; + s3fb0=s3fb1; + s3fb1=s3; + + if (waveGenFMCon1[3]) finalResult[i]+=s0; + if (waveGenFMCon2[2]) finalResult[i]+=s1; + if (waveGenFMCon3[1]) finalResult[i]+=s2; + finalResult[i]+=s3; + } } else { switch (waveGenBaseShape) { case 0: // sine @@ -104,6 +154,9 @@ void FurnaceGUI::doGenerateWave() { } } +#define CENTER_TEXT(text) \ + ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(text).x)); + void FurnaceGUI::drawWaveEdit() { if (nextWindow==GUI_WINDOW_WAVE_EDIT) { waveEditOpen=true; @@ -112,7 +165,14 @@ void FurnaceGUI::drawWaveEdit() { } if (!waveEditOpen) return; float wavePreview[257]; - ImGui::SetNextWindowSizeConstraints(ImVec2(300.0f*dpiScale,300.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + if (mobileUI) { + patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f)); + patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f))); + ImGui::SetNextWindowPos(patWindowPos); + ImGui::SetNextWindowSize(patWindowSize); + } else { + ImGui::SetNextWindowSizeConstraints(ImVec2(300.0f*dpiScale,300.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + } if (ImGui::Begin("Wavetable Editor",&waveEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) { if (curWave<0 || curWave>=(int)e->song.wave.size()) { ImGui::Text("no wavetable selected"); @@ -138,6 +198,15 @@ void FurnaceGUI::drawWaveEdit() { if (ImGui::Button(ICON_FA_FLOPPY_O "##WESave")) { doAction(GUI_ACTION_WAVE_LIST_SAVE); } + if (ImGui::BeginPopupContextItem("WaveSaveFormats",ImGuiMouseButton_Right)) { + if (ImGui::MenuItem("save as .dmw...")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_DMW); + } + if (ImGui::MenuItem("save raw...")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_RAW); + } + ImGui::EndPopup(); + } ImGui::SameLine(); if (ImGui::RadioButton("Steps",waveEditStyle==0)) { @@ -187,6 +256,9 @@ void FurnaceGUI::drawWaveEdit() { for (int i=0; ilen; i++) { if (wave->data[i]>wave->max) wave->data[i]=wave->max; wavePreview[i]=wave->data[i]; + if (waveSigned && !waveHex) { + wavePreview[i]-=(int)((wave->max+1)/2); + } } if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; @@ -201,9 +273,9 @@ void FurnaceGUI::drawWaveEdit() { ImVec2 contentRegion=ImGui::GetContentRegionAvail(); // wavetable graph size determined here contentRegion.y-=ImGui::GetFrameHeightWithSpacing()+ImGui::GetStyle().WindowPadding.y; if (waveEditStyle) { - PlotNoLerp("##Waveform",wavePreview,wave->len+1,0,NULL,0,wave->max,contentRegion); + PlotNoLerp("##Waveform",wavePreview,wave->len+1,0,NULL,(waveSigned && !waveHex)?(-(int)((wave->max+1)/2)):0,(waveSigned && !waveHex)?((int)(wave->max/2)):wave->max,contentRegion); } else { - PlotCustom("##Waveform",wavePreview,wave->len,0,NULL,0,wave->max,contentRegion,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,true); + PlotCustom("##Waveform",wavePreview,wave->len,0,NULL,(waveSigned && !waveHex)?(-(int)((wave->max+1)/2)):0,(waveSigned && !waveHex)?((int)(wave->max/2)):wave->max,contentRegion,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,NULL,true); } if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { waveDragStart=ImGui::GetItemRectMin(); @@ -312,10 +384,347 @@ void FurnaceGUI::drawWaveEdit() { if (ImGui::BeginTabItem("FM")) { waveGenFM=true; - ImGui::Text("FM stuff here"); + if (ImGui::BeginTable("WGFMProps",4)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,ImGui::CalcTextSize("Op").x); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.5); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.25); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.25); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Op"); + ImGui::TableNextColumn(); + ImGui::Text("Level"); + ImGui::TableNextColumn(); + ImGui::Text("Mult"); + ImGui::TableNextColumn(); + ImGui::Text("FB"); + + for (int i=0; i<4; i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%d",i+1); + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::PushID(i); + if (CWSliderFloat("##WGTL",&waveGenTL[i],0.0f,1.0f)) { + doGenerateWave(); + } + ImGui::PopID(); + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::PushID(i); + if (CWSliderInt("##WGMULT",&waveGenMult[i],1,16)) { + doGenerateWave(); + } + ImGui::PopID(); + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::PushID(i); + if (CWSliderInt("##WGFB",&waveGenFB[i],0,7)) { + doGenerateWave(); + } + ImGui::PopID(); + } + + ImGui::EndTable(); + } + + CENTER_TEXT("Connection Diagram"); + ImGui::Text("Connection Diagram"); + + if (ImGui::BeginTable("WGFMCon",5)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text(">>"); + ImGui::TableNextColumn(); + ImGui::Text("2"); + ImGui::TableNextColumn(); + ImGui::Text("3"); + ImGui::TableNextColumn(); + ImGui::Text("4"); + ImGui::TableNextColumn(); + ImGui::Text("Out"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("1"); + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##Con12",&waveGenFMCon1[0])) { + doGenerateWave(); + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##Con13",&waveGenFMCon1[1])) { + doGenerateWave(); + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##Con14",&waveGenFMCon1[2])) { + doGenerateWave(); + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##Con1O",&waveGenFMCon1[3])) { + doGenerateWave(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("2"); + ImGui::TableNextColumn(); + // blank + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##Con23",&waveGenFMCon2[0])) { + doGenerateWave(); + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##Con24",&waveGenFMCon2[1])) { + doGenerateWave(); + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##Con2O",&waveGenFMCon2[2])) { + doGenerateWave(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("3"); + ImGui::TableNextColumn(); + // blank + ImGui::TableNextColumn(); + // blank + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##Con34",&waveGenFMCon3[0])) { + doGenerateWave(); + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##Con3O",&waveGenFMCon3[1])) { + doGenerateWave(); + } + + ImGui::EndTable(); + } ImGui::EndTabItem(); } - if (ImGui::BeginTabItem("Mangle")) { + if (ImGui::BeginTabItem("WaveTools")) { + if (ImGui::BeginTable("WGParamItems",2)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##WGScaleX",&waveGenScaleX,1,16)) { + if (waveGenScaleX<2) waveGenScaleX=2; + if (waveGenScaleX>256) waveGenScaleX=256; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Scale X")) { + if (waveGenScaleX>0 && wave->len!=waveGenScaleX) e->lockEngine([this,wave]() { + int origData[256]; + memcpy(origData,wave->data,wave->len*sizeof(int)); + for (int i=0; idata[i]=origData[i*wave->len/waveGenScaleX]; + } + wave->len=waveGenScaleX; + MARK_MODIFIED; + }); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##WGScaleY",&waveGenScaleY,1,16)) { + if (waveGenScaleY<2) waveGenScaleY=2; + if (waveGenScaleY>256) waveGenScaleY=256; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Scale Y")) { + if (waveGenScaleY>0 && wave->max!=waveGenScaleY) e->lockEngine([this,wave]() { + for (int i=0; ilen; i++) { + wave->data[i]=(wave->data[i]*(waveGenScaleY+1))/(wave->max+1); + } + wave->max=waveGenScaleY; + MARK_MODIFIED; + }); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##WGOffsetX",&waveGenOffsetX,1,16)) { + if (waveGenOffsetX<-wave->len+1) waveGenOffsetX=-wave->len+1; + if (waveGenOffsetX>wave->len-1) waveGenOffsetX=wave->len-1; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Offset X")) { + if (waveGenOffsetX!=0 && wave->len>0) e->lockEngine([this,wave]() { + int origData[256]; + memcpy(origData,wave->data,wave->len*sizeof(int)); + int realOff=-waveGenOffsetX; + while (realOff<0) realOff+=wave->len; + + for (int i=0; ilen; i++) { + wave->data[i]=origData[(i+realOff)%wave->len]; + } + MARK_MODIFIED; + }); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##WGOffsetY",&waveGenOffsetY,1,16)) { + if (waveGenOffsetY<-wave->max) waveGenOffsetY=-wave->max; + if (waveGenOffsetY>wave->max) waveGenOffsetY=wave->max; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Offset Y")) { + if (waveGenOffsetY!=0) e->lockEngine([this,wave]() { + for (int i=0; ilen; i++) { + wave->data[i]=CLAMP(wave->data[i]+waveGenOffsetY,0,wave->max); + } + MARK_MODIFIED; + }); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##WGSmooth",&waveGenSmooth,1,4)) { + if (waveGenSmooth>wave->len) waveGenSmooth=wave->len; + if (waveGenSmooth<1) waveGenSmooth=1; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Smooth")) { + if (waveGenSmooth>0) e->lockEngine([this,wave]() { + int origData[256]; + memcpy(origData,wave->data,wave->len*sizeof(int)); + for (int i=0; ilen; i++) { + int dataSum=0; + for (int j=i; jlen; + dataSum+=origData[pos%wave->len]; + } + dataSum/=waveGenSmooth+1; + wave->data[i]=dataSum; + } + MARK_MODIFIED; + }); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + float amp=waveGenAmplify*100.0f; + if (ImGui::InputFloat("##WGAmplify",&,1.0f,10.0f)) { + waveGenAmplify=amp/100.0f; + if (waveGenAmplify<0.0f) waveGenAmplify=0.0f; + if (waveGenAmplify>100.0f) waveGenAmplify=100.0f; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Amplify")) { + if (waveGenAmplify!=1.0f) e->lockEngine([this,wave]() { + for (int i=0; ilen; i++) { + wave->data[i]=CLAMP(round((float)(wave->data[i]-(int)( /* Clang can you stop complaining */ (int)(wave->max+1)/(int)2))*waveGenAmplify),(int)(-((wave->max+1)/2)),(int)(wave->max/2))+(int)((wave->max+1)/2); + } + MARK_MODIFIED; + }); + } + + ImGui::EndTable(); + } + + ImVec2 buttonSize=ImGui::GetContentRegionAvail(); + buttonSize.y=0.0f; + ImVec2 buttonSizeHalf=buttonSize; + buttonSizeHalf.x-=ImGui::GetStyle().ItemSpacing.x; + buttonSizeHalf.x*=0.5; + + if (ImGui::Button("Normalize",buttonSize)) { + e->lockEngine([this,wave]() { + // find lowest point + int lowest=wave->max; + for (int i=0; ilen; i++) { + if (wave->data[i]data[i]; + } + + // find highest point + int highest=0; + for (int i=0; ilen; i++) { + if (wave->data[i]>highest) highest=wave->data[i]; + } + + // abort if lowest and highest points are equal + if (lowest==highest) return; + + // abort if lowest and highest points already span the entire height + if (lowest==wave->max && highest==0) return; + + // apply offset + for (int i=0; ilen; i++) { + wave->data[i]-=lowest; + } + highest-=lowest; + + // scale + for (int i=0; ilen; i++) { + wave->data[i]=(wave->data[i]*wave->max)/highest; + } + MARK_MODIFIED; + }); + } + if (ImGui::Button("Invert",buttonSize)) { + e->lockEngine([this,wave]() { + for (int i=0; ilen; i++) { + wave->data[i]=wave->max-wave->data[i]; + } + MARK_MODIFIED; + }); + } + + if (ImGui::Button("Half",buttonSizeHalf)) { + int origData[256]; + memcpy(origData,wave->data,wave->len*sizeof(int)); + + for (int i=0; ilen; i++) { + wave->data[i]=origData[i>>1]; + } + MARK_MODIFIED; + } + ImGui::SameLine(); + if (ImGui::Button("Double",buttonSizeHalf)) { + int origData[256]; + memcpy(origData,wave->data,wave->len*sizeof(int)); + + for (int i=0; ilen; i++) { + wave->data[i]=origData[(i*2)%wave->len]; + } + MARK_MODIFIED; + } + + if (ImGui::Button("Convert Signed/Unsigned",buttonSize)) { + if (wave->max>0) e->lockEngine([this,wave]() { + for (int i=0; ilen; i++) { + if (wave->data[i]>(wave->max/2)) { + wave->data[i]-=(wave->max+1)/2; + } else { + wave->data[i]+=(wave->max+1)/2; + } + } + MARK_MODIFIED; + }); + } + if (ImGui::Button("Randomize",buttonSize)) { + if (wave->max>0) e->lockEngine([this,wave]() { + for (int i=0; ilen; i++) { + wave->data[i]=rand()%wave->max; + } + MARK_MODIFIED; + }); + } ImGui::EndTabItem(); } ImGui::EndTabBar(); @@ -332,12 +741,33 @@ void FurnaceGUI::drawWaveEdit() { waveHex=true; } ImGui::SameLine(); + if (!waveHex) if (ImGui::Button(waveSigned?"±##WaveSign":"+##WaveSign",ImVec2(ImGui::GetFrameHeight(),ImGui::GetFrameHeight()))) { + waveSigned=!waveSigned; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Signed/Unsigned"); + } + ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); // wavetable text input size found here if (ImGui::InputText("##MMLWave",&mmlStringW)) { - decodeMMLStrW(mmlStringW,wave->data,wave->len,wave->max,waveHex); + int actualData[256]; + decodeMMLStrW(mmlStringW,actualData,wave->len,(waveSigned && !waveHex)?(-((wave->max+1)/2)):0,(waveSigned && !waveHex)?(wave->max/2):wave->max,waveHex); + if (waveSigned && !waveHex) { + for (int i=0; ilen; i++) { + actualData[i]+=(wave->max+1)/2; + } + } + memcpy(wave->data,actualData,wave->len*sizeof(int)); } if (!ImGui::IsItemActive()) { - encodeMMLStr(mmlStringW,wave->data,wave->len,-1,-1,waveHex); + int actualData[256]; + memcpy(actualData,wave->data,256*sizeof(int)); + if (waveSigned && !waveHex) { + for (int i=0; ilen; i++) { + actualData[i]-=(wave->max+1)/2; + } + } + encodeMMLStr(mmlStringW,actualData,wave->len,-1,-1,waveHex); } } } diff --git a/src/log.cpp b/src/log.cpp index 89602b42..ef2750a2 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -19,7 +19,11 @@ #include "ta-log.h" +#ifdef IS_MOBILE +int logLevel=LOGLEVEL_TRACE; +#else int logLevel=LOGLEVEL_INFO; +#endif std::atomic logPosition; diff --git a/test/assert_delta.c b/test/assert_delta.c new file mode 100644 index 00000000..6dad6992 --- /dev/null +++ b/test/assert_delta.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include + +#define BUF_SIZE 8192 + +// usage: assert_delta file +// return values: +// - 0: pass (file is silence) +// - 1: fail (noise found) +// - 2: command line error +// - 3: file open error +int main(int argc, char** argv) { + if (argc<2) return 2; + + SF_INFO si; + memset(&si,0,sizeof(SF_INFO)); + SNDFILE* sf=sf_open(argv[1],SFM_READ,&si); + if (sf==NULL) { + fprintf(stderr,"open: %s\n",sf_strerror(NULL)); + return 3; + } + + if (si.channels<1) { + fprintf(stderr,"invalid channel count\n"); + return 3; + } + + float* buf=malloc(BUF_SIZE*si.channels*sizeof(float)); + + sf_count_t totalRead=0; + size_t seekPos=0; + while ((totalRead=sf_readf_float(sf,buf,BUF_SIZE))!=0) { + for (int i=0; i