diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 20e26737..2646eb8d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -82,7 +82,7 @@ jobs: package_ext=".dmg" else package_name="${package_name}-Linux-${{ matrix.config.arch }}" - package_ext=".AppImage" + package_ext=".tar.gz" fi echo "Package identifier: ${package_name}" @@ -128,10 +128,7 @@ jobs: librtmidi-dev \ libsndfile1-dev \ zlib1g-dev \ - libjack-jackd2-dev \ - appstream - wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" || wget "https://tildearrow.org/storage/furnace/ci/appimagetool-x86_64.AppImage" - chmod +x appimagetool-x86_64.AppImage + libjack-jackd2-dev - name: Install Dependencies [Linux armhf] if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'armhf' }} @@ -151,9 +148,6 @@ jobs: libsndfile1-dev:armhf \ zlib1g-dev:armhf \ libjack-jackd2-dev:armhf - wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" || wget "https://tildearrow.org/storage/furnace/ci/appimagetool-x86_64.AppImage" - wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-armhf" || wget "https://tildearrow.org/storage/furnace/ci/runtime-armhf" - chmod +x appimagetool-x86_64.AppImage ls /usr/arm-linux-gnueabihf/lib - name: Configure (System Libraries) @@ -243,6 +237,7 @@ jobs: -DCMAKE_INSTALL_PREFIX=/usr \ -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ -DWARNINGS_ARE_ERRORS=${USE_WAE} \ + -DWITH_DEMOS=OFF -DWITH_INSTRUMENTS=OFF -DWITH_WAVETABLES=OFF \ "${CMAKE_EXTRA_ARGS[@]}" - name: Build @@ -300,24 +295,37 @@ jobs: # strip -s build/furnace #fi - mkdir -p target/furnace.AppDir - make -C ${PWD}/build DESTDIR=${PWD}/target/furnace.AppDir install - pushd target + mkdir -p target/furnace + make -C ${PWD}/build DESTDIR=${PWD}/target/furnace install + pushd target/furnace + + cp -v ../../res/logo.png .DirIcon + + cd usr + + mv bin/furnace .. + rmdir bin + + rm -r share/applications + rm -r share/doc + rm -r share/icons + rm -r share/licenses + rm -r share/metainfo + + rmdir share + + cd .. + + cp ../../LICENSE . + cp ../../README.md . + cp -r ../../papers papers + rmdir usr - pushd furnace.AppDir - cp -v usr/share/{icons/hicolor/1024x1024/apps/furnace.png,applications/furnace.desktop} ./ - ln -s furnace.png .DirIcon - mv -v usr/share/metainfo/{furnace.appdata,org.tildearrow.furnace.metainfo}.xml - cp -v ../../res/AppRun ./ popd - if [ '${{ matrix.config.arch }}' == 'armhf' ]; then - ../appimagetool-x86_64.AppImage --runtime-file=../runtime-armhf furnace.AppDir - else - ../appimagetool-x86_64.AppImage furnace.AppDir - fi - mv Furnace-*.AppImage ../${{ steps.package-identify.outputs.filename }} - popd + cd target + + tar -zcv -f ../${{ steps.package-identify.outputs.filename }} furnace - name: Upload artifact if: ${{ github.repository == 'tildearrow/furnace' && github.ref_name == 'master' }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 52f003fc..8cb5fe6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,12 +54,28 @@ else() set(WITH_JACK_DEFAULT OFF) endif() +set(WITH_RENDER_SDL_DEFAULT ON) +if (APPLE) + set(WITH_RENDER_OPENGL_DEFAULT OFF) +else() + set(WITH_RENDER_OPENGL_DEFAULT ON) +endif() + +if (ANDROID) + set(USE_GLES_DEFAULT ON) +else() + set(USE_GLES_DEFAULT OFF) +endif() + option(BUILD_GUI "Build the tracker (disable to build only a headless player)" ${BUILD_GUI_DEFAULT}) option(USE_RTMIDI "Build with MIDI support using RtMidi." ${USE_RTMIDI_DEFAULT}) option(USE_SDL2 "Build with SDL2. Required to build with GUI." ${USE_SDL2_DEFAULT}) option(USE_SNDFILE "Build with libsndfile. Required in order to work with audio files." ${USE_SNDFILE_DEFAULT}) option(USE_BACKWARD "Use backward-cpp to print a backtrace on crash/abort." ${USE_BACKWARD_DEFAULT}) option(WITH_JACK "Whether to build with JACK support. Auto-detects if JACK is available" ${WITH_JACK_DEFAULT}) +option(WITH_RENDER_SDL "Whether to build with the SDL_Renderer render backend." ${WITH_RENDER_SDL_DEFAULT}) +option(WITH_RENDER_OPENGL "Whether to build with the OpenGL render backend." ${WITH_RENDER_OPENGL_DEFAULT}) +option(USE_GLES "Use OpenGL ES for the OpenGL render backend." ${USE_GLES_DEFAULT}) option(SYSTEM_FFTW "Use a system-installed version of FFTW instead of the vendored one" OFF) option(SYSTEM_FMT "Use a system-installed version of fmt instead of the vendored one" OFF) option(SYSTEM_LIBSNDFILE "Use a system-installed version of libsndfile instead of the vendored one" OFF) @@ -92,7 +108,7 @@ set(DEPENDENCIES_LIBRARY_DIRS "") set(DEPENDENCIES_LINK_OPTIONS "") set(DEPENDENCIES_LEGACY_LDFLAGS "") -if (BUILD_GUI) +if (BUILD_GUI AND WITH_RENDER_SDL) set(SYSTEM_SDL_MIN_VER 2.0.18) else() set(SYSTEM_SDL_MIN_VER 2.0.0) @@ -278,6 +294,12 @@ else() endif() endif() +if (BUILD_GUI) + if (NOT WITH_RENDER_SDL AND NOT WITH_RENDER_OPENGL) + message(FATAL_ERROR "No render backends selected!") + endif() +endif() + set(AUDIO_SOURCES src/audio/abstract.cpp src/audio/midi.cpp @@ -583,13 +605,15 @@ extern/imgui_patched/imgui.cpp extern/imgui_patched/imgui_draw.cpp extern/imgui_patched/imgui_tables.cpp extern/imgui_patched/imgui_widgets.cpp -extern/imgui_patched/backends/imgui_impl_sdlrenderer.cpp extern/imgui_patched/backends/imgui_impl_sdl.cpp extern/imgui_patched/misc/cpp/imgui_stdlib.cpp extern/igfd/ImGuiFileDialog.cpp src/gui/plot_nolerp.cpp +src/gui/render.cpp +src/gui/render/abstract.cpp + src/gui/font_exo.cpp src/gui/font_liberationSans.cpp src/gui/font_mononoki.cpp @@ -678,6 +702,31 @@ if (APPLE) list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_cocoa.mm) endif() +if (WITH_RENDER_SDL) + list(APPEND GUI_SOURCES src/gui/render/renderSDL.cpp) + list(APPEND GUI_SOURCES extern/imgui_patched/backends/imgui_impl_sdlrenderer.cpp) + list(APPEND DEPENDENCIES_DEFINES HAVE_RENDER_SDL) + message(STATUS "UI render backend: SDL_Renderer") +endif() + +if (WITH_RENDER_OPENGL) + list(APPEND GUI_SOURCES src/gui/render/renderGL.cpp) + list(APPEND GUI_SOURCES extern/imgui_patched/backends/imgui_impl_opengl3.cpp) + list(APPEND DEPENDENCIES_DEFINES HAVE_RENDER_GL) + if (USE_GLES) + list(APPEND DEPENDENCIES_DEFINES USE_GLES) + list(APPEND DEPENDENCIES_DEFINES IMGUI_IMPL_OPENGL_ES2) + endif() + if (WIN32) + list(APPEND DEPENDENCIES_LIBRARIES opengl32) + elseif(USE_GLES) + list(APPEND DEPENDENCIES_LIBRARIES GLESv2) + else() + list(APPEND DEPENDENCIES_LIBRARIES GL) + endif() + message(STATUS "UI render backend: OpenGL") +endif() + if (NOT WIN32 AND NOT APPLE) CHECK_INCLUDE_FILE(sys/io.h SYS_IO_FOUND) CHECK_INCLUDE_FILE(linux/input.h LINUX_INPUT_FOUND) @@ -826,7 +875,8 @@ if (NOT ANDROID OR TERMUX) install(TARGETS furnace RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES res/furnace.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) install(FILES res/furnace.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo) - install(DIRECTORY papers DESTINATION ${CMAKE_INSTALL_DOCDIR}) + install(DIRECTORY doc DESTINATION ${CMAKE_INSTALL_DOCDIR}) + install(DIRECTORY papers DESTINATION ${CMAKE_INSTALL_DOCDIR}/other) install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/furnace) if (WITH_DEMOS) install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace) diff --git a/README.md b/README.md index 316deee7..4ecbd14c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Furnace (chiptune tracker) -![screenshot](papers/screenshot2.png) +![screenshot](papers/screenshot3.png) the biggest multi-system chiptune tracker ever made! -[downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [FAQ](#frequently-asked-questions) +[downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [Unix/Linux packages](#packages) | [FAQ](#frequently-asked-questions) --- ## downloads @@ -83,7 +83,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a - over 200 ready to use presets from computers, game consoles and arcade boards... - ...or create your own - up to 32 of them or a total of 128 channels! - DefleMask compatibility - - loads .dmf modules from all versions (beta 1 to 1.1.7) + - loads .dmf modules from all versions (beta 1 to 1.1.9) - saves .dmf modules - both modern and legacy - Furnace doubles as a module downgrader - loads/saves .dmp instruments and .dmw wavetables as well @@ -119,18 +119,19 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a --- # quick references - - **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z). - - **help**: check out the [documentation](papers/doc/README.md). it's mostly incomplete, but has details on effects. +- **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or the [official Discord server](https://discord.gg/EfrwT2wq7z). +- **help**: check out the [documentation](doc/README.md). it's incomplete though. -## unofficial packages +## packages [![Packaging status](https://repology.org/badge/tiny-repos/furnace.svg)](https://repology.org/project/furnace/versions) some people have provided packages for Unix/Unix-like distributions. here's a list. - - **Arch Linux**: [furnace](https://archlinux.org/packages/extra/x86_64/furnace/) is now in the official repositories! - - **FreeBSD**: [a package in ports](https://www.freshports.org/audio/furnace/) is available courtesy of ehaupt (warning: 0.5.8!). - - **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608. - - **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari. + +- **Arch Linux**: [furnace](https://archlinux.org/packages/extra/x86_64/furnace/) is in the official repositories. +- **FreeBSD**: [a package in ports](https://www.freshports.org/audio/furnace/) is available courtesy of ehaupt (warning: 0.5.8!). +- **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608. +- **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari. --- # developer info @@ -144,7 +145,9 @@ if you can't download these artifacts (because GitHub requires you to be logged ## dependencies - CMake +- Git (for cloning the repository) - JACK (optional, macOS/Linux only) +- a C/C++ compiler (e.g. Visual Studio or MinGW on Windows, Xcode (the command-line tools are enough) on macOS or GCC on Linux) if building under Windows or macOS, no additional dependencies are required. otherwise, you may also need the following: @@ -181,10 +184,32 @@ from the developer tools command prompt: mkdir build cd build cmake .. +``` + +then open the solution file in Visual Studio and build. + +alternatively, do: + +``` msbuild ALL_BUILD.vcxproj ``` -### macOS and Linux +### Windows using MinGW + +setting up MinGW is a bit more complicated. two benefits are a faster, hotter Furnace, and Windows XP support. + +however, one huge drawback is lack of backtrace support, so you'll have to use gdb when diagnosing a crash. + +``` +mkdir build +cd build +cmake -G "MinGW Makefiles" .. +mingw32-make +``` + +you may use "MSYS Makefiles" instead, depending on how you installed MinGW. + +### macOS, Linux and other Unix/Unix-like ``` mkdir build @@ -192,7 +217,16 @@ cd build cmake .. make ``` -Alternatively, build scripts are provided in the `scripts/` folder in the root of the repository. + +on macOS you may do the following instead: + +``` +mkdir build +cd build +cmake -G Xcode .. +``` + +...and then load the project on Xcode or type `xcodebuild`. ### CMake options @@ -223,6 +257,8 @@ Available options: ## console usage +(note: if on Windows, type `furnace.exe` instead, or `Debug\furnace.exe` on MSVC) + ``` ./furnace ``` @@ -241,23 +277,17 @@ this will play a compatible file. this will play a compatible file and enable the commands view. -**note that these commands only actually work in Linux environments. on other command lines, such as Windows' Command Prompt, or MacOS Terminal, it may not work correctly.** +**note that console mode may not work correctly on Windows. you may have to quit using the Task Manager.** --- # frequently asked questions -> woah! 50 sound chips?! I can't believe it! - -yup, it's real. - -> where's the manual? - -see [papers/](papers/doc/README.md). it's kind of incomplete, but at least the sound chips section is there. - > it doesn't open under macOS! this is due to Apple's application signing policy. a workaround is to right click on the Furnace app icon and select Open. +> it says "Furnace" is damaged and can't be opened! + **as of Monterey, this workaround no longer works (especially on ARM).** yeah, Apple has decided to be strict on the matter. if you happen to be on that version, use this workaround instead (on a Terminal): @@ -269,24 +299,25 @@ xattr -d com.apple.quarantine /path/to/Furnace.app you may need to log out and/or reboot after doing this. +> where's the manual? + +see [doc/](doc/README.md). it's kind of incomplete though. + +> is there a tutorial? + +sadly, the in-program tutorial isn't ready yet. however, [a video tutorial is available on YouTube](https://youtube.com/playlist?list=PLCELB6AsTZUnwv0PC5AAGHjvg47F44YQ1), made by Spinning Square Waves. + +> I've lost my song! + +Furnace keeps backups of the songs you've worked on before. go to **file > restore backup**. + > .spc export? **not yet!** coming in 0.7 though, eventually... -> how do I use C64 absolute filter/duty? +> ROM export? -on Instrument Editor in the C64 tab there are two options to toggle these. -also provided are two effects: - -- `3xxx`: set fine duty. -- `4xxx`: set fine cutoff. `xxx` range is 000-7ff. -additionally, you can change the cutoff and/or duty as a macro inside an instrument by clicking the `absolute cutoff macro` and/or `absolute duty macro` checkbox at the bottom of the instrument. (for the filter, you also need to click the checkbox that says `volume macro is cutoff macro`.) - -> how do I use PCM on a PCM-capable chip? - -two possibilities: -- the recommended way is by creating the "Sample" type instrument and assigning a sample to it. -- otherwise you may employ the DefleMask-compatible method, using `17xx` effect. +**not yet!** coming in 0.7 though, eventually... > my .dmf song sounds odd at a certain point @@ -296,10 +327,6 @@ Furnace's .dmf compatibility isn't perfect and it's mostly because DefleMask doe you should only save as .dmf if you're really sure, because the DefleMask format has several limitations. save in Furnace song format instead (.fur). -> how do I solo channels? - -right click on the channel name. - --- # footnotes @@ -312,4 +339,5 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -despite the fact this program works with the .dmf, .dmp and .dmw file formats (besides its native .fur format), it is NOT affiliated with Delek or DefleMask in any way, nor it is a replacement for the original program. +Furnace is NOT affiliated with Delek or DefleMask in any form, regardless of its ability to load and save the .dmf, .dmp and .dmw file formats. +additionally, Furnace does not intend to replace DefleMask, nor any other program. diff --git a/TODO.md b/TODO.md index 0b7f1cd9..4af99b6f 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,6 @@ -# to-do for 0.6pre5 +# to-do for 0.6pre6 -- tutorial +- tutorial? - ease-of-use improvements... ideas: - preset compat flags - setting to toggle the Choose a System screen on new project @@ -8,9 +8,9 @@ - a more preferable highlight/drag system - some speed/intuitive workflow improvements that go a long way - Had a hard time finding the docs on github and in Furnace's folder. - - make .pdf manual out of papers/doc/ - - you're going too fast; please slow down - - break compatibility if it relieves complexity + - make .pdf manual out of doc/ + - you're going too slow; please run + - break compatibility if it relieves complexity - ins/wave/sample organization (folders and all) - multi-key binds - bug fixes diff --git a/demos/arcade/Tubelectric_Fictional_Arcade.fur b/demos/arcade/Tubelectric_Fictional_Arcade.fur index 147c2ed3..08929edd 100644 Binary files a/demos/arcade/Tubelectric_Fictional_Arcade.fur and b/demos/arcade/Tubelectric_Fictional_Arcade.fur differ diff --git a/demos/arcade/UT99_Run_TaitoArcade.fur b/demos/arcade/UT99_Run_TaitoArcade.fur index 2bdf0242..bc46eb3a 100644 Binary files a/demos/arcade/UT99_Run_TaitoArcade.fur and b/demos/arcade/UT99_Run_TaitoArcade.fur differ diff --git a/demos/genesis/Fancy_Promenard.fur b/demos/genesis/Fancy_Promenard.fur new file mode 100644 index 00000000..f0ec68a9 Binary files /dev/null and b/demos/genesis/Fancy_Promenard.fur differ diff --git a/demos/genesis/Stereotactics_Rewritten.fur b/demos/genesis/Stereotactics_Rewritten.fur index 6e078724..f2190c8c 100644 Binary files a/demos/genesis/Stereotactics_Rewritten.fur and b/demos/genesis/Stereotactics_Rewritten.fur differ diff --git a/demos/misc/QSound_smile.fur b/demos/misc/QSound_smile.fur new file mode 100644 index 00000000..b3e361b4 Binary files /dev/null and b/demos/misc/QSound_smile.fur differ diff --git a/demos/misc/mushroomhill_SM8521.fur b/demos/misc/mushroomhill_SM8521.fur new file mode 100644 index 00000000..928ac767 Binary files /dev/null and b/demos/misc/mushroomhill_SM8521.fur differ diff --git a/demos/multichip/Jet_Pack_Adventure_GBAesque.fur b/demos/multichip/Jet_Pack_Adventure_GBAesque.fur index 928c4bd6..8f430813 100644 Binary files a/demos/multichip/Jet_Pack_Adventure_GBAesque.fur and b/demos/multichip/Jet_Pack_Adventure_GBAesque.fur differ diff --git a/demos/opl/Sliding_on_a_Rainbow.fur b/demos/opl/Sliding_on_a_Rainbow.fur new file mode 100644 index 00000000..934fda70 Binary files /dev/null and b/demos/opl/Sliding_on_a_Rainbow.fur differ diff --git a/demos/snes/MM8_Frost_Man.fur b/demos/snes/MM8_Frost_Man.fur index bad1ceee..f49221ee 100644 Binary files a/demos/snes/MM8_Frost_Man.fur and b/demos/snes/MM8_Frost_Man.fur differ diff --git a/doc/1-intro/README.md b/doc/1-intro/README.md new file mode 100644 index 00000000..c21ad355 --- /dev/null +++ b/doc/1-intro/README.md @@ -0,0 +1,21 @@ +# introduction + +Furnace is a tool which allows you to create music using sound chips ("chiptune"), most from the 8/16-bit era. + +it has a large selection of features and sound chips. from the NES, SNES and Genesis to ES5506, VIC-20 or even Arcade, Furnace has most likely covered your target with many presets to choose from. + +every chip is emulated using many emulation cores, therefore the sound that Furnace produces is authentic to that of real hardware. + +## hexadecimal + +Furnace uses hexadecimal (abbreviated as "hex") numbers frequently. see [this guide](hex.md) for a crash course. + +## interface + +Furnace uses a music tracker interface. think of a table with music notes written on it. then that table scrolls up and plays the notes. + +due to its nature of being feature-packed, it may be technical and somewhat difficult to get around. therefore we added a basic mode, which hides several advanced features. + +it also has a flexible windowing system which you may move around and organize. + +see [2-interface](../2-interface/README.md) and [3-pattern](../3-pattern/README.md) for more information. diff --git a/doc/1-intro/hex.md b/doc/1-intro/hex.md new file mode 100644 index 00000000..6139ca87 --- /dev/null +++ b/doc/1-intro/hex.md @@ -0,0 +1,97 @@ +# hexadecimal + +the hexadecimal numeral system differs from the decimal system by having 16 digits rather than 10: + +``` +hex| decimal +---|--------- + 0 | 0 + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 + 6 | 6 + 7 | 7 + 8 | 8 + 9 | 9 + A | 10 + B | 11 + C | 12 + D | 13 + E | 14 + F | 15 +``` + +when there is more than one digit, these are multiplied by 16, 256, 4096 and so on rather than 10, 100, 1000: + +``` +hex | decimal +----|--------- + 00 | 0 + 04 | 4 + 08 | 8 + 0F | 15 + 10 | 16 + 11 | 17 + 12 | 18 + 13 | 19 + 20 | 32 + 30 | 48 + 40 | 64 +``` + +# hex to decimal + +for example, take hexadecimal number `AA`: + +``` + 2nd digit -\ /- 1st digit + A A + 16^1*10 = 16*10 = 160 + 10 = 170 +``` + +now for hexadecimal number `4C5F`: + +``` + + 3rd digit -\ /- 2nd digit + 4th digit -\ | | /- 1st digit + 4 C 5 F + | | | | + | | | 15 = 15 = 15 + + | | \16^1*5 = 16 * 5 = 80 + | \--- 16^2*12 = 256 * 12 = 3072 + \--------- 16^3*4 = 4096 * 4 = 16384 + ------- + = 19551 +``` + +# decimal to hex + +if it's less than 16, just memorize the table at the top of this document. + +otherwise find the power of 16 that is closest to the number you want to convert, but no larger than the number. +then divide, and take the remainder. +divide the remainder with the previous power of 16, until the divider is 1. + +for example, for the decimal number `220`: + +``` +220 ÷ 16 = 13 (r = 12) D + 12 ÷ 1 = 12 (stop here) C + += DC +``` + +now for decimal number `69420`: + +``` +69420 ÷ 65536 = 1 (r = 3884) 1 + 3884 ÷ 4096 = 0 (r = 3884) 0 + 3884 ÷ 256 = 15 (r = 44) F + 44 ÷ 16 = 2 (r = 12) 2 + 12 ÷ 1 = 12 (stop here) C + += 10F2C +``` diff --git a/doc/2-interface/README.md b/doc/2-interface/README.md new file mode 100644 index 00000000..2ff1ebfc --- /dev/null +++ b/doc/2-interface/README.md @@ -0,0 +1,41 @@ +# interface + +the Furnace user interface is where the job gets done. + +the default layout of Furnace is depicted below. + +![interface](interface1.png) + +primary topics: + +- [menu bar](menu-bar.md) +- [play/edit controls](play-edit-controls.md) +- [instrument/wavetable/sample list](asset-list.md) +- [song information](song-info.md) +- [pattern view](../3-pattern/README.md) +- [instrument editor](../4-instrument/README.md) +- [wavetable editor](../5-wave/README.md) +- [sample editor](../6-sample/README.md) + +advanced topics: + +- [mixer](../8-advanced/mixer.md) +- [grooves](../8-advanced/grooves.md) +- [channels](../8-advanced/channels.md) +- [pattern manager](../8-advanced/pat-manager.md) +- [chip manager](../8-advanced/chip-manager.md) +- [compatibility flags](../8-advanced/compat-flags.md) +- [song comments](../8-advanced/comments.md) +- [piano/input pad](../8-advanced/piano.md) +- [oscilloscope](../8-advanced/osc.md) +- [oscilloscope (per channel)](../8-advanced/chanosc.md) +- [clock](../8-advanced/clock.md) +- [register view](../8-advanced/regview.md) +- [log viewer](../8-advanced/log-viewer.md) +- [statistics](../8-advanced/stats.md) + +other topics: + +- [UI components](components.md) +- [global keyboard shortcuts](keyboard.md) +- [basic mode](basic-mode.md) diff --git a/doc/2-interface/asset-list.md b/doc/2-interface/asset-list.md new file mode 100644 index 00000000..7e8dc9a6 --- /dev/null +++ b/doc/2-interface/asset-list.md @@ -0,0 +1,11 @@ +# instrument list + +image + +# wavetable list + +image + +# sample list + +image diff --git a/papers/doc/2-interface/basic-mode.md b/doc/2-interface/basic-mode.md similarity index 100% rename from papers/doc/2-interface/basic-mode.md rename to doc/2-interface/basic-mode.md diff --git a/papers/doc/2-interface/components.md b/doc/2-interface/components.md similarity index 72% rename from papers/doc/2-interface/components.md rename to doc/2-interface/components.md index 0243662b..c2fce999 100644 --- a/papers/doc/2-interface/components.md +++ b/doc/2-interface/components.md @@ -4,14 +4,14 @@ the user interface consists of several components. this paper describes some of ## windows -TODO: image +![window](window.png) windows may be moved, collapsed, closed or even docked around the workspace. to move a window, press and hold the mouse button while on title bar or any empty space on it. then drag your mouse, and release it to stop moving. -to resize a window, drag any of the bottom corners (marked by triangular tabs). +to resize a window, drag the bottom right corner (marked by a triangular tab) or the borders. to collapse a window, click on the triangle in the title bar. clicking again expands it. @@ -24,33 +24,40 @@ windows may be docked, which comes in handy. to dock a window, drag it from its title bar to another location in the workspace or to the location of another window. -while dragging, an overlay with five options will appear, allowing you to select where and how to dock that window. +while dragging, an overlay with some options will appear, allowing you to select where and how to dock that window. the options are: -``` - UP - LEFT CENTER RIGHT - DOWN -``` +![docking options](docking.png) drag your mouse cursor to any of the options to dock the window. -if you drag the window to `CENTER`, the window will be maximized to cover the workspace (if you do this on the workspace), or it will appear as another tab (if you do this on a window). +if you drag to the sides (marked with blue text), the window will cover that side of the workspace. + +if you drag it to a window or empty space (marked with yellow text), five docking positions will appear. + +if you drag the window to the center of another window, it will appear as another tab. + +if you drag the window to the center of empty space, the window will cover aforementioned empty space. otherwise the window will be split in two, with the first half covered by the window you docked and the second half covered by the other window. +![tab1](tab1.png) + when a window is docked, its title bar turns into a tab bar, and the function provided by the "collapse" triangle at the top left changes. -if this triangle is clicked, a menu will appear with a single option: "Hide tab bar". +![tab2](tab2.png) + +if this triangle is clicked, a menu will appear with a list of tabs, or a single option if there's only one tab: "Hide tab bar". selecting this option will hide the tab bar of that window. + +![tab3](tab3.png) + to bring it back, click on the top left corner. to undock a window, drag its tab away from where it is docked. then it will be floating again. ## text fields -TODO: image - text fields are able to hold... text. click on a text field to start editing, and click away to stop editing. @@ -60,24 +67,18 @@ the following keyboard shortcuts work while on a text field: - `Ctrl-X`: cut - `Ctrl-C`: copy - `Ctrl-V`: paste -- `Ctrl-Z`: undo -- `Ctrl-Y`: redo - `Ctrl-A`: select all (replace Ctrl with Command on macOS) ## number input fields -TODO: image - these work similar to text fields, but you may only input numbers. they also usually have two buttons which allow you to increase/decrease the amount when clicked (and rapidly do so when click-holding). ## sliders -TODO: image - sliders are used for controlling values in a quick manner by being dragged. alternatively, right-clicking or Ctrl-clicking or a slider (Command-click on macOS) will turn it into a number input field for a short period of time, allowing you to input fine values. diff --git a/doc/2-interface/docking.png b/doc/2-interface/docking.png new file mode 100644 index 00000000..7a4eaa77 Binary files /dev/null and b/doc/2-interface/docking.png differ diff --git a/doc/2-interface/interface1.png b/doc/2-interface/interface1.png new file mode 100644 index 00000000..ae995013 Binary files /dev/null and b/doc/2-interface/interface1.png differ diff --git a/papers/doc/2-interface/keyboard.md b/doc/2-interface/keyboard.md similarity index 100% rename from papers/doc/2-interface/keyboard.md rename to doc/2-interface/keyboard.md diff --git a/doc/2-interface/menu-bar.md b/doc/2-interface/menu-bar.md new file mode 100644 index 00000000..8fac9e65 --- /dev/null +++ b/doc/2-interface/menu-bar.md @@ -0,0 +1,226 @@ +# menu bar + +the menu bar allows you to select five menus: file, edit, settings, window and help. + +# file + +- **new...**: create a new song. +- **open...**: opens the file picker, allowing you to select a song to open. +- **open recent**: contains a list of the songs you've opened before. + - **clear history**: this option erases the file history. +- **save**: saves the current song. + - opens the file picker if this is a new song, or a backup. +- **save as...**: opens the file picker, allowing you to save the song under a different name. +- **save as .dmf (1.1.3+)...**: opens the file picker, allowing you to save your song as a .dmf which is compatible with DefleMask 1.1.3 onwards. + - this will only work with the systems mentioned in the next option, plus: + - Sega Master System (with FM expansion) + - NES + Konami VRC7 + - Famicom Disk System + - only use this option if you really need it. there are features which DefleMask does not support, like some effects and FM macros, so these will be lost. +- **save as .dmf (1.0/legacy)...**: opens the file picker, allowing you to save your song as a .dmf which is compatible with DefleMask Legacy (0.12) or 1.0. + - this will only work on the following systems: + - Sega Genesis/Mega Drive (YM2612 + SN76489) + - Sega Genesis/Mega Drive (YM2612 + SN76489, extended channel 3) + - Sega Master System + - Game Boy + - PC Engine + - NES + - Commodore 64 + - Arcade (YM2151 + SegaPCM 5-channel compatibility) + - Neo Geo CD (DefleMask 1.0+) + - only use this option if you really need it. there are features which DefleMask does not support, like some effects and FM macros, so these will be lost. +- **export audio...**: export your song to a .wav file. see next section for more details. +- **export VGM...**: export your song to a .vgm file. see next section for more details. +- **export ZSM...**: export your song to a .zsm file. see next section for more details. + - only available when there's a YM2151 and/or VERA. +- **export command stream...**: export song data to a command stream file. see next section for more details. + - this option is for developers. +- **add chip...**: add a chip to the current song. +- **configure chip...**: set a chip's parameters. + - for a list of parameters, see [7-systems](../7-systems/README.md). +- **change chip...**: change a chip to another. + - **Preserve channel positions**: enable this option to make sure Furnace does not auto-arrange/delete channels to compensate for differing channel counts. this can be useful for doing ports, e.g. from Genesis to PC-98. +- **remove chip...**: remove a chip. + - **Preserve channel positions**: same thing as above. +- **restore backup**: restore a previously saved backup. + - Furnace keeps up to 5 backups of a song. + - the backup directory is located in: + - Windows: `%USERPROFILE%\AppData\Roaming\furnace\backups` + - macOS: `~/Library/Application Support/Furnace/backups` + - Linux/other: `~/.config/furnace/backups` + - this directory grows in size as you use Furnace. remember to delete old backups periodically to save space. +- **exit**: I think you know what this does. + +## export audio + +this option allows you to export your song in .wav format. I know I know, no .mp3 or .ogg export yet, but you can use a converter. + +there are two parameters: + +- **Loops**: sets the number of times the song will loop. + - does not have effect if the song ends with `FFxx` effect. +- **Fade out (seconds)**: sets the fade out time when the song is over. + - does not have effect if the song ends with `FFxx` effect. + +and three export choices: + +- **one file**: exports your song to one .wav file. +- **multiple files (one per chip)**: exports the output of each chip to .wav files. +- **multiple files (one per channel)**: exports the output of each channel to .wav files. + - useful for usage with a channel visualizer such as corrscope. + +## export VGM + +this option allows exporting to a VGM (Video Game Music) file. these can be played back with VGMPlay (for example). + +the following settings exist: + +- **format version**: sets the VGM format version to use. + - versions under 1.70 do not support per-chip volumes, and therefore will ignore the Mixer completely. + - other versions may not support all chips. + - use this option if you need to export for a quirky player or parser. + - for example, RYMCast is picky with format versions. if you're going to use this player, select 1.60. +- **loop**: writes loop. if disabled, the resulting file won't loop. +- **loop trail**: this option allows you to set how much of the song is written after it loops. + - the reason this exists is to work around a VGM format limitation in where post-loop state isn't recorded at all. + - this may change the song length as it appears on a player. + - **auto-detect**: detect how much to write automatically. + - **add one loop**: add one more loop. + - **custom**: allows you to specify how many ticks to add. + - `0` is effectively none, disabling loop trail completely. + - this option will not appear if the loop modality isn't set to None as there wouldn't be a need to. +- **chips to export**: select which chips are going to be exported. + - due to VGM format limitations, you can only select up to two of each chip type. + - some chips will not be available, either because VGM doesn't support these yet, or because you selected an old format version. +- **add pattern change hints**: this option adds a "hint" when a pattern change occurs. only useful if you're a developer. + - the format of the "hint" data block that gets written is: `67 66 FE ll ll ll ll 01 oo rr pp pp pp ...` + - ll: length, a 32-bit little-endian number + - oo: order + - rr: initial row (a 0Dxx effect is able to select a different row) + - pp: pattern index (one per channel) +- **direct stream mode**: this option allows DualPCM to work. don't use this for other chips. + - may or may not play well with hardware VGM players. + +click on **click to export** to begin exporting. + +## export ZSM + +ZSM (ZSound Music) is a format designed for the Commander X16 to allow hardware playback. +it may contain data for either YM2151 or VERA chips. +Calliope is one of the programs that supports playback of ZSM files. + +the following settings are available: + +- **Tick Rate (Hz)**: select the tick rate the song will run at. + - I suggest you use the same rate as the song's. + - apparently ZSM doesn't support changing the rate mid-song. +- **loop**: enables loop. if disabled, the song won't loop. + +click on **Begin Export** to... you know. + +## export command stream + +this option exports a text or binary file which contains a dump of the internal command stream produced when playing the song. + +it's not really useful, unless you're a developer and want to use a command stream dump for some reason (e.g. writing a hardware sound driver). + +- **export (binary)**: exports in Furnace's own command stream format (FCS). see `export-tech.md` in `papers/` for details. +- **export (text)**: exports the command stream as a text file. only useful for analysis, really. + +# edit + +- **undo**: reverts the last action. +- **redo**: repeats what you undid previously. +- **cut**: moves the current selection in the pattern view to clipboard. +- **copy**: copies the current selection in the pattern view to clipboard. +- **paste**: inserts the clipboard's contents in the cursor position. +- **paste special...**: variants of the paste feature. + - **paste mix**: inserts the clipboard's contents in the cursor position, but does not erase the occupied region. + - **paste mix (background)**: does the same thing as paste mix, but doesn't alter content which is already there. + - **paste with ins (foreground)**: same thing as paste mix, but changes the instrument. + - **paste with ins (background)**: same thing as paste mix (background), but changes the instrument. + - **paste flood**: inserts the clipboard's contents in the cursor position, and repeats until it hits the end of a pattern. + - **paste overflow**: paste, but it will keep pasting even if it runs over another pattern. +- **delete**: clears the contents in the selection. +- **select all**: changes the selection so it covers a larger area. + - if the selection is wide, it will select the rows in a column. + - if the selection is tall, it will select the entire column. + - if a column is already selected, it will select the entire channel. + - if a channel is already selected, it will select the entire pattern. +- **operation mask**: this is an advanced feature. see [this page](../3-pattern/opmask.md) for more information. +- **input latch**: this is an advanced feature. see [this page](../3-pattern/inputlatch.md) for more information. +- **note/octave up/down**: transposes notes in the current selection. +- **values up/down**: changes values in the current selection by ±1 or ±16. +- **transpose**: transpose notes or change values by a specific amount. +- **interpolate**: fills in gaps in the selection by interpolation between values. +- **change instrument**: changes the instrument number in a selection. +- **gradient/fade**: replace the selection with a "gradient" that goes from the beginning of the selection to the end. + - does not affect the note column. + - **Nibble mode**: when enabled, the fade will be per-nibble (0 to F) rather than per-value (00 to FF). + - use for effects like `04xy` (vibrato). +- **scale**: scales values in the selection by a specific amount. + - use to change volume in a selection for example. +- **randomize**: replaces the selection with random values. + - does not affect the note column. +- **invert values**: `00` becomes `FF`, `01` becomes `FE`, `02` becomes `FD` and so on. +- **flip selection**: flips the selection so it is backwards. +- **collapse/expand amount**: allows you to specify how much to collapse/expand in the next options. +- **collapse**: shrinks the selected contents. +- **expand**: expands the selected contents. +- **collapse pattern**: same as collapse, but affects the entire pattern. +- **expand pattern**: same as expand, but affects the entire pattern. +- **collapse song**: same as collapse, but affects the entire song. + - it also changes speeds and pattern length to compensate. +- **expand song**: same as expand, but affects the entire song. + - it also changes speeds and pattern length to compensate. +- **find/replace**: opens the Find/Replace window. see [this page](../3-pattern/find-replace.md) for more information. +- **clear**: allows you to mass-delete things like songs, instruments and the like. + +# settings + +- **full screen**: expands the Furnace window so it covers your screen. +- **lock layout**: prevents you from dragging/resizing docked windows, or docking more. +- **basic mode**: toggles [Basic Mode](basic-mode.md). +- **visualizer**: toggles pattern view particle effects when the song plays. +- **reset layout**: resets the workspace to its defaults. +- **settings...**: opens the Settings window. + +# window + +- **song information**: shows/hides the Song Information window. +- **subsongs**: shows/hides the Subsongs window. +- **speed**: shows/hides the Speed window. +- **instruments**: shows/hides the instrument list. +- **wavetables**: shows/hides the wavetable list. +- **samples**: shows/hides the sample list. +- **orders**: shows/hides the Orders window. +- **pattern**: shows/hides the pattern view. +- **mixer**: shows/hides the Mixer window. +- **grooves**: shows/hides the Grooves window. +- **channels**: shows/hides the Channels window. +- **pattern manager**: shows/hides the Pattern Manager window. +- **chip manager**: shows/hides the Chip Manager window. +- **compatibility flags**: shows/hides the Compatibility Flags window. +- **song comments**: shows/hides the Song Comments window. +- **instrument editor**: shows/hides the Instrument Editor. +- **wavetable editor**: shows/hides the Wavetable Editor. +- **sample editor**: shows/hides the Sample Editor. +- **play/edit controls**: shows/hides the Play/Edit Controls. +- **piano/input pad**: shows/hides the Piano/Input Pad window. +- **oscilloscope (master)**: shows/hides the oscilloscope. +- **oscilloscope (per-channel)**: shows/hides the per-channel oscilloscope. +- **volume meter**: shows/hides the volume meter. +- **clock**: shows/hides the clock. +- **register view**: shows/hides the Register View window. +- **log viewer**: shows/hides the log Viewer. +- **statistics**: shows/hides the Statistics window. + +# help + +- **effect list**: displays the effect list. +- **debug menu**: this menu contains various debug utilities. + - unless you are working with the Furnace codebase, it's not useful. +- **inspector**: this options opens the Dear ImGui Metrics/Debugger window. + - unless you are working with the Furnace codebase, it's not useful. +- **panic**: this resets all chips while the song is playing, effectively silencing everything. +- **about...**: displays the About screen. diff --git a/doc/2-interface/play-edit-controls.md b/doc/2-interface/play-edit-controls.md new file mode 100644 index 00000000..db159cc2 --- /dev/null +++ b/doc/2-interface/play-edit-controls.md @@ -0,0 +1,23 @@ +# play/edit controls + +image + +these are used to control playback and change parameters of the pattern view. + +TODO: explain + +## other layouts + +the layout can be changed in Settings > Appearance to one of these: + +### classic + +image + +### compact + +image + +### compact (vertical) + +image diff --git a/doc/2-interface/song-info.md b/doc/2-interface/song-info.md new file mode 100644 index 00000000..e2aa806f --- /dev/null +++ b/doc/2-interface/song-info.md @@ -0,0 +1,5 @@ +# song info + +# sub-songs + +# speed diff --git a/doc/2-interface/tab1.png b/doc/2-interface/tab1.png new file mode 100644 index 00000000..2c63a7d9 Binary files /dev/null and b/doc/2-interface/tab1.png differ diff --git a/doc/2-interface/tab2.png b/doc/2-interface/tab2.png new file mode 100644 index 00000000..645f55a5 Binary files /dev/null and b/doc/2-interface/tab2.png differ diff --git a/doc/2-interface/tab3.png b/doc/2-interface/tab3.png new file mode 100644 index 00000000..4ebc1077 Binary files /dev/null and b/doc/2-interface/tab3.png differ diff --git a/doc/2-interface/window.png b/doc/2-interface/window.png new file mode 100644 index 00000000..5d7b32f3 Binary files /dev/null and b/doc/2-interface/window.png differ diff --git a/papers/doc/3-pattern/README.md b/doc/3-pattern/README.md similarity index 100% rename from papers/doc/3-pattern/README.md rename to doc/3-pattern/README.md diff --git a/papers/doc/3-pattern/channelbar.png b/doc/3-pattern/channelbar.png similarity index 100% rename from papers/doc/3-pattern/channelbar.png rename to doc/3-pattern/channelbar.png diff --git a/papers/doc/3-pattern/channels.png b/doc/3-pattern/channels.png similarity index 100% rename from papers/doc/3-pattern/channels.png rename to doc/3-pattern/channels.png diff --git a/papers/doc/3-pattern/effects.md b/doc/3-pattern/effects.md similarity index 100% rename from papers/doc/3-pattern/effects.md rename to doc/3-pattern/effects.md diff --git a/papers/doc/3-pattern/keyboard.png b/doc/3-pattern/keyboard.png similarity index 100% rename from papers/doc/3-pattern/keyboard.png rename to doc/3-pattern/keyboard.png diff --git a/papers/doc/3-pattern/pattern.png b/doc/3-pattern/pattern.png similarity index 100% rename from papers/doc/3-pattern/pattern.png rename to doc/3-pattern/pattern.png diff --git a/papers/doc/4-instrument/8930.md b/doc/4-instrument/8930.md similarity index 100% rename from papers/doc/4-instrument/8930.md rename to doc/4-instrument/8930.md diff --git a/papers/doc/4-instrument/README.md b/doc/4-instrument/README.md similarity index 100% rename from papers/doc/4-instrument/README.md rename to doc/4-instrument/README.md diff --git a/papers/doc/4-instrument/amiga.md b/doc/4-instrument/amiga.md similarity index 100% rename from papers/doc/4-instrument/amiga.md rename to doc/4-instrument/amiga.md diff --git a/papers/doc/4-instrument/ay8910.md b/doc/4-instrument/ay8910.md similarity index 100% rename from papers/doc/4-instrument/ay8910.md rename to doc/4-instrument/ay8910.md diff --git a/papers/doc/4-instrument/c64.md b/doc/4-instrument/c64.md similarity index 100% rename from papers/doc/4-instrument/c64.md rename to doc/4-instrument/c64.md diff --git a/papers/doc/4-instrument/fm.md b/doc/4-instrument/fm.md similarity index 100% rename from papers/doc/4-instrument/fm.md rename to doc/4-instrument/fm.md diff --git a/papers/doc/4-instrument/game-boy.md b/doc/4-instrument/game-boy.md similarity index 100% rename from papers/doc/4-instrument/game-boy.md rename to doc/4-instrument/game-boy.md diff --git a/papers/doc/4-instrument/list.png b/doc/4-instrument/list.png similarity index 100% rename from papers/doc/4-instrument/list.png rename to doc/4-instrument/list.png diff --git a/papers/doc/4-instrument/lynx.md b/doc/4-instrument/lynx.md similarity index 100% rename from papers/doc/4-instrument/lynx.md rename to doc/4-instrument/lynx.md diff --git a/papers/doc/4-instrument/macro.png b/doc/4-instrument/macro.png similarity index 100% rename from papers/doc/4-instrument/macro.png rename to doc/4-instrument/macro.png diff --git a/papers/doc/4-instrument/n163.md b/doc/4-instrument/n163.md similarity index 100% rename from papers/doc/4-instrument/n163.md rename to doc/4-instrument/n163.md diff --git a/papers/doc/4-instrument/pce.md b/doc/4-instrument/pce.md similarity index 100% rename from papers/doc/4-instrument/pce.md rename to doc/4-instrument/pce.md diff --git a/papers/doc/4-instrument/saa.md b/doc/4-instrument/saa.md similarity index 100% rename from papers/doc/4-instrument/saa.md rename to doc/4-instrument/saa.md diff --git a/papers/doc/4-instrument/scc.md b/doc/4-instrument/scc.md similarity index 100% rename from papers/doc/4-instrument/scc.md rename to doc/4-instrument/scc.md diff --git a/papers/doc/4-instrument/standard.md b/doc/4-instrument/standard.md similarity index 100% rename from papers/doc/4-instrument/standard.md rename to doc/4-instrument/standard.md diff --git a/papers/doc/4-instrument/tia.md b/doc/4-instrument/tia.md similarity index 100% rename from papers/doc/4-instrument/tia.md rename to doc/4-instrument/tia.md diff --git a/papers/doc/4-instrument/vera.md b/doc/4-instrument/vera.md similarity index 100% rename from papers/doc/4-instrument/vera.md rename to doc/4-instrument/vera.md diff --git a/papers/doc/4-instrument/vrc6.md b/doc/4-instrument/vrc6.md similarity index 100% rename from papers/doc/4-instrument/vrc6.md rename to doc/4-instrument/vrc6.md diff --git a/papers/doc/4-instrument/wonderswan.md b/doc/4-instrument/wonderswan.md similarity index 100% rename from papers/doc/4-instrument/wonderswan.md rename to doc/4-instrument/wonderswan.md diff --git a/papers/doc/4-instrument/x1_010.md b/doc/4-instrument/x1_010.md similarity index 100% rename from papers/doc/4-instrument/x1_010.md rename to doc/4-instrument/x1_010.md diff --git a/papers/doc/5-wave/README.md b/doc/5-wave/README.md similarity index 100% rename from papers/doc/5-wave/README.md rename to doc/5-wave/README.md diff --git a/papers/doc/6-sample/README.md b/doc/6-sample/README.md similarity index 100% rename from papers/doc/6-sample/README.md rename to doc/6-sample/README.md diff --git a/papers/doc/7-systems/README.md b/doc/7-systems/README.md similarity index 100% rename from papers/doc/7-systems/README.md rename to doc/7-systems/README.md diff --git a/papers/doc/7-systems/amiga.md b/doc/7-systems/amiga.md similarity index 100% rename from papers/doc/7-systems/amiga.md rename to doc/7-systems/amiga.md diff --git a/papers/doc/7-systems/ay8910.md b/doc/7-systems/ay8910.md similarity index 100% rename from papers/doc/7-systems/ay8910.md rename to doc/7-systems/ay8910.md diff --git a/papers/doc/7-systems/ay8930.md b/doc/7-systems/ay8930.md similarity index 100% rename from papers/doc/7-systems/ay8930.md rename to doc/7-systems/ay8930.md diff --git a/papers/doc/7-systems/bubblesystem.md b/doc/7-systems/bubblesystem.md similarity index 100% rename from papers/doc/7-systems/bubblesystem.md rename to doc/7-systems/bubblesystem.md diff --git a/papers/doc/7-systems/c64.md b/doc/7-systems/c64.md similarity index 100% rename from papers/doc/7-systems/c64.md rename to doc/7-systems/c64.md diff --git a/papers/doc/7-systems/dac.md b/doc/7-systems/dac.md similarity index 100% rename from papers/doc/7-systems/dac.md rename to doc/7-systems/dac.md diff --git a/papers/doc/7-systems/es5506.md b/doc/7-systems/es5506.md similarity index 100% rename from papers/doc/7-systems/es5506.md rename to doc/7-systems/es5506.md diff --git a/papers/doc/7-systems/fds.md b/doc/7-systems/fds.md similarity index 100% rename from papers/doc/7-systems/fds.md rename to doc/7-systems/fds.md diff --git a/papers/doc/7-systems/game-boy.md b/doc/7-systems/game-boy.md similarity index 100% rename from papers/doc/7-systems/game-boy.md rename to doc/7-systems/game-boy.md diff --git a/papers/doc/7-systems/genesis.md b/doc/7-systems/genesis.md similarity index 100% rename from papers/doc/7-systems/genesis.md rename to doc/7-systems/genesis.md diff --git a/papers/doc/7-systems/k007232.md b/doc/7-systems/k007232.md similarity index 100% rename from papers/doc/7-systems/k007232.md rename to doc/7-systems/k007232.md diff --git a/papers/doc/7-systems/lynx.md b/doc/7-systems/lynx.md similarity index 100% rename from papers/doc/7-systems/lynx.md rename to doc/7-systems/lynx.md diff --git a/papers/doc/7-systems/mmc5.md b/doc/7-systems/mmc5.md similarity index 100% rename from papers/doc/7-systems/mmc5.md rename to doc/7-systems/mmc5.md diff --git a/papers/doc/7-systems/msm5232.md b/doc/7-systems/msm5232.md similarity index 100% rename from papers/doc/7-systems/msm5232.md rename to doc/7-systems/msm5232.md diff --git a/papers/doc/7-systems/msm6258.md b/doc/7-systems/msm6258.md similarity index 100% rename from papers/doc/7-systems/msm6258.md rename to doc/7-systems/msm6258.md diff --git a/papers/doc/7-systems/msm6295.md b/doc/7-systems/msm6295.md similarity index 100% rename from papers/doc/7-systems/msm6295.md rename to doc/7-systems/msm6295.md diff --git a/papers/doc/7-systems/n163.md b/doc/7-systems/n163.md similarity index 100% rename from papers/doc/7-systems/n163.md rename to doc/7-systems/n163.md diff --git a/papers/doc/7-systems/namco.md b/doc/7-systems/namco.md similarity index 100% rename from papers/doc/7-systems/namco.md rename to doc/7-systems/namco.md diff --git a/papers/doc/7-systems/nes.md b/doc/7-systems/nes.md similarity index 100% rename from papers/doc/7-systems/nes.md rename to doc/7-systems/nes.md diff --git a/papers/doc/7-systems/opl.md b/doc/7-systems/opl.md similarity index 100% rename from papers/doc/7-systems/opl.md rename to doc/7-systems/opl.md diff --git a/papers/doc/7-systems/opll.md b/doc/7-systems/opll.md similarity index 100% rename from papers/doc/7-systems/opll.md rename to doc/7-systems/opll.md diff --git a/papers/doc/7-systems/opz.md b/doc/7-systems/opz.md similarity index 100% rename from papers/doc/7-systems/opz.md rename to doc/7-systems/opz.md diff --git a/papers/doc/7-systems/pce.md b/doc/7-systems/pce.md similarity index 100% rename from papers/doc/7-systems/pce.md rename to doc/7-systems/pce.md diff --git a/papers/doc/7-systems/pcspkr.md b/doc/7-systems/pcspkr.md similarity index 100% rename from papers/doc/7-systems/pcspkr.md rename to doc/7-systems/pcspkr.md diff --git a/papers/doc/7-systems/pet.md b/doc/7-systems/pet.md similarity index 100% rename from papers/doc/7-systems/pet.md rename to doc/7-systems/pet.md diff --git a/papers/doc/7-systems/pokey.md b/doc/7-systems/pokey.md similarity index 100% rename from papers/doc/7-systems/pokey.md rename to doc/7-systems/pokey.md diff --git a/papers/doc/7-systems/qsound.md b/doc/7-systems/qsound.md similarity index 100% rename from papers/doc/7-systems/qsound.md rename to doc/7-systems/qsound.md diff --git a/papers/doc/7-systems/ricoh.md b/doc/7-systems/ricoh.md similarity index 100% rename from papers/doc/7-systems/ricoh.md rename to doc/7-systems/ricoh.md diff --git a/papers/doc/7-systems/saa1099.md b/doc/7-systems/saa1099.md similarity index 100% rename from papers/doc/7-systems/saa1099.md rename to doc/7-systems/saa1099.md diff --git a/papers/doc/7-systems/scc.md b/doc/7-systems/scc.md similarity index 100% rename from papers/doc/7-systems/scc.md rename to doc/7-systems/scc.md diff --git a/papers/doc/7-systems/segapcm.md b/doc/7-systems/segapcm.md similarity index 100% rename from papers/doc/7-systems/segapcm.md rename to doc/7-systems/segapcm.md diff --git a/papers/doc/7-systems/sm8521.md b/doc/7-systems/sm8521.md similarity index 100% rename from papers/doc/7-systems/sm8521.md rename to doc/7-systems/sm8521.md diff --git a/papers/doc/7-systems/sms.md b/doc/7-systems/sms.md similarity index 100% rename from papers/doc/7-systems/sms.md rename to doc/7-systems/sms.md diff --git a/papers/doc/7-systems/snes.md b/doc/7-systems/snes.md similarity index 100% rename from papers/doc/7-systems/snes.md rename to doc/7-systems/snes.md diff --git a/papers/doc/7-systems/soundunit.md b/doc/7-systems/soundunit.md similarity index 100% rename from papers/doc/7-systems/soundunit.md rename to doc/7-systems/soundunit.md diff --git a/papers/doc/7-systems/t6w28.md b/doc/7-systems/t6w28.md similarity index 100% rename from papers/doc/7-systems/t6w28.md rename to doc/7-systems/t6w28.md diff --git a/papers/doc/7-systems/tia.md b/doc/7-systems/tia.md similarity index 100% rename from papers/doc/7-systems/tia.md rename to doc/7-systems/tia.md diff --git a/papers/doc/7-systems/vera.md b/doc/7-systems/vera.md similarity index 100% rename from papers/doc/7-systems/vera.md rename to doc/7-systems/vera.md diff --git a/papers/doc/7-systems/vic20.md b/doc/7-systems/vic20.md similarity index 100% rename from papers/doc/7-systems/vic20.md rename to doc/7-systems/vic20.md diff --git a/papers/doc/7-systems/virtual-boy.md b/doc/7-systems/virtual-boy.md similarity index 100% rename from papers/doc/7-systems/virtual-boy.md rename to doc/7-systems/virtual-boy.md diff --git a/papers/doc/7-systems/vrc6.md b/doc/7-systems/vrc6.md similarity index 100% rename from papers/doc/7-systems/vrc6.md rename to doc/7-systems/vrc6.md diff --git a/papers/doc/7-systems/wonderswan.md b/doc/7-systems/wonderswan.md similarity index 100% rename from papers/doc/7-systems/wonderswan.md rename to doc/7-systems/wonderswan.md diff --git a/papers/doc/7-systems/x1-010.md b/doc/7-systems/x1-010.md similarity index 100% rename from papers/doc/7-systems/x1-010.md rename to doc/7-systems/x1-010.md diff --git a/papers/doc/7-systems/ym2151.md b/doc/7-systems/ym2151.md similarity index 100% rename from papers/doc/7-systems/ym2151.md rename to doc/7-systems/ym2151.md diff --git a/papers/doc/7-systems/ym2203.md b/doc/7-systems/ym2203.md similarity index 100% rename from papers/doc/7-systems/ym2203.md rename to doc/7-systems/ym2203.md diff --git a/papers/doc/7-systems/ym2608.md b/doc/7-systems/ym2608.md similarity index 100% rename from papers/doc/7-systems/ym2608.md rename to doc/7-systems/ym2608.md diff --git a/papers/doc/7-systems/ym2610.md b/doc/7-systems/ym2610.md similarity index 100% rename from papers/doc/7-systems/ym2610.md rename to doc/7-systems/ym2610.md diff --git a/papers/doc/7-systems/ym2610b.md b/doc/7-systems/ym2610b.md similarity index 100% rename from papers/doc/7-systems/ym2610b.md rename to doc/7-systems/ym2610b.md diff --git a/papers/doc/7-systems/ym2612.md b/doc/7-systems/ym2612.md similarity index 100% rename from papers/doc/7-systems/ym2612.md rename to doc/7-systems/ym2612.md diff --git a/papers/doc/7-systems/ymu759.md b/doc/7-systems/ymu759.md similarity index 100% rename from papers/doc/7-systems/ymu759.md rename to doc/7-systems/ymu759.md diff --git a/papers/doc/7-systems/ymz280b.md b/doc/7-systems/ymz280b.md similarity index 100% rename from papers/doc/7-systems/ymz280b.md rename to doc/7-systems/ymz280b.md diff --git a/papers/doc/7-systems/zxbeep.md b/doc/7-systems/zxbeep.md similarity index 100% rename from papers/doc/7-systems/zxbeep.md rename to doc/7-systems/zxbeep.md diff --git a/doc/8-advanced/README.md b/doc/8-advanced/README.md new file mode 100644 index 00000000..3c001173 --- /dev/null +++ b/doc/8-advanced/README.md @@ -0,0 +1,3 @@ +# advanced + +advanced Furnace features. diff --git a/doc/8-advanced/channels.md b/doc/8-advanced/channels.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/8-advanced/chanosc.md b/doc/8-advanced/chanosc.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/8-advanced/chip-manager.md b/doc/8-advanced/chip-manager.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/8-advanced/clock.md b/doc/8-advanced/clock.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/8-advanced/comments.md b/doc/8-advanced/comments.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/8-advanced/compat-flags.md b/doc/8-advanced/compat-flags.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/8-advanced/grooves.md b/doc/8-advanced/grooves.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/8-advanced/log-viewer.md b/doc/8-advanced/log-viewer.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/8-advanced/mixer.md b/doc/8-advanced/mixer.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/8-advanced/osc.md b/doc/8-advanced/osc.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/8-advanced/pat-manager.md b/doc/8-advanced/pat-manager.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/8-advanced/piano.md b/doc/8-advanced/piano.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/8-advanced/regview.md b/doc/8-advanced/regview.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/8-advanced/stats.md b/doc/8-advanced/stats.md new file mode 100644 index 00000000..e69de29b diff --git a/papers/doc/README.md b/doc/README.md similarity index 100% rename from papers/doc/README.md rename to doc/README.md diff --git a/extern/imgui_patched/imgui.cpp b/extern/imgui_patched/imgui.cpp index 4d57efc0..e2552fe2 100644 --- a/extern/imgui_patched/imgui.cpp +++ b/extern/imgui_patched/imgui.cpp @@ -12478,9 +12478,12 @@ bool ImGui::LoadIniSettingsFromDisk(const char* ini_filename, bool redundancy) if (!file_data) continue; for (size_t j=0; j -#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier -#include // intptr_t -#else -#include // intptr_t -#endif - -// SDL -#include -#if !SDL_VERSION_ATLEAST(2,0,17) -#error This backend requires SDL 2.0.17+ because of SDL_RenderGeometry() function -#endif - -// SDL_Renderer data -struct ImGui_ImplSDLRenderer_Data -{ - SDL_Renderer* SDLRenderer; - SDL_Texture* FontTexture; - ImGui_ImplSDLRenderer_Data() { memset(this, 0, sizeof(*this)); } -}; - -// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts -// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. -static ImGui_ImplSDLRenderer_Data* ImGui_ImplSDLRenderer_GetBackendData() -{ - return ImGui::GetCurrentContext() ? (ImGui_ImplSDLRenderer_Data*)ImGui::GetIO().BackendRendererUserData : NULL; -} - -// Functions -bool ImGui_ImplSDLRenderer_Init(SDL_Renderer* renderer) -{ - ImGuiIO& io = ImGui::GetIO(); - IM_ASSERT(io.BackendRendererUserData == NULL && "Already initialized a renderer backend!"); - IM_ASSERT(renderer != NULL && "SDL_Renderer not initialized!"); - - // Setup backend capabilities flags - ImGui_ImplSDLRenderer_Data* bd = IM_NEW(ImGui_ImplSDLRenderer_Data)(); - io.BackendRendererUserData = (void*)bd; - io.BackendRendererName = "imgui_impl_sdlrenderer"; - io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. - - bd->SDLRenderer = renderer; - - return true; -} - -void ImGui_ImplSDLRenderer_Shutdown() -{ - ImGui_ImplSDLRenderer_Data* bd = ImGui_ImplSDLRenderer_GetBackendData(); - IM_ASSERT(bd != NULL && "No renderer backend to shutdown, or already shutdown?"); - ImGuiIO& io = ImGui::GetIO(); - - ImGui_ImplSDLRenderer_DestroyDeviceObjects(); - - io.BackendRendererName = NULL; - io.BackendRendererUserData = NULL; - IM_DELETE(bd); -} - -static void ImGui_ImplSDLRenderer_SetupRenderState() -{ - ImGui_ImplSDLRenderer_Data* bd = ImGui_ImplSDLRenderer_GetBackendData(); - - // Clear out any viewports and cliprect set by the user - // FIXME: Technically speaking there are lots of other things we could backup/setup/restore during our render process. - SDL_RenderSetViewport(bd->SDLRenderer, NULL); - SDL_RenderSetClipRect(bd->SDLRenderer, NULL); -} - -bool ImGui_ImplSDLRenderer_NewFrame() -{ - ImGui_ImplSDLRenderer_Data* bd = ImGui_ImplSDLRenderer_GetBackendData(); - IM_ASSERT(bd != NULL && "Did you call ImGui_ImplSDLRenderer_Init()?"); - - if (!bd->FontTexture) - return ImGui_ImplSDLRenderer_CreateDeviceObjects(); - - return true; -} - -void ImGui_ImplSDLRenderer_RenderDrawData(ImDrawData* draw_data) -{ - ImGui_ImplSDLRenderer_Data* bd = ImGui_ImplSDLRenderer_GetBackendData(); - - // If there's a scale factor set by the user, use that instead - // If the user has specified a scale factor to SDL_Renderer already via SDL_RenderSetScale(), SDL will scale whatever we pass - // to SDL_RenderGeometryRaw() by that scale factor. In that case we don't want to be also scaling it ourselves here. - float rsx = 1.0f; - float rsy = 1.0f; - SDL_RenderGetScale(bd->SDLRenderer, &rsx, &rsy); - ImVec2 render_scale; - render_scale.x = (rsx == 1.0f) ? draw_data->FramebufferScale.x : 1.0f; - render_scale.y = (rsy == 1.0f) ? draw_data->FramebufferScale.y : 1.0f; - - // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * render_scale.x); - int fb_height = (int)(draw_data->DisplaySize.y * render_scale.y); - if (fb_width == 0 || fb_height == 0) - return; - - // Backup SDL_Renderer state that will be modified to restore it afterwards - struct BackupSDLRendererState - { - SDL_Rect Viewport; - bool ClipEnabled; - SDL_Rect ClipRect; - }; - BackupSDLRendererState old = {}; - old.ClipEnabled = SDL_RenderIsClipEnabled(bd->SDLRenderer) == SDL_TRUE; - SDL_RenderGetViewport(bd->SDLRenderer, &old.Viewport); - SDL_RenderGetClipRect(bd->SDLRenderer, &old.ClipRect); - - // Will project scissor/clipping rectangles into framebuffer space - ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports - ImVec2 clip_scale = render_scale; - - // Render command lists - ImGui_ImplSDLRenderer_SetupRenderState(); - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList* cmd_list = draw_data->CmdLists[n]; - const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; - const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; - - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) - { - const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; - if (pcmd->UserCallback) - { - // User callback, registered via ImDrawList::AddCallback() - // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) - ImGui_ImplSDLRenderer_SetupRenderState(); - else - pcmd->UserCallback(cmd_list, pcmd); - } - else - { - // Project scissor/clipping rectangles into framebuffer space - ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); - ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y); - if (clip_min.x < 0.0f) { clip_min.x = 0.0f; } - if (clip_min.y < 0.0f) { clip_min.y = 0.0f; } - if (clip_max.x > fb_width) { clip_max.x = (float)fb_width; } - if (clip_max.y > fb_height) { clip_max.y = (float)fb_height; } - if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) - continue; - - SDL_Rect r = { (int)(clip_min.x), (int)(clip_min.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y) }; - SDL_RenderSetClipRect(bd->SDLRenderer, &r); - - const float* xy = (const float*)((const char*)(vtx_buffer + pcmd->VtxOffset) + IM_OFFSETOF(ImDrawVert, pos)); - const float* uv = (const float*)((const char*)(vtx_buffer + pcmd->VtxOffset) + IM_OFFSETOF(ImDrawVert, uv)); -#if SDL_VERSION_ATLEAST(2,0,19) - const SDL_Color* color = (const SDL_Color*)((const char*)(vtx_buffer + pcmd->VtxOffset) + IM_OFFSETOF(ImDrawVert, col)); // SDL 2.0.19+ -#else - const int* color = (const int*)((const char*)(vtx_buffer + pcmd->VtxOffset) + IM_OFFSETOF(ImDrawVert, col)); // SDL 2.0.17 and 2.0.18 -#endif - - // Bind texture, Draw - SDL_Texture* tex = (SDL_Texture*)pcmd->GetTexID(); - SDL_SetTextureScaleMode(tex, SDL_ScaleModeBest); // ??? - SDL_RenderGeometryRaw(bd->SDLRenderer, tex, - xy, (int)sizeof(ImDrawVert), - color, (int)sizeof(ImDrawVert), - uv, (int)sizeof(ImDrawVert), - cmd_list->VtxBuffer.Size - pcmd->VtxOffset, - idx_buffer + pcmd->IdxOffset, pcmd->ElemCount, sizeof(ImDrawIdx)); - } - } - } - - // Restore modified SDL_Renderer state - SDL_RenderSetViewport(bd->SDLRenderer, &old.Viewport); - SDL_RenderSetClipRect(bd->SDLRenderer, old.ClipEnabled ? &old.ClipRect : NULL); -} - -// Called by Init/NewFrame/Shutdown -bool ImGui_ImplSDLRenderer_CreateFontsTexture() -{ - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplSDLRenderer_Data* bd = ImGui_ImplSDLRenderer_GetBackendData(); - - // Build texture atlas - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. - - // Upload texture to graphics system - bd->FontTexture = SDL_CreateTexture(bd->SDLRenderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STATIC, width, height); - if (bd->FontTexture == NULL) - { - SDL_Log("error creating texture"); - return false; - } - SDL_UpdateTexture(bd->FontTexture, NULL, pixels, 4 * width); - SDL_SetTextureBlendMode(bd->FontTexture, SDL_BLENDMODE_BLEND); - - // Store our identifier - io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture); - - return true; -} - -void ImGui_ImplSDLRenderer_DestroyFontsTexture() -{ - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplSDLRenderer_Data* bd = ImGui_ImplSDLRenderer_GetBackendData(); - if (bd->FontTexture) - { - io.Fonts->SetTexID(0); - SDL_DestroyTexture(bd->FontTexture); - bd->FontTexture = NULL; - } -} - -bool ImGui_ImplSDLRenderer_CreateDeviceObjects() -{ - return ImGui_ImplSDLRenderer_CreateFontsTexture(); -} - -void ImGui_ImplSDLRenderer_DestroyDeviceObjects() -{ - ImGui_ImplSDLRenderer_DestroyFontsTexture(); -} diff --git a/extern/imgui_patched/imgui_impl_sdlrenderer.h b/extern/imgui_patched/imgui_impl_sdlrenderer.h deleted file mode 100644 index 379265de..00000000 --- a/extern/imgui_patched/imgui_impl_sdlrenderer.h +++ /dev/null @@ -1,31 +0,0 @@ -// dear imgui: Renderer Backend for SDL_Renderer -// (Requires: SDL 2.0.17+) - -// Important to understand: SDL_Renderer is an _optional_ component of SDL. -// For a multi-platform app consider using e.g. SDL+DirectX on Windows and SDL+OpenGL on Linux/OSX. -// If your application will want to render any non trivial amount of graphics other than UI, -// please be aware that SDL_Renderer offers a limited graphic API to the end-user and it might -// be difficult to step out of those boundaries. -// However, we understand it is a convenient choice to get an app started easily. - -// Implemented features: -// [X] Renderer: User texture binding. Use 'SDL_Texture*' as ImTextureID. Read the FAQ about ImTextureID! -// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. -// Missing features: -// [ ] Renderer: Multi-viewport support (multiple windows). - -#pragma once -#include "imgui.h" // IMGUI_IMPL_API - -struct SDL_Renderer; - -IMGUI_IMPL_API bool ImGui_ImplSDLRenderer_Init(SDL_Renderer* renderer); -IMGUI_IMPL_API void ImGui_ImplSDLRenderer_Shutdown(); -IMGUI_IMPL_API bool ImGui_ImplSDLRenderer_NewFrame(); -IMGUI_IMPL_API void ImGui_ImplSDLRenderer_RenderDrawData(ImDrawData* draw_data); - -// Called by Init/NewFrame/Shutdown -IMGUI_IMPL_API bool ImGui_ImplSDLRenderer_CreateFontsTexture(); -IMGUI_IMPL_API void ImGui_ImplSDLRenderer_DestroyFontsTexture(); -IMGUI_IMPL_API bool ImGui_ImplSDLRenderer_CreateDeviceObjects(); -IMGUI_IMPL_API void ImGui_ImplSDLRenderer_DestroyDeviceObjects(); diff --git a/papers/doc/1-intro/README.md b/papers/doc/1-intro/README.md deleted file mode 100644 index a550aca1..00000000 --- a/papers/doc/1-intro/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# introduction - -Furnace is a tool which allows you to create music using emulated sound chips from the 8/16-bit era. -For a full list of soundchips that Furnace supports, please see [this list](https://github.com/tildearrow/furnace/tree/master/papers/doc/7-systems). - -It has a music tracker interface. think of a piano roll, or a table that scrolls up and plays the notes. - -Another core feature of Furnace is its windowing system, similar to that of GEMS or Deflemask, but with a few more features. - -## Sound generation - -Furnace generates sound from 3 different main types of sound sources. - - Instruments are the most standard and most used type of sound source in Furnace. -The instrument format is how you can specify parameters and macros for certain channels on certain soundchips, as well as binding samples and wavetables to a format that you can sequence on the note grid. -See [4-instrument](https://github.com/tildearrow/furnace/tree/master/papers/doc/4-instrument) for more details. - - Wavetables are the way that you create custom waveform shapes for the HuC6280, Seta X1-010, WonderSwan, any PCM chip with wavetable synthesizer support, etc. -Wavetables only work in the sequencer if you bind them to an instrument. See [4-instrument](https://github.com/tildearrow/furnace/tree/master/papers/doc/4-instrument) and [5-wave](https://github.com/tildearrow/furnace/tree/master/papers/doc/5-wave) for more details. - - Samples are how you play back raw audio streams (samples) on certain channels, on certain soundchips, and in some cases, in certain modes. -To sequence a sample, you do not need to assign it to an instrument, however, to resample samples (change the speed of a sample), you need to bind it to a Sample instrument. -See [6-sample](https://github.com/tildearrow/furnace/tree/master/papers/doc/6-sample) and [4-instrument](https://github.com/tildearrow/furnace/tree/master/papers/doc/4-instrument) for more details. - -## Interface/other - -Furnace is built to have a user-friendly interface that is intentionally made so that it is quick and easy to get around when working in Furnace. -However, we understand that the interface may not be the easiest to learn, depending on how you learn, so there is documentation on it as well. - -See [2-interface](https://github.com/tildearrow/furnace/tree/master/papers/doc/2-interface) and [3-pattern](https://github.com/tildearrow/furnace/tree/master/papers/doc/3-pattern) to view said documentation. diff --git a/papers/doc/2-interface/README.md b/papers/doc/2-interface/README.md deleted file mode 100644 index c61bf575..00000000 --- a/papers/doc/2-interface/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# interface - -the Furnace user interface is where the job gets done. - -- [UI components](components.md) -- [global keyboard shortcuts](keyboard.md) -- [basic mode](basic-mode.md) diff --git a/papers/format.md b/papers/format.md index 198f6264..bf0b929e 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,7 +32,8 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: -- 158: Furnace dev158 +- 159: Furnace dev159 +- 158: Furnace 0.6pre5 - 157: Furnace dev157 - 156: Furnace dev156 - 155: Furnace dev155 @@ -442,9 +443,6 @@ size | description 4 | instrument directories 4 | wavetable directories 4 | sample directories - --- | **patchbay effects** (>=158) - 2 | number of effects - 4?? | pointers to effects ``` # patchbay @@ -552,31 +550,6 @@ size | description 1?? | assets in this directory ``` -# patchbay effect (>=158) - -these effects sit on the patchbay. - -``` -size | description ------|------------------------------------ - 4 | "EFFE" block ID - 4 | size of this block - 2 | slot - | - must be between 32 and 4092. - | - the other slots are reserved for chip/system portsets. - 2 | effect ID - | - 0x01: dummy - | - 0x02: external (plugin bridge) - | - not implemented yet - | - 0x03: volume - | - 0x04: filter (circuit) - 4f | dry/wet balance - 2 | reserved - 2 | storage version - 4 | storage length - ??? | storage data -``` - # instrument (>=127) Furnace dev127 and higher use the new instrument format. @@ -1260,7 +1233,8 @@ size | description | - 2: ping-pong 1 | flags (>=129) or reserved | - 0: BRR emphasis - 1 | reserved + 1 | flags 2 (>=159) or reserved + | - 0: dither 4 | loop start | - -1 means no loop 4 | loop end diff --git a/papers/screenshot3.png b/papers/screenshot3.png new file mode 100644 index 00000000..da598366 Binary files /dev/null and b/papers/screenshot3.png differ diff --git a/scripts/release-linux-AppImage.sh b/scripts/release-linux-AppImage.sh index 35be353d..4cf8a8cd 100755 --- a/scripts/release-linux-AppImage.sh +++ b/scripts/release-linux-AppImage.sh @@ -14,7 +14,7 @@ fi cd linuxbuild -cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Werror" .. || exit 1 +cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Werror" -DWITH_DEMOS=OFF -DWITH_INSTRUMENTS=OFF -DWITH_WAVETABLES=OFF .. || exit 1 make -j4 || exit 1 cd .. @@ -47,5 +47,3 @@ cd ../../.. [ -e appimagetool-x86_64.AppImage ] || { wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" && chmod 755 appimagetool-x86_64.AppImage; } ./appimagetool-x86_64.AppImage --appimage-extract ARCH=$(uname -m) ./squashfs-root/AppRun furnace.AppDir - -#zip -r furnace.zip LICENSE.txt Furnace*.dmg README.txt papers diff --git a/scripts/release-linux.sh b/scripts/release-linux.sh index 570712f0..030f8259 100755 --- a/scripts/release-linux.sh +++ b/scripts/release-linux.sh @@ -14,7 +14,7 @@ fi cd linuxbuild -cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Werror" .. || exit 1 +cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O3" -DCMAKE_CXX_FLAGS="-O3 -Wall -Wextra -Wno-unused-parameter -Werror" .. || exit 1 make -j4 || exit 1 cd .. @@ -51,6 +51,7 @@ cd .. cp ../../../LICENSE . || exit 1 cp ../../../README.md . || exit 1 cp -r ../../../papers papers || exit 1 +cp -r ../../../doc doc || exit 1 rmdir usr || exit 1 strip -s furnace diff --git a/scripts/release-win32.sh b/scripts/release-win32.sh index 9faf9c2f..e0c0eec6 100755 --- a/scripts/release-win32.sh +++ b/scripts/release-win32.sh @@ -17,7 +17,6 @@ cd win32build # TODO: potential Arch-ism? i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" -DBUILD_SHARED_LIBS=OFF -DSUPPORT_XP=ON .. || exit 1 make -j8 || exit 1 -#i686-w64-mingw32-strip -s furnace.exe || exit 1 cd .. @@ -28,11 +27,14 @@ cp ../../LICENSE LICENSE.txt || exit 1 cp ../../win32build/furnace.exe . || exit 1 cp ../../README.md README.txt || exit 1 cp -r ../../papers papers || exit 1 +cp -r ../../doc doc || exit 1 cp -r ../../demos demos || exit 1 cp -r ../../instruments instruments || exit 1 cp -r ../../wavetables wavetables || exit 1 -zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos instruments wavetables +i686-w64-mingw32-strip -s furnace.exe || exit 1 + +zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers doc demos instruments wavetables furName=$(git describe --tags | sed "s/v0/0/") diff --git a/scripts/release-win64.sh b/scripts/release-win64.sh index b349e571..9183007b 100755 --- a/scripts/release-win64.sh +++ b/scripts/release-win64.sh @@ -17,7 +17,6 @@ cd winbuild # TODO: potential Arch-ism? x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Wno-deprecated-declarations -Werror" .. || exit 1 make -j8 || exit 1 -#x86_64-w64-mingw32-strip -s furnace.exe || exit 1 cd .. @@ -28,11 +27,14 @@ cp ../../LICENSE LICENSE.txt || exit 1 cp ../../winbuild/furnace.exe . || exit 1 cp ../../README.md README.txt || exit 1 cp -r ../../papers papers || exit 1 +cp -r ../../doc doc || exit 1 cp -r ../../demos demos || exit 1 cp -r ../../instruments instruments || exit 1 cp -r ../../wavetables wavetables || exit 1 -zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos instruments wavetables +x86_64-w64-mingw32-strip -s furnace.exe || exit 1 + +zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers doc demos instruments wavetables furName=$(git describe --tags | sed "s/v0/0/") diff --git a/src/engine/config.cpp b/src/engine/config.cpp index d2333ab5..6d90a49f 100644 --- a/src/engine/config.cpp +++ b/src/engine/config.cpp @@ -148,9 +148,13 @@ bool DivConfig::loadFromFile(const char* path, bool createOnFail, bool redundanc } for (size_t j=0; jsoftLocked=true; e->isBusy.lock(); #define EXTERN_BUSY_END e->isBusy.unlock(); e->softLocked=false; -#define DIV_VERSION "0.6pre5" -#define DIV_ENGINE_VERSION 158 +#define DIV_VERSION "dev159" +#define DIV_ENGINE_VERSION 159 // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 @@ -608,7 +608,7 @@ class DivEngine { SafeWriter* saveDMF(unsigned char version); // save as .fur. // if notPrimary is true then the song will not be altered - SafeWriter* saveFur(bool notPrimary=false); + SafeWriter* saveFur(bool notPrimary=false, bool newPatternFormat=true); // build a ROM file (TODO). // specify system to build ROM for. std::vector buildROM(DivROMExportOptions sys); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index cdbb5c42..8330314f 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -5089,9 +5089,7 @@ DivDataErrors DivEngine::readAssetDirData(SafeReader& reader, std::vector subSongPtr; std::vector sysFlagsPtr; @@ -5526,132 +5524,132 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { DivPattern* pat=song.subsong[i.subsong]->pat[i.chan].getPattern(i.pat,false); patPtr.push_back(w->tell()); -#ifdef NEW_PATTERN_FORMAT - w->write("PATN",4); - blockStartSeek=w->tell(); - w->writeI(0); + if (newPatternFormat) { + w->write("PATN",4); + blockStartSeek=w->tell(); + w->writeI(0); - w->writeC(i.subsong); - w->writeC(i.chan); - w->writeS(i.pat); - w->writeString(pat->name,false); + w->writeC(i.subsong); + w->writeC(i.chan); + w->writeS(i.pat); + w->writeString(pat->name,false); - unsigned char emptyRows=0; + unsigned char emptyRows=0; - for (int j=0; jpatLen; j++) { - unsigned char mask=0; - unsigned char finalNote=255; - unsigned short effectMask=0; + for (int j=0; jpatLen; j++) { + unsigned char mask=0; + unsigned char finalNote=255; + unsigned short effectMask=0; - if (pat->data[j][0]==100) { - finalNote=180; - } else if (pat->data[j][0]==101) { // note release - finalNote=181; - } else if (pat->data[j][0]==102) { // macro release - finalNote=182; - } else if (pat->data[j][1]==0 && pat->data[j][0]==0) { - finalNote=255; - } else { - int seek=(pat->data[j][0]+(signed char)pat->data[j][1]*12)+60; - if (seek<0 || seek>=180) { + if (pat->data[j][0]==100) { + finalNote=180; + } else if (pat->data[j][0]==101) { // note release + finalNote=181; + } else if (pat->data[j][0]==102) { // macro release + finalNote=182; + } else if (pat->data[j][1]==0 && pat->data[j][0]==0) { finalNote=255; } else { - finalNote=seek; + int seek=(pat->data[j][0]+(signed char)pat->data[j][1]*12)+60; + if (seek<0 || seek>=180) { + finalNote=255; + } else { + finalNote=seek; + } } - } - if (finalNote!=255) mask|=1; // note - if (pat->data[j][2]!=-1) mask|=2; // instrument - if (pat->data[j][3]!=-1) mask|=4; // volume - for (int k=0; kpat[i.chan].effectCols*2; k+=2) { - if (k==0) { - if (pat->data[j][4+k]!=-1) mask|=8; - if (pat->data[j][5+k]!=-1) mask|=16; - } else if (k<8) { - if (pat->data[j][4+k]!=-1 || pat->data[j][5+k]!=-1) mask|=32; + if (finalNote!=255) mask|=1; // note + if (pat->data[j][2]!=-1) mask|=2; // instrument + if (pat->data[j][3]!=-1) mask|=4; // volume + for (int k=0; kpat[i.chan].effectCols*2; k+=2) { + if (k==0) { + if (pat->data[j][4+k]!=-1) mask|=8; + if (pat->data[j][5+k]!=-1) mask|=16; + } else if (k<8) { + if (pat->data[j][4+k]!=-1 || pat->data[j][5+k]!=-1) mask|=32; + } else { + if (pat->data[j][4+k]!=-1 || pat->data[j][5+k]!=-1) mask|=64; + } + + if (pat->data[j][4+k]!=-1) effectMask|=(1<data[j][5+k]!=-1) effectMask|=(2<127) { + w->writeC(128|(emptyRows-2)); + emptyRows=0; + } } else { - if (pat->data[j][4+k]!=-1 || pat->data[j][5+k]!=-1) mask|=64; - } + if (emptyRows>1) { + w->writeC(128|(emptyRows-2)); + emptyRows=0; + } else if (emptyRows) { + w->writeC(0); + emptyRows=0; + } - if (pat->data[j][4+k]!=-1) effectMask|=(1<data[j][5+k]!=-1) effectMask|=(2<writeC(mask); - if (mask==0) { - emptyRows++; - if (emptyRows>127) { - w->writeC(128|(emptyRows-2)); - emptyRows=0; - } - } else { - if (emptyRows>1) { - w->writeC(128|(emptyRows-2)); - emptyRows=0; - } else if (emptyRows) { - w->writeC(0); - emptyRows=0; - } + if (mask&32) w->writeC(effectMask&0xff); + if (mask&64) w->writeC((effectMask>>8)&0xff); - w->writeC(mask); - - if (mask&32) w->writeC(effectMask&0xff); - if (mask&64) w->writeC((effectMask>>8)&0xff); - - if (mask&1) w->writeC(finalNote); - if (mask&2) w->writeC(pat->data[j][2]); - if (mask&4) w->writeC(pat->data[j][3]); - if (mask&8) w->writeC(pat->data[j][4]); - if (mask&16) w->writeC(pat->data[j][5]); - if (mask&32) { - if (effectMask&4) w->writeC(pat->data[j][6]); - if (effectMask&8) w->writeC(pat->data[j][7]); - if (effectMask&16) w->writeC(pat->data[j][8]); - if (effectMask&32) w->writeC(pat->data[j][9]); - if (effectMask&64) w->writeC(pat->data[j][10]); - if (effectMask&128) w->writeC(pat->data[j][11]); - } - if (mask&64) { - if (effectMask&256) w->writeC(pat->data[j][12]); - if (effectMask&512) w->writeC(pat->data[j][13]); - if (effectMask&1024) w->writeC(pat->data[j][14]); - if (effectMask&2048) w->writeC(pat->data[j][15]); - if (effectMask&4096) w->writeC(pat->data[j][16]); - if (effectMask&8192) w->writeC(pat->data[j][17]); - if (effectMask&16384) w->writeC(pat->data[j][18]); - if (effectMask&32768) w->writeC(pat->data[j][19]); + if (mask&1) w->writeC(finalNote); + if (mask&2) w->writeC(pat->data[j][2]); + if (mask&4) w->writeC(pat->data[j][3]); + if (mask&8) w->writeC(pat->data[j][4]); + if (mask&16) w->writeC(pat->data[j][5]); + if (mask&32) { + if (effectMask&4) w->writeC(pat->data[j][6]); + if (effectMask&8) w->writeC(pat->data[j][7]); + if (effectMask&16) w->writeC(pat->data[j][8]); + if (effectMask&32) w->writeC(pat->data[j][9]); + if (effectMask&64) w->writeC(pat->data[j][10]); + if (effectMask&128) w->writeC(pat->data[j][11]); + } + if (mask&64) { + if (effectMask&256) w->writeC(pat->data[j][12]); + if (effectMask&512) w->writeC(pat->data[j][13]); + if (effectMask&1024) w->writeC(pat->data[j][14]); + if (effectMask&2048) w->writeC(pat->data[j][15]); + if (effectMask&4096) w->writeC(pat->data[j][16]); + if (effectMask&8192) w->writeC(pat->data[j][17]); + if (effectMask&16384) w->writeC(pat->data[j][18]); + if (effectMask&32768) w->writeC(pat->data[j][19]); + } } } - } - // stop - w->writeC(0xff); -#else - w->write("PATR",4); - blockStartSeek=w->tell(); - w->writeI(0); + // stop + w->writeC(0xff); + } else { + w->write("PATR",4); + blockStartSeek=w->tell(); + w->writeI(0); - w->writeS(i.chan); - w->writeS(i.pat); - w->writeS(i.subsong); + w->writeS(i.chan); + w->writeS(i.pat); + w->writeS(i.subsong); - w->writeS(0); // reserved + w->writeS(0); // reserved - for (int j=0; jpatLen; j++) { - w->writeS(pat->data[j][0]); // note - w->writeS(pat->data[j][1]); // octave - w->writeS(pat->data[j][2]); // instrument - w->writeS(pat->data[j][3]); // volume + for (int j=0; jpatLen; j++) { + w->writeS(pat->data[j][0]); // note + w->writeS(pat->data[j][1]); // octave + w->writeS(pat->data[j][2]); // instrument + w->writeS(pat->data[j][3]); // volume #ifdef TA_BIG_ENDIAN - for (int k=0; kpat[i.chan].effectCols*2; k++) { - w->writeS(pat->data[j][4+k]); - } + for (int k=0; kpat[i.chan].effectCols*2; k++) { + w->writeS(pat->data[j][4+k]); + } #else - w->write(&pat->data[j][4],2*song.subsong[i.subsong]->pat[i.chan].effectCols*2); // effects + w->write(&pat->data[j][4],2*song.subsong[i.subsong]->pat[i.chan].effectCols*2); // effects #endif - } + } - w->writeString(pat->name,false); -#endif + w->writeString(pat->name,false); + } blockEndSeek=w->tell(); w->seek(blockStartSeek,SEEK_SET); diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index e32ec528..9f905105 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -190,10 +190,10 @@ void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) { oscBuf[i]->data[oscBuf[i]->needle++]=fm.dacdata<<7; } } else { - oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]<<(chipType==2?0:7); + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(fm.ch_out[i]<<(chipType==2?2:7),-32768,32767); } } else { - oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]<<(chipType==2?0:7); + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(fm.ch_out[i]<<(chipType==2?2:7),-32768,32767); } } diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index ae4b311e..8ee3a169 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -156,7 +156,7 @@ void DivPlatformPOKEY::tick(bool sysTick) { for (int i=0; i<4; i++) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,parent->song.linearPitch?chan[i].pitch:0,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,parent->song.linearPitch?chan[i].pitch2:0,chipClock,CHIP_DIVIDER); if ((i==0 && !(audctl&64)) || (i==2 && !(audctl&32)) || i==1 || i==3) { chan[i].freq/=7; @@ -194,6 +194,11 @@ void DivPlatformPOKEY::tick(bool sysTick) { chan[i].freq>>=2; } + // non-linear pitch + if (parent->song.linearPitch==0) { + chan[i].freq-=chan[i].pitch; + } + if (--chan[i].freq<0) chan[i].freq=0; // snap buzz periods diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index dcf823e4..c20fe161 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -781,21 +781,17 @@ void DivEngine::processRow(int i, bool afterDelay) { dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; case 0x07: // tremolo - // TODO - // this effect is really weird. i thought it would alter the tremolo depth but turns out it's completely different - // this is how it works: - // - 07xy enables tremolo - // - when enabled, a "low" boundary is calculated based on the current volume - // - then a volume slide down starts to the low boundary, and then when this is reached a volume slide up begins - // - this process repeats until 0700 or 0Axy are found - // - note that a volume value does not stop tremolo - instead it glitches this whole thing up if (chan[i].tremoloDepth==0) { chan[i].tremoloPos=0; } chan[i].tremoloDepth=effectVal&15; chan[i].tremoloRate=effectVal>>4; - // tremolo and vol slides are incompatiblw - chan[i].volSpeed=0; + if (chan[i].tremoloDepth!=0) { + chan[i].volSpeed=0; + } else { + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); + } break; case 0x0a: // volume ramp // TODO: non-0x-or-x0 value should be treated as 00 diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 57bf7e09..5f9b2554 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -54,7 +54,7 @@ void DivSample::putSampleData(SafeWriter* w) { w->writeC(depth); w->writeC(loopMode); w->writeC(brrEmphasis); - w->writeC(0); // reserved + w->writeC(dither); w->writeI(loop?loopStart:-1); w->writeI(loop?loopEnd:-1); @@ -131,8 +131,11 @@ DivDataErrors DivSample::readSampleData(SafeReader& reader, short version) { } else { reader.readC(); } - // reserved - reader.readC(); + if (version>=159) { + dither=reader.readC()&1; + } else { + reader.readC(); + } loopStart=reader.readI(); loopEnd=reader.readI(); @@ -1196,8 +1199,23 @@ void DivSample::render(unsigned int formatMask) { } if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_8BIT)) { // 8-bit PCM if (!initInternal(DIV_SAMPLE_DEPTH_8BIT,samples)) return; - for (unsigned int i=0; i>8; + if (dither) { + unsigned short lfsr=0x6438; + unsigned short lfsr1=0x1283; + signed char errorLast=0; + signed char errorCur=0; + for (unsigned int i=0; i>8; + errorLast=errorCur; + errorCur=(val<<8)-data16[i]; + data8[i]=CLAMP(val-((((errorLast+errorCur)>>1)+(lfsr&0xff))>>8),-128,127); + lfsr=(lfsr<<1)|(((lfsr>>1)^(lfsr>>2)^(lfsr>>4)^(lfsr>>15))&1); + lfsr1=(lfsr1<<1)|(((lfsr1>>1)^(lfsr1>>2)^(lfsr1>>4)^(lfsr1>>15))&1); + } + } else { + for (unsigned int i=0; i>8; + } } } if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_BRR)) { // BRR @@ -1277,9 +1295,9 @@ DivSampleHistory* DivSample::prepareUndo(bool data, bool doNotPush) { duplicate=new unsigned char[getCurBufLen()]; memcpy(duplicate,getCurBuf(),getCurBufLen()); } - h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,rate,centerRate,loopStart,loopEnd,loop,brrEmphasis,loopMode); + h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,rate,centerRate,loopStart,loopEnd,loop,brrEmphasis,dither,loopMode); } else { - h=new DivSampleHistory(depth,rate,centerRate,loopStart,loopEnd,loop,brrEmphasis,loopMode); + h=new DivSampleHistory(depth,rate,centerRate,loopStart,loopEnd,loop,brrEmphasis,dither,loopMode); } if (!doNotPush) { while (!redoHist.empty()) { @@ -1312,6 +1330,8 @@ DivSampleHistory* DivSample::prepareUndo(bool data, bool doNotPush) { loopStart=h->loopStart; \ loopEnd=h->loopEnd; \ loop=h->loop; \ + brrEmphasis=h->brrEmphasis; \ + dither=h->dither; \ loopMode=h->loopMode; diff --git a/src/engine/sample.h b/src/engine/sample.h index c2f08ced..ce1bf1fa 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -61,10 +61,10 @@ struct DivSampleHistory { unsigned int length, samples; DivSampleDepth depth; int rate, centerRate, loopStart, loopEnd; - bool loop, brrEmphasis; + bool loop, brrEmphasis, dither; DivSampleLoopMode loopMode; bool hasSample; - DivSampleHistory(void* d, unsigned int l, unsigned int s, DivSampleDepth de, int r, int cr, int ls, int le, bool lp, bool be, DivSampleLoopMode lm): + DivSampleHistory(void* d, unsigned int l, unsigned int s, DivSampleDepth de, int r, int cr, int ls, int le, bool lp, bool be, bool di, DivSampleLoopMode lm): data((unsigned char*)d), length(l), samples(s), @@ -75,9 +75,10 @@ struct DivSampleHistory { loopEnd(le), loop(lp), brrEmphasis(be), + dither(di), loopMode(lm), hasSample(true) {} - DivSampleHistory(DivSampleDepth de, int r, int cr, int ls, int le, bool lp, bool be, DivSampleLoopMode lm): + DivSampleHistory(DivSampleDepth de, int r, int cr, int ls, int le, bool lp, bool be, bool di, DivSampleLoopMode lm): data(NULL), length(0), samples(0), @@ -88,6 +89,7 @@ struct DivSampleHistory { loopEnd(le), loop(lp), brrEmphasis(be), + dither(di), loopMode(lm), hasSample(false) {} ~DivSampleHistory(); @@ -108,7 +110,7 @@ struct DivSample { // - 10: VOX ADPCM // - 16: 16-bit PCM DivSampleDepth depth; - bool loop, brrEmphasis; + bool loop, brrEmphasis, dither; // valid values are: // - 0: Forward loop // - 1: Backward loop @@ -323,6 +325,7 @@ struct DivSample { depth(DIV_SAMPLE_DEPTH_16BIT), loop(false), brrEmphasis(true), + dither(false), loopMode(DIV_SAMPLE_LOOP_FORWARD), data8(NULL), data16(NULL), diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 8f867322..eb10521a 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -2098,6 +2098,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p if (nextTick(false,true)) { if (trailing) beenOneLoopAlready=true; trailing=true; + if (!loop) countDown=0; for (int i=0; iinit(); // write default ZSM data header w->write("zm",2); // magic header @@ -50,7 +50,7 @@ void DivZSM::init(unsigned int rate) { w->writeS((unsigned short)rate); // 2 reserved bytes (set to zero) w->writeS(0x00); - tickRate = rate; + tickRate=rate; loopOffset=-1; numWrites=0; memset(&ymState,-1,sizeof(ymState)); @@ -63,44 +63,43 @@ int DivZSM::getoffset() { } void DivZSM::writeYM(unsigned char a, unsigned char v) { - int lastMask = ymMask; + int lastMask=ymMask; if (a==0x19 && v>=0x80) a=0x1a; // AMD/PSD use same reg addr. store PMD as 0x1a - if (a==0x08 && (v&0xf8)) ymMask |= (1 << (v & 0x07)); // mark chan as in-use if keyDN - if (a!=0x08) ymState[ym_NEW][a] = v; // cache the newly-written value + if (a==0x08 && (v&0xf8)) ymMask|=(1<<(v&0x07)); // mark chan as in-use if keyDN + if (a!=0x08) ymState[ym_NEW][a]=v; // cache the newly-written value bool writeit=false; // used to suppress spurious writes to unused channels - if (a < 0x20) { - if (a == 0x08) { + if (a<0x20) { + if (a==0x08) { // write keyUPDN messages if channel is active. - writeit = (ymMask & (1 << (v & 0x07))) > 0; - } - else { + writeit=(ymMask&(1<<(v&0x07)))>0; + } else { // do not suppress global registers - writeit = true; + writeit=true; } } else { - writeit = (ymMask & (1 << (a & 0x07))) > 0; // a&0x07 = chan ID for regs >=0x20 + writeit=(ymMask&(1<<(a&0x07)))>0; // a&0x07 = chan ID for regs >=0x20 } - if (lastMask != ymMask) { + if (lastMask!=ymMask) { // if the ymMask just changed, then the channel has become active. - // This can only happen on a KeyDN event, so voice = v & 0x07 + // This can only happen on a KeyDN event, so voice=v&0x07 // insert a keyUP just to be safe. ymwrites.push_back(DivRegWrite(0x08,v&0x07)); numWrites++; // flush the ym_NEW cached states for this channel into the ZSM.... - for ( int i=0x20 + (v&0x07); i <= 0xff ; i+=8) { - if (ymState[ym_NEW][i] != ymState[ym_PREV][i]) { + for (int i=0x20+(v&0x07); i<=0xff; i+=8) { + if (ymState[ym_NEW][i]!=ymState[ym_PREV][i]) { ymwrites.push_back(DivRegWrite(i,ymState[ym_NEW][i])); numWrites++; // ...and update the shadow - ymState[ym_PREV][i] = ymState[ym_NEW][i]; + ymState[ym_PREV][i]=ymState[ym_NEW][i]; } } } // Handle the current write if channel is active - if (writeit && ((ymState[ym_NEW][a] != ymState[ym_PREV][a])||a==0x08) ) { + if (writeit && ((ymState[ym_NEW][a]!=ymState[ym_PREV][a]) || a==0x08)) { // update YM shadow if not the KeyUPDN register. - if (a!=0x008) ymState[ym_PREV][a] = ymState[ym_NEW][a]; - // if reg = PMD, then change back to real register 0x19 + if (a!=8) ymState[ym_PREV][a]=ymState[ym_NEW][a]; + // if reg=PMD, then change back to real register 0x19 if (a==0x1a) a=0x19; ymwrites.push_back(DivRegWrite(a,v)); numWrites++; @@ -109,24 +108,26 @@ void DivZSM::writeYM(unsigned char a, unsigned char v) { void DivZSM::writePSG(unsigned char a, unsigned char v) { // TODO: suppress writes to PSG voice that is not audible (volume=0) - if (a >= 64) { + if (a>=64) { logD ("ZSM: ignoring VERA PSG write a=%02x v=%02x",a,v); return; } - if(psgState[psg_PREV][a] == v) { - if (psgState[psg_NEW][a] != v) + if (psgState[psg_PREV][a]==v) { + if (psgState[psg_NEW][a]!=v) { // NEW value is being reset to the same as PREV value // so it is no longer a new write. numWrites--; + } } else { - if (psgState[psg_PREV][a] == psgState[psg_NEW][a]) + if (psgState[psg_PREV][a]==psgState[psg_NEW][a]) { // if this write changes the NEW cached value to something other // than the PREV value, then this is a new write. numWrites++; + } } - psgState[psg_NEW][a] = v; - // mark channel as used in the psgMask if volume is set > 0. - if ((a % 4 == 2) && (v & 0x3f)) psgMask |= (1 << (a>>2)); + psgState[psg_NEW][a]=v; + // mark channel as used in the psgMask if volume is set>0. + if ((a%4==2) && (v&0x3f)) psgMask|=(1<<(a>>2)); } void DivZSM::writePCM(unsigned char a, unsigned char v) { @@ -135,7 +136,7 @@ void DivZSM::writePCM(unsigned char a, unsigned char v) { void DivZSM::tick(int numticks) { flushWrites(); - ticks += numticks; + ticks+=numticks; } void DivZSM::setLoopPoint() { @@ -154,12 +155,14 @@ void DivZSM::setLoopPoint() { memset(&ymState[ym_PREV],-1,sizeof(ymState[ym_PREV])); // ... and cache (except for unused channels) memset(&ymState[ym_NEW],-1,0x20); - for (int chan=0; chan<8 ; chan++) { + for (int chan=0; chan<8; chan++) { // do not clear state for as-yet-unused channels - if (!(ymMask & (1<writeC(ZSM_EOF); // update channel use masks. w->seek(0x09,SEEK_SET); - w->writeC((unsigned char)(ymMask & 0xff)); - w->writeS((short)(psgMask & 0xffff)); + w->writeC((unsigned char)(ymMask&0xff)); + w->writeS((short)(psgMask&0xffff)); // todo: put PCM offset/data writes here once defined in ZSM standard. return w; } @@ -179,16 +182,16 @@ void DivZSM::flushWrites() { logD("ZSM: flushWrites.... numwrites=%d ticks=%d ymwrites=%d",numWrites,ticks,ymwrites.size()); if (numWrites==0) return; flushTicks(); // only flush ticks if there are writes pending. - for (unsigned char i=0;i<64;i++) { - if (psgState[psg_NEW][i] == psgState[psg_PREV][i]) continue; + for (unsigned char i=0; i<64; i++) { + if (psgState[psg_NEW][i]==psgState[psg_PREV][i]) continue; psgState[psg_PREV][i]=psgState[psg_NEW][i]; w->writeC(i); w->writeC(psgState[psg_NEW][i]); } - int n=0; // n = completed YM writes. used to determine when to write the CMD byte... + int n=0; // n=completed YM writes. used to determine when to write the CMD byte... for (DivRegWrite& write: ymwrites) { - if (n%ZSM_YM_MAX_WRITES == 0) { - if(ymwrites.size()-n > ZSM_YM_MAX_WRITES) { + if (n%ZSM_YM_MAX_WRITES==0) { + if (ymwrites.size()-n>ZSM_YM_MAX_WRITES) { w->writeC((unsigned char)(ZSM_YM_CMD+ZSM_YM_MAX_WRITES)); logD("ZSM: YM-write: %d (%02x) [max]",ZSM_YM_MAX_WRITES,ZSM_YM_MAX_WRITES+ZSM_YM_CMD); } else { @@ -205,10 +208,10 @@ void DivZSM::flushWrites() { } void DivZSM::flushTicks() { - while (ticks > ZSM_DELAY_MAX) { + while (ticks>ZSM_DELAY_MAX) { logD("ZSM: write delay %d (max)",ZSM_DELAY_MAX); w->writeC((unsigned char)(ZSM_DELAY_CMD+ZSM_DELAY_MAX)); - ticks -= ZSM_DELAY_MAX; + ticks-=ZSM_DELAY_MAX; } if (ticks>0) { logD("ZSM: write delay %d",ticks); diff --git a/src/engine/zsmOps.cpp b/src/engine/zsmOps.cpp index 0207a93d..deddd4ec 100644 --- a/src/engine/zsmOps.cpp +++ b/src/engine/zsmOps.cpp @@ -27,36 +27,42 @@ constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0; constexpr int MASTER_CLOCK_MASK=(sizeof(void*)==8)?0xff:0; SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) { + int VERA=-1; + int YM=-1; + int IGNORED=0; - int VERA = -1; - int YM = -1; - int IGNORED = 0; - - //loop = false; // find indexes for YM and VERA. Ignore other systems. for (int i=0; i= 0) { IGNORED++;break; } - VERA = i; + if (VERA>=0) { + IGNORED++; + break; + } + VERA=i; logD("VERA detected as chip id %d",i); break; case DIV_SYSTEM_YM2151: - if (YM >= 0) { IGNORED++;break; } - YM = i; + if (YM>=0) { + IGNORED++; + break; + } + YM=i; logD("YM detected as chip id %d",i); break; default: IGNORED++; logD("Ignoring chip %d systemID %d",i,song.system[i]); + break; } } - if (VERA < 0 && YM < 0) { - logE("No supported systems for ZSM"); - return NULL; + if (VERA<0 && YM<0) { + logE("No supported systems for ZSM"); + return NULL; } - if (IGNORED > 0) + if (IGNORED>0) { logW("ZSM export ignoring %d unsupported system%c",IGNORED,IGNORED>1?'s':' '); + } stop(); repeatPattern=false; @@ -64,7 +70,7 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) { BUSY_BEGIN_SOFT; double origRate=got.rate; - got.rate=zsmrate & 0xffff; + got.rate=zsmrate&0xffff; // determine loop point int loopOrder=0; @@ -89,15 +95,14 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) { //size_t tickCount=0; bool done=false; int loopPos=-1; - int writeCount=0; int fracWait=0; // accumulates fractional ticks - if (VERA >= 0) disCont[VERA].dispatch->toggleRegisterDump(true); - if (YM >= 0) { + if (VERA>=0) disCont[VERA].dispatch->toggleRegisterDump(true); + if (YM>=0) { disCont[YM].dispatch->toggleRegisterDump(true); // emit LFO initialization commands - zsm.writeYM(0x18,0); // freq = 0 - zsm.writeYM(0x19,0x7F); // AMD = 7F - zsm.writeYM(0x19,0xFF); // PMD = 7F + zsm.writeYM(0x18,0); // freq=0 + zsm.writeYM(0x19,0x7F); // AMD =7F + zsm.writeYM(0x19,0xFF); // PMD =7F // TODO: incorporate the Furnace meta-command for init data and filter // out writes to otherwise-unused channels. } @@ -126,35 +131,35 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) { int i=0; // dump YM writes first if (j==0) { - if (YM < 0) + if (YM<0) { continue; - else + } else { i=YM; + } } // dump VERA writes second if (j==1) { - if (VERA < 0) + if (VERA<0) { continue; - else { + } else { i=VERA; } } std::vector& writes=disCont[i].dispatch->getRegisterWrites(); - if (writes.size() > 0) - logD("zsmOps: Writing %d messages to chip %d",writes.size(), i); + if (writes.size()>0) + logD("zsmOps: Writing %d messages to chip %d",writes.size(),i); for (DivRegWrite& write: writes) { - if (i==YM) zsm.writeYM(write.addr&0xff, write.val); - if (i==VERA) zsm.writePSG(write.addr&0xff, write.val); - writeCount++; + if (i==YM) zsm.writeYM(write.addr&0xff,write.val); + if (i==VERA) zsm.writePSG(write.addr&0xff,write.val); } writes.clear(); } // write wait int totalWait=cycles>>MASTER_CLOCK_PREC; - fracWait += cycles & MASTER_CLOCK_MASK; - totalWait += fracWait>>MASTER_CLOCK_PREC; - fracWait &= MASTER_CLOCK_MASK; + fracWait+=cycles&MASTER_CLOCK_MASK; + totalWait+=fracWait>>MASTER_CLOCK_PREC; + fracWait&=MASTER_CLOCK_MASK; if (totalWait>0) { zsm.tick(totalWait); //tickCount+=totalWait; @@ -163,9 +168,9 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) { // end of song // done - close out. - got.rate = origRate; - if (VERA >= 0) disCont[VERA].dispatch->toggleRegisterDump(false); - if (YM >= 0) disCont[YM].dispatch->toggleRegisterDump(false); + got.rate=origRate; + if (VERA>=0) disCont[VERA].dispatch->toggleRegisterDump(false); + if (YM>=0) disCont[YM].dispatch->toggleRegisterDump(false); remainingLoops=-1; playing=false; diff --git a/src/gui/about.cpp b/src/gui/about.cpp index fa020b98..449a2714 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -85,6 +85,7 @@ const char* aboutLine[]={ "Fragmare", "freq-mod", "gtr3qq", + "Hortus", "iyatemu", "JayBOB18", "Jimmy-DS", @@ -102,6 +103,7 @@ const char* aboutLine[]={ "MelonadeM", "Miker", "nicco1690", + "niffuM", "", "NyaongI", "potatoTeto", diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index 7bcece95..d7d3c9fd 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -152,10 +152,10 @@ void FurnaceGUI::drawChanOsc() { if (chanOscUseGrad) { if (chanOscGradTex==NULL) { - chanOscGradTex=SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ABGR8888,SDL_TEXTUREACCESS_STREAMING,chanOscGrad.width,chanOscGrad.height); + chanOscGradTex=rend->createTexture(true,chanOscGrad.width,chanOscGrad.height); if (chanOscGradTex==NULL) { - logE("error while creating gradient texture! %s",SDL_GetError()); + logE("error while creating gradient texture!"); } else { updateChanOscGradTex=true; } @@ -170,16 +170,16 @@ void FurnaceGUI::drawChanOsc() { if (chanOscGradTex!=NULL) { if (updateChanOscGradTex) { chanOscGrad.render(); - if (SDL_UpdateTexture(chanOscGradTex,NULL,chanOscGrad.grad.get(),chanOscGrad.width*4)==0) { + if (rend->updateTexture(chanOscGradTex,chanOscGrad.grad.get(),chanOscGrad.width*4)) { updateChanOscGradTex=false; } else { - logE("error while updating gradient texture! %s",SDL_GetError()); + logE("error while updating gradient texture!"); } } ImVec2 gradLeft=ImGui::GetCursorPos(); ImVec2 gradSize=ImVec2(400.0f*dpiScale,400.0f*dpiScale); - ImGui::Image(chanOscGradTex,gradSize); + ImGui::Image(rend->getTextureID(chanOscGradTex),gradSize); ImVec2 gradLeftAbs=ImGui::GetItemRectMin(); if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { if (chanOscGrad.points.size()<32) { diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 72269988..2ccbca94 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -287,21 +287,17 @@ void FurnaceGUI::insListItem(int i, int dir, int asset) { } else { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); } - if (ImGui::Selectable(name.c_str(),(i==-1)?(curIns<0 || curIns>=e->song.insLen):(curIns==i))) { + bool insReleased=ImGui::Selectable(name.c_str(),(i==-1)?(curIns<0 || curIns>=e->song.insLen):(curIns==i)); + bool insPressed=ImGui::IsItemActivated(); + if (insReleased || (!insListDir && insPressed)) { curIns=i; wavePreviewInit=true; updateFMPreview=true; lastAssetType=0; - if (insListDir) nextWindow=GUI_WINDOW_PATTERN; + if (settings.insFocusesPattern && patternOpen) + nextWindow=GUI_WINDOW_PATTERN; } if (wantScrollList && curIns==i) ImGui::SetScrollHereY(); - if (settings.insFocusesPattern && patternOpen && ImGui::IsItemActivated()) { - if (!insListDir) nextWindow=GUI_WINDOW_PATTERN; - curIns=i; - wavePreviewInit=true; - updateFMPreview=true; - lastAssetType=0; - } if (ImGui::IsItemHovered() && i>=0 && !mobileUI) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); ImGui::SetTooltip("%s",insType); diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 7520162f..87d74e6c 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -791,6 +791,8 @@ void FurnaceGUI::doAction(int what) { sample->loopEnd=prevSample->loopEnd; sample->loop=prevSample->loop; sample->loopMode=prevSample->loopMode; + sample->brrEmphasis=prevSample->brrEmphasis; + sample->dither=prevSample->dither; sample->depth=prevSample->depth; if (sample->init(prevSample->samples)) { if (prevSample->getCurBuf()!=NULL) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e5680c3a..e95ecdda 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -30,7 +30,6 @@ #include "imgui.h" #include "imgui_internal.h" #include "imgui_impl_sdl.h" -#include "imgui_impl_sdlrenderer.h" #include "ImGuiFileDialog.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" @@ -1980,7 +1979,7 @@ int FurnaceGUI::save(String path, int dmfVersion) { if (dmfVersion<24) dmfVersion=24; w=e->saveDMF(dmfVersion); } else { - w=e->saveFur(); + w=e->saveFur(false,settings.newPatternFormat); } if (w==NULL) { lastError=e->getLastError(); @@ -2957,6 +2956,12 @@ int _processEvent(void* instance, SDL_Event* event) { return ((FurnaceGUI*)instance)->processEvent(event); } +#if SDL_VERSION_ATLEAST(2,0,17) +#define VALID_MODS KMOD_NUM|KMOD_CAPS|KMOD_SCROLL +#else +#define VALID_MODS KMOD_NUM|KMOD_CAPS +#endif + int FurnaceGUI::processEvent(SDL_Event* ev) { if (introPos<11.0 && !shortIntro) return 1; #ifdef IS_MOBILE @@ -2968,7 +2973,7 @@ int FurnaceGUI::processEvent(SDL_Event* ev) { } #endif if (ev->type==SDL_KEYDOWN) { - if (!ev->key.repeat && latchTarget==0 && !wantCaptureKeyboard && !sampleMapWaitingInput && (ev->key.keysym.mod&(~(KMOD_NUM|KMOD_CAPS|KMOD_SCROLL)))==0) { + if (!ev->key.repeat && latchTarget==0 && !wantCaptureKeyboard && !sampleMapWaitingInput && (ev->key.keysym.mod&(~(VALID_MODS)))==0) { if (settings.notePreviewBehavior==0) return 1; switch (curWindow) { case GUI_WINDOW_SAMPLE_EDIT: @@ -3455,6 +3460,7 @@ bool FurnaceGUI::loop() { break; } break; +#if SDL_VERSION_ATLEAST(2,0,17) case SDL_DISPLAYEVENT: { switch (ev.display.event) { case SDL_DISPLAYEVENT_CONNECTED: @@ -3472,6 +3478,7 @@ bool FurnaceGUI::loop() { } break; } +#endif case SDL_KEYDOWN: if (!ImGui::GetIO().WantCaptureKeyboard) { keyDown(ev); @@ -3542,8 +3549,8 @@ bool FurnaceGUI::loop() { } } // update canvas size as well - if (SDL_GetRendererOutputSize(sdlRend,&canvasW,&canvasH)!=0) { - logW("loop: error while getting output size! %s",SDL_GetError()); + if (!rend->getOutputSize(canvasW,canvasH)) { + logW("loop: error while getting output size!"); } else { //logV("updateWindow: canvas size %dx%d",canvasW,canvasH); // and therefore window size @@ -3768,7 +3775,7 @@ bool FurnaceGUI::loop() { layoutTimeBegin=SDL_GetPerformanceCounter(); - if (!ImGui_ImplSDLRenderer_NewFrame()) { + if (!rend->newFrame()) { fontsFailed=true; } ImGui_ImplSDL2_NewFrame(sdlWin); @@ -4352,6 +4359,7 @@ bool FurnaceGUI::loop() { MEASURE(log,drawLog()); MEASURE(compatFlags,drawCompatFlags()); MEASURE(stats,drawStats()); + MEASURE(chanOsc,drawChanOsc()); } else { globalWinFlags=0; ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoWindowMenuButton|ImGuiDockNodeFlags_NoMove|ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0); @@ -4423,7 +4431,7 @@ bool FurnaceGUI::loop() { portrait=(scrWgetOutputSize(canvasW,canvasH); #endif if (patternOpen) nextWindow=GUI_WINDOW_PATTERN; #ifdef __APPLE__ @@ -5679,7 +5687,7 @@ bool FurnaceGUI::loop() { } } logD("saving backup..."); - SafeWriter* w=e->saveFur(true); + SafeWriter* w=e->saveFur(true,true); logV("writing file..."); if (w!=NULL) { @@ -5801,30 +5809,24 @@ bool FurnaceGUI::loop() { } } - SDL_SetRenderDrawColor(sdlRend,uiColors[GUI_COLOR_BACKGROUND].x*255, - uiColors[GUI_COLOR_BACKGROUND].y*255, - uiColors[GUI_COLOR_BACKGROUND].z*255, - uiColors[GUI_COLOR_BACKGROUND].w*255); - SDL_RenderClear(sdlRend); + rend->clear(uiColors[GUI_COLOR_BACKGROUND]); renderTimeBegin=SDL_GetPerformanceCounter(); ImGui::Render(); renderTimeEnd=SDL_GetPerformanceCounter(); - ImGui_ImplSDLRenderer_RenderDrawData(ImGui::GetDrawData()); + rend->renderGUI(); if (mustClear) { - SDL_RenderClear(sdlRend); + rend->clear(ImVec4(0,0,0,0)); mustClear--; } else { if (initialScreenWipe>0.0f && !settings.disableFadeIn) { WAKE_UP; initialScreenWipe-=ImGui::GetIO().DeltaTime*5.0f; if (initialScreenWipe>0.0f) { - SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_BLEND); - SDL_SetRenderDrawColor(sdlRend,0,0,0,255*pow(initialScreenWipe,2.0f)); - SDL_RenderFillRect(sdlRend,NULL); + rend->wipe(pow(initialScreenWipe,2.0f)); } } } - SDL_RenderPresent(sdlRend); + rend->present(); layoutTimeDelta=layoutTimeEnd-layoutTimeBegin; renderTimeDelta=renderTimeEnd-renderTimeBegin; @@ -5859,9 +5861,11 @@ bool FurnaceGUI::loop() { ImGui::GetIO().Fonts->Clear(); mainFont=ImGui::GetIO().Fonts->AddFontDefault(); patFont=mainFont; - ImGui_ImplSDLRenderer_DestroyFontsTexture(); + if (rend) rend->destroyFontsTexture(); if (!ImGui::GetIO().Fonts->Build()) { logE("error again while building font atlas!"); + } else { + rend->createFontsTexture(); } } @@ -6017,7 +6021,9 @@ bool FurnaceGUI::init() { e->setAutoNotePoly(noteInputPoly); SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER,"1"); +#if SDL_VERSION_ATLEAST(2,0,17) SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS,"0"); +#endif SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS,"0"); // don't disable compositing on KWin #if SDL_VERSION_ATLEAST(2,0,22) @@ -6026,7 +6032,18 @@ bool FurnaceGUI::init() { #endif // initialize SDL - SDL_Init(SDL_INIT_VIDEO|SDL_INIT_HAPTIC); + logD("initializing video..."); + if (SDL_Init(SDL_INIT_VIDEO)!=0) { + logE("could not initialize video! %s",SDL_GetError()); + return false; + } + +#ifdef IS_MOBILE + logD("initializing haptic..."); + if (SDL_Init(SDL_INIT_HAPTIC)!=0) { + logW("could not initialize haptic! %s",SDL_GetError()); + } +#endif const char* videoBackend=SDL_GetCurrentVideoDriver(); if (videoBackend!=NULL) { @@ -6127,7 +6144,28 @@ bool FurnaceGUI::init() { logV("window size: %dx%d",scrW,scrH); - sdlWin=SDL_CreateWindow("Furnace",scrX,scrY,scrW,scrH,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|(scrMax?SDL_WINDOW_MAXIMIZED:0)|(fullScreen?SDL_WINDOW_FULLSCREEN_DESKTOP:0)); + if (!initRender()) { + if (settings.renderBackend=="OpenGL") { + settings.renderBackend=""; + e->setConf("renderBackend",""); + e->saveConf(); + lastError=fmt::sprintf("\r\nthe render backend has been set to a safe value. please restart Furnace."); + } else { + lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError()); + if (!settings.renderDriver.empty()) { + settings.renderDriver=""; + e->setConf("renderDriver",""); + e->saveConf(); + lastError=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace."); + } + } + return false; + } + + rend->preInit(); + + logD("creating window..."); + sdlWin=SDL_CreateWindow("Furnace",scrX,scrY,scrW,scrH,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|(scrMax?SDL_WINDOW_MAXIMIZED:0)|(fullScreen?SDL_WINDOW_FULLSCREEN_DESKTOP:0)|rend->getWindowFlags()); if (sdlWin==NULL) { lastError=fmt::sprintf("could not open window! %s",SDL_GetError()); return false; @@ -6191,22 +6229,28 @@ bool FurnaceGUI::init() { SDL_SetHint(SDL_HINT_RENDER_DRIVER,settings.renderDriver.c_str()); } - sdlRend=SDL_CreateRenderer(sdlWin,-1,SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC|SDL_RENDERER_TARGETTEXTURE); - - if (sdlRend==NULL) { - lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError()); - if (!settings.renderDriver.empty()) { - settings.renderDriver=""; - e->setConf("renderDriver",""); + logD("starting render backend..."); + if (!rend->init(sdlWin)) { + if (settings.renderBackend=="OpenGL") { + settings.renderBackend=""; + e->setConf("renderBackend",""); e->saveConf(); - lastError=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace."); + lastError=fmt::sprintf("\r\nthe render backend has been set to a safe value. please restart Furnace."); + } else { + lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError()); + if (!settings.renderDriver.empty()) { + settings.renderDriver=""; + e->setConf("renderDriver",""); + e->saveConf(); + lastError=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace."); + } } return false; } // try acquiring the canvas size - if (SDL_GetRendererOutputSize(sdlRend,&canvasW,&canvasH)!=0) { - logW("could not get renderer output size! %s",SDL_GetError()); + if (!rend->getOutputSize(canvasW,canvasH)) { + logW("could not get renderer output size!"); } else { logV("canvas size: %dx%d",canvasW,canvasH); } @@ -6214,30 +6258,39 @@ bool FurnaceGUI::init() { // special consideration for Wayland if (settings.dpiScale<0.5f) { if (strcmp(videoBackend,"wayland")==0) { - dpiScale=(double)canvasW/(double)scrW; + if (scrW<1) { + logW("screen width is zero!\n"); + dpiScale=1.0; + } else { + dpiScale=(double)canvasW/(double)scrW; + } } } - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); + updateWindowTitle(); - ImGui_ImplSDL2_InitForSDLRenderer(sdlWin,sdlRend); - ImGui_ImplSDLRenderer_Init(sdlRend); + rend->clear(ImVec4(0.0,0.0,0.0,1.0)); + rend->present(); + + logD("preparing user interface..."); + rend->initGUI(sdlWin); applyUISettings(); + logD("building font..."); if (!ImGui::GetIO().Fonts->Build()) { logE("error while building font atlas!"); showError("error while loading fonts! please check your settings."); ImGui::GetIO().Fonts->Clear(); mainFont=ImGui::GetIO().Fonts->AddFontDefault(); patFont=mainFont; - ImGui_ImplSDLRenderer_DestroyFontsTexture(); + if (rend) rend->destroyFontsTexture(); if (!ImGui::GetIO().Fonts->Build()) { logE("error again while building font atlas!"); } } + logD("preparing layout..."); strncpy(finalLayoutPath,(e->getConfigPath()+String(LAYOUT_INI)).c_str(),4095); backupPath=e->getConfigPath(); if (backupPath.size()>0) { @@ -6249,8 +6302,6 @@ bool FurnaceGUI::init() { ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_DockingEnable; toggleMobileUI(mobileUI,true); - updateWindowTitle(); - for (int i=0; iquitGUI(); ImGui_ImplSDL2_Shutdown(); ImGui::DestroyContext(); - SDL_DestroyRenderer(sdlRend); + quitRender(); SDL_DestroyWindow(sdlWin); if (vibrator) { @@ -6467,8 +6521,9 @@ bool FurnaceGUI::finish() { FurnaceGUI::FurnaceGUI(): e(NULL), + renderBackend(GUI_BACKEND_SDL), + rend(NULL), sdlWin(NULL), - sdlRend(NULL), vibrator(NULL), vibratorAvailable(false), sampleTex(NULL), diff --git a/src/gui/gui.h b/src/gui/gui.h index cdfa2359..eccb9502 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -24,7 +24,6 @@ #include "../engine/waveSynth.h" #include "imgui.h" #include "imgui_impl_sdl.h" -#include "imgui_impl_sdlrenderer.h" #include #include #include @@ -70,19 +69,53 @@ #define FM_PREVIEW_SIZE 512 +enum FurnaceGUIRenderBackend { + GUI_BACKEND_SDL=0, + GUI_BACKEND_GL +}; + +#ifdef HAVE_RENDER_SDL +#define GUI_BACKEND_DEFAULT GUI_BACKEND_SDL +#define GUI_BACKEND_DEFAULT_NAME "SDL" +#else +#define GUI_BACKEND_DEFAULT GUI_BACKEND_GL +#define GUI_BACKEND_DEFAULT_NAME "OpenGL" +#endif + // TODO: // - add colors for FM envelope and waveform // - maybe add "alternate" color for FM modulators/carriers (a bit difficult) enum FurnaceGUIColors { GUI_COLOR_BACKGROUND=0, GUI_COLOR_FRAME_BACKGROUND, + GUI_COLOR_FRAME_BACKGROUND_CHILD, + GUI_COLOR_FRAME_BACKGROUND_POPUP, GUI_COLOR_MODAL_BACKDROP, GUI_COLOR_HEADER, GUI_COLOR_TEXT, GUI_COLOR_ACCENT_PRIMARY, GUI_COLOR_ACCENT_SECONDARY, + GUI_COLOR_TITLE_INACTIVE, + GUI_COLOR_TITLE_COLLAPSED, + GUI_COLOR_MENU_BAR, GUI_COLOR_BORDER, GUI_COLOR_BORDER_SHADOW, + GUI_COLOR_SCROLL_BACKGROUND, + GUI_COLOR_SCROLL, + GUI_COLOR_SCROLL_HOVER, + GUI_COLOR_SCROLL_ACTIVE, + GUI_COLOR_SEPARATOR, + GUI_COLOR_SEPARATOR_HOVER, + GUI_COLOR_SEPARATOR_ACTIVE, + GUI_COLOR_DOCKING_PREVIEW, + GUI_COLOR_DOCKING_EMPTY, + GUI_COLOR_TABLE_HEADER, + GUI_COLOR_TABLE_BORDER_HARD, + GUI_COLOR_TABLE_BORDER_SOFT, + GUI_COLOR_DRAG_DROP_TARGET, + GUI_COLOR_NAV_HIGHLIGHT, + GUI_COLOR_NAV_WIN_HIGHLIGHT, + GUI_COLOR_NAV_WIN_BACKDROP, GUI_COLOR_TOGGLE_OFF, GUI_COLOR_TOGGLE_ON, GUI_COLOR_EDITING, @@ -1170,7 +1203,7 @@ struct FurnaceGUIQueryResult { struct FurnaceGUIImage { unsigned char* data; - SDL_Texture* tex; + void* tex; int width, height, ch; FurnaceGUIImage(): @@ -1192,15 +1225,51 @@ struct FurnaceGUIPerfMetric { elapsed(0) {} }; +enum FurnaceGUIBlendMode { + GUI_BLEND_MODE_NONE=0, + GUI_BLEND_MODE_BLEND, + GUI_BLEND_MODE_ADD, + GUI_BLEND_MODE_MULTIPLY +}; + +class FurnaceGUIRender { + public: + virtual ImTextureID getTextureID(void* which); + virtual bool lockTexture(void* which, void** data, int* pitch); + virtual bool unlockTexture(void* which); + virtual bool updateTexture(void* which, void* data, int pitch); + virtual void* createTexture(bool dynamic, int width, int height); + virtual bool destroyTexture(void* which); + virtual void setTextureBlendMode(void* which, FurnaceGUIBlendMode mode); + virtual void setBlendMode(FurnaceGUIBlendMode mode); + virtual void clear(ImVec4 color); + virtual bool newFrame(); + virtual void createFontsTexture(); + virtual void destroyFontsTexture(); + virtual void renderGUI(); + virtual void wipe(float alpha); + virtual void present(); + virtual bool getOutputSize(int& w, int& h); + virtual int getWindowFlags(); + virtual void preInit(); + virtual bool init(SDL_Window* win); + virtual void initGUI(SDL_Window* win); + virtual void quitGUI(); + virtual bool quit(); + virtual ~FurnaceGUIRender(); +}; + class FurnaceGUI { DivEngine* e; + FurnaceGUIRenderBackend renderBackend; + FurnaceGUIRender* rend; + SDL_Window* sdlWin; - SDL_Renderer* sdlRend; SDL_Haptic* vibrator; bool vibratorAvailable; - - SDL_Texture* sampleTex; + + void* sampleTex; int sampleTexW, sampleTexH; bool updateSampleTex; @@ -1425,6 +1494,7 @@ class FurnaceGUI { int iCannotWait; int orderButtonPos; int compress; + int newPatternFormat; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -1432,6 +1502,7 @@ class FurnaceGUI { String midiInDevice; String midiOutDevice; String c163Name; + String renderBackend; String renderDriver; String initialSysName; String noteOffLabel; @@ -1571,6 +1642,7 @@ class FurnaceGUI { iCannotWait(0), orderButtonPos(2), compress(1), + newPatternFormat(1), maxUndoSteps(100), mainFontPath(""), patFontPath(""), @@ -1578,6 +1650,7 @@ class FurnaceGUI { midiInDevice(""), midiOutDevice(""), c163Name(""), + renderBackend(""), renderDriver(""), initialSysName("Sega Genesis/Mega Drive"), noteOffLabel("OFF"), @@ -1865,7 +1938,7 @@ class FurnaceGUI { bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex, chanOscUseGrad; ImVec4 chanOscColor; Gradient2D chanOscGrad; - SDL_Texture* chanOscGradTex; + void* chanOscGradTex; float chanOscLP0[DIV_MAX_CHANS]; float chanOscLP1[DIV_MAX_CHANS]; float chanOscVol[DIV_MAX_CHANS]; @@ -2021,7 +2094,7 @@ class FurnaceGUI { void highlightWindow(const char* winName); FurnaceGUIImage* getImage(FurnaceGUIImages image); - SDL_Texture* getTexture(FurnaceGUIImages image, SDL_BlendMode blendMode=SDL_BLENDMODE_BLEND); + void* getTexture(FurnaceGUIImages image, FurnaceGUIBlendMode blendMode=GUI_BLEND_MODE_BLEND); void drawImage(ImDrawList* dl, FurnaceGUIImages image, const ImVec2& pos, const ImVec2& scale, double rotate, const ImVec2& uvMin, const ImVec2& uvMax, const ImVec4& imgColor); void drawMobileControls(); @@ -2170,6 +2243,9 @@ class FurnaceGUI { String encodeKeyMap(std::map& map); void decodeKeyMap(std::map& map, String source); + bool initRender(); + bool quitRender(); + const char* getSystemName(DivSystem which); public: diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index dc52def9..2989ae72 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -710,13 +710,34 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_BACKGROUND,"Background",ImVec4(0.1f,0.1f,0.1f,1.0f)), D(GUI_COLOR_FRAME_BACKGROUND,"",ImVec4(0.0f,0.0f,0.0f,0.85f)), + D(GUI_COLOR_FRAME_BACKGROUND_CHILD,"",ImVec4(0.0f,0.0f,0.0f,0.0f)), + D(GUI_COLOR_FRAME_BACKGROUND_POPUP,"",ImVec4(0.08f,0.08f,0.08f,0.94f)), D(GUI_COLOR_MODAL_BACKDROP,"",ImVec4(0.0f,0.0f,0.0f,0.55f)), D(GUI_COLOR_HEADER,"",ImVec4(0.2f,0.2f,0.2f,1.0f)), D(GUI_COLOR_TEXT,"",ImVec4(1.0f,1.0f,1.0f,1.0f)), D(GUI_COLOR_ACCENT_PRIMARY,"",ImVec4(0.06f,0.53f,0.98f,1.0f)), D(GUI_COLOR_ACCENT_SECONDARY,"",ImVec4(0.26f,0.59f,0.98f,1.0f)), + D(GUI_COLOR_TITLE_INACTIVE,"",ImVec4(0.04f,0.04f,0.04f,1.0f)), + D(GUI_COLOR_TITLE_COLLAPSED,"",ImVec4(0.0f,0.0f,0.0f,0.51f)), + D(GUI_COLOR_MENU_BAR,"",ImVec4(0.14f,0.14f,0.14f,1.0f)), D(GUI_COLOR_BORDER,"",ImVec4(0.43f,0.43f,0.5f,0.5f)), D(GUI_COLOR_BORDER_SHADOW,"",ImVec4(0.0f,0.0f,0.0f,0.0f)), + D(GUI_COLOR_SCROLL_BACKGROUND,"",ImVec4(0.02f,0.02f,0.02f,0.33f)), + D(GUI_COLOR_SCROLL,"",ImVec4(0.31f,0.31f,0.31f,1.0f)), + D(GUI_COLOR_SCROLL_HOVER,"",ImVec4(0.41f,0.41f,0.41f,1.0f)), + D(GUI_COLOR_SCROLL_ACTIVE,"",ImVec4(0.51f,0.51f,0.51f,1.0f)), + D(GUI_COLOR_SEPARATOR,"",ImVec4(0.43f,0.43f,0.5f,0.5f)), + D(GUI_COLOR_SEPARATOR_HOVER,"",ImVec4(0.1f,0.4f,0.75f,0.78f)), + D(GUI_COLOR_SEPARATOR_ACTIVE,"",ImVec4(0.1f,0.4f,0.75f,1.0f)), + D(GUI_COLOR_DOCKING_PREVIEW,"",ImVec4(0.26f,0.59f,0.98f,0.7f)), + D(GUI_COLOR_DOCKING_EMPTY,"",ImVec4(0.2f,0.2f,0.2f,1.0f)), + D(GUI_COLOR_TABLE_HEADER,"",ImVec4(0.19f,0.19f,0.2f,1.0f)), + D(GUI_COLOR_TABLE_BORDER_HARD,"",ImVec4(0.31f,0.31f,0.35f,1.0f)), + D(GUI_COLOR_TABLE_BORDER_SOFT,"",ImVec4(0.23f,0.23f,0.25f,1.0f)), + D(GUI_COLOR_DRAG_DROP_TARGET,"",ImVec4(1.0f,1.0f,0.0f,0.9f)), + D(GUI_COLOR_NAV_HIGHLIGHT,"",ImVec4(0.26f,0.59f,0.98f,1.0f)), + D(GUI_COLOR_NAV_WIN_HIGHLIGHT,"",ImVec4(1.0f,1.0f,1.0f,0.7f)), + D(GUI_COLOR_NAV_WIN_BACKDROP,"",ImVec4(0.8f,0.8f,0.8f,0.2f)), D(GUI_COLOR_TOGGLE_OFF,"",ImVec4(0.2f,0.2f,0.2f,1.0f)), D(GUI_COLOR_TOGGLE_ON,"",ImVec4(0.2f,0.6f,0.2f,1.0f)), D(GUI_COLOR_EDITING,"",ImVec4(0.2f,0.1f,0.1f,1.0f)), diff --git a/src/gui/image.cpp b/src/gui/image.cpp index 976504b8..c4528445 100644 --- a/src/gui/image.cpp +++ b/src/gui/image.cpp @@ -15,9 +15,6 @@ * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - * this license only applies to the code. for the license of each font used, - * see `papers/`. */ #include "gui.h" @@ -49,7 +46,7 @@ const unsigned int imageLen[GUI_IMAGE_MAX]={ image_pat_size }; -SDL_Texture* FurnaceGUI::getTexture(FurnaceGUIImages image, SDL_BlendMode blendMode) { +void* FurnaceGUI::getTexture(FurnaceGUIImages image, FurnaceGUIBlendMode blendMode) { FurnaceGUIImage* img=getImage(image); if (img==NULL) return NULL; @@ -57,14 +54,14 @@ SDL_Texture* FurnaceGUI::getTexture(FurnaceGUIImages image, SDL_BlendMode blendM if (img->width<=0 || img->height<=0) return NULL; if (img->tex==NULL) { - img->tex=SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ABGR8888,SDL_TEXTUREACCESS_STATIC,img->width,img->height); + img->tex=rend->createTexture(false,img->width,img->height); if (img->tex==NULL) { logE("error while creating image %d texture! %s",(int)image,SDL_GetError()); return NULL; } - SDL_SetTextureBlendMode(img->tex,blendMode); + rend->setTextureBlendMode(img->tex,blendMode); - if (SDL_UpdateTexture(img->tex,NULL,img->data,img->width*4)!=0) { + if (!rend->updateTexture(img->tex,img->data,img->width*4)) { logE("error while updating texture of image %d! %s",(int)image,SDL_GetError()); } } diff --git a/src/gui/intro.cpp b/src/gui/intro.cpp index 73f1cac1..db886316 100644 --- a/src/gui/intro.cpp +++ b/src/gui/intro.cpp @@ -24,7 +24,7 @@ void FurnaceGUI::drawImage(ImDrawList* dl, FurnaceGUIImages image, const ImVec2& pos, const ImVec2& scale, double rotate, const ImVec2& uvMin, const ImVec2& uvMax, const ImVec4& imgColor) { FurnaceGUIImage* imgI=getImage(image); - SDL_Texture* img=getTexture(image); + void* img=getTexture(image); float squareSize=MAX(introMax.x-introMin.x,introMax.y-introMin.y); float uDiff=uvMax.x-uvMin.x; @@ -69,7 +69,7 @@ void FurnaceGUI::drawImage(ImDrawList* dl, FurnaceGUIImages image, const ImVec2& ImU32 colorConverted=ImGui::GetColorU32(imgColor); - dl->AddImageQuad(img,quad0,quad1,quad2,quad3,uv0,uv1,uv2,uv3,colorConverted); + dl->AddImageQuad(rend->getTextureID(img),quad0,quad1,quad2,quad3,uv0,uv1,uv2,uv3,colorConverted); } void FurnaceGUI::endIntroTune() { @@ -162,7 +162,7 @@ void FurnaceGUI::drawIntro(double introTime, bool monitor) { getTexture(GUI_IMAGE_TALOGO); getTexture(GUI_IMAGE_TACHIP); getTexture(GUI_IMAGE_LOGO); - getTexture(GUI_IMAGE_INTROBG,SDL_BLENDMODE_ADD); + getTexture(GUI_IMAGE_INTROBG,GUI_BLEND_MODE_ADD); if (monitor) { ImVec2 textPos=ImLerp(top,bottom,ImVec2(0.5,0.5)); diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 55498404..a34a856d 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -49,11 +49,11 @@ void _popPartBlend(const ImDrawList* drawList, const ImDrawCmd* cmd) { } void FurnaceGUI::pushPartBlend() { - SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_ADD); + rend->setBlendMode(GUI_BLEND_MODE_ADD); } void FurnaceGUI::popPartBlend() { - SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_BLEND); + rend->setBlendMode(GUI_BLEND_MODE_BLEND); } // draw a pattern row @@ -287,7 +287,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int if (pat->data[i][index]>0xff) { snprintf(id,63,"??##PE%d_%d_%d",k,i,j); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]); - } else if (pat->data[i][index]>0x10 || settings.oneDigitEffects==0) { + } else if (pat->data[i][index]>=0x10 || settings.oneDigitEffects==0) { const unsigned char data=pat->data[i][index]; snprintf(id,63,"%.2X##PE%d_%d_%d",data,k,i,j); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[fxColors[data]]); diff --git a/src/gui/render.cpp b/src/gui/render.cpp new file mode 100644 index 00000000..908fd57d --- /dev/null +++ b/src/gui/render.cpp @@ -0,0 +1,68 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" +#include "../ta-log.h" +#ifdef HAVE_RENDER_SDL +#include "render/renderSDL.h" +#endif +#ifdef HAVE_RENDER_GL +#include "render/renderGL.h" +#endif + +bool FurnaceGUI::initRender() { + if (rend!=NULL) return false; + + if (settings.renderBackend=="OpenGL") { + renderBackend=GUI_BACKEND_GL; + } else if (settings.renderBackend=="SDL") { + renderBackend=GUI_BACKEND_SDL; + } else { + renderBackend=GUI_BACKEND_DEFAULT; + } + + switch (renderBackend) { +#ifdef HAVE_RENDER_GL + case GUI_BACKEND_GL: + logI("render backend: OpenGL"); + rend=new FurnaceGUIRenderGL; + break; +#endif +#ifdef HAVE_RENDER_SDL + case GUI_BACKEND_SDL: + logI("render backend: SDL_Renderer"); + rend=new FurnaceGUIRenderSDL; + break; +#endif + default: + logE("invalid render backend!"); + return false; + break; + } + + return true; +} + +bool FurnaceGUI::quitRender() { + if (rend==NULL) return false; + bool ret=rend->quit(); + delete rend; + rend=NULL; + return ret; +} diff --git a/src/gui/render/abstract.cpp b/src/gui/render/abstract.cpp new file mode 100644 index 00000000..996cf4e8 --- /dev/null +++ b/src/gui/render/abstract.cpp @@ -0,0 +1,100 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "../gui.h" + +ImTextureID FurnaceGUIRender::getTextureID(void* which) { + return NULL; +} + +bool FurnaceGUIRender::lockTexture(void* which, void** data, int* pitch) { + return false; +} + +bool FurnaceGUIRender::unlockTexture(void* which) { + return false; +} + +bool FurnaceGUIRender::updateTexture(void* which, void* data, int pitch) { + return false; +} + +void* FurnaceGUIRender::createTexture(bool dynamic, int width, int height) { + return NULL; +} + +bool FurnaceGUIRender::destroyTexture(void* which) { + return false; +} + +void FurnaceGUIRender::setTextureBlendMode(void* which, FurnaceGUIBlendMode mode) { +} + +void FurnaceGUIRender::setBlendMode(FurnaceGUIBlendMode mode) { +} + +void FurnaceGUIRender::clear(ImVec4 color) { +} + +bool FurnaceGUIRender::newFrame() { + return true; +} + +void FurnaceGUIRender::createFontsTexture() { +} + +void FurnaceGUIRender::destroyFontsTexture() { +} + +void FurnaceGUIRender::renderGUI() { +} + +void FurnaceGUIRender::wipe(float alpha) { +} + +void FurnaceGUIRender::present() { +} + +bool FurnaceGUIRender::getOutputSize(int& w, int& h) { + return false; +} + +int FurnaceGUIRender::getWindowFlags() { + return 0; +} + +void FurnaceGUIRender::preInit() { +} + +bool FurnaceGUIRender::init(SDL_Window* win) { + return false; +} + +void FurnaceGUIRender::initGUI(SDL_Window* win) { +} + +bool FurnaceGUIRender::quit() { + return false; +} + +void FurnaceGUIRender::quitGUI() { +} + +FurnaceGUIRender::~FurnaceGUIRender() { +} \ No newline at end of file diff --git a/src/gui/render/renderGL.cpp b/src/gui/render/renderGL.cpp new file mode 100644 index 00000000..06a057cb --- /dev/null +++ b/src/gui/render/renderGL.cpp @@ -0,0 +1,387 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "renderGL.h" +#include "../../ta-log.h" +#ifdef USE_GLES +#include "SDL_opengles2.h" +#define PIXEL_FORMAT GL_UNSIGNED_BYTE +#else +#include "SDL_opengl.h" +#define PIXEL_FORMAT GL_UNSIGNED_INT_8_8_8_8_REV +#endif +#include "backends/imgui_impl_opengl3.h" + +#define C(x) x; if (glGetError()!=GL_NO_ERROR) logW("OpenGL error in %s:%d: " #x,__FILE__,__LINE__); + +PFNGLGENBUFFERSPROC furGenBuffers=NULL; +PFNGLBINDBUFFERPROC furBindBuffer=NULL; +PFNGLBUFFERDATAPROC furBufferData=NULL; +PFNGLVERTEXATTRIBPOINTERPROC furVertexAttribPointer=NULL; +PFNGLENABLEVERTEXATTRIBARRAYPROC furEnableVertexAttribArray=NULL; +PFNGLACTIVETEXTUREPROC furActiveTexture=NULL; + +PFNGLCREATESHADERPROC furCreateShader=NULL; +PFNGLSHADERSOURCEPROC furShaderSource=NULL; +PFNGLCOMPILESHADERPROC furCompileShader=NULL; +PFNGLGETSHADERIVPROC furGetShaderiv=NULL; +PFNGLATTACHSHADERPROC furAttachShader=NULL; +PFNGLBINDATTRIBLOCATIONPROC furBindAttribLocation=NULL; +PFNGLCREATEPROGRAMPROC furCreateProgram=NULL; +PFNGLLINKPROGRAMPROC furLinkProgram=NULL; +PFNGLGETPROGRAMIVPROC furGetProgramiv=NULL; +PFNGLUSEPROGRAMPROC furUseProgram=NULL; +PFNGLGETUNIFORMLOCATIONPROC furGetUniformLocation=NULL; +PFNGLUNIFORM1FPROC furUniform1f=NULL; +PFNGLGETSHADERINFOLOGPROC furGetShaderInfoLog=NULL; + +struct FurnaceGLTexture { + GLuint id; + int width, height; + unsigned char* lockedData; + FurnaceGLTexture(): + id(0), + width(0), + height(0), + lockedData(NULL) {} +}; + +#ifdef USE_GLES +const char* sh_wipe_srcV= + "attribute vec4 fur_position;\n" + "void main() {\n" + " gl_Position=fur_position;\n" + "}\n"; + +const char* sh_wipe_srcF= + "uniform float uAlpha;\n" + "void main() {\n" + " gl_FragColor=vec4(0.0,0.0,0.0,uAlpha);\n" + "}\n"; +#else +const char* sh_wipe_srcV= + "#version 130\n" + "in vec4 fur_position;\n" + "void main() {\n" + " gl_Position=fur_position;\n" + "}\n"; + +const char* sh_wipe_srcF= + "#version 130\n" + "uniform float uAlpha;\n" + "out vec4 fur_FragColor;\n" + "void main() {\n" + " fur_FragColor=vec4(0.0,0.0,0.0,uAlpha);\n" + "}\n"; +#endif + +bool FurnaceGUIRenderGL::createShader(const char* vertexS, const char* fragmentS, int& vertex, int& fragment, int& program) { + int status; + char infoLog[4096]; + int infoLogLen; + + if (!furCreateShader || !furShaderSource || !furCompileShader || !furGetShaderiv || + !furGetShaderInfoLog || !furCreateProgram || !furAttachShader || !furLinkProgram || + !furBindAttribLocation || !furGetProgramiv) { + logW("I can't compile shaders"); + return false; + } + + vertex=furCreateShader(GL_VERTEX_SHADER); + furShaderSource(vertex,1,&vertexS,NULL); + furCompileShader(vertex); + furGetShaderiv(vertex,GL_COMPILE_STATUS,&status); + if (!status) { + logW("failed to compile vertex shader"); + furGetShaderInfoLog(vertex,4095,&infoLogLen,infoLog); + infoLog[infoLogLen]=0; + logW("%s",infoLog); + return false; + } + + fragment=furCreateShader(GL_FRAGMENT_SHADER); + furShaderSource(fragment,1,&fragmentS,NULL); + furCompileShader(fragment); + furGetShaderiv(fragment,GL_COMPILE_STATUS,&status); + if (!status) { + logW("failed to compile fragment shader"); + return false; + } + + program=furCreateProgram(); + furAttachShader(program,vertex); + furAttachShader(program,fragment); + furBindAttribLocation(program,0,"fur_position"); + furLinkProgram(program); + furGetProgramiv(program,GL_LINK_STATUS,&status); + if (!status) { + logW("failed to link shader!"); + return false; + } + + return true; +} + +ImTextureID FurnaceGUIRenderGL::getTextureID(void* which) { + intptr_t ret=((FurnaceGLTexture*)which)->id; + return (ImTextureID)ret; +} + +bool FurnaceGUIRenderGL::lockTexture(void* which, void** data, int* pitch) { + FurnaceGLTexture* t=(FurnaceGLTexture*)which; + if (t->lockedData!=NULL) return false; + t->lockedData=new unsigned char[t->width*t->height*4]; + + *data=t->lockedData; + *pitch=t->width*4; + return true; +} + +bool FurnaceGUIRenderGL::unlockTexture(void* which) { + FurnaceGLTexture* t=(FurnaceGLTexture*)which; + if (t->lockedData==NULL) return false; + + C(glBindTexture(GL_TEXTURE_2D,t->id)); + C(glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,t->width,t->height,0,GL_RGBA,PIXEL_FORMAT,t->lockedData)); + + C(glFlush()); + delete[] t->lockedData; + t->lockedData=NULL; + + return true; +} + +bool FurnaceGUIRenderGL::updateTexture(void* which, void* data, int pitch) { + FurnaceGLTexture* t=(FurnaceGLTexture*)which; + + if (t->width*4!=pitch) return false; + + C(glBindTexture(GL_TEXTURE_2D,t->id)); + C(glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,t->width,t->height,0,GL_RGBA,PIXEL_FORMAT,data)); + return true; +} + +void* FurnaceGUIRenderGL::createTexture(bool dynamic, int width, int height) { + FurnaceGLTexture* t=new FurnaceGLTexture; + C(glGenTextures(1,&t->id)); + C(glBindTexture(GL_TEXTURE_2D,t->id)); + C(glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR)); + C(glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR)); + C(glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,PIXEL_FORMAT,NULL)); + C(furActiveTexture(GL_TEXTURE0)); + t->width=width; + t->height=height; + return t; +} + +bool FurnaceGUIRenderGL::destroyTexture(void* which) { + FurnaceGLTexture* t=(FurnaceGLTexture*)which; + C(glDeleteTextures(1,&t->id)); + delete t; + return true; +} + +void FurnaceGUIRenderGL::setTextureBlendMode(void* which, FurnaceGUIBlendMode mode) { +} + +void FurnaceGUIRenderGL::setBlendMode(FurnaceGUIBlendMode mode) { + switch (mode) { + case GUI_BLEND_MODE_NONE: + C(glBlendFunc(GL_ONE,GL_ZERO)); + C(glDisable(GL_BLEND)); + break; + case GUI_BLEND_MODE_BLEND: + C(glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)); + C(glEnable(GL_BLEND)); + break; + case GUI_BLEND_MODE_ADD: + C(glBlendFunc(GL_SRC_ALPHA,GL_ONE)); + C(glEnable(GL_BLEND)); + break; + case GUI_BLEND_MODE_MULTIPLY: + C(glBlendFunc(GL_ZERO,GL_SRC_COLOR)); + C(glEnable(GL_BLEND)); + break; + } +} + +void FurnaceGUIRenderGL::clear(ImVec4 color) { + SDL_GL_MakeCurrent(sdlWin,context); + C(glClearColor(color.x,color.y,color.z,color.w)); + C(glClear(GL_COLOR_BUFFER_BIT)); +} + +bool FurnaceGUIRenderGL::newFrame() { + ImGui_ImplOpenGL3_NewFrame(); + return true; +} + +void FurnaceGUIRenderGL::createFontsTexture() { + ImGui_ImplOpenGL3_CreateFontsTexture(); +} + +void FurnaceGUIRenderGL::destroyFontsTexture() { + ImGui_ImplOpenGL3_DestroyFontsTexture(); +} + +void FurnaceGUIRenderGL::renderGUI() { + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); +} + +void FurnaceGUIRenderGL::wipe(float alpha) { + C(glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)); + C(glEnable(GL_BLEND)); + + quadVertex[0][0]=-1.0f; + quadVertex[0][1]=-1.0f; + quadVertex[0][2]=0.0f; + quadVertex[1][0]=1.0f; + quadVertex[1][1]=-1.0f; + quadVertex[1][2]=0.0f; + quadVertex[2][0]=-1.0f; + quadVertex[2][1]=1.0f; + quadVertex[2][2]=0.0f; + quadVertex[3][0]=1.0f; + quadVertex[3][1]=1.0f; + quadVertex[3][2]=0.0f; + + C(furBindBuffer(GL_ARRAY_BUFFER,quadBuf)); + C(furBufferData(GL_ARRAY_BUFFER,sizeof(quadVertex),quadVertex,GL_STATIC_DRAW)); + C(furVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,NULL)); + C(furEnableVertexAttribArray(0)); + C(furActiveTexture(GL_TEXTURE0)); + C(glBindTexture(GL_TEXTURE_2D,0)); + if (furUseProgram && furUniform1f) { + C(furUseProgram(sh_wipe_program)); + C(furUniform1f(sh_wipe_uAlpha,alpha)); + } + C(glDrawArrays(GL_TRIANGLE_STRIP,0,4)); +} + +void FurnaceGUIRenderGL::present() { + SDL_GL_SwapWindow(sdlWin); + C(glFlush()); +} + +bool FurnaceGUIRenderGL::getOutputSize(int& w, int& h) { + SDL_GL_GetDrawableSize(sdlWin,&w,&h); + return true; +} + +int FurnaceGUIRenderGL::getWindowFlags() { + return SDL_WINDOW_OPENGL; +} + +void FurnaceGUIRenderGL::preInit() { +#if defined(USE_GLES) + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION,2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION,0); +#elif defined(__APPLE__) + // not recommended... + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION,3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION,2); +#else + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS,0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION,3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION,0); +#endif + + SDL_GL_SetAttribute(SDL_GL_RED_SIZE,8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,8); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE,0); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,24); +} + +#define LOAD_PROC_MANDATORY(_v,_t,_s) \ + _v=(_t)SDL_GL_GetProcAddress(_s); \ + if (!_v) { \ + logE(_s " not found"); \ + return false; \ + } + +#define LOAD_PROC_OPTIONAL(_v,_t,_s) \ + _v=(_t)SDL_GL_GetProcAddress(_s); \ + if (!_v) { \ + logW(_s " not found"); \ + } + +bool FurnaceGUIRenderGL::init(SDL_Window* win) { + sdlWin=win; + context=SDL_GL_CreateContext(win); + if (context==NULL) { + return false; + } + SDL_GL_MakeCurrent(win,context); + SDL_GL_SetSwapInterval(1); + + LOAD_PROC_MANDATORY(furGenBuffers,PFNGLGENBUFFERSPROC,"glGenBuffers"); + LOAD_PROC_MANDATORY(furBindBuffer,PFNGLBINDBUFFERPROC,"glBindBuffer"); + LOAD_PROC_MANDATORY(furBufferData,PFNGLBUFFERDATAPROC,"glBufferData"); + LOAD_PROC_MANDATORY(furVertexAttribPointer,PFNGLVERTEXATTRIBPOINTERPROC,"glVertexAttribPointer"); + LOAD_PROC_MANDATORY(furEnableVertexAttribArray,PFNGLENABLEVERTEXATTRIBARRAYPROC,"glEnableVertexAttribArray"); + LOAD_PROC_MANDATORY(furActiveTexture,PFNGLACTIVETEXTUREPROC,"glActiveTexture"); + + LOAD_PROC_OPTIONAL(furCreateShader,PFNGLCREATESHADERPROC,"glCreateShader"); + LOAD_PROC_OPTIONAL(furShaderSource,PFNGLSHADERSOURCEPROC,"glShaderSource"); + LOAD_PROC_OPTIONAL(furCompileShader,PFNGLCOMPILESHADERPROC,"glCompileShader"); + LOAD_PROC_OPTIONAL(furGetShaderiv,PFNGLGETSHADERIVPROC,"glGetShaderiv"); + LOAD_PROC_OPTIONAL(furAttachShader,PFNGLATTACHSHADERPROC,"glAttachShader"); + LOAD_PROC_OPTIONAL(furBindAttribLocation,PFNGLBINDATTRIBLOCATIONPROC,"glBindAttribLocation"); + LOAD_PROC_OPTIONAL(furCreateProgram,PFNGLCREATEPROGRAMPROC,"glCreateProgram"); + LOAD_PROC_OPTIONAL(furLinkProgram,PFNGLLINKPROGRAMPROC,"glLinkProgram"); + LOAD_PROC_OPTIONAL(furGetProgramiv,PFNGLGETPROGRAMIVPROC,"glGetProgramiv"); + LOAD_PROC_OPTIONAL(furUseProgram,PFNGLUSEPROGRAMPROC,"glUseProgram"); + LOAD_PROC_OPTIONAL(furGetUniformLocation,PFNGLGETUNIFORMLOCATIONPROC,"glGetUniformLocation"); + LOAD_PROC_OPTIONAL(furUniform1f,PFNGLUNIFORM1FPROC,"glUniform1f"); + LOAD_PROC_OPTIONAL(furGetShaderInfoLog,PFNGLGETSHADERINFOLOGPROC,"glGetShaderInfoLog"); + + + if (createShader(sh_wipe_srcV,sh_wipe_srcF,sh_wipe_vertex,sh_wipe_fragment,sh_wipe_program)) { + sh_wipe_uAlpha=furGetUniformLocation(sh_wipe_program,"uAlpha"); + } + + C(furGenBuffers(1,&quadBuf)); + return true; +} + +void FurnaceGUIRenderGL::initGUI(SDL_Window* win) { + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGui_ImplSDL2_InitForOpenGL(win,context); + ImGui_ImplOpenGL3_Init(); +} + +bool FurnaceGUIRenderGL::quit() { + if (context==NULL) return false; + SDL_GL_DeleteContext(context); + context=NULL; + return true; +} + +void FurnaceGUIRenderGL::quitGUI() { + ImGui_ImplOpenGL3_Shutdown(); +} diff --git a/src/gui/render/renderGL.h b/src/gui/render/renderGL.h new file mode 100644 index 00000000..6f824a03 --- /dev/null +++ b/src/gui/render/renderGL.h @@ -0,0 +1,65 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "../gui.h" + +class FurnaceGUIRenderGL: public FurnaceGUIRender { + SDL_GLContext context; + SDL_Window* sdlWin; + float quadVertex[4][3]; + unsigned int quadBuf; + + // SHADERS // + // -> wipe + int sh_wipe_vertex; + int sh_wipe_fragment; + int sh_wipe_program; + int sh_wipe_uAlpha; + + bool createShader(const char* vertexS, const char* fragmentS, int& vertex, int& fragment, int& program); + + public: + ImTextureID getTextureID(void* which); + bool lockTexture(void* which, void** data, int* pitch); + bool unlockTexture(void* which); + bool updateTexture(void* which, void* data, int pitch); + void* createTexture(bool dynamic, int width, int height); + bool destroyTexture(void* which); + void setTextureBlendMode(void* which, FurnaceGUIBlendMode mode); + void setBlendMode(FurnaceGUIBlendMode mode); + void clear(ImVec4 color); + bool newFrame(); + void createFontsTexture(); + void destroyFontsTexture(); + void renderGUI(); + void wipe(float alpha); + void present(); + bool getOutputSize(int& w, int& h); + int getWindowFlags(); + void preInit(); + bool init(SDL_Window* win); + void initGUI(SDL_Window* win); + void quitGUI(); + bool quit(); + FurnaceGUIRenderGL(): + context(NULL), + sdlWin(NULL) { + memset(quadVertex,0,4*3*sizeof(float)); + } +}; \ No newline at end of file diff --git a/src/gui/render/renderSDL.cpp b/src/gui/render/renderSDL.cpp new file mode 100644 index 00000000..0eb6271a --- /dev/null +++ b/src/gui/render/renderSDL.cpp @@ -0,0 +1,147 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "renderSDL.h" +#include "backends/imgui_impl_sdlrenderer.h" + +ImTextureID FurnaceGUIRenderSDL::getTextureID(void* which) { + return which; +} + +bool FurnaceGUIRenderSDL::lockTexture(void* which, void** data, int* pitch) { + return SDL_LockTexture((SDL_Texture*)which,NULL,data,pitch)==0; +} + +bool FurnaceGUIRenderSDL::unlockTexture(void* which) { + SDL_UnlockTexture((SDL_Texture*)which); + return true; +} + +bool FurnaceGUIRenderSDL::updateTexture(void* which, void* data, int pitch) { + return SDL_UpdateTexture((SDL_Texture*)which,NULL,data,pitch)==0; +} + +void* FurnaceGUIRenderSDL::createTexture(bool dynamic, int width, int height) { + return SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ABGR8888,dynamic?SDL_TEXTUREACCESS_STREAMING:SDL_TEXTUREACCESS_STATIC,width,height); +} + +bool FurnaceGUIRenderSDL::destroyTexture(void* which) { + SDL_DestroyTexture((SDL_Texture*)which); + return true; +} + +void FurnaceGUIRenderSDL::setTextureBlendMode(void* which, FurnaceGUIBlendMode mode) { + switch (mode) { + case GUI_BLEND_MODE_NONE: + SDL_SetTextureBlendMode((SDL_Texture*)which,SDL_BLENDMODE_NONE); + break; + case GUI_BLEND_MODE_BLEND: + SDL_SetTextureBlendMode((SDL_Texture*)which,SDL_BLENDMODE_BLEND); + break; + case GUI_BLEND_MODE_ADD: + SDL_SetTextureBlendMode((SDL_Texture*)which,SDL_BLENDMODE_ADD); + break; + case GUI_BLEND_MODE_MULTIPLY: + SDL_SetTextureBlendMode((SDL_Texture*)which,SDL_BLENDMODE_MOD); + break; + } +} + +void FurnaceGUIRenderSDL::setBlendMode(FurnaceGUIBlendMode mode) { + switch (mode) { + case GUI_BLEND_MODE_NONE: + SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_NONE); + break; + case GUI_BLEND_MODE_BLEND: + SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_BLEND); + break; + case GUI_BLEND_MODE_ADD: + SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_ADD); + break; + case GUI_BLEND_MODE_MULTIPLY: + SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_MOD); + break; + } +} + +void FurnaceGUIRenderSDL::clear(ImVec4 color) { + SDL_SetRenderDrawColor(sdlRend,color.x*255,color.y*255,color.z*255,color.w*255); + SDL_RenderClear(sdlRend); +} + +bool FurnaceGUIRenderSDL::newFrame() { + return ImGui_ImplSDLRenderer_NewFrame(); +} + +void FurnaceGUIRenderSDL::createFontsTexture() { + ImGui_ImplSDLRenderer_CreateFontsTexture(); +} + +void FurnaceGUIRenderSDL::destroyFontsTexture() { + ImGui_ImplSDLRenderer_DestroyFontsTexture(); +} + +void FurnaceGUIRenderSDL::renderGUI() { + ImGui_ImplSDLRenderer_RenderDrawData(ImGui::GetDrawData()); +} + +void FurnaceGUIRenderSDL::wipe(float alpha) { + SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(sdlRend,0,0,0,255*alpha); + SDL_RenderFillRect(sdlRend,NULL); +} + +void FurnaceGUIRenderSDL::present() { + SDL_RenderPresent(sdlRend); +} + +bool FurnaceGUIRenderSDL::getOutputSize(int& w, int& h) { + return SDL_GetRendererOutputSize(sdlRend,&w,&h)==0; +} + +int FurnaceGUIRenderSDL::getWindowFlags() { + return 0; +} + +void FurnaceGUIRenderSDL::preInit() { +} + +bool FurnaceGUIRenderSDL::init(SDL_Window* win) { + sdlRend=SDL_CreateRenderer(win,-1,SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC|SDL_RENDERER_TARGETTEXTURE); + return (sdlRend!=NULL); +} + +void FurnaceGUIRenderSDL::initGUI(SDL_Window* win) { + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGui_ImplSDL2_InitForSDLRenderer(win,sdlRend); + ImGui_ImplSDLRenderer_Init(sdlRend); +} + +void FurnaceGUIRenderSDL::quitGUI() { + ImGui_ImplSDLRenderer_Shutdown(); +} + +bool FurnaceGUIRenderSDL::quit() { + if (sdlRend==NULL) return false; + SDL_DestroyRenderer(sdlRend); + sdlRend=NULL; + return true; +} \ No newline at end of file diff --git a/src/gui/render/renderSDL.h b/src/gui/render/renderSDL.h new file mode 100644 index 00000000..afae3b3c --- /dev/null +++ b/src/gui/render/renderSDL.h @@ -0,0 +1,49 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "../gui.h" + +class FurnaceGUIRenderSDL: public FurnaceGUIRender { + SDL_Renderer* sdlRend; + public: + ImTextureID getTextureID(void* which); + bool lockTexture(void* which, void** data, int* pitch); + bool unlockTexture(void* which); + bool updateTexture(void* which, void* data, int pitch); + void* createTexture(bool dynamic, int width, int height); + bool destroyTexture(void* which); + void setTextureBlendMode(void* which, FurnaceGUIBlendMode mode); + void setBlendMode(FurnaceGUIBlendMode mode); + void clear(ImVec4 color); + bool newFrame(); + void createFontsTexture(); + void destroyFontsTexture(); + void renderGUI(); + void wipe(float alpha); + void present(); + bool getOutputSize(int& w, int& h); + int getWindowFlags(); + void preInit(); + bool init(SDL_Window* win); + void initGUI(SDL_Window* win); + void quitGUI(); + bool quit(); + FurnaceGUIRenderSDL(): + sdlRend(NULL) {} +}; \ No newline at end of file diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 9d0fcc19..238f37e8 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -291,6 +291,19 @@ void FurnaceGUI::drawSampleEdit() { } } } + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && e->getSampleFormatMask()&(1L<dither; + if (ImGui::Checkbox("8-bit dither",&di)) { + sample->prepareUndo(true); + sample->dither=di; + e->renderSamplesP(); + updateSampleTex=true; + MARK_MODIFIED; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("dither the sample when used on a chip that only supports 8-bit samples."); + } + } int sampleNote=round(64.0+(128.0*12.0*log((double)targetRate/8363.0)/log(2.0))); int sampleNoteCoarse=60+(sampleNote>>7); @@ -1110,12 +1123,12 @@ void FurnaceGUI::drawSampleEdit() { if (sampleTex==NULL || sampleTexW!=avail.x || sampleTexH!=avail.y) { if (sampleTex!=NULL) { - SDL_DestroyTexture(sampleTex); + rend->destroyTexture(sampleTex); sampleTex=NULL; } if (avail.x>=1 && avail.y>=1) { logD("recreating sample texture."); - sampleTex=SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ABGR8888,SDL_TEXTUREACCESS_STREAMING,avail.x,avail.y); + sampleTex=rend->createTexture(true,avail.x,avail.y); sampleTexW=avail.x; sampleTexH=avail.y; if (sampleTex==NULL) { @@ -1131,7 +1144,7 @@ void FurnaceGUI::drawSampleEdit() { unsigned int* dataT=NULL; int pitch=0; logD("updating sample texture."); - if (SDL_LockTexture(sampleTex,NULL,(void**)&dataT,&pitch)!=0) { + if (!rend->lockTexture(sampleTex,(void**)&dataT,&pitch)) { logE("error while locking sample texture! %s",SDL_GetError()); } else { unsigned int* data=new unsigned int[sampleTexW*sampleTexH]; @@ -1217,13 +1230,13 @@ void FurnaceGUI::drawSampleEdit() { } memcpy(dataT,data,sampleTexW*sampleTexH*sizeof(unsigned int)); - SDL_UnlockTexture(sampleTex); + rend->unlockTexture(sampleTex); delete[] data; } updateSampleTex=false; } - ImGui::ImageButton(sampleTex,avail,ImVec2(0,0),ImVec2(1,1),0); + ImGui::ImageButton(rend->getTextureID(sampleTex),avail,ImVec2(0,0),ImVec2(1,1),0); ImVec2 rectMin=ImGui::GetItemRectMin(); ImVec2 rectMax=ImGui::GetItemRectMax(); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 85b28766..e66926b1 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -662,6 +662,13 @@ void FurnaceGUI::drawSettings() { ImGui::SetTooltip("use zlib to compress saved songs."); } + bool newPatternFormatB=settings.newPatternFormat; + if (ImGui::Checkbox("Use new pattern format when saving",&newPatternFormatB)) { + settings.newPatternFormat=newPatternFormatB; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("use a packed format which saves space when saving songs.\ndisable if you need compatibility with older Furnace and/or tools\nwhich do not support this format."); + } bool cursorFollowsOrderB=settings.cursorFollowsOrder; if (ImGui::Checkbox("Cursor follows current order when moving it",&cursorFollowsOrderB)) { @@ -1271,17 +1278,33 @@ void FurnaceGUI::drawSettings() { ImVec2 settingsViewSize=ImGui::GetContentRegionAvail(); settingsViewSize.y-=ImGui::GetFrameHeight()+ImGui::GetStyle().WindowPadding.y; if (ImGui::BeginChild("SettingsView",settingsViewSize)) { - if (ImGui::BeginCombo("Render driver",settings.renderDriver.empty()?"Automatic":settings.renderDriver.c_str())) { - if (ImGui::Selectable("Automatic",settings.renderDriver.empty())) { - settings.renderDriver=""; + String curRenderBackend=settings.renderBackend.empty()?GUI_BACKEND_DEFAULT_NAME:settings.renderBackend; + if (ImGui::BeginCombo("Render backend",curRenderBackend.c_str())) { +#ifdef HAVE_RENDER_SDL + if (ImGui::Selectable("SDL Renderer",curRenderBackend=="SDL")) { + settings.renderBackend="SDL"; } - for (String& i: availRenderDrivers) { - if (ImGui::Selectable(i.c_str(),i==settings.renderDriver)) { - settings.renderDriver=i; - } +#endif +#ifdef HAVE_RENDER_GL + if (ImGui::Selectable("OpenGL",curRenderBackend=="OpenGL")) { + settings.renderBackend="OpenGL"; } +#endif ImGui::EndCombo(); } + if (curRenderBackend=="SDL") { + if (ImGui::BeginCombo("Render driver",settings.renderDriver.empty()?"Automatic":settings.renderDriver.c_str())) { + if (ImGui::Selectable("Automatic",settings.renderDriver.empty())) { + settings.renderDriver=""; + } + for (String& i: availRenderDrivers) { + if (ImGui::Selectable(i.c_str(),i==settings.renderDriver)) { + settings.renderDriver=i; + } + } + ImGui::EndCombo(); + } + } bool dpiScaleAuto=(settings.dpiScale<0.5f); if (ImGui::Checkbox("Automatic UI scaling factor",&dpiScaleAuto)) { @@ -1844,13 +1867,33 @@ void FurnaceGUI::drawSettings() { } UI_COLOR_CONFIG(GUI_COLOR_BACKGROUND,"Background"); UI_COLOR_CONFIG(GUI_COLOR_FRAME_BACKGROUND,"Window background"); + UI_COLOR_CONFIG(GUI_COLOR_FRAME_BACKGROUND_CHILD,"Sub-window background"); + UI_COLOR_CONFIG(GUI_COLOR_FRAME_BACKGROUND_POPUP,"Pop-up background"); UI_COLOR_CONFIG(GUI_COLOR_MODAL_BACKDROP,"Modal backdrop"); UI_COLOR_CONFIG(GUI_COLOR_HEADER,"Header"); UI_COLOR_CONFIG(GUI_COLOR_TEXT,"Text"); UI_COLOR_CONFIG(GUI_COLOR_ACCENT_PRIMARY,"Primary"); UI_COLOR_CONFIG(GUI_COLOR_ACCENT_SECONDARY,"Secondary"); + UI_COLOR_CONFIG(GUI_COLOR_TITLE_INACTIVE,"Title bar (inactive)"); + UI_COLOR_CONFIG(GUI_COLOR_TITLE_COLLAPSED,"Title bar (collapsed)"); + UI_COLOR_CONFIG(GUI_COLOR_MENU_BAR,"Menu bar"); UI_COLOR_CONFIG(GUI_COLOR_BORDER,"Border"); UI_COLOR_CONFIG(GUI_COLOR_BORDER_SHADOW,"Border shadow"); + UI_COLOR_CONFIG(GUI_COLOR_SCROLL,"Scroll bar"); + UI_COLOR_CONFIG(GUI_COLOR_SCROLL_HOVER,"Scroll bar (hovered)"); + UI_COLOR_CONFIG(GUI_COLOR_SCROLL_ACTIVE,"Scroll bar (clicked)"); + UI_COLOR_CONFIG(GUI_COLOR_SCROLL_BACKGROUND,"Scroll bar background"); + UI_COLOR_CONFIG(GUI_COLOR_SEPARATOR,"Separator"); + UI_COLOR_CONFIG(GUI_COLOR_SEPARATOR_HOVER,"Separator (hover)"); + UI_COLOR_CONFIG(GUI_COLOR_SEPARATOR_ACTIVE,"Separator (active)"); + UI_COLOR_CONFIG(GUI_COLOR_DOCKING_PREVIEW,"Docking preview"); + UI_COLOR_CONFIG(GUI_COLOR_DOCKING_EMPTY,"Docking empty"); + UI_COLOR_CONFIG(GUI_COLOR_TABLE_HEADER,"Table header"); + UI_COLOR_CONFIG(GUI_COLOR_TABLE_BORDER_HARD,"Table border (hard)"); + UI_COLOR_CONFIG(GUI_COLOR_TABLE_BORDER_SOFT,"Table border (soft)"); + UI_COLOR_CONFIG(GUI_COLOR_DRAG_DROP_TARGET,"Drag and drop target"); + UI_COLOR_CONFIG(GUI_COLOR_NAV_WIN_HIGHLIGHT,"Window switcher (highlight)"); + UI_COLOR_CONFIG(GUI_COLOR_NAV_WIN_BACKDROP,"Window switcher backdrop"); UI_COLOR_CONFIG(GUI_COLOR_TOGGLE_ON,"Toggle on"); UI_COLOR_CONFIG(GUI_COLOR_TOGGLE_OFF,"Toggle off"); UI_COLOR_CONFIG(GUI_COLOR_EDITING,"Editing"); @@ -2683,6 +2726,8 @@ void FurnaceGUI::syncSettings() { settings.iCannotWait=e->getConfInt("iCannotWait",0); settings.orderButtonPos=e->getConfInt("orderButtonPos",2); settings.compress=e->getConfInt("compress",1); + settings.newPatternFormat=e->getConfInt("newPatternFormat",1); + settings.renderBackend=e->getConfString("renderBackend","SDL"); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2804,6 +2849,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.iCannotWait,0,1); clampSetting(settings.orderButtonPos,0,2); clampSetting(settings.compress,0,1); + clampSetting(settings.newPatternFormat,0,1); if (settings.exportLoops<0.0) settings.exportLoops=0.0; if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0; @@ -3020,6 +3066,8 @@ void FurnaceGUI::commitSettings() { e->setConf("iCannotWait",settings.iCannotWait); e->setConf("orderButtonPos",settings.orderButtonPos); e->setConf("compress",settings.compress); + e->setConf("newPatternFormat",settings.newPatternFormat); + e->setConf("renderBackend",settings.renderBackend); // colors for (int i=0; idestroyFontsTexture(); if (!ImGui::GetIO().Fonts->Build()) { logE("error while building font atlas!"); showError("error while loading fonts! please check your settings."); ImGui::GetIO().Fonts->Clear(); mainFont=ImGui::GetIO().Fonts->AddFontDefault(); patFont=mainFont; - ImGui_ImplSDLRenderer_DestroyFontsTexture(); + if (rend) rend->destroyFontsTexture(); if (!ImGui::GetIO().Fonts->Build()) { logE("error again while building font atlas!"); + } else { + rend->createFontsTexture(); } + } else { + rend->createFontsTexture(); } } @@ -3538,7 +3590,28 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { } sty.Colors[ImGuiCol_WindowBg]=uiColors[GUI_COLOR_FRAME_BACKGROUND]; + sty.Colors[ImGuiCol_ChildBg]=uiColors[GUI_COLOR_FRAME_BACKGROUND_CHILD]; + sty.Colors[ImGuiCol_PopupBg]=uiColors[GUI_COLOR_FRAME_BACKGROUND_POPUP]; + sty.Colors[ImGuiCol_TitleBg]=uiColors[GUI_COLOR_TITLE_INACTIVE]; + sty.Colors[ImGuiCol_TitleBgCollapsed]=uiColors[GUI_COLOR_TITLE_COLLAPSED]; + sty.Colors[ImGuiCol_MenuBarBg]=uiColors[GUI_COLOR_MENU_BAR]; sty.Colors[ImGuiCol_ModalWindowDimBg]=uiColors[GUI_COLOR_MODAL_BACKDROP]; + sty.Colors[ImGuiCol_ScrollbarBg]=uiColors[GUI_COLOR_SCROLL_BACKGROUND]; + sty.Colors[ImGuiCol_ScrollbarGrab]=uiColors[GUI_COLOR_SCROLL]; + sty.Colors[ImGuiCol_ScrollbarGrabHovered]=uiColors[GUI_COLOR_SCROLL_HOVER]; + sty.Colors[ImGuiCol_ScrollbarGrabActive]=uiColors[GUI_COLOR_SCROLL_ACTIVE]; + sty.Colors[ImGuiCol_Separator]=uiColors[GUI_COLOR_SEPARATOR]; + sty.Colors[ImGuiCol_SeparatorHovered]=uiColors[GUI_COLOR_SEPARATOR_HOVER]; + sty.Colors[ImGuiCol_SeparatorActive]=uiColors[GUI_COLOR_SEPARATOR_ACTIVE]; + sty.Colors[ImGuiCol_DockingPreview]=uiColors[GUI_COLOR_DOCKING_PREVIEW]; + sty.Colors[ImGuiCol_DockingEmptyBg]=uiColors[GUI_COLOR_DOCKING_EMPTY]; + sty.Colors[ImGuiCol_TableHeaderBg]=uiColors[GUI_COLOR_TABLE_HEADER]; + sty.Colors[ImGuiCol_TableBorderStrong]=uiColors[GUI_COLOR_TABLE_BORDER_HARD]; + sty.Colors[ImGuiCol_TableBorderLight]=uiColors[GUI_COLOR_TABLE_BORDER_SOFT]; + sty.Colors[ImGuiCol_DragDropTarget]=uiColors[GUI_COLOR_DRAG_DROP_TARGET]; + sty.Colors[ImGuiCol_NavHighlight]=uiColors[GUI_COLOR_NAV_HIGHLIGHT]; + sty.Colors[ImGuiCol_NavWindowingHighlight]=uiColors[GUI_COLOR_NAV_WIN_HIGHLIGHT]; + sty.Colors[ImGuiCol_NavWindowingDimBg]=uiColors[GUI_COLOR_NAV_WIN_BACKDROP]; sty.Colors[ImGuiCol_Text]=uiColors[GUI_COLOR_TEXT]; sty.Colors[ImGuiCol_Button]=primary; diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 07d39582..58ee524e 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -863,7 +863,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo if (echoBufSize1<0) echoBufSize1=0; if (echoBufSize1>2725) echoBufSize1=2725; echoDelay=2725-echoBufSize1; - altered=true;; + altered=true; } rightClickable ImGui::Text("Echo feedback:"); if (CWSliderInt("##EchoFeedback",&echoFeedback,0,255)) { diff --git a/src/gui/tutorial.cpp b/src/gui/tutorial.cpp index 71ad3ca4..c1be8912 100644 --- a/src/gui/tutorial.cpp +++ b/src/gui/tutorial.cpp @@ -279,7 +279,7 @@ void FurnaceGUI::drawTutorial() { ImGui::TextWrapped( "if you need help, you may:\n" - "- read the (incomplete) manual: https://github.com/tildearrow/furnace/blob/master/papers/doc/README.md\n" + "- read the (incomplete) manual: https://github.com/tildearrow/furnace/blob/master/doc/README.md\n" "- ask for help in Discussions (https://github.com/tildearrow/furnace/discussions) or the Furnace Discord (https://discord.gg/EfrwT2wq7z)" );