diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7e060924..ec8ed890 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,8 +22,10 @@ jobs: - { name: 'Windows MSVC x86_64', os: windows-latest, compiler: msvc, arch: x86_64 } - { name: 'Windows MinGW x86', os: ubuntu-20.04, compiler: mingw, arch: x86 } - { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 } - - { name: 'macOS', os: macos-latest } - - { name: 'Ubuntu', os: ubuntu-18.04 } + - { name: 'macOS x86_64', os: macos-latest, arch: x86_64 } + - { name: 'macOS ARM', os: macos-latest, arch: arm64 } + - { name: 'Linux x86_64', os: ubuntu-18.04, arch: x86_64 } + - { name: 'Linux ARM', os: ubuntu-18.04, arch: armhf } fail-fast: false name: ${{ matrix.config.name }} @@ -76,10 +78,10 @@ jobs: package_name="${package_name}-${{ matrix.config.arch }}" package_ext="" # Directory, uploading will automatically zip it elif [ '${{ runner.os }}' == 'macOS' ]; then - package_name="${package_name}-macOS" + package_name="${package_name}-macOS-${{ matrix.config.arch }}" package_ext=".dmg" else - package_name="${package_name}-Linux" + package_name="${package_name}-Linux-${{ matrix.config.arch }}" package_ext=".AppImage" fi @@ -116,8 +118,8 @@ jobs: mingw-w64 \ mingw-w64-tools - - name: Install Dependencies [Ubuntu] - if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} + - name: Install Dependencies [Linux x86_64] + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }} run: | sudo apt update sudo apt install \ @@ -128,11 +130,34 @@ jobs: zlib1g-dev \ libjack-jackd2-dev \ appstream - wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" + 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 + - name: Install Dependencies [Linux armhf] + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'armhf' }} + run: | + sudo sed -ri "s/^deb /deb [arch=amd64] /" /etc/apt/sources.list + echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports/ bionic main universe" | sudo tee -a /etc/apt/sources.list + echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main universe" | sudo tee -a /etc/apt/sources.list + sudo dpkg --add-architecture armhf + sudo apt update + sudo apt install \ + crossbuild-essential-armhf \ + appstream + sudo apt install \ + libsdl2-dev:armhf \ + libfmt-dev:armhf \ + librtmidi-dev:armhf \ + 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) - if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }} run: | export USE_WAE=ON export CMAKE_EXTRA_ARGS=() @@ -163,7 +188,7 @@ jobs: "${CMAKE_EXTRA_ARGS[@]}" - name: Build (System Libraries) - if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }} run: | cmake \ --build ${PWD}/build \ @@ -171,14 +196,14 @@ jobs: --parallel ${{ steps.build-cores.outputs.amount }} - name: Install (System Libraries) - if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }} run: | cmake \ --install ${PWD}/build \ --config ${{ env.BUILD_TYPE }} - name: Cleanup (System Libraries) - if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }} run: | rm -rf build/ target/ @@ -201,7 +226,13 @@ jobs: elif [ '${{ matrix.config.compiler }}' == 'mingw' ]; then CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-${{ steps.windows-identify.outputs.mingw-target }}.cmake') elif [ '${{ runner.os }}' == 'macOS' ]; then - CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"') + if [ '${{ matrix.config.arch }}' == 'arm64' ]; then + CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="11.0"' '-DCMAKE_OSX_ARCHITECTURES=arm64') + else + CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"') + fi + elif [ '${{ runner.os }}' == 'Linux' ] && [ '${{ matrix.config.arch }}' == 'armhf' ]; then + CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-Linux-armhf.cmake') fi cmake \ @@ -255,7 +286,7 @@ jobs: mv Furnace-*-Darwin.dmg ../${{ steps.package-identify.outputs.filename }} popd - - name: Package [Ubuntu] + - name: Package [Linux] if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} run: | #if [ '${{ env.BUILD_TYPE }}' == 'Release' ]; then @@ -273,7 +304,11 @@ jobs: cp -v ../../res/AppRun ./ popd - ../appimagetool-x86_64.AppImage furnace.AppDir + 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 diff --git a/.gitignore b/.gitignore index 06576ee5..25bd6d0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .vscode/ build/ +clangbuild/ nosdl/ release/ t/ @@ -13,6 +14,7 @@ linuxbuild/ test/songs/ test/delta/ test/result/ +test/assert_delta android/.gradle/ android/app/build/ android/app/.cxx/ diff --git a/CMakeLists.txt b/CMakeLists.txt index bc720fc2..330b8c4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,9 @@ set(USE_SDL2_DEFAULT ON) set(USE_SNDFILE_DEFAULT ON) set(SYSTEM_SDL2_DEFAULT OFF) +include(CheckIncludeFile) +include(TestBigEndian) + if (ANDROID) set(USE_RTMIDI_DEFAULT OFF) set(USE_BACKWARD_DEFAULT OFF) @@ -31,7 +34,16 @@ if (ANDROID) endif() else() set(USE_RTMIDI_DEFAULT ON) - set(USE_BACKWARD_DEFAULT ON) + if (WIN32 OR APPLE) + set(USE_BACKWARD_DEFAULT ON) + else() + CHECK_INCLUDE_FILE(execinfo.h EXECINFO_FOUND) + if (EXECINFO_FOUND) + set(USE_BACKWARD_DEFAULT ON) + else() + set(USE_BACKWARD_DEFAULT OFF) + endif() + endif() endif() find_package(PkgConfig) @@ -55,13 +67,21 @@ option(SYSTEM_RTMIDI "Use a system-installed version of RtMidi instead of the ve option(SYSTEM_ZLIB "Use a system-installed version of zlib instead of the vendored one" OFF) option(SYSTEM_SDL2 "Use a system-installed version of SDL2 instead of the vendored one" ${SYSTEM_SDL2_DEFAULT}) option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be treated as errors" OFF) +option(WITH_DEMOS "Install demo songs" ON) +option(WITH_INSTRUMENTS "Install instruments" ON) set(DEPENDENCIES_INCLUDE_DIRS "") if (ANDROID AND NOT TERMUX) -set(DEPENDENCIES_DEFINES "IS_MOBILE") + set(DEPENDENCIES_DEFINES "IS_MOBILE") else() -set(DEPENDENCIES_DEFINES "") + set(DEPENDENCIES_DEFINES "") +endif() + +TEST_BIG_ENDIAN(IS_BIG_ENDIAN) + +if (IS_BIG_ENDIAN) + list(APPEND DEPENDENCIES_DEFINES "TA_BIG_ENDIAN") endif() set(DEPENDENCIES_COMPILE_OPTIONS "") @@ -95,6 +115,7 @@ else() if (WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(WITH_OUR_MALLOC ON CACHE BOOL "aaa" FORCE) endif() + set(BUILD_TESTS OFF CACHE BOOL "come on" FORCE) add_subdirectory(extern/fftw EXCLUDE_FROM_ALL) list(APPEND DEPENDENCIES_INCLUDE_DIRS extern/fftw/api) list(APPEND DEPENDENCIES_LIBRARIES fftw3) @@ -220,6 +241,11 @@ if (USE_SDL2) set(SDL_SHARED OFF CACHE BOOL "Force no dynamically-linked SDL" FORCE) set(SDL_STATIC ON CACHE BOOL "Force statically-linked SDL" FORCE) endif() + # https://github.com/libsdl-org/SDL/issues/5535 + # disable PipeWire support due to an unfixable bug: + # Looks like their headers have a C90 violation... I imagine they're probably on C99 so not the craziest bug in the world. Definitely file this at the PipeWire repository as well so they know this is out there. + set(SDL_PIPEWIRE OFF CACHE BOOL "Use Pipewire audio" FORCE) + # https://github.com/libsdl-org/SDL/issues/1481 # On 2014-06-22 17:15:50 +0000, Sam Lantinga wrote: # If you link SDL statically, you also need to define HAVE_LIBC so it builds with the C runtime that your application uses. @@ -252,7 +278,7 @@ src/audio/midi.cpp ) if (USE_SDL2) - list(APPEND AUDIO_SOURCES src/audio/sdl.cpp) + list(APPEND AUDIO_SOURCES src/audio/sdlAudio.cpp) endif() if (WITH_JACK) @@ -342,7 +368,26 @@ src/engine/platform/sound/c64/wave8580_PST.cc src/engine/platform/sound/c64/wave8580_P_T.cc src/engine/platform/sound/c64/wave8580__ST.cc -src/engine/platform/sound/tia/TIASnd.cpp +src/engine/platform/sound/c64_fp/Dac.cpp +src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp +src/engine/platform/sound/c64_fp/ExternalFilter.cpp +src/engine/platform/sound/c64_fp/Filter6581.cpp +src/engine/platform/sound/c64_fp/Filter8580.cpp +src/engine/platform/sound/c64_fp/Filter.cpp +src/engine/platform/sound/c64_fp/FilterModelConfig6581.cpp +src/engine/platform/sound/c64_fp/FilterModelConfig8580.cpp +src/engine/platform/sound/c64_fp/FilterModelConfig.cpp +src/engine/platform/sound/c64_fp/Integrator6581.cpp +src/engine/platform/sound/c64_fp/Integrator8580.cpp +src/engine/platform/sound/c64_fp/OpAmp.cpp +src/engine/platform/sound/c64_fp/SID.cpp +src/engine/platform/sound/c64_fp/Spline.cpp +src/engine/platform/sound/c64_fp/WaveformCalculator.cpp +src/engine/platform/sound/c64_fp/WaveformGenerator.cpp +src/engine/platform/sound/c64_fp/resample/SincResampler.cpp + +src/engine/platform/sound/tia/AudioChannel.cpp +src/engine/platform/sound/tia/Audio.cpp src/engine/platform/sound/ymfm/ymfm_adpcm.cpp src/engine/platform/sound/ymfm/ymfm_opm.cpp @@ -450,15 +495,24 @@ src/engine/platform/ymz280b.cpp src/engine/platform/namcowsg.cpp src/engine/platform/rf5c68.cpp src/engine/platform/snes.cpp +src/engine/platform/pcmdac.cpp src/engine/platform/dummy.cpp ) +if (USE_SNDFILE) + list(APPEND ENGINE_SOURCES src/engine/sfWrapper.cpp) +endif() + if (WIN32) list(APPEND ENGINE_SOURCES src/utfutils.cpp) list(APPEND ENGINE_SOURCES src/engine/winStuff.cpp) list(APPEND ENGINE_SOURCES res/furnace.rc) endif() +set(CLI_SOURCES +src/cli/cli.cpp +) + set(GUI_SOURCES extern/imgui_patched/imgui.cpp extern/imgui_patched/imgui_draw.cpp @@ -498,6 +552,7 @@ src/gui/editing.cpp src/gui/editControls.cpp src/gui/effectList.cpp src/gui/findReplace.cpp +src/gui/gradient.cpp src/gui/insEdit.cpp src/gui/log.cpp src/gui/mixer.cpp @@ -505,6 +560,7 @@ src/gui/midiMap.cpp src/gui/newSong.cpp src/gui/orders.cpp src/gui/osc.cpp +src/gui/patManager.cpp src/gui/pattern.cpp src/gui/piano.cpp src/gui/presets.cpp @@ -513,31 +569,75 @@ src/gui/sampleEdit.cpp src/gui/settings.cpp src/gui/songInfo.cpp src/gui/songNotes.cpp +src/gui/spoiler.cpp src/gui/stats.cpp src/gui/subSongs.cpp src/gui/sysConf.cpp src/gui/sysEx.cpp +src/gui/sysManager.cpp +src/gui/sysPicker.cpp src/gui/util.cpp src/gui/waveEdit.cpp src/gui/volMeter.cpp src/gui/gui.cpp ) +if (WIN32 OR APPLE) + list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_common.cpp) +endif() + +if (WIN32) + list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_win.cpp) +endif() + if (APPLE) list(APPEND GUI_SOURCES src/gui/macstuff.m) + list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_cocoa.mm) endif() if (NOT WIN32 AND NOT APPLE) list(APPEND GUI_SOURCES src/gui/icon.c) + + CHECK_INCLUDE_FILE(sys/io.h SYS_IO_FOUND) + CHECK_INCLUDE_FILE(linux/input.h LINUX_INPUT_FOUND) + CHECK_INCLUDE_FILE(linux/kd.h LINUX_KD_FOUND) + if (SYS_IO_FOUND) + try_compile(HAVE_INOUTB ${CMAKE_BINARY_DIR}/check SOURCES ${CMAKE_SOURCE_DIR}/src/check/check_sysIO.c) + if (HAVE_INOUTB) + list(APPEND DEPENDENCIES_DEFINES HAVE_SYS_IO) + message(STATUS "PC speaker output: outb()") + else() + message(STATUS "sys/io.h found but inb()/outb() not present") + endif() + endif() + if (LINUX_INPUT_FOUND) + list(APPEND DEPENDENCIES_DEFINES HAVE_LINUX_INPUT) + message(STATUS "PC speaker output: evdev") + endif() + if (LINUX_KD_FOUND) + list(APPEND DEPENDENCIES_DEFINES HAVE_LINUX_KD) + message(STATUS "PC speaker output: KIOCSOUND") + endif() endif() -set(USED_SOURCES ${ENGINE_SOURCES} ${AUDIO_SOURCES} src/main.cpp) +if (NOT WIN32) + try_compile(HAVE_DIRENT_TYPE ${CMAKE_BINARY_DIR}/check SOURCES ${CMAKE_SOURCE_DIR}/src/check/check_dirent_type.c) + if (HAVE_DIRENT_TYPE) + list(APPEND DEPENDENCIES_DEFINES HAVE_DIRENT_TYPE) + endif() +endif() + +set(USED_SOURCES ${ENGINE_SOURCES} ${AUDIO_SOURCES} ${CLI_SOURCES} src/main.cpp) if (USE_BACKWARD) list(APPEND USED_SOURCES src/backtrace.cpp) if (WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") list(APPEND DEPENDENCIES_LIBRARIES dbghelp psapi) endif() + find_library(EXECINFO_IS_LIBRARY execinfo) + if (EXECINFO_IS_LIBRARY) + list(APPEND DEPENDENCIES_LIBRARIES execinfo) + endif() message(STATUS "Using backward-cpp") else() message(STATUS "Not using backward-cpp") @@ -552,6 +652,11 @@ if (BUILD_GUI) extern/IconFontCppHeaders extern/igfd ) + if (WIN32 OR APPLE) + list(APPEND DEPENDENCIES_INCLUDE_DIRS + extern/nfd-modified/src/include + ) + endif() list(APPEND DEPENDENCIES_DEFINES HAVE_GUI) message(STATUS "Building GUI") else() @@ -573,7 +678,11 @@ endif() if (NOT MSVC) set(WARNING_FLAGS -Wall -Wextra -Wno-unused-parameter) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - list(APPEND WARNING_FLAGS -Wno-cast-function-type) + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8.0.0) + # nothing + else() + list(APPEND WARNING_FLAGS -Wno-cast-function-type) + endif() endif() if (WARNINGS_ARE_ERRORS) list(APPEND WARNING_FLAGS -Werror) @@ -627,24 +736,29 @@ if (PKG_CONFIG_FOUND AND (SYSTEM_FMT OR SYSTEM_LIBSNDFILE OR SYSTEM_ZLIB OR SYST endif() if (NOT ANDROID OR TERMUX) - install(TARGETS furnace RUNTIME DESTINATION bin) - if (NOT WIN32 AND NOT APPLE) include(GNUInstallDirs) + 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(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/furnace) - install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace) - install(DIRECTORY instruments DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace) + if (WITH_DEMOS) + install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace) + endif() + if (WITH_INSTRUMENTS) + install(DIRECTORY instruments DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace) + endif() foreach(num 16 32 64 128 256 512) set(res ${num}x${num}) install(FILES res/icon.iconset/icon_${res}.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${res}/apps) install(FILES res/icon.iconset/icon_${res}@2x.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${res}@2/apps) endforeach() install(FILES res/logo.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/1024x1024/apps) + else() + install(TARGETS furnace RUNTIME DESTINATION bin) endif() - + set(CPACK_PACKAGE_NAME "Furnace") set(CPACK_PACKAGE_VENDOR "tildearrow") set(CPACK_PACKAGE_DESCRIPTION "free and open-source chiptune tracker") diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 97efdf8c..853020f9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,7 +22,7 @@ bug fixes, improvements and several other things accepted. the coding style is described here: -- indentation: two spaces +- indentation: two spaces. **strictly** spaces. do NOT use tabs. - modified 1TBS style: - no spaces in function calls - spaces between arguments in function declarations @@ -33,6 +33,7 @@ the coding style is described here: - indent switch cases - preprocessor directives not intended - if macro comprises more than one line, indent + - no new line after `template<>` - prefer built-in types: - `bool` - `signed char` or `unsigned char` are 8-bit @@ -53,6 +54,11 @@ the coding style is described here: - don't use `auto` unless needed. - use `String` for `std::string` (this is typedef'd in ta-utils.h). - prefer using operator for String (std::string) comparisons (a==""). +- if you have to work with C strings, only use safe C string operations: + - snprintf + - strncpy + - strncat + - any other operation which specifies a limit some files (particularly the ones in `src/engine/platform/sound` and `extern/`) don't follow this style. @@ -76,6 +82,7 @@ just put your demo song in `demos/`! be noted there are some guidelines: - avoid Nintendo song covers. - avoid big label song covers. +- avoid poor quality songs. # Finishing diff --git a/README.md b/README.md index caa4714c..7efe2c2a 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Furnace Tracker -![screenshot](papers/screenshot1.png) +![screenshot](papers/screenshot2.png) -this is a multi-system chiptune tracker. +the biggest multi-system chiptune tracker ever made! [downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [FAQ](#frequently-asked-questions) -*** +--- ## downloads check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage). @@ -15,37 +15,95 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a ## features -- supports the following systems: - - Sega Genesis - - Sega Master System +- over 50 sound chips - and counting: + - Yamaha FM chips: + - YM2151 (OPM) + - YM2203 (OPN) + - YM2413 (OPLL) + - YM2414 (OPZ) used in Yamaha TX81Z + - YM2608 (OPNA) used in PC-98 + - YM2610 (OPNB) used in Neo Geo + - YM2610B (OPNB2) + - YM2612 (OPN2) used in Sega Genesis and FM Towns + - YM3526 (OPL) used in C64 Sound Expander + - YM3812 (OPL2) + - YMF262 (OPL3) with full 4-op support! + - Y8950 (OPL with ADPCM) + - square wave chips: + - AY-3-8910/YM2149(F) used in several computers and game consoles + - Commodore VIC used in the VIC-20 + - Microchip AY8930 + - TI SN76489 used in Sega Master System and BBC Micro + - PC Speaker + - Philips SAA1099 used in SAM Coupé + - sample chips: + - Amiga + - SegaPCM - all 16 channels + - Capcom QSound + - Yamaha YMZ280B (PCMD8) + - Ricoh RF5C68 used in Sega CD and FM Towns + - OKI MSM6258 and MSM6295 + - wavetable chips: + - HuC6280 used in PC Engine + - Konami Bubble System WSG + - Konami SCC/SCC+ + - Namco arcade chips (WSG/C15/C30) + - WonderSwan + - Seta/Allumer X1-010 + - NES (Ricoh 2A03/2A07), with additional expansion sound support: + - Konami VRC6 + - Konami VRC7 + - MMC5 + - Famicom Disk System + - Sunsoft 5B + - Namco 163 + - Family Noraebang (OPLL) + - SID (6581/8580) used in Commodore 64 + - Mikey used in Atari Lynx + - ZX Spectrum beeper (SFX-like engine) + - Commodore PET + - TIA used in Atari 2600 - Game Boy - - PC Engine - - NES - - Commodore 64 - - Yamaha YM2151 (plus PCM) - - Neo Geo - - AY-3-8910 (ZX Spectrum, Atari ST, etc.) - - Microchip AY8930 - - Philips SAA1099 - - Amiga - - TIA (Atari 2600/7800) -- multiple sound chips in a single song! -- DefleMask compatibility - loads .dmf modules, .dmp instruments and .dmw wavetables -- clean-room design (guesswork and ABX tests only, no decompilation involved) -- bug/quirk implementation for increased playback accuracy -- VGM and audio file export -- accurate emulation cores whether possible (Nuked, MAME, SameBoy, Mednafen PCE, puNES, reSID, Stella, SAASound and ymfm) -- additional features on top: + - modern/fantasy: + - Commander X16 VERA + - tildearrow Sound Unit +- mix and match sound chips! + - 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.3) + - saves .dmf modules - both modern and legacy + - Furnace doubles as a module downgrader + - loads/saves .dmp instruments and .dmw wavetables as well + - clean-room design (guesswork and ABX tests only, no decompilation involved) + - bug/quirk implementation for increased playback accuracy through compatibility flags +- VGM export +- modular layout that you may adapt to your needs +- audio file export - entire song, per system or per channel +- quality emulation cores (Nuked, MAME, SameBoy, Mednafen PCE, NSFplay, puNES, reSID, Stella, SAASound, vgsound_emu and ymfm) +- wavetable synthesizer + - available on wavetable chips + - create complex sounds with ease - provide up to two wavetables, select and effect and let go! +- MIDI input support +- [Fractal Sound](https://gitlab.com/Natsumi/Fractal-Sound) support! + - the game-ready Sega Genesis/Mega Drive sound driver! + - compose your songs in Furnace using the Fractal Sound presets and then use them in your games with Fractal! +- additional features: - FM macros! - negative octaves - arbitrary pitch samples - sample loop points - - SSG envelopes in Neo Geo + - SSG envelopes and ADPCM-B in Neo Geo - full duty/cutoff range in C64 - - ability to change tempo mid-song with `Cxxx` effect (`xxx` between `000` and `3ff`) + - ability to change tempo mid-song + - multiple sub-songs in a module + - per-channel oscilloscope with waveform centering + - built-in sample editor + - chip mixing settings + - built-in visualizer in pattern view - open-source under GPLv2 or later. -*** +--- # quick references - **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z). @@ -59,9 +117,9 @@ some people have provided packages for Unix/Unix-like distributions. here's a li - **Arch Linux**: [furnace-git is in the AUR.](https://aur.archlinux.org/packages/furnace-git) thank you Essem! - **FreeBSD**: [a package in ports](https://www.freshports.org/audio/furnace/) is available courtesy of ehaupt. - **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608. - - **OpenSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari. + - **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari. -*** +--- # developer info [![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml) @@ -73,11 +131,17 @@ if you can't download these artifacts (because GitHub requires you to be logged ## dependencies - CMake -- SDL2 -- zlib -- JACK (optional) +- JACK (optional, macOS/Linux only) -SDL2 and zlib are included as submodules. +if building under Windows or macOS, no additional dependencies are required. +otherwise, you may also need the following: + +- libpulse +- libx11 +- libasound +- libGL + +some Linux distributions (e.g. Ubuntu or openSUSE) will require you to install the `-dev` versions of these. ## getting the source @@ -127,14 +191,22 @@ Available options: | Name | Default | Description | | :--: | :-----: | ----------- | | `BUILD_GUI` | `ON` | Build the tracker (disable to build only a headless player) | +| `USE_RTMIDI` | `ON` | Build with MIDI support using RtMidi | +| `USE_SDL2` | `ON` | Build with SDL2 (required to build with GUI) | +| `USE_SNDFILE` | `ON` | Build with libsndfile (required in order to work with audio files) | +| `USE_BACKWARD` | `ON` | Use backward-cpp to print a backtrace on crash/abort | | `WITH_JACK` | `ON` if system-installed JACK detected, otherwise `OFF` | Whether to build with JACK support. Auto-detects if JACK is available | +| `SYSTEM_FFTW` | `OFF` | Use a system-installed version of FFTW instead of the vendored one | | `SYSTEM_FMT` | `OFF` | Use a system-installed version of fmt instead of the vendored one | | `SYSTEM_LIBSNDFILE` | `OFF` | Use a system-installed version of libsndfile instead of the vendored one | +| `SYSTEM_RTMIDI` | `OFF` | Use a system-installed version of RtMidi instead of the vendored one | | `SYSTEM_ZLIB` | `OFF` | Use a system-installed version of zlib instead of the vendored one | | `SYSTEM_SDL2` | `OFF` | Use a system-installed version of SDL2 instead of the vendored one | | `WARNINGS_ARE_ERRORS` | `OFF` (but consider enabling this & reporting any errors that arise from it!) | Whether warnings in furnace's C++ code should be treated as errors | +| `WITH_DEMOS` | `ON` | Install demo songs on `make install` | +| `WITH_INSTRUMENTS` | `ON` | Install demo instruments on `make install` | -## usage +## console usage ``` ./furnace @@ -156,36 +228,21 @@ 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.** -*** -# notes - -> how do I use Neo Geo SSG envelopes? - -the following effects are provided: - -- `22xy`: set envelope mode. - - `x` sets the envelope shape, which may be one of the following: - - `0: \___` decay - - `4: /___` attack once - - `8: \\\\` saw - - `9: \___` decay - - `A: \/\/` inverse obelisco - - `B: \¯¯¯` decay once - - `C: ////` inverse saw - - `D: /¯¯¯` attack - - `E: /\/\` obelisco - - `F: /___` attack once - - if `y` is 1 then the envelope will affect this channel. -- `23xx`: set envelope period low byte. -- `24xx`: set envelope period high byte. -- `25xx`: slide envelope period up. -- `26xx`: slide envelope period down. - -a lower envelope period will make the envelope run faster. - -*** +--- # 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 systems (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. + > how do I use C64 absolute filter/duty? on Instrument Editor in the C64 tab there are two options to toggle these. @@ -195,27 +252,29 @@ also provided are two effects: - `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`.) -> Q: how do I use PCM on a PCM-capable system? +> how do I use PCM on a PCM-capable system? -A: Two possibilities: the recommended way is via creating the "Amiga/Sample" type instrument and assigning sample to it, or via old, Deflemask-compatible method, using `17xx` effect +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. -> Q: my song sounds very odd at a certain point +> my .dmf song sounds very odd at a certain point -A: file a bug report. use the Issues page. it's probably another playback inaccuracy. +file a bug report. use the Issues page. it's probably another playback inaccuracy. -> Q: my song sounds correct, but it doesn't in DefleMask +> my .dmf song sounds correct, but it doesn't in DefleMask -A: file a bug report **here**. it still is a playback inaccuracy. +file a bug report **here**. it still is a playback inaccuracy. -> Q: my C64 song sounds terrible after saving as .dmf! +> my song sounds terrible after saving as .dmf! -A: that's a limitation of the DefleMask format. save in Furnace song format instead (.fur). +the DefleMask format has several limitations. save in Furnace song format instead (.fur). -> Q: how do I solo channels? +> how do I solo channels? -A: right click on the channel name. +right click on the channel name. -*** +--- # footnotes copyright (C) 2021-2022 tildearrow and contributors. diff --git a/TODO.md b/TODO.md index 5bd4e892..a0da2bbd 100644 --- a/TODO.md +++ b/TODO.md @@ -1,12 +1,7 @@ -# to-do for 0.6pre1 +# to-do for 0.6pre1.5 -- rewrite the system name detection function anyway -- add another FM editor layout -- add ability to move selection by dragging -- find and replace -- implement Defle slide bug when using E1xy/E2xy and repeating origin note (requires format change) - -# to-do for 0.6pre2 (as this requires new data structures) - -- Game Boy envelope macro/sequence -- volume commands should work on Game Boy +- stereo separation control for AY +- "paste with instrument" +- channel appearance settings +- auto-detect system +- bug fixes diff --git a/android/app/build.gradle b/android/app/build.gradle index cb4b3c59..92a79e4d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -15,8 +15,8 @@ android { } minSdkVersion 21 targetSdkVersion 26 - versionCode 93 - versionName "0.6pre1" + versionCode 113 + versionName "dev113" externalNativeBuild { cmake { arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static" diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 5f7a06ef..b77cf32e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ @@ -83,13 +83,17 @@ - diff --git a/demos/AY-3-8910_Jam.fur b/demos/AY-3-8910_Jam.fur index b249c5bf..dad4f190 100644 Binary files a/demos/AY-3-8910_Jam.fur and b/demos/AY-3-8910_Jam.fur differ diff --git a/demos/BONUS. Sonic 2 Boss.fur b/demos/BONUS. Sonic 2 Boss.fur new file mode 100644 index 00000000..8f5263f7 Binary files /dev/null and b/demos/BONUS. Sonic 2 Boss.fur differ diff --git a/demos/Bullet_Hell.fur b/demos/Bullet_Hell.fur new file mode 100644 index 00000000..dfd2a042 Binary files /dev/null and b/demos/Bullet_Hell.fur differ diff --git a/demos/Cafe - 010 Editor 2.0crk.fur b/demos/Cafe - 010 Editor 2.0crk.fur new file mode 100644 index 00000000..339e7238 Binary files /dev/null and b/demos/Cafe - 010 Editor 2.0crk.fur differ diff --git a/demos/ChaosTune.fur b/demos/ChaosTune.fur new file mode 100644 index 00000000..b75fbf9c Binary files /dev/null and b/demos/ChaosTune.fur differ diff --git a/demos/Checknobankh.fur b/demos/Checknobankh.fur new file mode 100644 index 00000000..ac465df9 Binary files /dev/null and b/demos/Checknobankh.fur differ diff --git a/demos/Contraduct Design OPL3 Cover.fur b/demos/Contraduct Design OPL3 Cover.fur new file mode 100644 index 00000000..750fb2d3 Binary files /dev/null and b/demos/Contraduct Design OPL3 Cover.fur differ diff --git a/demos/E1M4OPL2.fur b/demos/E1M4OPL2.fur new file mode 100644 index 00000000..19f8ce54 Binary files /dev/null and b/demos/E1M4OPL2.fur differ diff --git a/demos/Egyptian_Rule.fur b/demos/Egyptian_Rule.fur new file mode 100644 index 00000000..1715906e Binary files /dev/null and b/demos/Egyptian_Rule.fur differ diff --git a/demos/FDS TEST.fur b/demos/FDS TEST.fur new file mode 100644 index 00000000..54bdd36c Binary files /dev/null and b/demos/FDS TEST.fur differ diff --git a/demos/FEDMS.fur b/demos/FEDMS.fur new file mode 100644 index 00000000..4c521dad Binary files /dev/null and b/demos/FEDMS.fur differ diff --git a/demos/Fake Gameboy.fur b/demos/Fake Gameboy.fur new file mode 100644 index 00000000..50d7e36d Binary files /dev/null and b/demos/Fake Gameboy.fur differ diff --git a/demos/Funky_Bubbles_OPL3.fur b/demos/Funky_Bubbles_OPL3.fur new file mode 100644 index 00000000..5f2ffd24 Binary files /dev/null and b/demos/Funky_Bubbles_OPL3.fur differ diff --git a/demos/Fusion.fur b/demos/Fusion.fur new file mode 100644 index 00000000..a69f26e8 Binary files /dev/null and b/demos/Fusion.fur differ diff --git a/demos/GEN Equinox Intro.fur b/demos/GEN Equinox Intro.fur new file mode 100644 index 00000000..04a5bb05 Binary files /dev/null and b/demos/GEN Equinox Intro.fur differ diff --git a/demos/MetalSlug_BaseCamp_SMS_TIA.fur b/demos/MetalSlug_BaseCamp_SMS_TIA.fur new file mode 100644 index 00000000..dd2fa6af Binary files /dev/null and b/demos/MetalSlug_BaseCamp_SMS_TIA.fur differ diff --git a/demos/Phoenix_cover.fur b/demos/Phoenix_cover.fur new file mode 100644 index 00000000..1a031443 Binary files /dev/null and b/demos/Phoenix_cover.fur differ diff --git a/demos/Rise_against_the_ashes_to_the_new_dawn.fur b/demos/Rise_against_the_ashes_to_the_new_dawn.fur new file mode 100644 index 00000000..be28182f Binary files /dev/null and b/demos/Rise_against_the_ashes_to_the_new_dawn.fur differ diff --git a/demos/Silver Surfer - Stage Music 1.fur b/demos/Silver Surfer - Stage Music 1.fur new file mode 100644 index 00000000..bbe6a3c7 Binary files /dev/null and b/demos/Silver Surfer - Stage Music 1.fur differ diff --git a/demos/The Cheetahmen.fur b/demos/The Cheetahmen.fur new file mode 100644 index 00000000..105b64b1 Binary files /dev/null and b/demos/The Cheetahmen.fur differ diff --git a/demos/UNATCOPCM.fur b/demos/UNATCOPCM.fur deleted file mode 100644 index ca8a0a9f..00000000 Binary files a/demos/UNATCOPCM.fur and /dev/null differ diff --git a/demos/Waterworld_-_Map.fur b/demos/Waterworld_-_Map.fur new file mode 100644 index 00000000..2bf8a7af Binary files /dev/null and b/demos/Waterworld_-_Map.fur differ diff --git a/demos/atmosphere.fur b/demos/atmosphere.fur new file mode 100644 index 00000000..7600b42c Binary files /dev/null and b/demos/atmosphere.fur differ diff --git a/demos/c64 ring test.fur b/demos/c64 ring test.fur new file mode 100644 index 00000000..72304532 Binary files /dev/null and b/demos/c64 ring test.fur differ diff --git a/demos/ecolove.fur b/demos/ecolove.fur deleted file mode 100644 index 9f287ec6..00000000 Binary files a/demos/ecolove.fur and /dev/null differ diff --git a/demos/game boy thing.fur b/demos/game boy thing.fur new file mode 100644 index 00000000..297ca9f9 Binary files /dev/null and b/demos/game boy thing.fur differ diff --git a/demos/going_up_a_step_at_time.fur b/demos/going_up_a_step_at_time.fur new file mode 100644 index 00000000..7d52d155 Binary files /dev/null and b/demos/going_up_a_step_at_time.fur differ diff --git a/demos/government funding breakcore-ish remix.fur b/demos/government funding breakcore-ish remix.fur new file mode 100644 index 00000000..7acbabc4 Binary files /dev/null and b/demos/government funding breakcore-ish remix.fur differ diff --git a/demos/her11_veraedit.fur b/demos/her11_veraedit.fur new file mode 100644 index 00000000..1cfabac1 Binary files /dev/null and b/demos/her11_veraedit.fur differ diff --git a/demos/home_wfl_opl3.fur b/demos/home_wfl_opl3.fur new file mode 100644 index 00000000..af2e952f Binary files /dev/null and b/demos/home_wfl_opl3.fur differ diff --git a/demos/hope_for_the_dream.fur b/demos/hope_for_the_dream.fur new file mode 100644 index 00000000..77a3fed2 Binary files /dev/null and b/demos/hope_for_the_dream.fur differ diff --git a/demos/iji_tor.fur b/demos/iji_tor.fur new file mode 100644 index 00000000..84a6ac5b Binary files /dev/null and b/demos/iji_tor.fur differ diff --git a/demos/lunacommdemo.fur b/demos/lunacommdemo.fur new file mode 100644 index 00000000..43531b8b Binary files /dev/null and b/demos/lunacommdemo.fur differ diff --git a/demos/massive_x_opz.fur b/demos/massive_x_opz.fur new file mode 100644 index 00000000..f32a1f26 Binary files /dev/null and b/demos/massive_x_opz.fur differ diff --git a/demos/rule2.fur b/demos/rule2.fur new file mode 100644 index 00000000..aa637591 Binary files /dev/null and b/demos/rule2.fur differ diff --git a/demos/su_memory.fur b/demos/su_memory.fur index cb80b8b1..ff030dbe 100644 Binary files a/demos/su_memory.fur and b/demos/su_memory.fur differ diff --git a/demos/the_king_of_crisp.fur b/demos/the_king_of_crisp.fur new file mode 100644 index 00000000..afb4cbb8 Binary files /dev/null and b/demos/the_king_of_crisp.fur differ diff --git a/demos/the_machines_are_socialising.fur b/demos/the_machines_are_socialising.fur new file mode 100644 index 00000000..870874da Binary files /dev/null and b/demos/the_machines_are_socialising.fur differ diff --git a/demos/the_serenity_of_lonliness.fur b/demos/the_serenity_of_lonliness.fur new file mode 100644 index 00000000..dc733370 Binary files /dev/null and b/demos/the_serenity_of_lonliness.fur differ diff --git a/demos/thick bass test.fur b/demos/thick bass test.fur new file mode 100644 index 00000000..13268e48 Binary files /dev/null and b/demos/thick bass test.fur differ diff --git a/extern/Nuked-OPLL/opll.c b/extern/Nuked-OPLL/opll.c index ecff69fd..bd59709a 100644 --- a/extern/Nuked-OPLL/opll.c +++ b/extern/Nuked-OPLL/opll.c @@ -1019,6 +1019,10 @@ static void OPLL_Operator(opll_t *chip) { } chip->ch_out = ismod1 ? routput : (output>>3); + + if (!ismod1) { + chip->output_ch[(chip->cycles+1)%9] = chip->ch_out; + } } static void OPLL_DoRhythm(opll_t *chip) { diff --git a/extern/Nuked-OPLL/opll.h b/extern/Nuked-OPLL/opll.h index 85c721a7..98113939 100644 --- a/extern/Nuked-OPLL/opll.h +++ b/extern/Nuked-OPLL/opll.h @@ -191,6 +191,8 @@ typedef struct { int16_t output_m; int16_t output_r; + int16_t output_ch[9]; + } opll_t; const opll_patch_t* OPLL_GetPatchROM(uint32_t chip_type); diff --git a/extern/Nuked-OPN2 b/extern/Nuked-OPN2 index 64704a44..b0e9de0f 160000 --- a/extern/Nuked-OPN2 +++ b/extern/Nuked-OPN2 @@ -1 +1 @@ -Subproject commit 64704a443f8f6c1906ba26297092ea70fa1d45d7 +Subproject commit b0e9de0f816943ad3820ddfefa0fff276d659250 diff --git a/extern/Nuked-PSG/ympsg.c b/extern/Nuked-PSG/ympsg.c index 3df4f8e3..0cc065ed 100644 --- a/extern/Nuked-PSG/ympsg.c +++ b/extern/Nuked-PSG/ympsg.c @@ -130,7 +130,7 @@ static void YMPSG_ClockInternal1(ympsg_t *chip) else if (noise_of && !chip->noise_of) { noise_bit1 = (chip->noise >> chip->noise_tap2) & 1; - noise_bit2 = (chip->noise >> 12) & 1; + noise_bit2 = (chip->noise >> chip->noise_tap1) & 1; noise_bit1 ^= noise_bit2; noise_next = ((noise_bit1 && ((chip->noise_data >> 2) & 1)) || ((chip->noise & chip->noise_size) == 0)); chip->noise <<= 1; @@ -245,6 +245,11 @@ void YMPSG_Write(ympsg_t *chip, uint8_t data) chip->write_flag = 1; } +void YMPSG_WriteStereo(ympsg_t *chip, uint8_t data) +{ + chip->stereo = data; +} + uint16_t YMPSG_Read(ympsg_t *chip) { uint16_t data = 0; @@ -257,13 +262,15 @@ uint16_t YMPSG_Read(ympsg_t *chip) return data; } -void YMPSG_Init(ympsg_t *chip, uint8_t real_sn) +void YMPSG_Init(ympsg_t *chip, uint8_t real_sn, uint8_t noise_tap1, uint8_t noise_tap2, uint32_t noise_size) { uint32_t i; memset(chip, 0, sizeof(ympsg_t)); YMPSG_SetIC(chip, 1); - chip->noise_tap2 = real_sn ? 13 : 15; - chip->noise_size = real_sn ? 16383 : 32767; + chip->noise_tap1 = noise_tap1; + chip->noise_tap2 = noise_tap2; + chip->noise_size = noise_size; + chip->stereo = 0xff; for (i = 0; i < 17; i++) { chip->vol_table[i]=(real_sn?tipsg_vol[i]:ympsg_vol[i]) * 8192.0f; @@ -315,32 +322,62 @@ void YMPSG_Clock(ympsg_t *chip) } } -int YMPSG_GetOutput(ympsg_t *chip) +void YMPSG_GetOutput(ympsg_t *chip, int* left, int* right) { - int sample = 0; + int sample_left = 0; + int sample_right = 0; uint32_t i; YMPSG_UpdateSample(chip); if (chip->test & 1) { - sample += chip->vol_table[chip->volume_out[chip->test >> 1]]; - sample += chip->vol_table[16] * 3; + sample_left += chip->vol_table[chip->volume_out[chip->test >> 1]]; + sample_left += chip->vol_table[16] * 3; + sample_right += chip->vol_table[chip->volume_out[chip->test >> 1]]; + sample_right += chip->vol_table[16] * 3; } else if (!chip->mute) { - sample += chip->vol_table[chip->volume_out[0]]; - sample += chip->vol_table[chip->volume_out[1]]; - sample += chip->vol_table[chip->volume_out[2]]; - sample += chip->vol_table[chip->volume_out[3]]; + if (chip->stereo&(0x10)) { + sample_left += chip->vol_table[chip->volume_out[0]]; + } + if (chip->stereo&(0x01)) { + sample_right += chip->vol_table[chip->volume_out[0]]; + } + if (chip->stereo&(0x20)) { + sample_left += chip->vol_table[chip->volume_out[1]]; + } + if (chip->stereo&(0x02)) { + sample_right += chip->vol_table[chip->volume_out[1]]; + } + if (chip->stereo&(0x40)) { + sample_left += chip->vol_table[chip->volume_out[2]]; + } + if (chip->stereo&(0x04)) { + sample_right += chip->vol_table[chip->volume_out[2]]; + } + if (chip->stereo&(0x80)) { + sample_left += chip->vol_table[chip->volume_out[3]]; + } + if (chip->stereo&(0x08)) { + sample_right += chip->vol_table[chip->volume_out[3]]; + } } else { for (i = 0; i < 4; i++) { - if (!((chip->mute>>i) & 1)) - sample += chip->vol_table[chip->volume_out[i]]; + if (!((chip->mute>>i) & 1)) { + if (chip->stereo&(0x10<vol_table[chip->volume_out[i]]; + } + if (chip->stereo&(0x01<vol_table[chip->volume_out[i]]; + } + } } } - return sample; + *left=sample_left; + *right=sample_right; } void YMPSG_Test(ympsg_t *chip, uint16_t test) @@ -348,7 +385,7 @@ void YMPSG_Test(ympsg_t *chip, uint16_t test) chip->test = (test >> 9) & 7; } - +/* void YMPSG_Generate(ympsg_t *chip, int32_t *buf) { uint32_t i; @@ -372,7 +409,7 @@ void YMPSG_Generate(ympsg_t *chip, int32_t *buf) } out = YMPSG_GetOutput(chip); *buf = (int32_t)(out * 8192.f); -} +}*/ void YMPSG_WriteBuffered(ympsg_t *chip, uint8_t data) { diff --git a/extern/Nuked-PSG/ympsg.h b/extern/Nuked-PSG/ympsg.h index c00b3d72..bb3f6009 100644 --- a/extern/Nuked-PSG/ympsg.h +++ b/extern/Nuked-PSG/ympsg.h @@ -46,11 +46,14 @@ typedef struct { uint8_t sign_l; uint8_t noise_sign_l; uint16_t noise; + uint8_t noise_tap1; uint8_t noise_tap2; - uint16_t noise_size; + uint32_t noise_size; uint8_t test; uint8_t volume_out[4]; + uint8_t stereo; + // uint64_t writebuf_samplecnt; uint32_t writebuf_cur; @@ -67,15 +70,16 @@ typedef struct { void YMPSG_Write(ympsg_t *chip, uint8_t data); +void YMPSG_WriteStereo(ympsg_t *chip, uint8_t data); uint16_t YMPSG_Read(ympsg_t *chip); -void YMPSG_Init(ympsg_t *chip, uint8_t real_sn); +void YMPSG_Init(ympsg_t *chip, uint8_t real_sn, uint8_t noise_tap1, uint8_t noise_tap2, uint32_t noise_size); void YMPSG_SetIC(ympsg_t *chip, uint32_t ic); void YMPSG_Clock(ympsg_t *chip); -int YMPSG_GetOutput(ympsg_t *chip); +void YMPSG_GetOutput(ympsg_t *chip, int* left, int* right); void YMPSG_Test(ympsg_t *chip, uint16_t test); -void YMPSG_Generate(ympsg_t *chip, int32_t *buf); +//void YMPSG_Generate(ympsg_t *chip, int32_t *buf); void YMPSG_WriteBuffered(ympsg_t *chip, uint8_t data); void YMPSG_SetMute(ympsg_t *chip, uint8_t mute); diff --git a/extern/backward/backward.hpp b/extern/backward/backward.hpp index 5edda65a..04032a4d 100644 --- a/extern/backward/backward.hpp +++ b/extern/backward/backward.hpp @@ -221,6 +221,14 @@ #include #include #include +// https://github.com/tildearrow/furnace/issues/588 +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#include +#undef _GNU_SOURCE +#else +#include +#endif #if BACKWARD_HAS_BFD == 1 // NOTE: defining PACKAGE{,_VERSION} is required before including @@ -233,13 +241,6 @@ #define PACKAGE_VERSION #endif #include -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#include -#undef _GNU_SOURCE -#else -#include -#endif #endif #if BACKWARD_HAS_DW == 1 @@ -254,13 +255,6 @@ #include #include #include -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#include -#undef _GNU_SOURCE -#else -#include -#endif #endif #if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1) diff --git a/extern/igfd/ImGuiFileDialog.cpp b/extern/igfd/ImGuiFileDialog.cpp index f7a2be5b..63ae3b87 100644 --- a/extern/igfd/ImGuiFileDialog.cpp +++ b/extern/igfd/ImGuiFileDialog.cpp @@ -58,13 +58,13 @@ SOFTWARE. #ifndef PATH_MAX #define PATH_MAX 260 #endif // PATH_MAX -#elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined (__EMSCRIPTEN__) +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined (__EMSCRIPTEN__) || defined(__HAIKU__) #define UNIX #define stricmp strcasecmp #include // this option need c++17 #ifndef USE_STD_FILESYSTEM - #include + #include #endif // USE_STD_FILESYSTEM #define PATH_SEP '/' #endif // defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) @@ -1287,8 +1287,6 @@ namespace IGFD std::sort(prFileList.begin(), prFileList.end(), [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool { - if (a==NULL || b==NULL) - return false; if (!a.use_count() || !b.use_count()) return false; @@ -1549,28 +1547,53 @@ namespace IGFD for (i = 0; i < n; i++) { struct dirent* ent = files[i]; - + std::string where = path + std::string("/") + std::string(ent->d_name); char fileType = 0; - switch (ent->d_type) +#ifdef HAVE_DIRENT_TYPE + if (ent->d_type != DT_UNKNOWN) { - case DT_REG: - fileType = 'f'; break; - case DT_DIR: - fileType = 'd'; break; - case DT_LNK: - std::string where = path+std::string("/")+std::string(ent->d_name); - DIR* dirTest = opendir(where.c_str()); - if (dirTest==NULL) { - if (errno==ENOTDIR) { - fileType = 'f'; - } else { - fileType = 'l'; - } - } else { - fileType = 'd'; - closedir(dirTest); - } - break; + switch (ent->d_type) + { + case DT_REG: + fileType = 'f'; break; + case DT_DIR: + fileType = 'd'; break; + case DT_LNK: + DIR* dirTest = opendir(where.c_str()); + if (dirTest == NULL) + { + if (errno == ENOTDIR) + { + fileType = 'f'; + } + else + { + fileType = 'l'; + } + } + else + { + fileType = 'd'; + closedir(dirTest); + } + break; + } + } + else +#endif // HAVE_DIRENT_TYPE + { + struct stat filestat; + if (stat(where.c_str(), &filestat) == 0) + { + if (S_ISDIR(filestat.st_mode)) + { + fileType = 'd'; + } + else + { + fileType = 'f'; + } + } } auto fileNameExt = ent->d_name; @@ -1760,7 +1783,7 @@ namespace IGFD struct stat statInfos = {}; char timebuf[100]; int result = stat(fpn.c_str(), &statInfos); - if (!result) + if (result!=-1) { if (vInfos->fileType != 'd') { @@ -1781,7 +1804,11 @@ namespace IGFD { vInfos->fileModifDate = std::string(timebuf, len); } - } + } else { + vInfos->fileSize=0; + vInfos->formatedFileSize = prFormatFileSize(vInfos->fileSize); + vInfos->fileModifDate="???"; + } } } diff --git a/extern/imgui_patched/imgui_widgets.cpp b/extern/imgui_patched/imgui_widgets.cpp index c6fec68b..93dfd534 100644 --- a/extern/imgui_patched/imgui_widgets.cpp +++ b/extern/imgui_patched/imgui_widgets.cpp @@ -4545,27 +4545,52 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size // of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used // without any storage on user's side. - IM_ASSERT(apply_new_text_length >= 0); - if (is_resizable) - { - ImGuiInputTextCallbackData callback_data; - callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize; - callback_data.Flags = flags; - callback_data.Buf = buf; - callback_data.BufTextLen = apply_new_text_length; - callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1); - callback_data.UserData = callback_user_data; - callback(&callback_data); - buf = callback_data.Buf; - buf_size = callback_data.BufSize; - apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1); - IM_ASSERT(apply_new_text_length <= buf_size); - } - //IMGUI_DEBUG_LOG("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length); - // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size. - ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size)); - value_changed = true; + // don't assert because maybe the user passes an invalid UTF-8 string... + if (apply_new_text_length >= 0) { + if (is_resizable) + { + ImGuiInputTextCallbackData callback_data; + callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize; + callback_data.Flags = flags; + callback_data.Buf = buf; + callback_data.BufTextLen = apply_new_text_length; + callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1); + callback_data.UserData = callback_user_data; + callback(&callback_data); + buf = callback_data.Buf; + buf_size = callback_data.BufSize; + apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1); + IM_ASSERT(apply_new_text_length <= buf_size); + } + //IMGUI_DEBUG_LOG("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length); + + // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size. + ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size)); + value_changed = true; + } else { + printf("invalid buffer!\n"); + if (is_resizable) + { + ImGuiInputTextCallbackData callback_data; + callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize; + callback_data.Flags = flags; + callback_data.Buf = buf; + callback_data.BufTextLen = 0; + callback_data.BufSize = ImMax(buf_size, 1); + callback_data.UserData = callback_user_data; + callback(&callback_data); + buf = callback_data.Buf; + buf_size = callback_data.BufSize; + apply_new_text_length = 0; + IM_ASSERT(apply_new_text_length <= buf_size); + } + //IMGUI_DEBUG_LOG("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length); + + // clear the buffer + ImStrncpy(buf, "", 1); + value_changed = true; + } } // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value) diff --git a/extern/nfd-modified/.gitignore b/extern/nfd-modified/.gitignore new file mode 100644 index 00000000..5f144cbe --- /dev/null +++ b/extern/nfd-modified/.gitignore @@ -0,0 +1,181 @@ +.sconsign.dblite +# Object files +*.o +*.ko +*.obj +*.elf +# Precompiled Headers +*.gch +*.pch +# Libraries +*.lib +*.a +*.la +*.lo +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates +# User-specific folders +*.sln.ide/ +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +# Roslyn cache directories +*.ide/ +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* +#NUNIT +*.VisualState.xml +TestResult.xml +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc +# Chutzpah Test files +_Chutzpah* +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile +# Visual Studio profiler +*.psess +*.vsp +*.vspx +# TFS 2012 Local Workspace +$tf/ +# Guidance Automation Toolkit +*.gpState +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user +# JustCode is a .NET coding addin-in +.JustCode +# TeamCity is a build add-in +_TeamCity* +# DotCover is a Code Coverage Tool +*.dotCover +# NCrunch +_NCrunch_* +.*crunch*.local.xml +# MightyMoose +*.mm.* +AutoTest.Net/ +# Web workbench (sass) +.sass-cache/ +# Installshield output folder +[Ee]xpress/ +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html +# Click-Once directory +publish/ +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# If using the old MSBuild-Integrated Package Restore, uncomment this: +#!**/packages/repositories.config +# Windows Azure Build Output +csx/ +*.build.csdef +# Windows Store app package directory +AppPackages/ +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +bower_components/ +# RIA/Silverlight projects +Generated_Code/ +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +# SQL Server files +*.mdf +*.ldf +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +# Microsoft Fakes +FakesAssemblies/ diff --git a/extern/nfd-modified/LICENSE b/extern/nfd-modified/LICENSE new file mode 100644 index 00000000..3ab103c5 --- /dev/null +++ b/extern/nfd-modified/LICENSE @@ -0,0 +1,16 @@ +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + diff --git a/extern/nfd-modified/MODIFIED.md b/extern/nfd-modified/MODIFIED.md new file mode 100644 index 00000000..bb542f8b --- /dev/null +++ b/extern/nfd-modified/MODIFIED.md @@ -0,0 +1,7 @@ +# MODIFIED + +this is a modified, altered, edited and revised version of the Native File Dialog library used to display native-feeling, original and operating-system specific file archive dialog picker selectors, which is done by the Native File Dialog library. + +it should not and shall NOT be mistaken for the original, authentic or actual version and revision of the library. + +this is a version tailored for Furnace. diff --git a/extern/nfd-modified/README.md b/extern/nfd-modified/README.md new file mode 100644 index 00000000..fd7a9783 --- /dev/null +++ b/extern/nfd-modified/README.md @@ -0,0 +1,182 @@ +# Native File Dialog Modified Version!!! # + +A tiny, neat C library that portably invokes native file open, folder select and save dialogs. Write dialog code once and have it pop up native dialogs on all supported platforms. Avoid linking large dependencies like wxWidgets and qt. + +This is a modified version of Native File Dialog, tailored for Furnace. + +Features: + + - Lean C API, static library -- no ObjC, no C++, no STL. + - Zlib licensed. + - Consistent UTF-8 support on all platforms. + - Simple universal file filter syntax. + - Paid support available. + - Multiple file selection support. + - 64-bit and 32-bit friendly. + - GCC, Clang, Xcode, Mingw and Visual Studio supported. + - No third party dependencies for building or linking. + - Support for Vista's modern `IFileDialog` on Windows. + - Support for non-deprecated Cocoa APIs on OS X. + - GTK3 dialog on Linux. + - Optional Zenity support on Linux to avoid linking GTK. + - Tested, works alongside [http://www.libsdl.org](SDL2) on all platforms, for the game developers out there. + +# Example Usage # + +```C +#include +#include +#include + +int main( void ) +{ + nfdchar_t *outPath = NULL; + nfdresult_t result = NFD_OpenDialog( NULL, NULL, &outPath ); + + if ( result == NFD_OKAY ) { + puts("Success!"); + puts(outPath); + free(outPath); + } + else if ( result == NFD_CANCEL ) { + puts("User pressed cancel."); + } + else { + printf("Error: %s\n", NFD_GetError() ); + } + + return 0; +} +``` + +See self-documenting API [NFD.h](src/include/nfd.h) for more options. + +# Screenshots # + +![Windows rendering a dialog](screens/open_win.png?raw=true) +![GTK3 on Linux rendering a dialog](screens/open_gtk3.png?raw=true) +![Cocoa on MacOS rendering a dialog](screens/open_cocoa.png?raw=true) + +## Changelog ## + + - **Major** version increments denote API or ABI departure. + - **Minor** version increments denote build or trivial departures. + - **Micro** version increments just recompile and drop-in. + +release | what's new | date +--------|-----------------------------|--------- +1.0.0 | initial | oct 2014 +1.1.0 | premake5; scons deprecated | aug 2016 +1.1.1 | mingw support, build fixes | aug 2016 +1.1.2 | test_pickfolder() added | aug 2016 +1.1.3 | zenity linux backend added | nov 2017 + | fix char type in decls | nov 2017 +1.1.4 | fix win32 memleaks | dec 2018 + | improve win32 errorhandling | dec 2018 + | macos fix focus bug | dec 2018 +1.1.5 | win32 fix com reinitialize | aug 2019 +1.1.6 | fix osx filter bug | aug 2019 + | remove deprecated scons | aug 2019 + | fix mingw compilation | aug 2019 + | -Wextra warning cleanup | aug 2019 + +## Building ## + +NFD uses [Premake5](https://premake.github.io/download.html) generated Makefiles and IDE project files. The generated project files are checked in under `build/` so you don't have to download and use Premake in most cases. + +If you need to run Premake5 directly, further [build documentation](docs/build.md) is available. + +Previously, NFD used SCons to build. As of 1.1.6, SCons support has been removed entirely. + +`nfd.a` will be built for release builds, and `nfd_d.a` will be built for debug builds. + +### Makefiles ### + +The makefile offers up to four options, with `release_x64` as the default. + + make config=release_x86 + make config=release_x64 + make config=debug_x86 + make config=debug_x64 + +### Compiling Your Programs ### + + 1. Add `src/include` to your include search path. + 2. Add `nfd.lib` or `nfd_d.lib` to the list of list of static libraries to link against (for release or debug, respectively). + 3. Add `build//` to the library search path. + +#### Linux GTK #### + +`apt-get libgtk-3-dev` installs the gtk dependency for library compilation. + +On Linux, you have the option of compiling and linking against GTK. If you use it, the recommended way to compile is to include the arguments of `pkg-config --cflags --libs gtk+-3.0`. + +#### Linux Zenity #### + +Alternatively, you can use the Zenity backend by running the Makefile in `build/gmake_linux_zenity`. Zenity runs the dialog in its own address space, but requires the user to have Zenity correctly installed and configured on their system. + +#### MacOS #### + +On Mac OS, add `AppKit` to the list of frameworks. + +#### Windows #### + +On Windows, ensure you are linking against `comctl32.lib`. + +## Usage ## + +See `NFD.h` for API calls. See `tests/*.c` for example code. + +After compiling, `build/bin` contains compiled test programs. The appropriate subdirectory under `build/lib` contains the built library. + +## File Filter Syntax ## + +There is a form of file filtering in every file dialog API, but no consistent means of supporting it. NFD provides support for filtering files by groups of extensions, providing its own descriptions (where applicable) for the extensions. + +A wildcard filter is always added to every dialog. + +### Separators ### + + - `;` Begin a new filter. + - `,` Add a separate type to the filter. + +#### Examples #### + +`txt` The default filter is for text files. There is a wildcard option in a dropdown. + +`png,jpg;psd` The default filter is for png and jpg files. A second filter is available for psd files. There is a wildcard option in a dropdown. + +`NULL` Wildcard only. + +## Iterating Over PathSets ## + +See [test_opendialogmultiple.c](test/test_opendialogmultiple.c). + +# Known Limitations # + +I accept quality code patches, or will resolve these and other matters through support. See [contributing](docs/contributing.md) for details. + + - No support for Windows XP's legacy dialogs such as `GetOpenFileName`. + - No support for file filter names -- ex: "Image Files" (*.png, *.jpg). Nameless filters are supported, however. + - GTK Zenity implementation's process exec error handling does not gracefully handle numerous error cases, choosing to abort rather than cleanup and return. + - GTK 3 spams one warning per dialog created. + +# Copyright and Credit # + +Copyright © 2014-2019 [Frogtoss Games](http://www.frogtoss.com), Inc. +File [LICENSE](LICENSE) covers all files in this repo. + +Native File Dialog by Michael Labbe + + +Tomasz Konojacki for [microutf8](http://puszcza.gnu.org.ua/software/microutf8/) + +[Denis Kolodin](https://github.com/DenisKolodin) for mingw support. + +[Tom Mason](https://github.com/wheybags) for Zenity support. + +## Support ## + +Directed support for this work is available from the original author under a paid agreement. + +[Contact Frogtoss Games](http://www.frogtoss.com/pages/contact.html). diff --git a/extern/nfd-modified/docs/build.md b/extern/nfd-modified/docs/build.md new file mode 100644 index 00000000..a1f73271 --- /dev/null +++ b/extern/nfd-modified/docs/build.md @@ -0,0 +1,39 @@ +# Building NFD # + +Most of the building instructions are included in [README.md](/README.md). This file just contains apocrypha. + +## Running Premake5 Directly ## + +*You shouldn't have to run Premake5 directly to build Native File Dialog. This is for package maintainers or people with exotic demands only!* + +1. [Clone premake-core](https://github.com/premake/premake-core) +2. [Follow instructions on how to build premake](https://github.com/premake/premake-core/wiki/Building-Premake) +3. `cd` to `build` +4. Type `premake5 `, where is the build you want to create. + +### Package Maintainer Only ### + +I support a custom Premake action: `premake5 dist`, which generates all of the checked in project types in subdirectories. It is useful to run this command if you are submitting a pull request to test all of the supported premake configurations. Do not check in the built projects; I will do so while accepting your pull request. + +## SCons build (deprecated) ## + +As of 1.1.6, the deprecated and unmaintained SCons support is removed. + +## Compiling with Mingw ## + +Use the Makefile in `build/gmake_windows` to build Native File Dialog with mingw. Mingw has many distributions and not all of them are reliable. Here is what worked for me, the primary author of Native File Dialog: + +1. Use mingw64, not mingw32. Downloaded from [sourceforge.net](https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/installer/mingw-w64-install.exe/download). +2. When prompted in the intsaller, install the basic compiler and g++. +3. Add the installed bin dir to command prompt path. +4. Run `set CC=g++` to enforce `g++` instead of the default, `cc` for compiling and linking. +5. In `build/gmake_windows`, run `mingw32-make config=release_x64 clean`. Running clean ensures no Visual Studio build products conflict which can cause link errors. +6. Now run `mingw32-make config=release_x64`. + +The author has not attempted to build or even install an x86 toolchain for mingw. + +If you report an issue, be sure to run make with `verbose=1` so commands are visible. + +## Adding NFD source directly to your project ## + +Lots of developers add NFD source directly to their projects instead of using the included build scripts. As of 1.1.6, this is an acknowledged approach to building. Of course, everyone has a slightly different toolchain with various warnings and linters enabled. If you run a linter or catch a warning, please consider submitting a pull request to help NFD build cleanly for everyone. diff --git a/extern/nfd-modified/docs/contributing.md b/extern/nfd-modified/docs/contributing.md new file mode 100644 index 00000000..f6c3b54c --- /dev/null +++ b/extern/nfd-modified/docs/contributing.md @@ -0,0 +1,25 @@ +# Pull Requests # + +I have had to turn away a number of pull requests due to avoidable circumstances. Please read this file before submitting a pull request. Also look at existing, rejected pull requests which state the reason for rejection. + +Here are the rules: + +- **Submit pull requests to the devel branch**. The library must be tested on every compiler and OS, so there is no way I am going to just put your change in the master before it has been sync'd and tested on a number of machines. Master branch is depended upon by hundreds of projects. + +- **Test your changes on all platforms and compilers that you can.** Also, state which platforms you have tested your code on. 32-bit or 64-bit. Clang or GCC. Visual Studio or Mingw. I have to test all these to accept pull requests, so I prioritize changes that respect my time. + +- **Submit Premake build changes only**. As of 1.1, SCons is deprecated. Also, do not submit altered generated projects. I will re-run Premake to re-generate them to ensure that I can still generate the project prior to admitting your pull request. + +- **Do not alter existing behavior to support your desired behavior**. For instance, rewriting file open dialogs to behave differently, while trading off functionality for compatibility, will get you rejected. Consider creating an additional code path. Instead of altering `nfd_win.cpp` to support Windows XP, create `nfd_win_legacy.cpp`, which exists alongside the newer file dialog. + +- **Do not submit anything I can't verify or maintain**. If you add support for a compiler, include from-scratch install instructions that you have tested yourself. Accepting a pull request means I am now the maintainer of your code, so I must understand what it does and how to test it. + +- **Do not change the externally facing API**. NFD needs to maintain ABI compatibility. + +## Submitting Cloud Autobuild systems ## + +I have received a few pull requests for Travis and AppVeyor-based autobuilding which I have not accepted. NativeFileDialog is officially covered by my private BuildBot network which supports all three target OSes, both CPU architectures and four compilers. I take the view that having a redundant, lesser autobuild system does not improve coverage: it gives a false positive when partial building succeeds. Please do not invest time into cloud-based building with the hope of a pull request being accepted. + +## Contact Me ## + +Despite all of the "do nots" above, I am happy to recieve new pull requests! If you have any questions about style, or what I would need to accept your specific request, please contact me ahead of submitting the pull request by opening an issue on Github with your question. I will do my best to answer you. diff --git a/extern/nfd-modified/src/common.h b/extern/nfd-modified/src/common.h new file mode 100644 index 00000000..7745d323 --- /dev/null +++ b/extern/nfd-modified/src/common.h @@ -0,0 +1,21 @@ +/* + Native File Dialog + + Internal, common across platforms + + http://www.frogtoss.com/labs + */ + + +#ifndef _NFD_COMMON_H +#define _NFD_COMMON_H + +#define NFD_MAX_STRLEN 256 +#define _NFD_UNUSED(x) ((void)x) + +void *NFDi_Malloc( size_t bytes ); +void NFDi_Free( void *ptr ); +void NFDi_SetError( const char *msg ); +void NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy ); + +#endif diff --git a/extern/nfd-modified/src/include/nfd.h b/extern/nfd-modified/src/include/nfd.h new file mode 100644 index 00000000..7f630167 --- /dev/null +++ b/extern/nfd-modified/src/include/nfd.h @@ -0,0 +1,73 @@ +/* + Native File Dialog + + User API + + http://www.frogtoss.com/labs + */ + + +#ifndef _NFD_H +#define _NFD_H + +#include +#include +#include +#include + +/* denotes UTF-8 char */ +typedef char nfdchar_t; + +typedef std::function nfdselcallback_t; + +/* opaque data structure -- see NFD_PathSet_* */ +typedef struct { + nfdchar_t *buf; + size_t *indices; /* byte offsets into buf */ + size_t count; /* number of indices into buf */ +}nfdpathset_t; + +typedef enum { + NFD_ERROR, /* programmatic error */ + NFD_OKAY, /* user pressed okay, or successful return */ + NFD_CANCEL /* user pressed cancel */ +}nfdresult_t; + + +/* nfd_.c */ + +/* single file open dialog */ +nfdresult_t NFD_OpenDialog( const std::vector& filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath, + nfdselcallback_t selCallback = NULL ); + +/* multiple file open dialog */ +nfdresult_t NFD_OpenDialogMultiple( const std::vector& filterList, + const nfdchar_t *defaultPath, + nfdpathset_t *outPaths, + nfdselcallback_t selCallback = NULL ); + +/* save dialog */ +nfdresult_t NFD_SaveDialog( const std::vector& filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath, + nfdselcallback_t selCallback = NULL ); + + +/* select folder dialog */ +nfdresult_t NFD_PickFolder( const nfdchar_t *defaultPath, + nfdchar_t **outPath); + +/* nfd_common.c */ + +/* get last error -- set when nfdresult_t returns NFD_ERROR */ +const char *NFD_GetError( void ); +/* get the number of entries stored in pathSet */ +size_t NFD_PathSet_GetCount( const nfdpathset_t *pathSet ); +/* Get the UTF-8 path at offset index */ +nfdchar_t *NFD_PathSet_GetPath( const nfdpathset_t *pathSet, size_t index ); +/* Free the pathSet */ +void NFD_PathSet_Free( nfdpathset_t *pathSet ); + +#endif diff --git a/extern/nfd-modified/src/nfd_cocoa.mm b/extern/nfd-modified/src/nfd_cocoa.mm new file mode 100644 index 00000000..204d79f8 --- /dev/null +++ b/extern/nfd-modified/src/nfd_cocoa.mm @@ -0,0 +1,301 @@ +/* + Native File Dialog + + http://www.frogtoss.com/labs + */ + +#include +#include "nfd.h" +#include "nfd_common.h" + +// this language is a mess! +// who thought it was a good idea to combine Objective-C and C++ together +// when you could just have used C++ and call it a day!!! +// +// might as well make Objective-Ruswift++... + +static NSArray *BuildAllowedFileTypes( const std::vector& filterList ) +{ + // Commas and semicolons are the same thing on this platform + + // like what about THIS INSTEAD! + // NSMutableArray *buildFilterList = NSMutableArray::alloc()->init(); + NSMutableArray *buildFilterList = [[NSMutableArray alloc] init]; + + std::string typebuf; + int index=-1; + for (const std::string& i: filterList) { + index++; + if (!(index&1)) { + continue; + } + typebuf=""; + for (const char& j: i) { + if (j==' ' || j==',' || j ==';') { + // or this: NSString::stringWithUTF8String(typebuf); + // buildFilterList->addObject(thisType); + // really? did you have to make this mess?! + const char* typebufC=typebuf.c_str(); + NSString *thisType = [NSString stringWithUTF8String:typebufC]; + [buildFilterList addObject:thisType]; + typebuf=""; + } else if (j!='.' && j!='*') { + typebuf+=j; + } + } + if (!typebuf.empty()) { + // I don't think this will work, but come on... + const char* typebufC=typebuf.c_str(); + NSString *thisType = [NSString stringWithUTF8String:typebufC]; + [buildFilterList addObject:thisType]; + } + } + + NSArray *returnArray = [NSArray arrayWithArray:buildFilterList]; + + [buildFilterList release]; + return returnArray; +} + +static void AddFilterListToDialog( NSSavePanel *dialog, const std::vector& filterList ) +{ + if ( filterList.size()&1 ) + return; + + NSArray *allowedFileTypes = BuildAllowedFileTypes( filterList ); + if ( [allowedFileTypes count] != 0 ) + { + [dialog setAllowedFileTypes:allowedFileTypes]; + } +} + +static void SetDefaultPath( NSSavePanel *dialog, const nfdchar_t *defaultPath ) +{ + if ( !defaultPath || strlen(defaultPath) == 0 ) + return; + + NSString *defaultPathString = [NSString stringWithUTF8String: defaultPath]; + NSURL *url = [NSURL fileURLWithPath:defaultPathString isDirectory:YES]; + [dialog setDirectoryURL:url]; +} + + +/* fixme: pathset should be pathSet */ +static nfdresult_t AllocPathSet( NSArray *urls, nfdpathset_t *pathset ) +{ + assert(pathset); + assert([urls count]); + + pathset->count = (size_t)[urls count]; + pathset->indices = (size_t*)NFDi_Malloc( sizeof(size_t)*pathset->count ); + if ( !pathset->indices ) + { + return NFD_ERROR; + } + + // count the total space needed for buf + size_t bufsize = 0; + for ( NSURL *url in urls ) + { + NSString *path = [url path]; + bufsize += [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1; + } + + pathset->buf = (nfdchar_t*)NFDi_Malloc( sizeof(nfdchar_t) * bufsize ); + if ( !pathset->buf ) + { + return NFD_ERROR; + } + + // fill buf + nfdchar_t *p_buf = pathset->buf; + size_t count = 0; + for ( NSURL *url in urls ) + { + NSString *path = [url path]; + const nfdchar_t *utf8Path = [path UTF8String]; + size_t byteLen = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1; + memcpy( p_buf, utf8Path, byteLen ); + + ptrdiff_t index = p_buf - pathset->buf; + assert( index >= 0 ); + pathset->indices[count] = (size_t)index; + + p_buf += byteLen; + ++count; + } + + return NFD_OKAY; +} + +/* public */ + + +nfdresult_t NFD_OpenDialog( const std::vector& filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath, + nfdselcallback_t selCallback ) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; + NSOpenPanel *dialog = [NSOpenPanel openPanel]; + [dialog setAllowsMultipleSelection:NO]; + + // Build the filter list + AddFilterListToDialog(dialog, filterList); + + // Set the starting directory + SetDefaultPath(dialog, defaultPath); + + nfdresult_t nfdResult = NFD_CANCEL; + if ( [dialog runModal] == NSModalResponseOK ) + { + NSURL *url = [dialog URL]; + const char *utf8Path = [[url path] UTF8String]; + + // byte count, not char count + size_t len = strlen(utf8Path);//NFDi_UTF8_Strlen(utf8Path); + + *outPath = (nfdchar_t*)NFDi_Malloc( len+1 ); + if ( !*outPath ) + { + [pool release]; + [keyWindow makeKeyAndOrderFront:nil]; + return NFD_ERROR; + } + memcpy( *outPath, utf8Path, len+1 ); /* copy null term */ + nfdResult = NFD_OKAY; + } + [pool release]; + + [keyWindow makeKeyAndOrderFront:nil]; + return nfdResult; +} + + +nfdresult_t NFD_OpenDialogMultiple( const std::vector& filterList, + const nfdchar_t *defaultPath, + nfdpathset_t *outPaths, + nfdselcallback_t selCallback ) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; + + NSOpenPanel *dialog = [NSOpenPanel openPanel]; + [dialog setAllowsMultipleSelection:YES]; + + // Build the fiter list. + AddFilterListToDialog(dialog, filterList); + + // Set the starting directory + SetDefaultPath(dialog, defaultPath); + + nfdresult_t nfdResult = NFD_CANCEL; + if ( [dialog runModal] == NSModalResponseOK ) + { + NSArray *urls = [dialog URLs]; + + if ( [urls count] == 0 ) + { + [pool release]; + [keyWindow makeKeyAndOrderFront:nil]; + return NFD_CANCEL; + } + + if ( AllocPathSet( urls, outPaths ) == NFD_ERROR ) + { + [pool release]; + [keyWindow makeKeyAndOrderFront:nil]; + return NFD_ERROR; + } + + nfdResult = NFD_OKAY; + } + [pool release]; + + [keyWindow makeKeyAndOrderFront:nil]; + return nfdResult; +} + + +nfdresult_t NFD_SaveDialog( const std::vector& filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath, + nfdselcallback_t selCallback ) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; + + NSSavePanel *dialog = [NSSavePanel savePanel]; + [dialog setExtensionHidden:NO]; + + // Build the filter list. + AddFilterListToDialog(dialog, filterList); + + // Set the starting directory + SetDefaultPath(dialog, defaultPath); + + nfdresult_t nfdResult = NFD_CANCEL; + if ( [dialog runModal] == NSModalResponseOK ) + { + NSURL *url = [dialog URL]; + const char *utf8Path = [[url path] UTF8String]; + + size_t byteLen = [url.path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1; + + *outPath = (nfdchar_t*)NFDi_Malloc( byteLen ); + if ( !*outPath ) + { + [pool release]; + [keyWindow makeKeyAndOrderFront:nil]; + return NFD_ERROR; + } + memcpy( *outPath, utf8Path, byteLen ); + nfdResult = NFD_OKAY; + } + + [pool release]; + [keyWindow makeKeyAndOrderFront:nil]; + return nfdResult; +} + +nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath, + nfdchar_t **outPath) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; + NSOpenPanel *dialog = [NSOpenPanel openPanel]; + [dialog setAllowsMultipleSelection:NO]; + [dialog setCanChooseDirectories:YES]; + [dialog setCanCreateDirectories:YES]; + [dialog setCanChooseFiles:NO]; + + // Set the starting directory + SetDefaultPath(dialog, defaultPath); + + nfdresult_t nfdResult = NFD_CANCEL; + if ( [dialog runModal] == NSModalResponseOK ) + { + NSURL *url = [dialog URL]; + const char *utf8Path = [[url path] UTF8String]; + + // byte count, not char count + size_t len = strlen(utf8Path);//NFDi_UTF8_Strlen(utf8Path); + + *outPath = (nfdchar_t*)NFDi_Malloc( len+1 ); + if ( !*outPath ) + { + [pool release]; + [keyWindow makeKeyAndOrderFront:nil]; + return NFD_ERROR; + } + memcpy( *outPath, utf8Path, len+1 ); /* copy null term */ + nfdResult = NFD_OKAY; + } + [pool release]; + + [keyWindow makeKeyAndOrderFront:nil]; + return nfdResult; +} diff --git a/extern/nfd-modified/src/nfd_common.cpp b/extern/nfd-modified/src/nfd_common.cpp new file mode 100644 index 00000000..55517f5d --- /dev/null +++ b/extern/nfd-modified/src/nfd_common.cpp @@ -0,0 +1,142 @@ +/* + Native File Dialog + + http://www.frogtoss.com/labs + */ + +#include +#include +#include +#include "nfd_common.h" + +static char g_errorstr[NFD_MAX_STRLEN] = {0}; + +/* public routines */ + +const char *NFD_GetError( void ) +{ + return g_errorstr; +} + +size_t NFD_PathSet_GetCount( const nfdpathset_t *pathset ) +{ + assert(pathset); + return pathset->count; +} + +nfdchar_t *NFD_PathSet_GetPath( const nfdpathset_t *pathset, size_t num ) +{ + assert(pathset); + assert(num < pathset->count); + + return pathset->buf + pathset->indices[num]; +} + +void NFD_PathSet_Free( nfdpathset_t *pathset ) +{ + assert(pathset); + NFDi_Free( pathset->indices ); + NFDi_Free( pathset->buf ); +} + +/* internal routines */ + +void *NFDi_Malloc( size_t bytes ) +{ + void *ptr = malloc(bytes); + if ( !ptr ) + NFDi_SetError("NFDi_Malloc failed."); + + return ptr; +} + +void NFDi_Free( void *ptr ) +{ + assert(ptr); + free(ptr); +} + +void NFDi_SetError( const char *msg ) +{ + int bTruncate = NFDi_SafeStrncpy( g_errorstr, msg, NFD_MAX_STRLEN ); + assert( !bTruncate ); _NFD_UNUSED(bTruncate); +} + + +int NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy ) +{ + size_t n = maxCopy; + char *d = dst; + + assert( src ); + assert( dst ); + + while ( n > 0 && *src != '\0' ) + { + *d++ = *src++; + --n; + } + + /* Truncation case - + terminate string and return true */ + if ( n == 0 ) + { + dst[maxCopy-1] = '\0'; + return 1; + } + + /* No truncation. Append a single NULL and return. */ + *d = '\0'; + return 0; +} + + +/* adapted from microutf8 */ +int32_t NFDi_UTF8_Strlen( const nfdchar_t *str ) +{ + /* This function doesn't properly check validity of UTF-8 character + sequence, it is supposed to use only with valid UTF-8 strings. */ + + int32_t character_count = 0; + int32_t i = 0; /* Counter used to iterate over string. */ + nfdchar_t maybe_bom[4]; + + /* If there is UTF-8 BOM ignore it. */ + if (strlen(str) > 2) + { + strncpy(maybe_bom, str, 3); + maybe_bom[3] = 0; + if (strcmp(maybe_bom, (nfdchar_t*)NFD_UTF8_BOM) == 0) + i += 3; + } + + while(str[i]) + { + if (str[i] >> 7 == 0) + { + /* If bit pattern begins with 0 we have ascii character. */ + ++character_count; + } + else if (str[i] >> 6 == 3) + { + /* If bit pattern begins with 11 it is beginning of UTF-8 byte sequence. */ + ++character_count; + } + else if (str[i] >> 6 == 2) + ; /* If bit pattern begins with 10 it is middle of utf-8 byte sequence. */ + else + { + /* In any other case this is not valid UTF-8. */ + return -1; + } + ++i; + } + + return character_count; +} + +int NFDi_IsFilterSegmentChar( char ch ) +{ + return (ch==','||ch==';'||ch=='\0'); +} + diff --git a/extern/nfd-modified/src/nfd_common.h b/extern/nfd-modified/src/nfd_common.h new file mode 100644 index 00000000..a1dd74b5 --- /dev/null +++ b/extern/nfd-modified/src/nfd_common.h @@ -0,0 +1,31 @@ +/* + Native File Dialog + + Internal, common across platforms + + http://www.frogtoss.com/labs + */ + + +#ifndef _NFD_COMMON_H +#define _NFD_COMMON_H + +#include "nfd.h" + +#include + +#define NFD_MAX_STRLEN 256 +#define _NFD_UNUSED(x) ((void)x) + +#define NFD_UTF8_BOM "\xEF\xBB\xBF" + + +void *NFDi_Malloc( size_t bytes ); +void NFDi_Free( void *ptr ); +void NFDi_SetError( const char *msg ); +int NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy ); +int32_t NFDi_UTF8_Strlen( const nfdchar_t *str ); +int NFDi_IsFilterSegmentChar( char ch ); + + +#endif diff --git a/extern/nfd-modified/src/nfd_gtk.cpp b/extern/nfd-modified/src/nfd_gtk.cpp new file mode 100644 index 00000000..7a9958ed --- /dev/null +++ b/extern/nfd-modified/src/nfd_gtk.cpp @@ -0,0 +1,379 @@ +/* + Native File Dialog + + http://www.frogtoss.com/labs +*/ + +#include +#include +#include +#include +#include "nfd.h" +#include "nfd_common.h" + + +const char INIT_FAIL_MSG[] = "gtk_init_check failed to initilaize GTK+"; + + +static void AddTypeToFilterName( const char *typebuf, char *filterName, size_t bufsize ) +{ + const char SEP[] = ", "; + + size_t len = strlen(filterName); + if ( len != 0 ) + { + strncat( filterName, SEP, bufsize - len - 1 ); + len += strlen(SEP); + } + + strncat( filterName, typebuf, bufsize - len - 1 ); +} + +static void AddFiltersToDialog( GtkWidget *dialog, const char *filterList ) +{ + GtkFileFilter *filter; + char typebuf[NFD_MAX_STRLEN] = {0}; + const char *p_filterList = filterList; + char *p_typebuf = typebuf; + char filterName[NFD_MAX_STRLEN] = {0}; + + if ( !filterList || strlen(filterList) == 0 ) + return; + + filter = gtk_file_filter_new(); + while ( 1 ) + { + + if ( NFDi_IsFilterSegmentChar(*p_filterList) ) + { + char typebufWildcard[NFD_MAX_STRLEN]; + /* add another type to the filter */ + assert( strlen(typebuf) > 0 ); + assert( strlen(typebuf) < NFD_MAX_STRLEN-1 ); + + snprintf( typebufWildcard, NFD_MAX_STRLEN, "*.%s", typebuf ); + AddTypeToFilterName( typebuf, filterName, NFD_MAX_STRLEN ); + + gtk_file_filter_add_pattern( filter, typebufWildcard ); + + p_typebuf = typebuf; + memset( typebuf, 0, sizeof(char) * NFD_MAX_STRLEN ); + } + + if ( *p_filterList == ';' || *p_filterList == '\0' ) + { + /* end of filter -- add it to the dialog */ + + gtk_file_filter_set_name( filter, filterName ); + gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter ); + + filterName[0] = '\0'; + + if ( *p_filterList == '\0' ) + break; + + filter = gtk_file_filter_new(); + } + + if ( !NFDi_IsFilterSegmentChar( *p_filterList ) ) + { + *p_typebuf = *p_filterList; + p_typebuf++; + } + + p_filterList++; + } + + /* always append a wildcard option to the end*/ + + filter = gtk_file_filter_new(); + gtk_file_filter_set_name( filter, "*.*" ); + gtk_file_filter_add_pattern( filter, "*" ); + gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter ); +} + +static void SetDefaultPath( GtkWidget *dialog, const char *defaultPath ) +{ + if ( !defaultPath || strlen(defaultPath) == 0 ) + return; + + /* GTK+ manual recommends not specifically setting the default path. + We do it anyway in order to be consistent across platforms. + + If consistency with the native OS is preferred, this is the line + to comment out. -ml */ + gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), defaultPath ); +} + +static nfdresult_t AllocPathSet( GSList *fileList, nfdpathset_t *pathSet ) +{ + size_t bufSize = 0; + GSList *node; + nfdchar_t *p_buf; + size_t count = 0; + + assert(fileList); + assert(pathSet); + + pathSet->count = (size_t)g_slist_length( fileList ); + assert( pathSet->count > 0 ); + + pathSet->indices = NFDi_Malloc( sizeof(size_t)*pathSet->count ); + if ( !pathSet->indices ) + { + return NFD_ERROR; + } + + /* count the total space needed for buf */ + for ( node = fileList; node; node = node->next ) + { + assert(node->data); + bufSize += strlen( (const gchar*)node->data ) + 1; + } + + pathSet->buf = NFDi_Malloc( sizeof(nfdchar_t) * bufSize ); + + /* fill buf */ + p_buf = pathSet->buf; + for ( node = fileList; node; node = node->next ) + { + nfdchar_t *path = (nfdchar_t*)(node->data); + size_t byteLen = strlen(path)+1; + ptrdiff_t index; + + memcpy( p_buf, path, byteLen ); + g_free(node->data); + + index = p_buf - pathSet->buf; + assert( index >= 0 ); + pathSet->indices[count] = (size_t)index; + + p_buf += byteLen; + ++count; + } + + g_slist_free( fileList ); + + return NFD_OKAY; +} + +static void WaitForCleanup(void) +{ + while (gtk_events_pending()) + gtk_main_iteration(); +} + +/* public */ + +nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath ) +{ + GtkWidget *dialog; + nfdresult_t result; + + if ( !gtk_init_check( NULL, NULL ) ) + { + NFDi_SetError(INIT_FAIL_MSG); + return NFD_ERROR; + } + + dialog = gtk_file_chooser_dialog_new( "Open File", + NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Open", GTK_RESPONSE_ACCEPT, + NULL ); + + /* Build the filter list */ + AddFiltersToDialog(dialog, filterList); + + /* Set the default path */ + SetDefaultPath(dialog, defaultPath); + + result = NFD_CANCEL; + if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) + { + char *filename; + + filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) ); + + { + size_t len = strlen(filename); + *outPath = NFDi_Malloc( len + 1 ); + memcpy( *outPath, filename, len + 1 ); + if ( !*outPath ) + { + g_free( filename ); + gtk_widget_destroy(dialog); + return NFD_ERROR; + } + } + g_free( filename ); + + result = NFD_OKAY; + } + + WaitForCleanup(); + gtk_widget_destroy(dialog); + WaitForCleanup(); + + return result; +} + + +nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList, + const nfdchar_t *defaultPath, + nfdpathset_t *outPaths ) +{ + GtkWidget *dialog; + nfdresult_t result; + + if ( !gtk_init_check( NULL, NULL ) ) + { + NFDi_SetError(INIT_FAIL_MSG); + return NFD_ERROR; + } + + dialog = gtk_file_chooser_dialog_new( "Open Files", + NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Open", GTK_RESPONSE_ACCEPT, + NULL ); + gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(dialog), TRUE ); + + /* Build the filter list */ + AddFiltersToDialog(dialog, filterList); + + /* Set the default path */ + SetDefaultPath(dialog, defaultPath); + + result = NFD_CANCEL; + if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) + { + GSList *fileList = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER(dialog) ); + if ( AllocPathSet( fileList, outPaths ) == NFD_ERROR ) + { + gtk_widget_destroy(dialog); + return NFD_ERROR; + } + + result = NFD_OKAY; + } + + WaitForCleanup(); + gtk_widget_destroy(dialog); + WaitForCleanup(); + + return result; +} + +nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath ) +{ + GtkWidget *dialog; + nfdresult_t result; + + if ( !gtk_init_check( NULL, NULL ) ) + { + NFDi_SetError(INIT_FAIL_MSG); + return NFD_ERROR; + } + + dialog = gtk_file_chooser_dialog_new( "Save File", + NULL, + GTK_FILE_CHOOSER_ACTION_SAVE, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Save", GTK_RESPONSE_ACCEPT, + NULL ); + gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE ); + + /* Build the filter list */ + AddFiltersToDialog(dialog, filterList); + + /* Set the default path */ + SetDefaultPath(dialog, defaultPath); + + result = NFD_CANCEL; + if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) + { + char *filename; + filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) ); + + { + size_t len = strlen(filename); + *outPath = NFDi_Malloc( len + 1 ); + memcpy( *outPath, filename, len + 1 ); + if ( !*outPath ) + { + g_free( filename ); + gtk_widget_destroy(dialog); + return NFD_ERROR; + } + } + g_free(filename); + + result = NFD_OKAY; + } + + WaitForCleanup(); + gtk_widget_destroy(dialog); + WaitForCleanup(); + + return result; +} + +nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath, + nfdchar_t **outPath) +{ + GtkWidget *dialog; + nfdresult_t result; + + if (!gtk_init_check(NULL, NULL)) + { + NFDi_SetError(INIT_FAIL_MSG); + return NFD_ERROR; + } + + dialog = gtk_file_chooser_dialog_new( "Select folder", + NULL, + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Select", GTK_RESPONSE_ACCEPT, + NULL ); + gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE ); + + + /* Set the default path */ + SetDefaultPath(dialog, defaultPath); + + result = NFD_CANCEL; + if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) + { + char *filename; + filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) ); + + { + size_t len = strlen(filename); + *outPath = NFDi_Malloc( len + 1 ); + memcpy( *outPath, filename, len + 1 ); + if ( !*outPath ) + { + g_free( filename ); + gtk_widget_destroy(dialog); + return NFD_ERROR; + } + } + g_free(filename); + + result = NFD_OKAY; + } + + WaitForCleanup(); + gtk_widget_destroy(dialog); + WaitForCleanup(); + + return result; +} diff --git a/extern/nfd-modified/src/nfd_win.cpp b/extern/nfd-modified/src/nfd_win.cpp new file mode 100644 index 00000000..b4fa5a5f --- /dev/null +++ b/extern/nfd-modified/src/nfd_win.cpp @@ -0,0 +1,801 @@ +/* + Native File Dialog + + http://www.frogtoss.com/labs + */ + + +#ifdef __MINGW32__ +// Explicitly setting NTDDI version, this is necessary for the MinGW compiler +#define NTDDI_VERSION NTDDI_VISTA +#define _WIN32_WINNT _WIN32_WINNT_VISTA +#endif + +#define _CRTDBG_MAP_ALLOC +#include +#include + +/* only locally define UNICODE in this compilation unit */ +#ifndef UNICODE +#define UNICODE +#endif + +#include +#include +#include +#include +#include +#include "nfd_common.h" + +// hack I know +#include "../../../src/utfutils.h" + +class NFDWinEvents: public IFileDialogEvents { + nfdselcallback_t selCallback; + size_t refCount; + + virtual ~NFDWinEvents() { + } + public: + IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv) { + printf("QueryInterface called DAMN IT\n"); + *ppv=NULL; + return E_NOTIMPL; + } + + IFACEMETHODIMP_(ULONG) AddRef() { + printf("AddRef() called\n"); + return InterlockedIncrement(&refCount); + } + + IFACEMETHODIMP_(ULONG) Release() { + printf("Release() called\n"); + LONG ret=InterlockedDecrement(&refCount); + if (ret==0) { + printf("Destroying the final object.\n"); + delete this; + } + return ret; + } + + IFACEMETHODIMP OnFileOk(IFileDialog*) { return E_NOTIMPL; } + IFACEMETHODIMP OnFolderChange(IFileDialog*) { return E_NOTIMPL; } + IFACEMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) { return E_NOTIMPL; } + IFACEMETHODIMP OnOverwrite(IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE*) { return E_NOTIMPL; } + IFACEMETHODIMP OnShareViolation(IFileDialog*, IShellItem*, FDE_SHAREVIOLATION_RESPONSE*) { return E_NOTIMPL; } + IFACEMETHODIMP OnTypeChange(IFileDialog*) { return E_NOTIMPL; } + + IFACEMETHODIMP OnSelectionChange(IFileDialog* dialog) { + // Get the file name + ::IShellItem *shellItem(NULL); + HRESULT result = dialog->GetCurrentSelection(&shellItem); + if ( !SUCCEEDED(result) ) + { + printf("failure!\n"); + return S_OK; + } + wchar_t *filePath(NULL); + result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath); + if ( !SUCCEEDED(result) ) + { + printf("GDN failure!\n"); + shellItem->Release(); + return S_OK; + } + std::string utf8FilePath=utf16To8(filePath); + if (selCallback!=NULL) selCallback(utf8FilePath.c_str()); + printf("I got you for a value of %s\n",utf8FilePath.c_str()); + shellItem->Release(); + return S_OK; + } + NFDWinEvents(nfdselcallback_t callback): + selCallback(callback), + refCount(1) { + } +}; + +#define COM_INITFLAGS ::COINIT_APARTMENTTHREADED | ::COINIT_DISABLE_OLE1DDE + +static BOOL COMIsInitialized(HRESULT coResult) +{ + if (coResult == RPC_E_CHANGED_MODE) + { + // If COM was previously initialized with different init flags, + // NFD still needs to operate. Eat this warning. + return TRUE; + } + + return SUCCEEDED(coResult); +} + +static HRESULT COMInit(void) +{ + return ::CoInitializeEx(NULL, COM_INITFLAGS); +} + +static void COMUninit(HRESULT coResult) +{ + // do not uninitialize if RPC_E_CHANGED_MODE occurred -- this + // case does not refcount COM. + if (SUCCEEDED(coResult)) + ::CoUninitialize(); +} + +// allocs the space in outPath -- call free() +static void CopyWCharToNFDChar( const wchar_t *inStr, nfdchar_t **outStr ) +{ + int inStrCharacterCount = static_cast(wcslen(inStr)); + int bytesNeeded = WideCharToMultiByte( CP_UTF8, 0, + inStr, inStrCharacterCount, + NULL, 0, NULL, NULL ); + assert( bytesNeeded ); + bytesNeeded += 1; + + *outStr = (nfdchar_t*)NFDi_Malloc( bytesNeeded ); + if ( !*outStr ) + return; + + int bytesWritten = WideCharToMultiByte( CP_UTF8, 0, + inStr, -1, + *outStr, bytesNeeded, + NULL, NULL ); + assert( bytesWritten ); _NFD_UNUSED( bytesWritten ); +} + +/* includes NULL terminator byte in return */ +static size_t GetUTF8ByteCountForWChar( const wchar_t *str ) +{ + size_t bytesNeeded = WideCharToMultiByte( CP_UTF8, 0, + str, -1, + NULL, 0, NULL, NULL ); + assert( bytesNeeded ); + return bytesNeeded+1; +} + +// write to outPtr -- no free() necessary. +static int CopyWCharToExistingNFDCharBuffer( const wchar_t *inStr, nfdchar_t *outPtr ) +{ + int bytesNeeded = static_cast(GetUTF8ByteCountForWChar( inStr )); + + /* invocation copies null term */ + int bytesWritten = WideCharToMultiByte( CP_UTF8, 0, + inStr, -1, + outPtr, bytesNeeded, + NULL, 0 ); + assert( bytesWritten ); + + return bytesWritten; + +} + + +// allocs the space in outStr -- call free() +static void CopyNFDCharToWChar( const nfdchar_t *inStr, wchar_t **outStr ) +{ + int inStrByteCount = static_cast(strlen(inStr)); + int charsNeeded = MultiByteToWideChar(CP_UTF8, 0, + inStr, inStrByteCount, + NULL, 0 ); + assert( charsNeeded ); + assert( !*outStr ); + charsNeeded += 1; // terminator + + *outStr = (wchar_t*)NFDi_Malloc( charsNeeded * sizeof(wchar_t) ); + if ( !*outStr ) + return; + + int ret = MultiByteToWideChar(CP_UTF8, 0, + inStr, inStrByteCount, + *outStr, charsNeeded); + (*outStr)[charsNeeded-1] = '\0'; + +#ifdef _DEBUG + int inStrCharacterCount = static_cast(NFDi_UTF8_Strlen(inStr)); + assert( ret == inStrCharacterCount ); +#else + _NFD_UNUSED(ret); +#endif +} + +static nfdresult_t AddFiltersToDialog( ::IFileDialog *fileOpenDialog, const std::vector& filterList ) +{ + const wchar_t WILDCARD[] = L"*.*"; + + if (filterList.empty()) + return NFD_OKAY; + + // list size has to be an even number (name/filter) + if (filterList.size()&1) + return NFD_ERROR; + + // Count rows to alloc + UINT filterCount = filterList.size()>>1; /* guaranteed to have one filter on a correct, non-empty parse */ + + if (filterCount==0) filterCount=1; + + if ( !filterCount ) + { + NFDi_SetError("Error parsing filters."); + return NFD_ERROR; + } + + /* filterCount plus 1 because we hardcode the *.* wildcard after the while loop */ + COMDLG_FILTERSPEC *specList = (COMDLG_FILTERSPEC*)NFDi_Malloc( sizeof(COMDLG_FILTERSPEC) * ((size_t)filterCount) ); + if ( !specList ) + { + return NFD_ERROR; + } + for (UINT i = 0; i < filterCount; ++i ) + { + specList[i].pszName = NULL; + specList[i].pszSpec = NULL; + } + + size_t specIdx = 0; + + for (size_t i=0; iSetFileTypes( filterCount, specList ); + + /* free speclist */ + for ( size_t i = 0; i < filterCount; ++i ) + { + NFDi_Free( (void*)specList[i].pszSpec ); + } + NFDi_Free( specList ); + + return NFD_OKAY; +} + +static nfdresult_t AllocPathSet( IShellItemArray *shellItems, nfdpathset_t *pathSet ) +{ + const char ERRORMSG[] = "Error allocating pathset."; + + assert(shellItems); + assert(pathSet); + + // How many items in shellItems? + DWORD numShellItems; + HRESULT result = shellItems->GetCount(&numShellItems); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError(ERRORMSG); + return NFD_ERROR; + } + + pathSet->count = static_cast(numShellItems); + assert( pathSet->count > 0 ); + + pathSet->indices = (size_t*)NFDi_Malloc( sizeof(size_t)*pathSet->count ); + if ( !pathSet->indices ) + { + return NFD_ERROR; + } + + /* count the total bytes needed for buf */ + size_t bufSize = 0; + for ( DWORD i = 0; i < numShellItems; ++i ) + { + ::IShellItem *shellItem; + result = shellItems->GetItemAt(i, &shellItem); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError(ERRORMSG); + return NFD_ERROR; + } + + // Confirm SFGAO_FILESYSTEM is true for this shellitem, or ignore it. + SFGAOF attribs; + result = shellItem->GetAttributes( SFGAO_FILESYSTEM, &attribs ); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError(ERRORMSG); + return NFD_ERROR; + } + if ( !(attribs & SFGAO_FILESYSTEM) ) + continue; + + LPWSTR name; + shellItem->GetDisplayName(SIGDN_FILESYSPATH, &name); + + // Calculate length of name with UTF-8 encoding + bufSize += GetUTF8ByteCountForWChar( name ); + + CoTaskMemFree(name); + } + + assert(bufSize); + + pathSet->buf = (nfdchar_t*)NFDi_Malloc( sizeof(nfdchar_t) * bufSize ); + memset( pathSet->buf, 0, sizeof(nfdchar_t) * bufSize ); + + /* fill buf */ + nfdchar_t *p_buf = pathSet->buf; + for (DWORD i = 0; i < numShellItems; ++i ) + { + ::IShellItem *shellItem; + result = shellItems->GetItemAt(i, &shellItem); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError(ERRORMSG); + return NFD_ERROR; + } + + // Confirm SFGAO_FILESYSTEM is true for this shellitem, or ignore it. + SFGAOF attribs; + result = shellItem->GetAttributes( SFGAO_FILESYSTEM, &attribs ); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError(ERRORMSG); + return NFD_ERROR; + } + if ( !(attribs & SFGAO_FILESYSTEM) ) + continue; + + LPWSTR name; + shellItem->GetDisplayName(SIGDN_FILESYSPATH, &name); + + int bytesWritten = CopyWCharToExistingNFDCharBuffer(name, p_buf); + CoTaskMemFree(name); + + ptrdiff_t index = p_buf - pathSet->buf; + assert( index >= 0 ); + pathSet->indices[i] = static_cast(index); + + p_buf += bytesWritten; + } + + return NFD_OKAY; +} + + +static nfdresult_t SetDefaultPath( IFileDialog *dialog, const char *defaultPath ) +{ + if ( !defaultPath || strlen(defaultPath) == 0 || strcmp(defaultPath,"\\")==0 ) + return NFD_OKAY; + + wchar_t *defaultPathW = {0}; + CopyNFDCharToWChar( defaultPath, &defaultPathW ); + + IShellItem *folder; + HRESULT result = SHCreateItemFromParsingName( defaultPathW, NULL, IID_PPV_ARGS(&folder) ); + + // Valid non results. + if ( result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || result == HRESULT_FROM_WIN32(ERROR_INVALID_DRIVE) ) + { + NFDi_Free( defaultPathW ); + return NFD_OKAY; + } + + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Error creating ShellItem"); + NFDi_Free( defaultPathW ); + return NFD_ERROR; + } + + // Could also call SetDefaultFolder(), but this guarantees defaultPath -- more consistency across API. + dialog->SetFolder( folder ); + + NFDi_Free( defaultPathW ); + folder->Release(); + + return NFD_OKAY; +} + +/* public */ + + +nfdresult_t NFD_OpenDialog( const std::vector& filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath, + nfdselcallback_t selCallback ) +{ + nfdresult_t nfdResult = NFD_ERROR; + NFDWinEvents* winEvents; + bool hasEvents=true; + DWORD eventID=0; + + HRESULT coResult = COMInit(); + if (!COMIsInitialized(coResult)) + { + NFDi_SetError("Could not initialize COM."); + return nfdResult; + } + + // Create dialog + ::IFileOpenDialog *fileOpenDialog(NULL); + HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL, + CLSCTX_ALL, ::IID_IFileOpenDialog, + reinterpret_cast(&fileOpenDialog) ); + + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Could not create dialog."); + goto end; + } + + // Build the filter list + if ( !AddFiltersToDialog( fileOpenDialog, filterList ) ) + { + goto end; + } + + // Set the default path + if ( !SetDefaultPath( fileOpenDialog, defaultPath ) ) + { + goto end; + } + + // Pass the callback + winEvents=new NFDWinEvents(selCallback); + if ( !SUCCEEDED(fileOpenDialog->Advise(winEvents,&eventID)) ) { + // error... ignore + hasEvents=false; + winEvents->Release(); + } else { + winEvents->Release(); + } + + // Show the dialog. + // TODO: pass the Furnace window here + result = fileOpenDialog->Show(NULL); + if ( SUCCEEDED(result) ) + { + // Get the file name + ::IShellItem *shellItem(NULL); + result = fileOpenDialog->GetResult(&shellItem); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Could not get shell item from dialog."); + goto end; + } + wchar_t *filePath(NULL); + result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Could not get file path for selected."); + shellItem->Release(); + goto end; + } + + CopyWCharToNFDChar( filePath, outPath ); + CoTaskMemFree(filePath); + if ( !*outPath ) + { + /* error is malloc-based, error message would be redundant */ + shellItem->Release(); + goto end; + } + + nfdResult = NFD_OKAY; + shellItem->Release(); + } + else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) ) + { + nfdResult = NFD_CANCEL; + } + else + { + NFDi_SetError("File dialog box show failed."); + nfdResult = NFD_ERROR; + } + +end: + if (fileOpenDialog) { + if (hasEvents) { + fileOpenDialog->Unadvise(eventID); + } + fileOpenDialog->Release(); + } + + COMUninit(coResult); + + return nfdResult; +} + +nfdresult_t NFD_OpenDialogMultiple( const std::vector& filterList, + const nfdchar_t *defaultPath, + nfdpathset_t *outPaths, + nfdselcallback_t selCallback ) +{ + nfdresult_t nfdResult = NFD_ERROR; + + + HRESULT coResult = COMInit(); + if (!COMIsInitialized(coResult)) + { + NFDi_SetError("Could not initialize COM."); + return nfdResult; + } + + // Create dialog + ::IFileOpenDialog *fileOpenDialog(NULL); + HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL, + CLSCTX_ALL, ::IID_IFileOpenDialog, + reinterpret_cast(&fileOpenDialog) ); + + if ( !SUCCEEDED(result) ) + { + fileOpenDialog = NULL; + NFDi_SetError("Could not create dialog."); + goto end; + } + + // Build the filter list + if ( !AddFiltersToDialog( fileOpenDialog, filterList ) ) + { + goto end; + } + + // Set the default path + if ( !SetDefaultPath( fileOpenDialog, defaultPath ) ) + { + goto end; + } + + // Set a flag for multiple options + DWORD dwFlags; + result = fileOpenDialog->GetOptions(&dwFlags); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Could not get options."); + goto end; + } + result = fileOpenDialog->SetOptions(dwFlags | FOS_ALLOWMULTISELECT); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Could not set options."); + goto end; + } + + // Show the dialog. + result = fileOpenDialog->Show(NULL); + if ( SUCCEEDED(result) ) + { + IShellItemArray *shellItems; + result = fileOpenDialog->GetResults( &shellItems ); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Could not get shell items."); + goto end; + } + + if ( AllocPathSet( shellItems, outPaths ) == NFD_ERROR ) + { + shellItems->Release(); + goto end; + } + + shellItems->Release(); + nfdResult = NFD_OKAY; + } + else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) ) + { + nfdResult = NFD_CANCEL; + } + else + { + NFDi_SetError("File dialog box show failed."); + nfdResult = NFD_ERROR; + } + +end: + if ( fileOpenDialog ) + fileOpenDialog->Release(); + + COMUninit(coResult); + + return nfdResult; +} + +nfdresult_t NFD_SaveDialog( const std::vector& filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath, + nfdselcallback_t selCallback ) +{ + nfdresult_t nfdResult = NFD_ERROR; + + HRESULT coResult = COMInit(); + if (!COMIsInitialized(coResult)) + { + NFDi_SetError("Could not initialize COM."); + return nfdResult; + } + + // Create dialog + ::IFileSaveDialog *fileSaveDialog(NULL); + HRESULT result = ::CoCreateInstance(::CLSID_FileSaveDialog, NULL, + CLSCTX_ALL, ::IID_IFileSaveDialog, + reinterpret_cast(&fileSaveDialog) ); + + if ( !SUCCEEDED(result) ) + { + fileSaveDialog = NULL; + NFDi_SetError("Could not create dialog."); + goto end; + } + + // Build the filter list + if ( !AddFiltersToDialog( fileSaveDialog, filterList ) ) + { + goto end; + } + + // Set the default path + if ( !SetDefaultPath( fileSaveDialog, defaultPath ) ) + { + goto end; + } + + // Show the dialog. + result = fileSaveDialog->Show(NULL); + if ( SUCCEEDED(result) ) + { + // Get the file name + ::IShellItem *shellItem; + result = fileSaveDialog->GetResult(&shellItem); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Could not get shell item from dialog."); + goto end; + } + wchar_t *filePath(NULL); + result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath); + if ( !SUCCEEDED(result) ) + { + shellItem->Release(); + NFDi_SetError("Could not get file path for selected."); + goto end; + } + + CopyWCharToNFDChar( filePath, outPath ); + CoTaskMemFree(filePath); + if ( !*outPath ) + { + /* error is malloc-based, error message would be redundant */ + shellItem->Release(); + goto end; + } + + nfdResult = NFD_OKAY; + shellItem->Release(); + } + else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) ) + { + nfdResult = NFD_CANCEL; + } + else + { + NFDi_SetError("File dialog box show failed."); + nfdResult = NFD_ERROR; + } + +end: + if ( fileSaveDialog ) + fileSaveDialog->Release(); + + COMUninit(coResult); + + return nfdResult; +} + + + +nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath, + nfdchar_t **outPath) +{ + nfdresult_t nfdResult = NFD_ERROR; + DWORD dwOptions = 0; + + HRESULT coResult = COMInit(); + if (!COMIsInitialized(coResult)) + { + NFDi_SetError("CoInitializeEx failed."); + return nfdResult; + } + + // Create dialog + ::IFileOpenDialog *fileDialog(NULL); + HRESULT result = CoCreateInstance(CLSID_FileOpenDialog, + NULL, + CLSCTX_ALL, + IID_PPV_ARGS(&fileDialog)); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("CoCreateInstance for CLSID_FileOpenDialog failed."); + goto end; + } + + // Set the default path + if (SetDefaultPath(fileDialog, defaultPath) != NFD_OKAY) + { + NFDi_SetError("SetDefaultPath failed."); + goto end; + } + + // Get the dialogs options + if (!SUCCEEDED(fileDialog->GetOptions(&dwOptions))) + { + NFDi_SetError("GetOptions for IFileDialog failed."); + goto end; + } + + // Add in FOS_PICKFOLDERS which hides files and only allows selection of folders + if (!SUCCEEDED(fileDialog->SetOptions(dwOptions | FOS_PICKFOLDERS))) + { + NFDi_SetError("SetOptions for IFileDialog failed."); + goto end; + } + + // Show the dialog to the user + result = fileDialog->Show(NULL); + if ( SUCCEEDED(result) ) + { + // Get the folder name + ::IShellItem *shellItem(NULL); + + result = fileDialog->GetResult(&shellItem); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Could not get file path for selected."); + shellItem->Release(); + goto end; + } + + wchar_t *path = NULL; + result = shellItem->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &path); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("GetDisplayName for IShellItem failed."); + shellItem->Release(); + goto end; + } + + CopyWCharToNFDChar(path, outPath); + CoTaskMemFree(path); + if ( !*outPath ) + { + shellItem->Release(); + goto end; + } + + nfdResult = NFD_OKAY; + shellItem->Release(); + } + else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) ) + { + nfdResult = NFD_CANCEL; + } + else + { + NFDi_SetError("Show for IFileDialog failed."); + nfdResult = NFD_ERROR; + } + + end: + + if (fileDialog) + fileDialog->Release(); + + COMUninit(coResult); + + return nfdResult; +} diff --git a/extern/nfd-modified/src/nfd_zenity.cpp b/extern/nfd-modified/src/nfd_zenity.cpp new file mode 100644 index 00000000..5f931337 --- /dev/null +++ b/extern/nfd-modified/src/nfd_zenity.cpp @@ -0,0 +1,307 @@ +/* + Native File Dialog + + http://www.frogtoss.com/labs +*/ + +#include +#include +#include +#include "nfd.h" +#include "nfd_common.h" + +#define SIMPLE_EXEC_IMPLEMENTATION +#include "simple_exec.h" + + +const char NO_ZENITY_MSG[] = "zenity not installed"; + + +static void AddTypeToFilterName( const char *typebuf, char *filterName, size_t bufsize ) +{ + size_t len = strlen(filterName); + if( len > 0 ) + strncat( filterName, " *.", bufsize - len - 1 ); + else + strncat( filterName, "--file-filter=*.", bufsize - len - 1 ); + + len = strlen(filterName); + strncat( filterName, typebuf, bufsize - len - 1 ); +} + +static void AddFiltersToCommandArgs(char** commandArgs, int commandArgsLen, const char *filterList ) +{ + char typebuf[NFD_MAX_STRLEN] = {0}; + const char *p_filterList = filterList; + char *p_typebuf = typebuf; + char filterName[NFD_MAX_STRLEN] = {0}; + int i; + + if ( !filterList || strlen(filterList) == 0 ) + return; + + while ( 1 ) + { + + if ( NFDi_IsFilterSegmentChar(*p_filterList) ) + { + char typebufWildcard[NFD_MAX_STRLEN]; + /* add another type to the filter */ + assert( strlen(typebuf) > 0 ); + assert( strlen(typebuf) < NFD_MAX_STRLEN-1 ); + + snprintf( typebufWildcard, NFD_MAX_STRLEN, "*.%s", typebuf ); + + AddTypeToFilterName( typebuf, filterName, NFD_MAX_STRLEN ); + + p_typebuf = typebuf; + memset( typebuf, 0, sizeof(char) * NFD_MAX_STRLEN ); + } + + if ( *p_filterList == ';' || *p_filterList == '\0' ) + { + /* end of filter -- add it to the dialog */ + + for(i = 0; commandArgs[i] != NULL && i < commandArgsLen; i++); + + commandArgs[i] = strdup(filterName); + + filterName[0] = '\0'; + + if ( *p_filterList == '\0' ) + break; + } + + if ( !NFDi_IsFilterSegmentChar( *p_filterList ) ) + { + *p_typebuf = *p_filterList; + p_typebuf++; + } + + p_filterList++; + } + + /* always append a wildcard option to the end*/ + + for(i = 0; commandArgs[i] != NULL && i < commandArgsLen; i++); + + commandArgs[i] = strdup("--file-filter=*.*"); +} + +static nfdresult_t ZenityCommon(char** command, int commandLen, const char* defaultPath, const char* filterList, char** stdOut) +{ + if(defaultPath != NULL) + { + char* prefix = "--filename="; + int len = strlen(prefix) + strlen(defaultPath) + 1; + + char* tmp = (char*) calloc(len, 1); + strcat(tmp, prefix); + strcat(tmp, defaultPath); + + int i; + for(i = 0; command[i] != NULL && i < commandLen; i++); + + command[i] = tmp; + } + + AddFiltersToCommandArgs(command, commandLen, filterList); + + int byteCount = 0; + int exitCode = 0; + int processInvokeError = runCommandArray(stdOut, &byteCount, &exitCode, 0, command); + + for(int i = 0; command[i] != NULL && i < commandLen; i++) + free(command[i]); + + nfdresult_t result = NFD_OKAY; + + if(processInvokeError == COMMAND_NOT_FOUND) + { + NFDi_SetError(NO_ZENITY_MSG); + result = NFD_ERROR; + } + else + { + if(exitCode == 1) + result = NFD_CANCEL; + } + + return result; +} + + +static nfdresult_t AllocPathSet(char* zenityList, nfdpathset_t *pathSet ) +{ + assert(zenityList); + assert(pathSet); + + size_t len = strlen(zenityList) + 1; + pathSet->buf = NFDi_Malloc(len); + + int numEntries = 1; + + for(size_t i = 0; i < len; i++) + { + char ch = zenityList[i]; + + if(ch == '|') + { + numEntries++; + ch = '\0'; + } + + pathSet->buf[i] = ch; + } + + pathSet->count = numEntries; + assert( pathSet->count > 0 ); + + pathSet->indices = NFDi_Malloc( sizeof(size_t)*pathSet->count ); + + int entry = 0; + pathSet->indices[0] = 0; + for(size_t i = 0; i < len; i++) + { + char ch = zenityList[i]; + + if(ch == '|') + { + entry++; + pathSet->indices[entry] = i + 1; + } + } + + return NFD_OKAY; +} + +/* public */ + +nfdresult_t NFD_OpenDialog( const char *filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath ) +{ + int commandLen = 100; + char* command[commandLen]; + memset(command, 0, commandLen * sizeof(char*)); + + command[0] = strdup("zenity"); + command[1] = strdup("--file-selection"); + command[2] = strdup("--title=Open File"); + + char* stdOut = NULL; + nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut); + + if(stdOut != NULL) + { + size_t len = strlen(stdOut); + *outPath = NFDi_Malloc(len); + memcpy(*outPath, stdOut, len); + (*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator + free(stdOut); + } + else + { + *outPath = NULL; + } + + return result; +} + + +nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList, + const nfdchar_t *defaultPath, + nfdpathset_t *outPaths ) +{ + int commandLen = 100; + char* command[commandLen]; + memset(command, 0, commandLen * sizeof(char*)); + + command[0] = strdup("zenity"); + command[1] = strdup("--file-selection"); + command[2] = strdup("--title=Open Files"); + command[3] = strdup("--multiple"); + + char* stdOut = NULL; + nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut); + + if(stdOut != NULL) + { + size_t len = strlen(stdOut); + stdOut[len-1] = '\0'; // remove trailing newline + + if ( AllocPathSet( stdOut, outPaths ) == NFD_ERROR ) + result = NFD_ERROR; + + free(stdOut); + } + else + { + result = NFD_ERROR; + } + + return result; +} + +nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath ) +{ + int commandLen = 100; + char* command[commandLen]; + memset(command, 0, commandLen * sizeof(char*)); + + command[0] = strdup("zenity"); + command[1] = strdup("--file-selection"); + command[2] = strdup("--title=Save File"); + command[3] = strdup("--save"); + + char* stdOut = NULL; + nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut); + + if(stdOut != NULL) + { + size_t len = strlen(stdOut); + *outPath = NFDi_Malloc(len); + memcpy(*outPath, stdOut, len); + (*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator + free(stdOut); + } + else + { + *outPath = NULL; + } + + return result; +} + +nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath, + nfdchar_t **outPath) +{ + int commandLen = 100; + char* command[commandLen]; + memset(command, 0, commandLen * sizeof(char*)); + + command[0] = strdup("zenity"); + command[1] = strdup("--file-selection"); + command[2] = strdup("--directory"); + command[3] = strdup("--title=Select folder"); + + char* stdOut = NULL; + nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, "", &stdOut); + + if(stdOut != NULL) + { + size_t len = strlen(stdOut); + *outPath = NFDi_Malloc(len); + memcpy(*outPath, stdOut, len); + (*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator + free(stdOut); + } + else + { + *outPath = NULL; + } + + return result; +} diff --git a/extern/nfd-modified/src/simple_exec.h b/extern/nfd-modified/src/simple_exec.h new file mode 100644 index 00000000..6308dfe0 --- /dev/null +++ b/extern/nfd-modified/src/simple_exec.h @@ -0,0 +1,218 @@ +// copied from: https://github.com/wheybags/simple_exec/blob/5a74c507c4ce1b2bb166177ead4cca7cfa23cb35/simple_exec.h + +// simple_exec.h, single header library to run external programs + retrieve their status code and output (unix only for now) +// +// do this: +// #define SIMPLE_EXEC_IMPLEMENTATION +// before you include this file in *one* C or C++ file to create the implementation. +// i.e. it should look like this: +// #define SIMPLE_EXEC_IMPLEMENTATION +// #include "simple_exec.h" + +#ifndef SIMPLE_EXEC_H +#define SIMPLE_EXEC_H + +int runCommand(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* command, ...); +int runCommandArray(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* const* allArgs); + +#endif // SIMPLE_EXEC_H + +#ifdef SIMPLE_EXEC_IMPLEMENTATION + +#include +#include +#include +#include +#include +#include +#include +#include + +#define release_assert(exp) { if (!(exp)) { abort(); } } + +enum PIPE_FILE_DESCRIPTORS +{ + READ_FD = 0, + WRITE_FD = 1 +}; + +enum RUN_COMMAND_ERROR +{ + COMMAND_RAN_OK = 0, + COMMAND_NOT_FOUND = 1 +}; + +int runCommandArray(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* const* allArgs) +{ + // adapted from: https://stackoverflow.com/a/479103 + + int bufferSize = 256; + char buffer[bufferSize + 1]; + + int dataReadFromChildDefaultSize = bufferSize * 5; + int dataReadFromChildSize = dataReadFromChildDefaultSize; + int dataReadFromChildUsed = 0; + char* dataReadFromChild = (char*)malloc(dataReadFromChildSize); + + + int parentToChild[2]; + release_assert(pipe(parentToChild) == 0); + + int childToParent[2]; + release_assert(pipe(childToParent) == 0); + + int errPipe[2]; + release_assert(pipe(errPipe) == 0); + + pid_t pid; + switch( pid = fork() ) + { + case -1: + { + release_assert(0 && "Fork failed"); + break; + } + + case 0: // child + { + release_assert(dup2(parentToChild[READ_FD ], STDIN_FILENO ) != -1); + release_assert(dup2(childToParent[WRITE_FD], STDOUT_FILENO) != -1); + + if(includeStdErr) + { + release_assert(dup2(childToParent[WRITE_FD], STDERR_FILENO) != -1); + } + else + { + int devNull = open("/dev/null", O_WRONLY); + release_assert(dup2(devNull, STDERR_FILENO) != -1); + } + + // unused + release_assert(close(parentToChild[WRITE_FD]) == 0); + release_assert(close(childToParent[READ_FD ]) == 0); + release_assert(close(errPipe[READ_FD]) == 0); + + const char* command = allArgs[0]; + execvp(command, allArgs); + + char err = 1; + ssize_t result = write(errPipe[WRITE_FD], &err, 1); + release_assert(result != -1); + + close(errPipe[WRITE_FD]); + close(parentToChild[READ_FD]); + close(childToParent[WRITE_FD]); + + exit(0); + } + + + default: // parent + { + // unused + release_assert(close(parentToChild[READ_FD]) == 0); + release_assert(close(childToParent[WRITE_FD]) == 0); + release_assert(close(errPipe[WRITE_FD]) == 0); + + while(1) + { + ssize_t bytesRead = 0; + switch(bytesRead = read(childToParent[READ_FD], buffer, bufferSize)) + { + case 0: // End-of-File, or non-blocking read. + { + int status = 0; + release_assert(waitpid(pid, &status, 0) == pid); + + // done with these now + release_assert(close(parentToChild[WRITE_FD]) == 0); + release_assert(close(childToParent[READ_FD]) == 0); + + char errChar = 0; + ssize_t result = read(errPipe[READ_FD], &errChar, 1); + release_assert(result != -1); + close(errPipe[READ_FD]); + + if(errChar) + { + free(dataReadFromChild); + return COMMAND_NOT_FOUND; + } + + // free any un-needed memory with realloc + add a null terminator for convenience + dataReadFromChild = (char*)realloc(dataReadFromChild, dataReadFromChildUsed + 1); + dataReadFromChild[dataReadFromChildUsed] = '\0'; + + if(stdOut != NULL) + *stdOut = dataReadFromChild; + else + free(dataReadFromChild); + + if(stdOutByteCount != NULL) + *stdOutByteCount = dataReadFromChildUsed; + if(returnCode != NULL) + *returnCode = WEXITSTATUS(status); + + return COMMAND_RAN_OK; + } + case -1: + { + release_assert(0 && "read() failed"); + break; + } + + default: + { + if(dataReadFromChildUsed + bytesRead + 1 >= dataReadFromChildSize) + { + dataReadFromChildSize += dataReadFromChildDefaultSize; + dataReadFromChild = (char*)realloc(dataReadFromChild, dataReadFromChildSize); + } + + memcpy(dataReadFromChild + dataReadFromChildUsed, buffer, bytesRead); + dataReadFromChildUsed += bytesRead; + break; + } + } + } + } + } +} + +int runCommand(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* command, ...) +{ + va_list vl; + va_start(vl, command); + + char* currArg = NULL; + + int allArgsInitialSize = 16; + int allArgsSize = allArgsInitialSize; + char** allArgs = (char**)malloc(sizeof(char*) * allArgsSize); + allArgs[0] = command; + + int i = 1; + do + { + currArg = va_arg(vl, char*); + allArgs[i] = currArg; + + i++; + + if(i >= allArgsSize) + { + allArgsSize += allArgsInitialSize; + allArgs = (char**)realloc(allArgs, sizeof(char*) * allArgsSize); + } + + } while(currArg != NULL); + + va_end(vl); + + int retval = runCommandArray(stdOut, stdOutByteCount, returnCode, includeStdErr, allArgs); + free(allArgs); + return retval; +} + +#endif //SIMPLE_EXEC_IMPLEMENTATION diff --git a/extern/nfd-modified/test/test_opendialog.c b/extern/nfd-modified/test/test_opendialog.c new file mode 100644 index 00000000..54bf3742 --- /dev/null +++ b/extern/nfd-modified/test/test_opendialog.c @@ -0,0 +1,29 @@ +#include "nfd.h" + +#include +#include + + +/* this test should compile on all supported platforms */ + +int main( void ) +{ + nfdchar_t *outPath = NULL; + nfdresult_t result = NFD_OpenDialog( "png,jpg;pdf", NULL, &outPath ); + if ( result == NFD_OKAY ) + { + puts("Success!"); + puts(outPath); + free(outPath); + } + else if ( result == NFD_CANCEL ) + { + puts("User pressed cancel."); + } + else + { + printf("Error: %s\n", NFD_GetError() ); + } + + return 0; +} diff --git a/extern/nfd-modified/test/test_opendialogmultiple.c b/extern/nfd-modified/test/test_opendialogmultiple.c new file mode 100644 index 00000000..45db6b65 --- /dev/null +++ b/extern/nfd-modified/test/test_opendialogmultiple.c @@ -0,0 +1,32 @@ +#include "nfd.h" + +#include +#include + +/* this test should compile on all supported platforms */ + +int main( void ) +{ + nfdpathset_t pathSet; + nfdresult_t result = NFD_OpenDialogMultiple( "png,jpg;pdf", NULL, &pathSet ); + if ( result == NFD_OKAY ) + { + size_t i; + for ( i = 0; i < NFD_PathSet_GetCount(&pathSet); ++i ) + { + nfdchar_t *path = NFD_PathSet_GetPath(&pathSet, i); + printf("Path %i: %s\n", (int)i, path ); + } + NFD_PathSet_Free(&pathSet); + } + else if ( result == NFD_CANCEL ) + { + puts("User pressed cancel."); + } + else + { + printf("Error: %s\n", NFD_GetError() ); + } + + return 0; +} diff --git a/extern/nfd-modified/test/test_pickfolder.c b/extern/nfd-modified/test/test_pickfolder.c new file mode 100644 index 00000000..708fee85 --- /dev/null +++ b/extern/nfd-modified/test/test_pickfolder.c @@ -0,0 +1,29 @@ +#include "nfd.h" + +#include +#include + + +/* this test should compile on all supported platforms */ + +int main( void ) +{ + nfdchar_t *outPath = NULL; + nfdresult_t result = NFD_PickFolder( NULL, &outPath ); + if ( result == NFD_OKAY ) + { + puts("Success!"); + puts(outPath); + free(outPath); + } + else if ( result == NFD_CANCEL ) + { + puts("User pressed cancel."); + } + else + { + printf("Error: %s\n", NFD_GetError() ); + } + + return 0; +} diff --git a/extern/nfd-modified/test/test_savedialog.c b/extern/nfd-modified/test/test_savedialog.c new file mode 100644 index 00000000..7ec37f08 --- /dev/null +++ b/extern/nfd-modified/test/test_savedialog.c @@ -0,0 +1,28 @@ +#include "nfd.h" + +#include +#include + +/* this test should compile on all supported platforms */ + +int main( void ) +{ + nfdchar_t *savePath = NULL; + nfdresult_t result = NFD_SaveDialog( "png,jpg;pdf", NULL, &savePath ); + if ( result == NFD_OKAY ) + { + puts("Success!"); + puts(savePath); + free(savePath); + } + else if ( result == NFD_CANCEL ) + { + puts("User pressed cancel."); + } + else + { + printf("Error: %s\n", NFD_GetError() ); + } + + return 0; +} diff --git a/extern/opm/opm.c b/extern/opm/opm.c index 79f6414c..aafce483 100644 --- a/extern/opm/opm.c +++ b/extern/opm/opm.c @@ -1,5 +1,5 @@ /* Nuked OPM - * Copyright (C) 2020 Nuke.YKT + * Copyright (C) 2022 Nuke.YKT * * This file is part of Nuked OPM. * @@ -21,7 +21,7 @@ * siliconpr0n.org(digshadow, John McMaster): * YM2151 and other FM chip decaps and die shots. * - * version: 0.9.2 beta + * version: 0.9.3 beta */ #include #include @@ -651,7 +651,7 @@ static inline void OPM_EnvelopePhase4(opm_t *chip) chip->eg_instantattack = chip->eg_ratemax[1] && (kon || !chip->eg_ratemax[1]); eg_off = (chip->eg_level[slot] & 0x3f0) == 0x3f0; - slreach = (chip->eg_level[slot] >> 5) == chip->eg_sl[1]; + slreach = (chip->eg_level[slot] >> 4) == (chip->eg_sl[1] << 1); eg_zero = chip->eg_level[slot] == 0; chip->eg_mute = eg_off && chip->eg_state[slot] != eg_num_attack && !kon; diff --git a/extern/rtmidi/RtMidi.cpp b/extern/rtmidi/RtMidi.cpp index 8958e6cc..b54da7e1 100644 --- a/extern/rtmidi/RtMidi.cpp +++ b/extern/rtmidi/RtMidi.cpp @@ -40,6 +40,7 @@ #include "RtMidi.h" #include +#ifdef TARGET_OS_IPHONE #if (TARGET_OS_IPHONE == 1) #define AudioGetCurrentHostTime CAHostTimeBase::GetCurrentTime @@ -66,6 +67,7 @@ #define EndianS32_BtoN(n) n #endif +#endif // Default for Windows is to add an identifier to the port names; this // flag can be defined (e.g. in your project file) to disable this behaviour. @@ -814,7 +816,7 @@ MidiOutApi :: ~MidiOutApi( void ) // time values. // These are not available on iOS. -#if (TARGET_OS_IPHONE == 0) +#ifdef TARGET_OS_IPHONE #include #include #endif diff --git a/instruments/FM/accordion/Accordion.dmp b/instruments/FM/accordion/Accordion.dmp new file mode 100644 index 00000000..10b8011c Binary files /dev/null and b/instruments/FM/accordion/Accordion.dmp differ diff --git a/instruments/FM/accordion/Harmonium.dmp b/instruments/FM/accordion/Harmonium.dmp new file mode 100644 index 00000000..3dba2392 Binary files /dev/null and b/instruments/FM/accordion/Harmonium.dmp differ diff --git a/instruments/FM/accordion/brickblock369 Accordion.dmp b/instruments/FM/accordion/brickblock369 Accordion.dmp new file mode 100644 index 00000000..f3a1cd95 Binary files /dev/null and b/instruments/FM/accordion/brickblock369 Accordion.dmp differ diff --git a/instruments/FM/bass/(CH3) Double Synth.dmp b/instruments/FM/bass/(CH3) Double Synth.dmp new file mode 100644 index 00000000..3b399724 Binary files /dev/null and b/instruments/FM/bass/(CH3) Double Synth.dmp differ diff --git a/instruments/FM/bass/(GEN) Bass Guitar 1.dmp b/instruments/FM/bass/(GEN) Bass Guitar 1.dmp new file mode 100644 index 00000000..4a7dff8d Binary files /dev/null and b/instruments/FM/bass/(GEN) Bass Guitar 1.dmp differ diff --git a/instruments/FM/bass/(GEN) Bass Guitar 2.dmp b/instruments/FM/bass/(GEN) Bass Guitar 2.dmp new file mode 100644 index 00000000..f2ab416c Binary files /dev/null and b/instruments/FM/bass/(GEN) Bass Guitar 2.dmp differ diff --git a/instruments/FM/bass/(GEN) Bass Guitar 3.dmp b/instruments/FM/bass/(GEN) Bass Guitar 3.dmp new file mode 100644 index 00000000..f4b99414 Binary files /dev/null and b/instruments/FM/bass/(GEN) Bass Guitar 3.dmp differ diff --git a/instruments/FM/bass/(GEN) Common Genesis Bass.dmp b/instruments/FM/bass/(GEN) Common Genesis Bass.dmp new file mode 100644 index 00000000..5b408d4e Binary files /dev/null and b/instruments/FM/bass/(GEN) Common Genesis Bass.dmp differ diff --git a/instruments/FM/bass/(GEN) Electric Bass Guitar.dmp b/instruments/FM/bass/(GEN) Electric Bass Guitar.dmp new file mode 100644 index 00000000..a82e47bc Binary files /dev/null and b/instruments/FM/bass/(GEN) Electric Bass Guitar.dmp differ diff --git a/instruments/FM/bass/(GEN) Growl Bass.dmp b/instruments/FM/bass/(GEN) Growl Bass.dmp new file mode 100644 index 00000000..0a8dcf47 Binary files /dev/null and b/instruments/FM/bass/(GEN) Growl Bass.dmp differ diff --git a/instruments/FM/bass/(GEN) Heavy Electric Bass.dmp b/instruments/FM/bass/(GEN) Heavy Electric Bass.dmp new file mode 100644 index 00000000..33e69bb3 Binary files /dev/null and b/instruments/FM/bass/(GEN) Heavy Electric Bass.dmp differ diff --git a/instruments/FM/bass/(GEN) Low Piano.dmp b/instruments/FM/bass/(GEN) Low Piano.dmp new file mode 100644 index 00000000..3e559a7c Binary files /dev/null and b/instruments/FM/bass/(GEN) Low Piano.dmp differ diff --git a/instruments/FM/bass/(GEN) Low Sax.dmp b/instruments/FM/bass/(GEN) Low Sax.dmp new file mode 100644 index 00000000..5a01285f Binary files /dev/null and b/instruments/FM/bass/(GEN) Low Sax.dmp differ diff --git a/instruments/FM/bass/(GEN) Low Square.dmp b/instruments/FM/bass/(GEN) Low Square.dmp new file mode 100644 index 00000000..010a4460 Binary files /dev/null and b/instruments/FM/bass/(GEN) Low Square.dmp differ diff --git a/instruments/FM/bass/(GEN) Low Trumpet.dmp b/instruments/FM/bass/(GEN) Low Trumpet.dmp new file mode 100644 index 00000000..407a1504 Binary files /dev/null and b/instruments/FM/bass/(GEN) Low Trumpet.dmp differ diff --git a/instruments/FM/bass/(GEN) Punch Packer.dmp b/instruments/FM/bass/(GEN) Punch Packer.dmp new file mode 100644 index 00000000..8c33422b Binary files /dev/null and b/instruments/FM/bass/(GEN) Punch Packer.dmp differ diff --git a/instruments/FM/bass/(GEN) Rounded Saw.dmp b/instruments/FM/bass/(GEN) Rounded Saw.dmp new file mode 100644 index 00000000..62e3b0ef Binary files /dev/null and b/instruments/FM/bass/(GEN) Rounded Saw.dmp differ diff --git a/instruments/FM/bass/(GEN) Slap Bass 1.dmp b/instruments/FM/bass/(GEN) Slap Bass 1.dmp new file mode 100644 index 00000000..d011abcd Binary files /dev/null and b/instruments/FM/bass/(GEN) Slap Bass 1.dmp differ diff --git a/instruments/FM/bass/(GEN) Slap Bass 2.dmp b/instruments/FM/bass/(GEN) Slap Bass 2.dmp new file mode 100644 index 00000000..fbd6ae8d Binary files /dev/null and b/instruments/FM/bass/(GEN) Slap Bass 2.dmp differ diff --git a/instruments/FM/bass/(GEN) Slap Bass 3.dmp b/instruments/FM/bass/(GEN) Slap Bass 3.dmp new file mode 100644 index 00000000..431b43fe Binary files /dev/null and b/instruments/FM/bass/(GEN) Slap Bass 3.dmp differ diff --git a/instruments/FM/bass/(GEN) Space Synth.dmp b/instruments/FM/bass/(GEN) Space Synth.dmp new file mode 100644 index 00000000..d84a7969 Binary files /dev/null and b/instruments/FM/bass/(GEN) Space Synth.dmp differ diff --git a/instruments/FM/bass/(GEN) Sunsoft Slap Bass.dmp b/instruments/FM/bass/(GEN) Sunsoft Slap Bass.dmp new file mode 100644 index 00000000..2da8452d Binary files /dev/null and b/instruments/FM/bass/(GEN) Sunsoft Slap Bass.dmp differ diff --git a/instruments/FM/bass/(GEN) Synth.dmp b/instruments/FM/bass/(GEN) Synth.dmp new file mode 100644 index 00000000..c2ea0d0e Binary files /dev/null and b/instruments/FM/bass/(GEN) Synth.dmp differ diff --git a/instruments/FM/bass/(GEN) Techno Bass 1.dmp b/instruments/FM/bass/(GEN) Techno Bass 1.dmp new file mode 100644 index 00000000..85d3cf96 Binary files /dev/null and b/instruments/FM/bass/(GEN) Techno Bass 1.dmp differ diff --git a/instruments/FM/bass/(GEN) Techno Bass 2.dmp b/instruments/FM/bass/(GEN) Techno Bass 2.dmp new file mode 100644 index 00000000..84e12a14 Binary files /dev/null and b/instruments/FM/bass/(GEN) Techno Bass 2.dmp differ diff --git a/instruments/FM/bass/(GEN) Thiel Bass.dmp b/instruments/FM/bass/(GEN) Thiel Bass.dmp new file mode 100644 index 00000000..17817d34 Binary files /dev/null and b/instruments/FM/bass/(GEN) Thiel Bass.dmp differ diff --git a/instruments/FM/bass/(GEN) Tuba 1.dmp b/instruments/FM/bass/(GEN) Tuba 1.dmp new file mode 100644 index 00000000..9b5250d9 Binary files /dev/null and b/instruments/FM/bass/(GEN) Tuba 1.dmp differ diff --git a/instruments/FM/bass/(GEN) Tuba 2.dmp b/instruments/FM/bass/(GEN) Tuba 2.dmp new file mode 100644 index 00000000..0a8dfcaf Binary files /dev/null and b/instruments/FM/bass/(GEN) Tuba 2.dmp differ diff --git a/instruments/FM/bass/(GEN) Twang.dmp b/instruments/FM/bass/(GEN) Twang.dmp new file mode 100644 index 00000000..093a0f8c Binary files /dev/null and b/instruments/FM/bass/(GEN) Twang.dmp differ diff --git a/instruments/FM/bass/(GEN) VRC7 Bass.dmp b/instruments/FM/bass/(GEN) VRC7 Bass.dmp new file mode 100644 index 00000000..6f79a339 Binary files /dev/null and b/instruments/FM/bass/(GEN) VRC7 Bass.dmp differ diff --git a/instruments/FM/bass/4-OP_Vowel_Bass.fui b/instruments/FM/bass/4-OP_Vowel_Bass.fui new file mode 100644 index 00000000..b45a50b1 Binary files /dev/null and b/instruments/FM/bass/4-OP_Vowel_Bass.fui differ diff --git a/instruments/FM/bass/4_OP_Funky_Bass.fui b/instruments/FM/bass/4_OP_Funky_Bass.fui new file mode 100644 index 00000000..afc22155 Binary files /dev/null and b/instruments/FM/bass/4_OP_Funky_Bass.fui differ diff --git a/instruments/FM/bass/Acoustic Bass 2.dmp b/instruments/FM/bass/Acoustic Bass 2.dmp new file mode 100644 index 00000000..6f58b76c Binary files /dev/null and b/instruments/FM/bass/Acoustic Bass 2.dmp differ diff --git a/instruments/FM/bass/Acoustic Bass.dmp b/instruments/FM/bass/Acoustic Bass.dmp new file mode 100644 index 00000000..fb37c096 Binary files /dev/null and b/instruments/FM/bass/Acoustic Bass.dmp differ diff --git a/instruments/FM/bass/Algorithmic Bass.fui b/instruments/FM/bass/Algorithmic Bass.fui new file mode 100644 index 00000000..b1e2deb1 Binary files /dev/null and b/instruments/FM/bass/Algorithmic Bass.fui differ diff --git a/instruments/FM/bass/Bass Guitar.dmp b/instruments/FM/bass/Bass Guitar.dmp new file mode 100644 index 00000000..c5565557 Binary files /dev/null and b/instruments/FM/bass/Bass Guitar.dmp differ diff --git a/instruments/FM/bass/Bass.fui b/instruments/FM/bass/Bass.fui new file mode 100644 index 00000000..147b43e7 Binary files /dev/null and b/instruments/FM/bass/Bass.fui differ diff --git a/instruments/FM/bass/BassThing.dmp b/instruments/FM/bass/BassThing.dmp new file mode 100644 index 00000000..57c74f3b Binary files /dev/null and b/instruments/FM/bass/BassThing.dmp differ diff --git a/instruments/FM/bass/Basses.opm b/instruments/FM/bass/Basses.opm new file mode 100644 index 00000000..0b308cbf --- /dev/null +++ b/instruments/FM/bass/Basses.opm @@ -0,0 +1,87 @@ +//LFO: LFRQ AMD PMD WF NFRQ +//@:[Num] [Name] +//CH: PAN FL CON AMS PMS SLOT NE +//OP: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN + +// vgm offset = 000001d3, channels used = 1------- +@:0 Acoustic Bass +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 120 0 +M1: 25 18 14 6 3 28 0 5 6 0 0 +C1: 28 26 8 6 7 31 0 4 2 0 0 +M2: 27 7 0 6 15 21 0 1 1 0 0 +C2: 29 11 10 8 2 0 0 1 3 0 0 + +// vgm offset = 0000024e, channels used = 12------ +@:1 StringSlapSFX +LFO: 0 0 0 0 0 +CH: 64 7 4 0 0 120 0 +M1: 19 16 0 10 15 3 0 0 5 0 0 +C1: 31 20 0 9 15 4 0 7 0 0 0 +M2: 31 18 0 1 15 16 0 2 3 0 0 +C2: 31 17 0 9 15 30 0 1 3 0 0 + +// vgm offset = 0000093e, channels used = 1------- +@:2 Finger Bass +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 120 0 +M1: 31 15 0 11 1 31 0 3 3 0 0 +C1: 31 13 0 10 1 46 0 2 3 0 0 +M2: 31 10 0 10 1 24 0 1 3 0 0 +C2: 31 4 0 11 15 0 0 1 3 0 0 + +// vgm offset = 00000b30, channels used = 1------- +@:3 Fretless Bass +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 120 0 +M1: 31 0 0 3 15 21 0 1 5 0 0 +C1: 31 0 0 6 15 54 0 1 0 0 0 +M2: 31 0 0 7 11 26 0 1 5 0 0 +C2: 31 4 0 12 15 2 0 1 0 0 0 + +// vgm offset = 00000d2b, channels used = 1------- +@:4 Picked Bass +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 120 0 +M1: 31 18 1 11 3 24 0 5 3 0 0 +C1: 31 18 2 10 1 33 0 3 3 0 0 +M2: 31 10 3 10 1 33 0 1 3 0 0 +C2: 31 4 0 11 15 0 0 1 3 0 0 + +// vgm offset = 00000f2c, channels used = 1------- +@:5 Slap Bass +LFO: 0 0 0 0 0 +CH: 64 0 2 0 0 120 0 +M1: 31 20 0 3 15 0 1 0 0 0 0 +C1: 31 23 0 4 0 43 0 15 3 0 0 +M2: 31 17 0 1 2 16 1 1 3 0 0 +C2: 31 9 0 10 15 2 0 1 3 0 0 + +// vgm offset = 00001444, channels used = 1------- +@:6 Synth Bass 1 +LFO: 0 0 0 0 0 +CH: 64 6 2 0 0 120 0 +M1: 31 12 0 3 15 16 2 1 3 0 0 +C1: 31 10 0 4 15 127 0 1 0 0 0 +M2: 31 4 0 1 0 33 0 1 3 0 0 +C2: 31 9 0 11 15 0 0 1 3 0 0 + +// vgm offset = 0000189b, channels used = 1------- +@:7 SynthBass101 +LFO: 0 0 0 0 0 +CH: 64 5 2 0 0 120 0 +M1: 31 11 0 3 15 24 2 2 3 0 0 +C1: 31 10 0 4 15 127 0 1 0 0 0 +M2: 31 0 0 1 15 24 0 1 3 0 0 +C2: 31 9 0 11 15 0 0 1 3 0 0 + +// vgm offset = 00001d22, channels used = 1------- +@:8 Synth Bass 2 +LFO: 0 0 0 0 0 +CH: 64 5 4 0 0 120 0 +M1: 27 10 5 11 7 21 1 1 3 0 0 +C1: 31 0 15 11 7 3 0 1 3 0 0 +M2: 22 13 13 8 6 21 3 12 3 0 0 +C2: 31 15 16 11 8 13 0 2 3 0 0 + + diff --git a/instruments/FM/bass/Double Bass.dmp b/instruments/FM/bass/Double Bass.dmp new file mode 100644 index 00000000..4542d741 Binary files /dev/null and b/instruments/FM/bass/Double Bass.dmp differ diff --git a/instruments/FM/bass/Electric Finger Bass.dmp b/instruments/FM/bass/Electric Finger Bass.dmp new file mode 100644 index 00000000..e189f5f5 Binary files /dev/null and b/instruments/FM/bass/Electric Finger Bass.dmp differ diff --git a/instruments/FM/bass/Electric Fretless Bass.dmp b/instruments/FM/bass/Electric Fretless Bass.dmp new file mode 100644 index 00000000..f191ec76 Binary files /dev/null and b/instruments/FM/bass/Electric Fretless Bass.dmp differ diff --git a/instruments/FM/bass/Electric Picked Bass.dmp b/instruments/FM/bass/Electric Picked Bass.dmp new file mode 100644 index 00000000..2d49e919 Binary files /dev/null and b/instruments/FM/bass/Electric Picked Bass.dmp differ diff --git a/instruments/FM/bass/Electric Slap Bass.dmp b/instruments/FM/bass/Electric Slap Bass.dmp new file mode 100644 index 00000000..2be2803c Binary files /dev/null and b/instruments/FM/bass/Electric Slap Bass.dmp differ diff --git a/instruments/FM/bass/FM-Bass-2.dmp b/instruments/FM/bass/FM-Bass-2.dmp new file mode 100644 index 00000000..f87bfbff Binary files /dev/null and b/instruments/FM/bass/FM-Bass-2.dmp differ diff --git a/instruments/FM/bass/FM_Bass.dmp b/instruments/FM/bass/FM_Bass.dmp new file mode 100644 index 00000000..bcef63c5 Binary files /dev/null and b/instruments/FM/bass/FM_Bass.dmp differ diff --git a/instruments/FM/bass/FM_CriminalBass.fui b/instruments/FM/bass/FM_CriminalBass.fui new file mode 100644 index 00000000..35705d22 Binary files /dev/null and b/instruments/FM/bass/FM_CriminalBass.fui differ diff --git a/instruments/FM/bass/GEMSLike Bass.dmp b/instruments/FM/bass/GEMSLike Bass.dmp new file mode 100644 index 00000000..04581696 Binary files /dev/null and b/instruments/FM/bass/GEMSLike Bass.dmp differ diff --git a/instruments/FM/bass/Heavy_synth_bass.fui b/instruments/FM/bass/Heavy_synth_bass.fui new file mode 100644 index 00000000..d1ba7890 Binary files /dev/null and b/instruments/FM/bass/Heavy_synth_bass.fui differ diff --git a/instruments/FM/bass/House_Bass.fui b/instruments/FM/bass/House_Bass.fui new file mode 100644 index 00000000..51676d26 Binary files /dev/null and b/instruments/FM/bass/House_Bass.fui differ diff --git a/instruments/FM/bass/Levin Bass.dmp b/instruments/FM/bass/Levin Bass.dmp new file mode 100644 index 00000000..6c57392a Binary files /dev/null and b/instruments/FM/bass/Levin Bass.dmp differ diff --git a/instruments/FM/bass/OPNx_FingerBass.fui b/instruments/FM/bass/OPNx_FingerBass.fui new file mode 100644 index 00000000..a9f71261 Binary files /dev/null and b/instruments/FM/bass/OPNx_FingerBass.fui differ diff --git a/instruments/FM/bass/OPNx_PickBass.fui b/instruments/FM/bass/OPNx_PickBass.fui new file mode 100644 index 00000000..03914c42 Binary files /dev/null and b/instruments/FM/bass/OPNx_PickBass.fui differ diff --git a/instruments/FM/bass/OPZ-Fake_Famicom_Tri.fui b/instruments/FM/bass/OPZ-Fake_Famicom_Tri.fui new file mode 100644 index 00000000..16330fd1 Binary files /dev/null and b/instruments/FM/bass/OPZ-Fake_Famicom_Tri.fui differ diff --git a/instruments/FM/bass/Passport Bass.dmp b/instruments/FM/bass/Passport Bass.dmp new file mode 100644 index 00000000..58c07286 Binary files /dev/null and b/instruments/FM/bass/Passport Bass.dmp differ diff --git a/instruments/FM/bass/Piano Bass.dmp b/instruments/FM/bass/Piano Bass.dmp new file mode 100644 index 00000000..d50da1b9 Binary files /dev/null and b/instruments/FM/bass/Piano Bass.dmp differ diff --git a/instruments/FM/bass/PickMD.fui b/instruments/FM/bass/PickMD.fui new file mode 100644 index 00000000..094c805a Binary files /dev/null and b/instruments/FM/bass/PickMD.fui differ diff --git a/instruments/FM/bass/Power Bass.dmp b/instruments/FM/bass/Power Bass.dmp new file mode 100644 index 00000000..a7b5e98e Binary files /dev/null and b/instruments/FM/bass/Power Bass.dmp differ diff --git a/instruments/FM/bass/Punch Bass.dmp b/instruments/FM/bass/Punch Bass.dmp new file mode 100644 index 00000000..90189feb Binary files /dev/null and b/instruments/FM/bass/Punch Bass.dmp differ diff --git a/instruments/FM/bass/Radical Bass.dmp b/instruments/FM/bass/Radical Bass.dmp new file mode 100644 index 00000000..095d225d Binary files /dev/null and b/instruments/FM/bass/Radical Bass.dmp differ diff --git a/instruments/FM/bass/Real Bass 1.dmp b/instruments/FM/bass/Real Bass 1.dmp new file mode 100644 index 00000000..58a0169b Binary files /dev/null and b/instruments/FM/bass/Real Bass 1.dmp differ diff --git a/instruments/FM/bass/Real Bass 2.dmp b/instruments/FM/bass/Real Bass 2.dmp new file mode 100644 index 00000000..5e6cefcc Binary files /dev/null and b/instruments/FM/bass/Real Bass 2.dmp differ diff --git a/instruments/FM/bass/Resonant Bass.dmp b/instruments/FM/bass/Resonant Bass.dmp new file mode 100644 index 00000000..2ae09ad0 Binary files /dev/null and b/instruments/FM/bass/Resonant Bass.dmp differ diff --git a/instruments/FM/bass/SC-55 Synth Bass 1.dmp b/instruments/FM/bass/SC-55 Synth Bass 1.dmp new file mode 100644 index 00000000..9ac49622 Binary files /dev/null and b/instruments/FM/bass/SC-55 Synth Bass 1.dmp differ diff --git a/instruments/FM/bass/SC-55 SynthBass101.dmp b/instruments/FM/bass/SC-55 SynthBass101.dmp new file mode 100644 index 00000000..2feefab3 Binary files /dev/null and b/instruments/FM/bass/SC-55 SynthBass101.dmp differ diff --git a/instruments/FM/bass/Slap Bass.dmp b/instruments/FM/bass/Slap Bass.dmp new file mode 100644 index 00000000..5c15c14c Binary files /dev/null and b/instruments/FM/bass/Slap Bass.dmp differ diff --git a/instruments/FM/bass/Spooky Bass.dmp b/instruments/FM/bass/Spooky Bass.dmp new file mode 100644 index 00000000..b511715b Binary files /dev/null and b/instruments/FM/bass/Spooky Bass.dmp differ diff --git a/instruments/FM/bass/Squire Bass.dmp b/instruments/FM/bass/Squire Bass.dmp new file mode 100644 index 00000000..887f25e4 Binary files /dev/null and b/instruments/FM/bass/Squire Bass.dmp differ diff --git a/instruments/FM/bass/Sublime Bass.dmp b/instruments/FM/bass/Sublime Bass.dmp new file mode 100644 index 00000000..3bae37d1 Binary files /dev/null and b/instruments/FM/bass/Sublime Bass.dmp differ diff --git a/instruments/FM/bass/Yamaha MU Synth Bass 2.dmp b/instruments/FM/bass/Yamaha MU Synth Bass 2.dmp new file mode 100644 index 00000000..08f8faca Binary files /dev/null and b/instruments/FM/bass/Yamaha MU Synth Bass 2.dmp differ diff --git a/instruments/FM/bass/algobass.dmp b/instruments/FM/bass/algobass.dmp new file mode 100644 index 00000000..e2d4197c Binary files /dev/null and b/instruments/FM/bass/algobass.dmp differ diff --git a/instruments/FM/bass/bass1.fui b/instruments/FM/bass/bass1.fui new file mode 100644 index 00000000..e4b6c127 Binary files /dev/null and b/instruments/FM/bass/bass1.fui differ diff --git a/instruments/FM/bass/bass2.fui b/instruments/FM/bass/bass2.fui new file mode 100644 index 00000000..5957d801 Binary files /dev/null and b/instruments/FM/bass/bass2.fui differ diff --git a/instruments/FM/bass/bass3.fui b/instruments/FM/bass/bass3.fui new file mode 100644 index 00000000..2466f433 Binary files /dev/null and b/instruments/FM/bass/bass3.fui differ diff --git a/instruments/FM/bass/bass4.fui b/instruments/FM/bass/bass4.fui new file mode 100644 index 00000000..ff36a2dc Binary files /dev/null and b/instruments/FM/bass/bass4.fui differ diff --git a/instruments/FM/bass/bass5.fui b/instruments/FM/bass/bass5.fui new file mode 100644 index 00000000..36e1641f Binary files /dev/null and b/instruments/FM/bass/bass5.fui differ diff --git a/instruments/FM/bass/bass6.fui b/instruments/FM/bass/bass6.fui new file mode 100644 index 00000000..093af97c Binary files /dev/null and b/instruments/FM/bass/bass6.fui differ diff --git a/instruments/FM/bass/bass_acoustic_00.fui b/instruments/FM/bass/bass_acoustic_00.fui new file mode 100644 index 00000000..0ba14ba9 Binary files /dev/null and b/instruments/FM/bass/bass_acoustic_00.fui differ diff --git a/instruments/FM/bass/bass_electric_00.fui b/instruments/FM/bass/bass_electric_00.fui new file mode 100644 index 00000000..7eedbc8e Binary files /dev/null and b/instruments/FM/bass/bass_electric_00.fui differ diff --git a/instruments/FM/bass/bass_electric_01.fui b/instruments/FM/bass/bass_electric_01.fui new file mode 100644 index 00000000..f0a9f14b Binary files /dev/null and b/instruments/FM/bass/bass_electric_01.fui differ diff --git a/instruments/FM/bass/bass_electric_02.fui b/instruments/FM/bass/bass_electric_02.fui new file mode 100644 index 00000000..c33d5a8c Binary files /dev/null and b/instruments/FM/bass/bass_electric_02.fui differ diff --git a/instruments/FM/bass/bass_electric_03_muffled.fui b/instruments/FM/bass/bass_electric_03_muffled.fui new file mode 100644 index 00000000..5fd71d8a Binary files /dev/null and b/instruments/FM/bass/bass_electric_03_muffled.fui differ diff --git a/instruments/FM/bass/bass_electric_04.fui b/instruments/FM/bass/bass_electric_04.fui new file mode 100644 index 00000000..9a015fbd Binary files /dev/null and b/instruments/FM/bass/bass_electric_04.fui differ diff --git a/instruments/FM/bass/bass_electric_05_amp.fui b/instruments/FM/bass/bass_electric_05_amp.fui new file mode 100644 index 00000000..a5ac0c04 Binary files /dev/null and b/instruments/FM/bass/bass_electric_05_amp.fui differ diff --git a/instruments/FM/bass/bass_growl_00_wobbly.fui b/instruments/FM/bass/bass_growl_00_wobbly.fui new file mode 100644 index 00000000..f8084eb8 Binary files /dev/null and b/instruments/FM/bass/bass_growl_00_wobbly.fui differ diff --git a/instruments/FM/bass/bass_growl_01_wobbly2.fui b/instruments/FM/bass/bass_growl_01_wobbly2.fui new file mode 100644 index 00000000..7111e281 Binary files /dev/null and b/instruments/FM/bass/bass_growl_01_wobbly2.fui differ diff --git a/instruments/FM/bass/bass_growl_02_wobbly3.fui b/instruments/FM/bass/bass_growl_02_wobbly3.fui new file mode 100644 index 00000000..ac91ef7c Binary files /dev/null and b/instruments/FM/bass/bass_growl_02_wobbly3.fui differ diff --git a/instruments/FM/bass/bass_picked_00.fui b/instruments/FM/bass/bass_picked_00.fui new file mode 100644 index 00000000..45d50b83 Binary files /dev/null and b/instruments/FM/bass/bass_picked_00.fui differ diff --git a/instruments/FM/bass/bass_picked_01_snappy.fui b/instruments/FM/bass/bass_picked_01_snappy.fui new file mode 100644 index 00000000..5561af9c Binary files /dev/null and b/instruments/FM/bass/bass_picked_01_snappy.fui differ diff --git a/instruments/FM/bass/bass_picked_02.fui b/instruments/FM/bass/bass_picked_02.fui new file mode 100644 index 00000000..5093a621 Binary files /dev/null and b/instruments/FM/bass/bass_picked_02.fui differ diff --git a/instruments/FM/bass/bass_slap_00.fui b/instruments/FM/bass/bass_slap_00.fui new file mode 100644 index 00000000..7cadc66c Binary files /dev/null and b/instruments/FM/bass/bass_slap_00.fui differ diff --git a/instruments/FM/bass/bass_slap_01.fui b/instruments/FM/bass/bass_slap_01.fui new file mode 100644 index 00000000..280a0d1f Binary files /dev/null and b/instruments/FM/bass/bass_slap_01.fui differ diff --git a/instruments/FM/bass/bass_slap_02.fui b/instruments/FM/bass/bass_slap_02.fui new file mode 100644 index 00000000..5aae7b79 Binary files /dev/null and b/instruments/FM/bass/bass_slap_02.fui differ diff --git a/instruments/FM/bass/dxbass1.dmp b/instruments/FM/bass/dxbass1.dmp new file mode 100644 index 00000000..d77bb762 Binary files /dev/null and b/instruments/FM/bass/dxbass1.dmp differ diff --git a/instruments/FM/bass/dxbass2.dmp b/instruments/FM/bass/dxbass2.dmp new file mode 100644 index 00000000..800646cf Binary files /dev/null and b/instruments/FM/bass/dxbass2.dmp differ diff --git a/instruments/FM/bass/dxbass3.dmp b/instruments/FM/bass/dxbass3.dmp new file mode 100644 index 00000000..7bf0a553 Binary files /dev/null and b/instruments/FM/bass/dxbass3.dmp differ diff --git a/instruments/FM/bass/dxbass4.dmp b/instruments/FM/bass/dxbass4.dmp new file mode 100644 index 00000000..a7db224a Binary files /dev/null and b/instruments/FM/bass/dxbass4.dmp differ diff --git a/instruments/FM/bass/dxbass5.dmp b/instruments/FM/bass/dxbass5.dmp new file mode 100644 index 00000000..abe46a08 Binary files /dev/null and b/instruments/FM/bass/dxbass5.dmp differ diff --git a/instruments/FM/bass/dxbass5slap.dmp b/instruments/FM/bass/dxbass5slap.dmp new file mode 100644 index 00000000..0ee63eb9 Binary files /dev/null and b/instruments/FM/bass/dxbass5slap.dmp differ diff --git a/instruments/FM/bass/fm_bass1.fui b/instruments/FM/bass/fm_bass1.fui new file mode 100644 index 00000000..d72ad4d9 Binary files /dev/null and b/instruments/FM/bass/fm_bass1.fui differ diff --git a/instruments/FM/bass/fm_bass2.fui b/instruments/FM/bass/fm_bass2.fui new file mode 100644 index 00000000..35fab8fb Binary files /dev/null and b/instruments/FM/bass/fm_bass2.fui differ diff --git a/instruments/FM/bass/fm_bass3.fui b/instruments/FM/bass/fm_bass3.fui new file mode 100644 index 00000000..f2be9901 Binary files /dev/null and b/instruments/FM/bass/fm_bass3.fui differ diff --git a/instruments/FM/bass/generic alg 0 bass.dmp b/instruments/FM/bass/generic alg 0 bass.dmp new file mode 100644 index 00000000..c784e11f Binary files /dev/null and b/instruments/FM/bass/generic alg 0 bass.dmp differ diff --git a/instruments/FM/bass/nicco1690 Slap Bass.dmp b/instruments/FM/bass/nicco1690 Slap Bass.dmp new file mode 100644 index 00000000..32a80d19 Binary files /dev/null and b/instruments/FM/bass/nicco1690 Slap Bass.dmp differ diff --git a/instruments/FM/bass/opm_nestri.fui b/instruments/FM/bass/opm_nestri.fui new file mode 100644 index 00000000..2b0ac741 Binary files /dev/null and b/instruments/FM/bass/opm_nestri.fui differ diff --git a/instruments/FM/bass/sonic_modbass.dmp b/instruments/FM/bass/sonic_modbass.dmp new file mode 100644 index 00000000..2faf77c6 Binary files /dev/null and b/instruments/FM/bass/sonic_modbass.dmp differ diff --git a/instruments/FM/bass/tarylbass1.fui b/instruments/FM/bass/tarylbass1.fui new file mode 100644 index 00000000..f3b59213 Binary files /dev/null and b/instruments/FM/bass/tarylbass1.fui differ diff --git a/instruments/FM/bass/weird bass.dmp b/instruments/FM/bass/weird bass.dmp new file mode 100644 index 00000000..7a81b3f1 Binary files /dev/null and b/instruments/FM/bass/weird bass.dmp differ diff --git a/instruments/FM/drums/(GEN) Crash.dmp b/instruments/FM/drums/(GEN) Crash.dmp new file mode 100644 index 00000000..3ca63d3c Binary files /dev/null and b/instruments/FM/drums/(GEN) Crash.dmp differ diff --git a/instruments/FM/drums/(GEN) Double Tom.dmp b/instruments/FM/drums/(GEN) Double Tom.dmp new file mode 100644 index 00000000..f2c20d24 Binary files /dev/null and b/instruments/FM/drums/(GEN) Double Tom.dmp differ diff --git a/instruments/FM/drums/(GEN) Hi-Hat Closed.dmp b/instruments/FM/drums/(GEN) Hi-Hat Closed.dmp new file mode 100644 index 00000000..11b708be Binary files /dev/null and b/instruments/FM/drums/(GEN) Hi-Hat Closed.dmp differ diff --git a/instruments/FM/drums/(GEN) Hi-Hat Open.dmp b/instruments/FM/drums/(GEN) Hi-Hat Open.dmp new file mode 100644 index 00000000..48b2a553 Binary files /dev/null and b/instruments/FM/drums/(GEN) Hi-Hat Open.dmp differ diff --git a/instruments/FM/drums/(GEN) Kick 1.dmp b/instruments/FM/drums/(GEN) Kick 1.dmp new file mode 100644 index 00000000..e260ccd2 Binary files /dev/null and b/instruments/FM/drums/(GEN) Kick 1.dmp differ diff --git a/instruments/FM/drums/(GEN) Kick 2.dmp b/instruments/FM/drums/(GEN) Kick 2.dmp new file mode 100644 index 00000000..7865aba2 Binary files /dev/null and b/instruments/FM/drums/(GEN) Kick 2.dmp differ diff --git a/instruments/FM/drums/(GEN) Light Snare.dmp b/instruments/FM/drums/(GEN) Light Snare.dmp new file mode 100644 index 00000000..9c7ee957 Binary files /dev/null and b/instruments/FM/drums/(GEN) Light Snare.dmp differ diff --git a/instruments/FM/drums/(GEN) Obvious Kick (C-5 02CC).dmp b/instruments/FM/drums/(GEN) Obvious Kick (C-5 02CC).dmp new file mode 100644 index 00000000..5b69c87c Binary files /dev/null and b/instruments/FM/drums/(GEN) Obvious Kick (C-5 02CC).dmp differ diff --git a/instruments/FM/drums/(GEN) Obvious Ride Closed (C-5).dmp b/instruments/FM/drums/(GEN) Obvious Ride Closed (C-5).dmp new file mode 100644 index 00000000..742fe944 Binary files /dev/null and b/instruments/FM/drums/(GEN) Obvious Ride Closed (C-5).dmp differ diff --git a/instruments/FM/drums/(GEN) Obvious Ride Open (C-5).dmp b/instruments/FM/drums/(GEN) Obvious Ride Open (C-5).dmp new file mode 100644 index 00000000..bc01447b Binary files /dev/null and b/instruments/FM/drums/(GEN) Obvious Ride Open (C-5).dmp differ diff --git a/instruments/FM/drums/(GEN) Obvious Snare (D#5 0299).dmp b/instruments/FM/drums/(GEN) Obvious Snare (D#5 0299).dmp new file mode 100644 index 00000000..ade18f3a Binary files /dev/null and b/instruments/FM/drums/(GEN) Obvious Snare (D#5 0299).dmp differ diff --git a/instruments/FM/drums/(GEN) Obvious Tom (C-5 0266, G#4 0255, E-4 0244).dmp b/instruments/FM/drums/(GEN) Obvious Tom (C-5 0266, G#4 0255, E-4 0244).dmp new file mode 100644 index 00000000..fad653d3 Binary files /dev/null and b/instruments/FM/drums/(GEN) Obvious Tom (C-5 0266, G#4 0255, E-4 0244).dmp differ diff --git a/instruments/FM/drums/(GEN) Snare 1.dmp b/instruments/FM/drums/(GEN) Snare 1.dmp new file mode 100644 index 00000000..e2150df5 Binary files /dev/null and b/instruments/FM/drums/(GEN) Snare 1.dmp differ diff --git a/instruments/FM/drums/(GEN) Snare 2.dmp b/instruments/FM/drums/(GEN) Snare 2.dmp new file mode 100644 index 00000000..76b5100d Binary files /dev/null and b/instruments/FM/drums/(GEN) Snare 2.dmp differ diff --git a/instruments/FM/drums/(GEN) Snare 3.dmp b/instruments/FM/drums/(GEN) Snare 3.dmp new file mode 100644 index 00000000..77e488be Binary files /dev/null and b/instruments/FM/drums/(GEN) Snare 3.dmp differ diff --git a/instruments/FM/drums/(GEN) Timpani.dmp b/instruments/FM/drums/(GEN) Timpani.dmp new file mode 100644 index 00000000..e603fb3c Binary files /dev/null and b/instruments/FM/drums/(GEN) Timpani.dmp differ diff --git a/instruments/FM/drums/(GEN) Tom.dmp b/instruments/FM/drums/(GEN) Tom.dmp new file mode 100644 index 00000000..23e8fba2 Binary files /dev/null and b/instruments/FM/drums/(GEN) Tom.dmp differ diff --git a/instruments/FM/drums/(GEN) Trap Snare Carry (C-4, 0201).dmp b/instruments/FM/drums/(GEN) Trap Snare Carry (C-4, 0201).dmp new file mode 100644 index 00000000..7412d8b5 Binary files /dev/null and b/instruments/FM/drums/(GEN) Trap Snare Carry (C-4, 0201).dmp differ diff --git a/instruments/FM/drums/(GEN) Trap Snare Start (D-4 023F).dmp b/instruments/FM/drums/(GEN) Trap Snare Start (D-4 023F).dmp new file mode 100644 index 00000000..24e76a6b Binary files /dev/null and b/instruments/FM/drums/(GEN) Trap Snare Start (D-4 023F).dmp differ diff --git a/instruments/FM/drums/(GEN) Triangle.dmp b/instruments/FM/drums/(GEN) Triangle.dmp new file mode 100644 index 00000000..d6c81582 Binary files /dev/null and b/instruments/FM/drums/(GEN) Triangle.dmp differ diff --git a/instruments/FM/drums/(GEN) Wood Block.dmp b/instruments/FM/drums/(GEN) Wood Block.dmp new file mode 100644 index 00000000..7db9914f Binary files /dev/null and b/instruments/FM/drums/(GEN) Wood Block.dmp differ diff --git a/instruments/FM/drums/909 Hat.dmp b/instruments/FM/drums/909 Hat.dmp new file mode 100644 index 00000000..f741549e Binary files /dev/null and b/instruments/FM/drums/909 Hat.dmp differ diff --git a/instruments/FM/drums/Compressed Clap.fui b/instruments/FM/drums/Compressed Clap.fui new file mode 100644 index 00000000..febdab11 Binary files /dev/null and b/instruments/FM/drums/Compressed Clap.fui differ diff --git a/instruments/FM/drums/Cymbal.dmp b/instruments/FM/drums/Cymbal.dmp new file mode 100644 index 00000000..d479515e Binary files /dev/null and b/instruments/FM/drums/Cymbal.dmp differ diff --git a/instruments/FM/drums/Drum.fui b/instruments/FM/drums/Drum.fui new file mode 100644 index 00000000..fdc6b4fd Binary files /dev/null and b/instruments/FM/drums/Drum.fui differ diff --git a/instruments/FM/drums/GB Tom.fui b/instruments/FM/drums/GB Tom.fui new file mode 100644 index 00000000..41d0fe40 Binary files /dev/null and b/instruments/FM/drums/GB Tom.fui differ diff --git a/instruments/FM/drums/Heavy Kick.fui b/instruments/FM/drums/Heavy Kick.fui new file mode 100644 index 00000000..79a85e51 Binary files /dev/null and b/instruments/FM/drums/Heavy Kick.fui differ diff --git a/instruments/FM/drums/Kick_B0.dmp b/instruments/FM/drums/Kick_B0.dmp new file mode 100644 index 00000000..765d486a Binary files /dev/null and b/instruments/FM/drums/Kick_B0.dmp differ diff --git a/instruments/FM/drums/Less Chrunchy Snare.dmp b/instruments/FM/drums/Less Chrunchy Snare.dmp new file mode 100644 index 00000000..3d23eaee Binary files /dev/null and b/instruments/FM/drums/Less Chrunchy Snare.dmp differ diff --git a/instruments/FM/drums/Snare_B2.dmp b/instruments/FM/drums/Snare_B2.dmp new file mode 100644 index 00000000..7a659bf0 Binary files /dev/null and b/instruments/FM/drums/Snare_B2.dmp differ diff --git a/instruments/FM/drums/Squishy Snare.fui b/instruments/FM/drums/Squishy Snare.fui new file mode 100644 index 00000000..3ccef338 Binary files /dev/null and b/instruments/FM/drums/Squishy Snare.fui differ diff --git a/instruments/FM/drums/Trap Kick.dmp b/instruments/FM/drums/Trap Kick.dmp new file mode 100644 index 00000000..5ec7e633 Binary files /dev/null and b/instruments/FM/drums/Trap Kick.dmp differ diff --git a/instruments/FM/drums/Trap Snare.dmp b/instruments/FM/drums/Trap Snare.dmp new file mode 100644 index 00000000..32bedfd8 Binary files /dev/null and b/instruments/FM/drums/Trap Snare.dmp differ diff --git a/instruments/FM/drums/[2OPHigh]HatClosed_B2.dmp b/instruments/FM/drums/[2OPHigh]HatClosed_B2.dmp new file mode 100644 index 00000000..89d5af4d Binary files /dev/null and b/instruments/FM/drums/[2OPHigh]HatClosed_B2.dmp differ diff --git a/instruments/FM/drums/[2OPHigh]HatOpen_B2.dmp b/instruments/FM/drums/[2OPHigh]HatOpen_B2.dmp new file mode 100644 index 00000000..3977bb67 Binary files /dev/null and b/instruments/FM/drums/[2OPHigh]HatOpen_B2.dmp differ diff --git a/instruments/FM/drums/crash_00.fui b/instruments/FM/drums/crash_00.fui new file mode 100644 index 00000000..cacf151c Binary files /dev/null and b/instruments/FM/drums/crash_00.fui differ diff --git a/instruments/FM/drums/fmbigkick.fui b/instruments/FM/drums/fmbigkick.fui new file mode 100644 index 00000000..1166f2b0 Binary files /dev/null and b/instruments/FM/drums/fmbigkick.fui differ diff --git a/instruments/FM/drums/fmclap.fui b/instruments/FM/drums/fmclap.fui new file mode 100644 index 00000000..be582001 Binary files /dev/null and b/instruments/FM/drums/fmclap.fui differ diff --git a/instruments/FM/drums/fmclap2.fui b/instruments/FM/drums/fmclap2.fui new file mode 100644 index 00000000..5b399793 Binary files /dev/null and b/instruments/FM/drums/fmclap2.fui differ diff --git a/instruments/FM/drums/fmhat.fui b/instruments/FM/drums/fmhat.fui new file mode 100644 index 00000000..f26b7fe4 Binary files /dev/null and b/instruments/FM/drums/fmhat.fui differ diff --git a/instruments/FM/drums/fmopenhat.fui b/instruments/FM/drums/fmopenhat.fui new file mode 100644 index 00000000..a5198fc0 Binary files /dev/null and b/instruments/FM/drums/fmopenhat.fui differ diff --git a/instruments/FM/drums/hihat_00.fui b/instruments/FM/drums/hihat_00.fui new file mode 100644 index 00000000..90e56468 Binary files /dev/null and b/instruments/FM/drums/hihat_00.fui differ diff --git a/instruments/FM/drums/idk_something_percussion.fui b/instruments/FM/drums/idk_something_percussion.fui new file mode 100644 index 00000000..13380460 Binary files /dev/null and b/instruments/FM/drums/idk_something_percussion.fui differ diff --git a/instruments/FM/drums/kick_00_classic.fui b/instruments/FM/drums/kick_00_classic.fui new file mode 100644 index 00000000..e48795c8 Binary files /dev/null and b/instruments/FM/drums/kick_00_classic.fui differ diff --git a/instruments/FM/drums/kick_01_power.fui b/instruments/FM/drums/kick_01_power.fui new file mode 100644 index 00000000..d091d9e8 Binary files /dev/null and b/instruments/FM/drums/kick_01_power.fui differ diff --git a/instruments/FM/drums/kick_02_power2.fui b/instruments/FM/drums/kick_02_power2.fui new file mode 100644 index 00000000..2e348ba4 Binary files /dev/null and b/instruments/FM/drums/kick_02_power2.fui differ diff --git a/instruments/FM/drums/kick_03_muffled.fui b/instruments/FM/drums/kick_03_muffled.fui new file mode 100644 index 00000000..3e021953 Binary files /dev/null and b/instruments/FM/drums/kick_03_muffled.fui differ diff --git a/instruments/FM/drums/kick_04_bassdrum.fui b/instruments/FM/drums/kick_04_bassdrum.fui new file mode 100644 index 00000000..d86c2450 Binary files /dev/null and b/instruments/FM/drums/kick_04_bassdrum.fui differ diff --git a/instruments/FM/drums/kick_05_noisy.fui b/instruments/FM/drums/kick_05_noisy.fui new file mode 100644 index 00000000..5dbabbfd Binary files /dev/null and b/instruments/FM/drums/kick_05_noisy.fui differ diff --git a/instruments/FM/drums/kick_06_fat.fui b/instruments/FM/drums/kick_06_fat.fui new file mode 100644 index 00000000..151f0172 Binary files /dev/null and b/instruments/FM/drums/kick_06_fat.fui differ diff --git a/instruments/FM/drums/kick_07_fat2.fui b/instruments/FM/drums/kick_07_fat2.fui new file mode 100644 index 00000000..99dbe4df Binary files /dev/null and b/instruments/FM/drums/kick_07_fat2.fui differ diff --git a/instruments/FM/drums/powersnare.fui b/instruments/FM/drums/powersnare.fui new file mode 100644 index 00000000..d46eb7f2 Binary files /dev/null and b/instruments/FM/drums/powersnare.fui differ diff --git a/instruments/FM/drums/snare_00_simple.fui b/instruments/FM/drums/snare_00_simple.fui new file mode 100644 index 00000000..882e1091 Binary files /dev/null and b/instruments/FM/drums/snare_00_simple.fui differ diff --git a/instruments/FM/drums/snare_01_curvy.fui b/instruments/FM/drums/snare_01_curvy.fui new file mode 100644 index 00000000..e8211473 Binary files /dev/null and b/instruments/FM/drums/snare_01_curvy.fui differ diff --git a/instruments/FM/drums/snare_02_weak.fui b/instruments/FM/drums/snare_02_weak.fui new file mode 100644 index 00000000..b7e8487a Binary files /dev/null and b/instruments/FM/drums/snare_02_weak.fui differ diff --git a/instruments/FM/drums/snare_03_weak2.fui b/instruments/FM/drums/snare_03_weak2.fui new file mode 100644 index 00000000..2206f139 Binary files /dev/null and b/instruments/FM/drums/snare_03_weak2.fui differ diff --git a/instruments/FM/drums/snare_04_muffled.fui b/instruments/FM/drums/snare_04_muffled.fui new file mode 100644 index 00000000..a5fc768b Binary files /dev/null and b/instruments/FM/drums/snare_04_muffled.fui differ diff --git a/instruments/FM/drums/snare_05_intense.fui b/instruments/FM/drums/snare_05_intense.fui new file mode 100644 index 00000000..087f8d6c Binary files /dev/null and b/instruments/FM/drums/snare_05_intense.fui differ diff --git a/instruments/FM/drums/snareclap_G-2.fui b/instruments/FM/drums/snareclap_G-2.fui new file mode 100644 index 00000000..8455de32 Binary files /dev/null and b/instruments/FM/drums/snareclap_G-2.fui differ diff --git a/instruments/FM/drums/thinclosedhihat.dmp b/instruments/FM/drums/thinclosedhihat.dmp new file mode 100644 index 00000000..4c6f2240 Binary files /dev/null and b/instruments/FM/drums/thinclosedhihat.dmp differ diff --git a/instruments/FM/effect/(GEN) Buzzer.dmp b/instruments/FM/effect/(GEN) Buzzer.dmp new file mode 100644 index 00000000..bf3b95db Binary files /dev/null and b/instruments/FM/effect/(GEN) Buzzer.dmp differ diff --git a/instruments/FM/effect/(GEN) C64 Wave Combo.dmp b/instruments/FM/effect/(GEN) C64 Wave Combo.dmp new file mode 100644 index 00000000..57cb4fd4 Binary files /dev/null and b/instruments/FM/effect/(GEN) C64 Wave Combo.dmp differ diff --git a/instruments/FM/effect/(GEN) Clap.dmp b/instruments/FM/effect/(GEN) Clap.dmp new file mode 100644 index 00000000..ba8295fd Binary files /dev/null and b/instruments/FM/effect/(GEN) Clap.dmp differ diff --git a/instruments/FM/effect/(GEN) Clink.dmp b/instruments/FM/effect/(GEN) Clink.dmp new file mode 100644 index 00000000..3fd1f28b Binary files /dev/null and b/instruments/FM/effect/(GEN) Clink.dmp differ diff --git a/instruments/FM/effect/(GEN) Crank Turn.dmp b/instruments/FM/effect/(GEN) Crank Turn.dmp new file mode 100644 index 00000000..99a632c0 Binary files /dev/null and b/instruments/FM/effect/(GEN) Crank Turn.dmp differ diff --git a/instruments/FM/effect/(GEN) Decay Laser.dmp b/instruments/FM/effect/(GEN) Decay Laser.dmp new file mode 100644 index 00000000..7b5218ed Binary files /dev/null and b/instruments/FM/effect/(GEN) Decay Laser.dmp differ diff --git a/instruments/FM/effect/(GEN) Laser Gun.dmp b/instruments/FM/effect/(GEN) Laser Gun.dmp new file mode 100644 index 00000000..a3e5f860 Binary files /dev/null and b/instruments/FM/effect/(GEN) Laser Gun.dmp differ diff --git a/instruments/FM/effect/(GEN) Laser.dmp b/instruments/FM/effect/(GEN) Laser.dmp new file mode 100644 index 00000000..0198eaf4 Binary files /dev/null and b/instruments/FM/effect/(GEN) Laser.dmp differ diff --git a/instruments/FM/effect/(GEN) Lightning Bolt.dmp b/instruments/FM/effect/(GEN) Lightning Bolt.dmp new file mode 100644 index 00000000..969483bc Binary files /dev/null and b/instruments/FM/effect/(GEN) Lightning Bolt.dmp differ diff --git a/instruments/FM/effect/(GEN) TV Static.dmp b/instruments/FM/effect/(GEN) TV Static.dmp new file mode 100644 index 00000000..a00524f5 Binary files /dev/null and b/instruments/FM/effect/(GEN) TV Static.dmp differ diff --git a/instruments/FM/effect/(GEN) Techno TL Pitch Slider.dmp b/instruments/FM/effect/(GEN) Techno TL Pitch Slider.dmp new file mode 100644 index 00000000..c10cc7d9 Binary files /dev/null and b/instruments/FM/effect/(GEN) Techno TL Pitch Slider.dmp differ diff --git a/instruments/FM/effect/(GEN) Thunder Strike.dmp b/instruments/FM/effect/(GEN) Thunder Strike.dmp new file mode 100644 index 00000000..9d4a8dc7 Binary files /dev/null and b/instruments/FM/effect/(GEN) Thunder Strike.dmp differ diff --git a/instruments/FM/effect/(GEN) U.F.O.dmp b/instruments/FM/effect/(GEN) U.F.O.dmp new file mode 100644 index 00000000..a54e9736 Binary files /dev/null and b/instruments/FM/effect/(GEN) U.F.O.dmp differ diff --git a/instruments/FM/effect/Acoustic String Slap SFX.dmp b/instruments/FM/effect/Acoustic String Slap SFX.dmp new file mode 100644 index 00000000..a60d21e9 Binary files /dev/null and b/instruments/FM/effect/Acoustic String Slap SFX.dmp differ diff --git a/instruments/FM/effect/GEN_Wind.fui b/instruments/FM/effect/GEN_Wind.fui new file mode 100644 index 00000000..181d90d7 Binary files /dev/null and b/instruments/FM/effect/GEN_Wind.fui differ diff --git a/instruments/FM/effect/GEN_weird.fui b/instruments/FM/effect/GEN_weird.fui new file mode 100644 index 00000000..1e5bc1cd Binary files /dev/null and b/instruments/FM/effect/GEN_weird.fui differ diff --git a/instruments/FM/effect/Glide.dmp b/instruments/FM/effect/Glide.dmp new file mode 100644 index 00000000..0364388f Binary files /dev/null and b/instruments/FM/effect/Glide.dmp differ diff --git a/instruments/FM/effect/Heart.dmp b/instruments/FM/effect/Heart.dmp new file mode 100644 index 00000000..bb32d70c Binary files /dev/null and b/instruments/FM/effect/Heart.dmp differ diff --git a/instruments/FM/effect/Interference.dmp b/instruments/FM/effect/Interference.dmp new file mode 100644 index 00000000..9a436c77 Binary files /dev/null and b/instruments/FM/effect/Interference.dmp differ diff --git a/instruments/FM/effect/Ocean.dmp b/instruments/FM/effect/Ocean.dmp new file mode 100644 index 00000000..c9e6f339 Binary files /dev/null and b/instruments/FM/effect/Ocean.dmp differ diff --git a/instruments/FM/effect/Skid.dmp b/instruments/FM/effect/Skid.dmp new file mode 100644 index 00000000..a6d4a6f1 Binary files /dev/null and b/instruments/FM/effect/Skid.dmp differ diff --git a/instruments/FM/effect/Sore Throat.dmp b/instruments/FM/effect/Sore Throat.dmp new file mode 100644 index 00000000..b4975acd Binary files /dev/null and b/instruments/FM/effect/Sore Throat.dmp differ diff --git a/instruments/FM/effect/[2OPLow]Analog Horror 1.dmp b/instruments/FM/effect/[2OPLow]Analog Horror 1.dmp new file mode 100644 index 00000000..fdb79f2a Binary files /dev/null and b/instruments/FM/effect/[2OPLow]Analog Horror 1.dmp differ diff --git a/instruments/FM/effect/[2OPLow]Analog Horror 2.dmp b/instruments/FM/effect/[2OPLow]Analog Horror 2.dmp new file mode 100644 index 00000000..89418553 Binary files /dev/null and b/instruments/FM/effect/[2OPLow]Analog Horror 2.dmp differ diff --git a/instruments/FM/effect/fm_propeller.fui b/instruments/FM/effect/fm_propeller.fui new file mode 100644 index 00000000..f7e5dd97 Binary files /dev/null and b/instruments/FM/effect/fm_propeller.fui differ diff --git a/instruments/FM/effect/white nosie.dmp b/instruments/FM/effect/white nosie.dmp new file mode 100644 index 00000000..fd1eae3c Binary files /dev/null and b/instruments/FM/effect/white nosie.dmp differ diff --git a/instruments/FM/guitar/(CH3) Double Guitar.dmp b/instruments/FM/guitar/(CH3) Double Guitar.dmp new file mode 100644 index 00000000..ddb05eab Binary files /dev/null and b/instruments/FM/guitar/(CH3) Double Guitar.dmp differ diff --git a/instruments/FM/guitar/(GEN) Banjo.dmp b/instruments/FM/guitar/(GEN) Banjo.dmp new file mode 100644 index 00000000..b388e3de Binary files /dev/null and b/instruments/FM/guitar/(GEN) Banjo.dmp differ diff --git a/instruments/FM/guitar/(GEN) Electric Guitar 1.dmp b/instruments/FM/guitar/(GEN) Electric Guitar 1.dmp new file mode 100644 index 00000000..8c418100 Binary files /dev/null and b/instruments/FM/guitar/(GEN) Electric Guitar 1.dmp differ diff --git a/instruments/FM/guitar/(GEN) Electric Guitar 2.dmp b/instruments/FM/guitar/(GEN) Electric Guitar 2.dmp new file mode 100644 index 00000000..75bd6feb Binary files /dev/null and b/instruments/FM/guitar/(GEN) Electric Guitar 2.dmp differ diff --git a/instruments/FM/guitar/(GEN) Electric Guitar 3.dmp b/instruments/FM/guitar/(GEN) Electric Guitar 3.dmp new file mode 100644 index 00000000..2eea9cf0 Binary files /dev/null and b/instruments/FM/guitar/(GEN) Electric Guitar 3.dmp differ diff --git a/instruments/FM/guitar/(GEN) Electric Guitar EX.dmp b/instruments/FM/guitar/(GEN) Electric Guitar EX.dmp new file mode 100644 index 00000000..cb5d36aa Binary files /dev/null and b/instruments/FM/guitar/(GEN) Electric Guitar EX.dmp differ diff --git a/instruments/FM/guitar/(GEN) Grunge.dmp b/instruments/FM/guitar/(GEN) Grunge.dmp new file mode 100644 index 00000000..5a1764a1 Binary files /dev/null and b/instruments/FM/guitar/(GEN) Grunge.dmp differ diff --git a/instruments/FM/guitar/(GEN) Guitar.dmp b/instruments/FM/guitar/(GEN) Guitar.dmp new file mode 100644 index 00000000..3100a10e Binary files /dev/null and b/instruments/FM/guitar/(GEN) Guitar.dmp differ diff --git a/instruments/FM/guitar/(GEN) Rough Sitar.dmp b/instruments/FM/guitar/(GEN) Rough Sitar.dmp new file mode 100644 index 00000000..4ea936f2 Binary files /dev/null and b/instruments/FM/guitar/(GEN) Rough Sitar.dmp differ diff --git a/instruments/FM/guitar/(GEN) Shamisen.dmp b/instruments/FM/guitar/(GEN) Shamisen.dmp new file mode 100644 index 00000000..489bbb64 Binary files /dev/null and b/instruments/FM/guitar/(GEN) Shamisen.dmp differ diff --git a/instruments/FM/guitar/(GEN) Sitar.dmp b/instruments/FM/guitar/(GEN) Sitar.dmp new file mode 100644 index 00000000..1bbfb88e Binary files /dev/null and b/instruments/FM/guitar/(GEN) Sitar.dmp differ diff --git a/instruments/FM/guitar/(GEN) Space Sitar.dmp b/instruments/FM/guitar/(GEN) Space Sitar.dmp new file mode 100644 index 00000000..507fcc41 Binary files /dev/null and b/instruments/FM/guitar/(GEN) Space Sitar.dmp differ diff --git a/instruments/FM/guitar/Acoustic Guitar.dmp b/instruments/FM/guitar/Acoustic Guitar.dmp new file mode 100644 index 00000000..d31998da Binary files /dev/null and b/instruments/FM/guitar/Acoustic Guitar.dmp differ diff --git a/instruments/FM/guitar/Acoustic Nylon Guitar.dmp b/instruments/FM/guitar/Acoustic Nylon Guitar.dmp new file mode 100644 index 00000000..35fac263 Binary files /dev/null and b/instruments/FM/guitar/Acoustic Nylon Guitar.dmp differ diff --git a/instruments/FM/guitar/Acoustic Steel Guitar.dmp b/instruments/FM/guitar/Acoustic Steel Guitar.dmp new file mode 100644 index 00000000..295a29d6 Binary files /dev/null and b/instruments/FM/guitar/Acoustic Steel Guitar.dmp differ diff --git a/instruments/FM/guitar/Banjo (Muted).opni b/instruments/FM/guitar/Banjo (Muted).opni new file mode 100644 index 00000000..1bf6e88f Binary files /dev/null and b/instruments/FM/guitar/Banjo (Muted).opni differ diff --git a/instruments/FM/guitar/Banjo.opni b/instruments/FM/guitar/Banjo.opni new file mode 100644 index 00000000..bfe78989 Binary files /dev/null and b/instruments/FM/guitar/Banjo.opni differ diff --git a/instruments/FM/guitar/Distorted Guitar 1.dmp b/instruments/FM/guitar/Distorted Guitar 1.dmp new file mode 100644 index 00000000..319c4cf0 Binary files /dev/null and b/instruments/FM/guitar/Distorted Guitar 1.dmp differ diff --git a/instruments/FM/guitar/Distorted Guitar 2.dmp b/instruments/FM/guitar/Distorted Guitar 2.dmp new file mode 100644 index 00000000..67f361cf Binary files /dev/null and b/instruments/FM/guitar/Distorted Guitar 2.dmp differ diff --git a/instruments/FM/guitar/Electric Clean Guitar.dmp b/instruments/FM/guitar/Electric Clean Guitar.dmp new file mode 100644 index 00000000..08c7ec70 Binary files /dev/null and b/instruments/FM/guitar/Electric Clean Guitar.dmp differ diff --git a/instruments/FM/guitar/Electric Distorted Guitar (High).dmp b/instruments/FM/guitar/Electric Distorted Guitar (High).dmp new file mode 100644 index 00000000..aa328852 Binary files /dev/null and b/instruments/FM/guitar/Electric Distorted Guitar (High).dmp differ diff --git a/instruments/FM/guitar/Electric Distorted Guitar (Low).dmp b/instruments/FM/guitar/Electric Distorted Guitar (Low).dmp new file mode 100644 index 00000000..12715c4d Binary files /dev/null and b/instruments/FM/guitar/Electric Distorted Guitar (Low).dmp differ diff --git a/instruments/FM/guitar/Electric Funk Guitar.dmp b/instruments/FM/guitar/Electric Funk Guitar.dmp new file mode 100644 index 00000000..64800f37 Binary files /dev/null and b/instruments/FM/guitar/Electric Funk Guitar.dmp differ diff --git a/instruments/FM/guitar/Electric Guitar Harmonics.dmp b/instruments/FM/guitar/Electric Guitar Harmonics.dmp new file mode 100644 index 00000000..14a14b11 Binary files /dev/null and b/instruments/FM/guitar/Electric Guitar Harmonics.dmp differ diff --git a/instruments/FM/guitar/Electric Jazz Guitar.dmp b/instruments/FM/guitar/Electric Jazz Guitar.dmp new file mode 100644 index 00000000..0660deff Binary files /dev/null and b/instruments/FM/guitar/Electric Jazz Guitar.dmp differ diff --git a/instruments/FM/guitar/Electric Lap Guitar.dmp b/instruments/FM/guitar/Electric Lap Guitar.dmp new file mode 100644 index 00000000..97fcdfe2 Binary files /dev/null and b/instruments/FM/guitar/Electric Lap Guitar.dmp differ diff --git a/instruments/FM/guitar/Electric Muted Guitar.dmp b/instruments/FM/guitar/Electric Muted Guitar.dmp new file mode 100644 index 00000000..97e30e94 Binary files /dev/null and b/instruments/FM/guitar/Electric Muted Guitar.dmp differ diff --git a/instruments/FM/guitar/Electric Overdriven Guitar (High).dmp b/instruments/FM/guitar/Electric Overdriven Guitar (High).dmp new file mode 100644 index 00000000..a6fdfa68 Binary files /dev/null and b/instruments/FM/guitar/Electric Overdriven Guitar (High).dmp differ diff --git a/instruments/FM/guitar/Electric Overdriven Guitar (Low).dmp b/instruments/FM/guitar/Electric Overdriven Guitar (Low).dmp new file mode 100644 index 00000000..8d276a1a Binary files /dev/null and b/instruments/FM/guitar/Electric Overdriven Guitar (Low).dmp differ diff --git a/instruments/FM/guitar/Funk Guitar.fui b/instruments/FM/guitar/Funk Guitar.fui new file mode 100644 index 00000000..15db0445 Binary files /dev/null and b/instruments/FM/guitar/Funk Guitar.fui differ diff --git a/instruments/FM/guitar/Gardenshroom.dmp b/instruments/FM/guitar/Gardenshroom.dmp new file mode 100644 index 00000000..2fbf5431 Binary files /dev/null and b/instruments/FM/guitar/Gardenshroom.dmp differ diff --git a/instruments/FM/guitar/Guitar Test.opm b/instruments/FM/guitar/Guitar Test.opm new file mode 100644 index 00000000..d1d27a04 --- /dev/null +++ b/instruments/FM/guitar/Guitar Test.opm @@ -0,0 +1,1030 @@ +//MiOPMdrv sound bank Paramer Ver2002.04.22 +//LFO: LFRQ AMD PMD WF NFRQ +//@:[Num] [Name] +//CH: PAN FL CON AMS PMS SLOT NE +//[OPname]: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN + +@:0 Nylon Guitar +LFO: 0 0 0 0 0 +CH: 64 0 2 0 0 120 0 +M1: 25 28 0 15 15 50 0 8 3 0 0 +C1: 31 0 0 4 15 40 0 5 3 0 0 +M2: 31 4 0 1 9 38 0 2 3 0 0 +C2: 31 10 0 9 15 0 0 1 3 0 0 + +@:1 Ukulele +LFO: 0 0 0 0 0 +CH: 64 0 2 0 0 120 0 +M1: 20 24 0 15 15 42 0 4 3 0 0 +C1: 31 11 0 4 15 40 0 5 3 0 0 +M2: 31 4 0 1 11 50 0 2 3 0 0 +C2: 31 11 10 10 4 0 0 1 3 0 0 + +@:2 Steel Guitar +LFO: 0 0 0 0 0 +CH: 64 0 2 0 0 120 0 +M1: 25 28 0 15 15 39 0 8 3 0 0 +C1: 31 4 0 4 15 24 0 3 3 0 0 +M2: 31 5 0 1 9 40 0 1 3 0 0 +C2: 31 10 0 9 15 0 0 1 3 0 0 + +@:3 Jazz Guitar +LFO: 0 0 0 0 0 +CH: 64 7 2 0 0 120 0 +M1: 23 21 0 15 15 36 0 1 3 0 0 +C1: 31 0 0 4 15 45 0 4 2 0 0 +M2: 31 0 0 1 15 43 0 1 3 0 0 +C2: 31 10 0 9 15 0 0 1 3 0 0 + +@:4 Lap Guitar +LFO: 0 0 0 0 0 +CH: 64 7 2 0 0 120 0 +M1: 23 21 0 15 15 42 0 1 3 0 0 +C1: 31 0 0 4 15 37 0 2 2 0 0 +M2: 31 0 0 1 15 45 0 1 5 0 0 +C2: 31 8 0 9 15 0 0 1 3 0 0 + +@:5 Clean Guitar +LFO: 0 0 0 0 0 +CH: 64 7 0 0 0 120 0 +M1: 31 13 0 9 15 46 0 9 2 0 0 +C1: 31 8 0 4 15 15 0 1 2 0 0 +M2: 31 4 0 1 11 30 0 1 3 0 0 +C2: 31 9 0 11 15 0 0 1 3 0 0 + +@:6 Muted Guitar +LFO: 0 0 0 0 0 +CH: 64 4 2 0 0 120 0 +M1: 28 21 0 15 15 32 0 4 5 0 0 +C1: 31 3 0 4 15 50 0 1 0 0 0 +M2: 31 2 0 1 11 33 0 1 5 0 0 +C2: 31 10 0 9 15 0 0 1 0 0 0 + +@:7 Funk Guitar +LFO: 0 0 0 0 0 +CH: 64 7 0 0 0 120 0 +M1: 23 13 0 15 15 56 0 9 3 0 0 +C1: 31 8 0 4 15 20 0 1 2 0 0 +M2: 31 4 0 1 11 30 0 1 3 0 0 +C2: 31 9 0 11 15 0 0 1 3 0 0 + +@:8 Overdrive(Low) +LFO: 0 0 0 0 0 +CH: 64 6 2 0 0 120 0 +M1: 31 23 0 15 15 9 0 3 3 0 0 +C1: 31 0 0 4 15 18 0 1 4 0 0 +M2: 31 0 0 1 11 14 0 1 1 0 0 +C2: 31 7 0 12 15 0 0 1 3 0 0 + +@:9 Distorted(Low) +LFO: 0 0 0 0 0 +CH: 64 6 2 0 0 120 0 +M1: 31 23 0 15 15 9 0 4 3 0 0 +C1: 31 0 0 4 15 19 0 1 4 0 0 +M2: 31 6 0 1 11 6 0 1 1 0 0 +C2: 31 7 0 12 15 0 0 1 3 0 0 + +@:10 Overdrive(High) +LFO: 0 0 0 0 0 +CH: 64 7 2 0 0 120 0 +M1: 31 23 0 15 15 16 0 3 3 0 0 +C1: 31 0 0 4 15 37 0 2 3 0 0 +M2: 31 0 0 1 11 22 0 1 1 0 0 +C2: 31 7 0 11 15 0 0 3 3 0 0 + +@:11 Distorted(High) +LFO: 0 0 0 0 0 +CH: 64 7 2 0 0 120 0 +M1: 31 23 0 15 15 9 0 3 3 0 0 +C1: 31 0 0 4 15 27 0 1 3 0 0 +M2: 31 0 0 1 11 20 0 1 1 0 0 +C2: 31 7 0 11 15 0 0 3 3 0 0 + +@:12 GtrHarmonics +LFO: 0 0 0 0 0 +CH: 64 7 2 0 0 120 0 +M1: 31 23 0 15 15 16 0 2 3 0 0 +C1: 31 0 0 4 15 33 0 8 4 0 0 +M2: 31 0 0 1 11 22 0 4 4 0 0 +C2: 31 6 0 11 15 0 0 2 3 0 0 + +@:13 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:14 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:15 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:16 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:17 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:18 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:19 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:20 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:21 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:22 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:23 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:24 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:25 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:26 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:27 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:28 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:29 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:30 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:31 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:32 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:33 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:34 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:35 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:36 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:37 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:38 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:39 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:40 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:41 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:42 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:43 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:44 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:45 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:46 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:47 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:48 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:49 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:50 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:51 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:52 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:53 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:54 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:55 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:56 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:57 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:58 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:59 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:60 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:61 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:62 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:63 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:64 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:65 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:66 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:67 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:68 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:69 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:70 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:71 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:72 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:73 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:74 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:75 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:76 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:77 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:78 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:79 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:80 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:81 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:82 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:83 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:84 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:85 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:86 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:87 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:88 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:89 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:90 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:91 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:92 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:93 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:94 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:95 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:96 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:97 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:98 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:99 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:100 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:101 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:102 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:103 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:104 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:105 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:106 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:107 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:108 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:109 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:110 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:111 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:112 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:113 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:114 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:115 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:116 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:117 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:118 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:119 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:120 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:121 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:122 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:123 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:124 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:125 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:126 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:127 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + diff --git a/instruments/FM/guitar/Hard Guitar.fui b/instruments/FM/guitar/Hard Guitar.fui new file mode 100644 index 00000000..5d7c60ef Binary files /dev/null and b/instruments/FM/guitar/Hard Guitar.fui differ diff --git a/instruments/FM/guitar/Koto.opni b/instruments/FM/guitar/Koto.opni new file mode 100644 index 00000000..9bac79ba Binary files /dev/null and b/instruments/FM/guitar/Koto.opni differ diff --git a/instruments/FM/guitar/OPNx_ElectricGuitar.fui b/instruments/FM/guitar/OPNx_ElectricGuitar.fui new file mode 100644 index 00000000..8916da67 Binary files /dev/null and b/instruments/FM/guitar/OPNx_ElectricGuitar.fui differ diff --git a/instruments/FM/guitar/Oud.opni b/instruments/FM/guitar/Oud.opni new file mode 100644 index 00000000..029a0c2a Binary files /dev/null and b/instruments/FM/guitar/Oud.opni differ diff --git a/instruments/FM/guitar/SSGuitar_A2.dmp b/instruments/FM/guitar/SSGuitar_A2.dmp new file mode 100644 index 00000000..a0b01b7e Binary files /dev/null and b/instruments/FM/guitar/SSGuitar_A2.dmp differ diff --git a/instruments/FM/guitar/SSGuitar_B2.dmp b/instruments/FM/guitar/SSGuitar_B2.dmp new file mode 100644 index 00000000..5f541f31 Binary files /dev/null and b/instruments/FM/guitar/SSGuitar_B2.dmp differ diff --git a/instruments/FM/guitar/SSGuitar_F#2.dmp b/instruments/FM/guitar/SSGuitar_F#2.dmp new file mode 100644 index 00000000..87cca094 Binary files /dev/null and b/instruments/FM/guitar/SSGuitar_F#2.dmp differ diff --git a/instruments/FM/guitar/SSGuitar_G2.dmp b/instruments/FM/guitar/SSGuitar_G2.dmp new file mode 100644 index 00000000..8303444f Binary files /dev/null and b/instruments/FM/guitar/SSGuitar_G2.dmp differ diff --git a/instruments/FM/guitar/Shamisen (Regular Pluck).opni b/instruments/FM/guitar/Shamisen (Regular Pluck).opni new file mode 100644 index 00000000..c25acca1 Binary files /dev/null and b/instruments/FM/guitar/Shamisen (Regular Pluck).opni differ diff --git a/instruments/FM/guitar/Shamisen (Tsugaru Slap).opni b/instruments/FM/guitar/Shamisen (Tsugaru Slap).opni new file mode 100644 index 00000000..ed9c9497 Binary files /dev/null and b/instruments/FM/guitar/Shamisen (Tsugaru Slap).opni differ diff --git a/instruments/FM/guitar/Sitar.opni b/instruments/FM/guitar/Sitar.opni new file mode 100644 index 00000000..3427fe2a Binary files /dev/null and b/instruments/FM/guitar/Sitar.opni differ diff --git a/instruments/FM/guitar/Spanish Guitar.dmp b/instruments/FM/guitar/Spanish Guitar.dmp new file mode 100644 index 00000000..ca731849 Binary files /dev/null and b/instruments/FM/guitar/Spanish Guitar.dmp differ diff --git a/instruments/FM/guitar/Tamboura (Bass Sitar).opni b/instruments/FM/guitar/Tamboura (Bass Sitar).opni new file mode 100644 index 00000000..e6514b73 Binary files /dev/null and b/instruments/FM/guitar/Tamboura (Bass Sitar).opni differ diff --git a/instruments/FM/guitar/Ukulele.dmp b/instruments/FM/guitar/Ukulele.dmp new file mode 100644 index 00000000..a23c5114 Binary files /dev/null and b/instruments/FM/guitar/Ukulele.dmp differ diff --git a/instruments/FM/guitar/YM2151_DistGuitar.fui b/instruments/FM/guitar/YM2151_DistGuitar.fui new file mode 100644 index 00000000..983328c6 Binary files /dev/null and b/instruments/FM/guitar/YM2151_DistGuitar.fui differ diff --git a/instruments/FM/guitar/distguitar_00.fui b/instruments/FM/guitar/distguitar_00.fui new file mode 100644 index 00000000..ac54e77c Binary files /dev/null and b/instruments/FM/guitar/distguitar_00.fui differ diff --git a/instruments/FM/guitar/high guitar.dmp b/instruments/FM/guitar/high guitar.dmp new file mode 100644 index 00000000..04095f16 Binary files /dev/null and b/instruments/FM/guitar/high guitar.dmp differ diff --git a/instruments/FM/guitar/powerchord.fui b/instruments/FM/guitar/powerchord.fui new file mode 100644 index 00000000..40e8ba34 Binary files /dev/null and b/instruments/FM/guitar/powerchord.fui differ diff --git a/instruments/FM/guitar/sine distorted guitar.dmp b/instruments/FM/guitar/sine distorted guitar.dmp new file mode 100644 index 00000000..b8ad3dbf Binary files /dev/null and b/instruments/FM/guitar/sine distorted guitar.dmp differ diff --git a/instruments/FM/guitar/trashy guitar.dmp b/instruments/FM/guitar/trashy guitar.dmp new file mode 100644 index 00000000..8677f9a0 Binary files /dev/null and b/instruments/FM/guitar/trashy guitar.dmp differ diff --git a/instruments/FM/horn/(CH3) Trumpet + Echo.dmp b/instruments/FM/horn/(CH3) Trumpet + Echo.dmp new file mode 100644 index 00000000..b7cb7e75 Binary files /dev/null and b/instruments/FM/horn/(CH3) Trumpet + Echo.dmp differ diff --git a/instruments/FM/horn/(GEN) Dubious Trumpet.dmp b/instruments/FM/horn/(GEN) Dubious Trumpet.dmp new file mode 100644 index 00000000..bed9dee0 Binary files /dev/null and b/instruments/FM/horn/(GEN) Dubious Trumpet.dmp differ diff --git a/instruments/FM/horn/(GEN) Ringing Trumpet.dmp b/instruments/FM/horn/(GEN) Ringing Trumpet.dmp new file mode 100644 index 00000000..0cf615ef Binary files /dev/null and b/instruments/FM/horn/(GEN) Ringing Trumpet.dmp differ diff --git a/instruments/FM/horn/(GEN) Trumpet 1.dmp b/instruments/FM/horn/(GEN) Trumpet 1.dmp new file mode 100644 index 00000000..bea22138 Binary files /dev/null and b/instruments/FM/horn/(GEN) Trumpet 1.dmp differ diff --git a/instruments/FM/horn/(GEN) Trumpet 2.dmp b/instruments/FM/horn/(GEN) Trumpet 2.dmp new file mode 100644 index 00000000..e333c327 Binary files /dev/null and b/instruments/FM/horn/(GEN) Trumpet 2.dmp differ diff --git a/instruments/FM/horn/(GEN) Trumpet 3.dmp b/instruments/FM/horn/(GEN) Trumpet 3.dmp new file mode 100644 index 00000000..a7771344 Binary files /dev/null and b/instruments/FM/horn/(GEN) Trumpet 3.dmp differ diff --git a/instruments/FM/horn/(GEN) Trumpet EX.dmp b/instruments/FM/horn/(GEN) Trumpet EX.dmp new file mode 100644 index 00000000..238f701a Binary files /dev/null and b/instruments/FM/horn/(GEN) Trumpet EX.dmp differ diff --git a/instruments/FM/horn/(GEN) VRC7 Trumpet.dmp b/instruments/FM/horn/(GEN) VRC7 Trumpet.dmp new file mode 100644 index 00000000..931482df Binary files /dev/null and b/instruments/FM/horn/(GEN) VRC7 Trumpet.dmp differ diff --git a/instruments/FM/horn/FM Brass.dmp b/instruments/FM/horn/FM Brass.dmp new file mode 100644 index 00000000..852bcfe1 Binary files /dev/null and b/instruments/FM/horn/FM Brass.dmp differ diff --git a/instruments/FM/horn/GEN_Tuba.fui b/instruments/FM/horn/GEN_Tuba.fui new file mode 100644 index 00000000..b7885277 Binary files /dev/null and b/instruments/FM/horn/GEN_Tuba.fui differ diff --git a/instruments/FM/horn/OPNx_SynthBrass.fui b/instruments/FM/horn/OPNx_SynthBrass.fui new file mode 100644 index 00000000..90066013 Binary files /dev/null and b/instruments/FM/horn/OPNx_SynthBrass.fui differ diff --git a/instruments/FM/horn/Saw Trumpet.dmp b/instruments/FM/horn/Saw Trumpet.dmp new file mode 100644 index 00000000..e8f3e43f Binary files /dev/null and b/instruments/FM/horn/Saw Trumpet.dmp differ diff --git a/instruments/FM/horn/Trumpet.dmp b/instruments/FM/horn/Trumpet.dmp new file mode 100644 index 00000000..5abd62dd Binary files /dev/null and b/instruments/FM/horn/Trumpet.dmp differ diff --git a/instruments/FM/horn/ass overtone brass.dmp b/instruments/FM/horn/ass overtone brass.dmp new file mode 100644 index 00000000..f6aca14b Binary files /dev/null and b/instruments/FM/horn/ass overtone brass.dmp differ diff --git a/instruments/FM/horn/brass_00_wobbly.fui b/instruments/FM/horn/brass_00_wobbly.fui new file mode 100644 index 00000000..3631dcd2 Binary files /dev/null and b/instruments/FM/horn/brass_00_wobbly.fui differ diff --git a/instruments/FM/horn/brass_01_bassish.fui b/instruments/FM/horn/brass_01_bassish.fui new file mode 100644 index 00000000..9a967d18 Binary files /dev/null and b/instruments/FM/horn/brass_01_bassish.fui differ diff --git a/instruments/FM/horn/brass_02_boring.fui b/instruments/FM/horn/brass_02_boring.fui new file mode 100644 index 00000000..3d9dcc31 Binary files /dev/null and b/instruments/FM/horn/brass_02_boring.fui differ diff --git a/instruments/FM/horn/brass_03_boring2.fui b/instruments/FM/horn/brass_03_boring2.fui new file mode 100644 index 00000000..14159e71 Binary files /dev/null and b/instruments/FM/horn/brass_03_boring2.fui differ diff --git a/instruments/FM/keys/(CH3) Double Piano.dmp b/instruments/FM/keys/(CH3) Double Piano.dmp new file mode 100644 index 00000000..8706f963 Binary files /dev/null and b/instruments/FM/keys/(CH3) Double Piano.dmp differ diff --git a/instruments/FM/keys/(CH3) Double Reed Organ.dmp b/instruments/FM/keys/(CH3) Double Reed Organ.dmp new file mode 100644 index 00000000..bf5bd184 Binary files /dev/null and b/instruments/FM/keys/(CH3) Double Reed Organ.dmp differ diff --git a/instruments/FM/keys/(GEN) Church Organ.dmp b/instruments/FM/keys/(GEN) Church Organ.dmp new file mode 100644 index 00000000..f923ab38 Binary files /dev/null and b/instruments/FM/keys/(GEN) Church Organ.dmp differ diff --git a/instruments/FM/keys/(GEN) Piano 1.dmp b/instruments/FM/keys/(GEN) Piano 1.dmp new file mode 100644 index 00000000..8354b9f9 Binary files /dev/null and b/instruments/FM/keys/(GEN) Piano 1.dmp differ diff --git a/instruments/FM/keys/(GEN) Piano 2.dmp b/instruments/FM/keys/(GEN) Piano 2.dmp new file mode 100644 index 00000000..dffd316e Binary files /dev/null and b/instruments/FM/keys/(GEN) Piano 2.dmp differ diff --git a/instruments/FM/keys/(GEN) Reed Organ.dmp b/instruments/FM/keys/(GEN) Reed Organ.dmp new file mode 100644 index 00000000..020074b1 Binary files /dev/null and b/instruments/FM/keys/(GEN) Reed Organ.dmp differ diff --git a/instruments/FM/keys/(GEN) Short Piano.dmp b/instruments/FM/keys/(GEN) Short Piano.dmp new file mode 100644 index 00000000..1d5cb18f Binary files /dev/null and b/instruments/FM/keys/(GEN) Short Piano.dmp differ diff --git a/instruments/FM/keys/(GEN) Space Piano.dmp b/instruments/FM/keys/(GEN) Space Piano.dmp new file mode 100644 index 00000000..543b6938 Binary files /dev/null and b/instruments/FM/keys/(GEN) Space Piano.dmp differ diff --git a/instruments/FM/keys/(GEN) Spoopy Organ.dmp b/instruments/FM/keys/(GEN) Spoopy Organ.dmp new file mode 100644 index 00000000..4217049a Binary files /dev/null and b/instruments/FM/keys/(GEN) Spoopy Organ.dmp differ diff --git a/instruments/FM/keys/(GEN) VRC7 Reed Organ.dmp b/instruments/FM/keys/(GEN) VRC7 Reed Organ.dmp new file mode 100644 index 00000000..79cc2836 Binary files /dev/null and b/instruments/FM/keys/(GEN) VRC7 Reed Organ.dmp differ diff --git a/instruments/FM/keys/80's Organ.dmp b/instruments/FM/keys/80's Organ.dmp new file mode 100644 index 00000000..647a5254 Binary files /dev/null and b/instruments/FM/keys/80's Organ.dmp differ diff --git a/instruments/FM/keys/Bite Organ.dmp b/instruments/FM/keys/Bite Organ.dmp new file mode 100644 index 00000000..c2447801 Binary files /dev/null and b/instruments/FM/keys/Bite Organ.dmp differ diff --git a/instruments/FM/keys/Celesta (High).dmp b/instruments/FM/keys/Celesta (High).dmp new file mode 100644 index 00000000..37ff712d Binary files /dev/null and b/instruments/FM/keys/Celesta (High).dmp differ diff --git a/instruments/FM/keys/Celesta (Low).dmp b/instruments/FM/keys/Celesta (Low).dmp new file mode 100644 index 00000000..8e348b28 Binary files /dev/null and b/instruments/FM/keys/Celesta (Low).dmp differ diff --git a/instruments/FM/keys/Church Organ.dmp b/instruments/FM/keys/Church Organ.dmp new file mode 100644 index 00000000..958f04ed Binary files /dev/null and b/instruments/FM/keys/Church Organ.dmp differ diff --git a/instruments/FM/keys/Circus Organ.fui b/instruments/FM/keys/Circus Organ.fui new file mode 100644 index 00000000..fa4fa718 Binary files /dev/null and b/instruments/FM/keys/Circus Organ.fui differ diff --git a/instruments/FM/keys/Clavinet.dmp b/instruments/FM/keys/Clavinet.dmp new file mode 100644 index 00000000..038d3bf4 Binary files /dev/null and b/instruments/FM/keys/Clavinet.dmp differ diff --git a/instruments/FM/keys/Creep Organ.dmp b/instruments/FM/keys/Creep Organ.dmp new file mode 100644 index 00000000..048d2ae9 Binary files /dev/null and b/instruments/FM/keys/Creep Organ.dmp differ diff --git a/instruments/FM/keys/E. Piano 1 (Rhodes).dmp b/instruments/FM/keys/E. Piano 1 (Rhodes).dmp new file mode 100644 index 00000000..1047d95a Binary files /dev/null and b/instruments/FM/keys/E. Piano 1 (Rhodes).dmp differ diff --git a/instruments/FM/keys/E. Piano 2 (DX).dmp b/instruments/FM/keys/E. Piano 2 (DX).dmp new file mode 100644 index 00000000..cf7a206d Binary files /dev/null and b/instruments/FM/keys/E. Piano 2 (DX).dmp differ diff --git a/instruments/FM/keys/ElisOrgan.fui b/instruments/FM/keys/ElisOrgan.fui new file mode 100644 index 00000000..82bc77aa Binary files /dev/null and b/instruments/FM/keys/ElisOrgan.fui differ diff --git a/instruments/FM/keys/Harpsichord.dmp b/instruments/FM/keys/Harpsichord.dmp new file mode 100644 index 00000000..ce0d9822 Binary files /dev/null and b/instruments/FM/keys/Harpsichord.dmp differ diff --git a/instruments/FM/keys/Less Soft E Piano.dmp b/instruments/FM/keys/Less Soft E Piano.dmp new file mode 100644 index 00000000..e55ec116 Binary files /dev/null and b/instruments/FM/keys/Less Soft E Piano.dmp differ diff --git a/instruments/FM/keys/Low Piano.dmp b/instruments/FM/keys/Low Piano.dmp new file mode 100644 index 00000000..60d7f571 Binary files /dev/null and b/instruments/FM/keys/Low Piano.dmp differ diff --git a/instruments/FM/keys/OPNx_Piano.fui b/instruments/FM/keys/OPNx_Piano.fui new file mode 100644 index 00000000..1db80df8 Binary files /dev/null and b/instruments/FM/keys/OPNx_Piano.fui differ diff --git a/instruments/FM/keys/Organ 1 (Drawbar).dmp b/instruments/FM/keys/Organ 1 (Drawbar).dmp new file mode 100644 index 00000000..062870a7 Binary files /dev/null and b/instruments/FM/keys/Organ 1 (Drawbar).dmp differ diff --git a/instruments/FM/keys/Organ 2 (Percussive).dmp b/instruments/FM/keys/Organ 2 (Percussive).dmp new file mode 100644 index 00000000..34bc54c5 Binary files /dev/null and b/instruments/FM/keys/Organ 2 (Percussive).dmp differ diff --git a/instruments/FM/keys/Organ 3 (Rock Organ).dmp b/instruments/FM/keys/Organ 3 (Rock Organ).dmp new file mode 100644 index 00000000..10e1bfdb Binary files /dev/null and b/instruments/FM/keys/Organ 3 (Rock Organ).dmp differ diff --git a/instruments/FM/keys/Organ 3 Alt (Rock Organ Alt).dmp b/instruments/FM/keys/Organ 3 Alt (Rock Organ Alt).dmp new file mode 100644 index 00000000..e5e472e4 Binary files /dev/null and b/instruments/FM/keys/Organ 3 Alt (Rock Organ Alt).dmp differ diff --git a/instruments/FM/keys/Organ 5 (Percussive Variation).dmp b/instruments/FM/keys/Organ 5 (Percussive Variation).dmp new file mode 100644 index 00000000..40a7b335 Binary files /dev/null and b/instruments/FM/keys/Organ 5 (Percussive Variation).dmp differ diff --git a/instruments/FM/keys/Organ.dmp b/instruments/FM/keys/Organ.dmp new file mode 100644 index 00000000..1fba6f19 Binary files /dev/null and b/instruments/FM/keys/Organ.dmp differ diff --git a/instruments/FM/keys/Piano 1 (Acoustic).dmp b/instruments/FM/keys/Piano 1 (Acoustic).dmp new file mode 100644 index 00000000..32fa8c64 Binary files /dev/null and b/instruments/FM/keys/Piano 1 (Acoustic).dmp differ diff --git a/instruments/FM/keys/Piano 2 (Bright Acoustic).dmp b/instruments/FM/keys/Piano 2 (Bright Acoustic).dmp new file mode 100644 index 00000000..887c9abc Binary files /dev/null and b/instruments/FM/keys/Piano 2 (Bright Acoustic).dmp differ diff --git a/instruments/FM/keys/Piano 3 (Electric Grand).dmp b/instruments/FM/keys/Piano 3 (Electric Grand).dmp new file mode 100644 index 00000000..c5041b1f Binary files /dev/null and b/instruments/FM/keys/Piano 3 (Electric Grand).dmp differ diff --git a/instruments/FM/keys/Piano Test.opm b/instruments/FM/keys/Piano Test.opm new file mode 100644 index 00000000..ad372590 --- /dev/null +++ b/instruments/FM/keys/Piano Test.opm @@ -0,0 +1,1030 @@ +//MiOPMdrv sound bank Paramer Ver2002.04.22 +//LFO: LFRQ AMD PMD WF NFRQ +//@:[Num] [Name] +//CH: PAN FL CON AMS PMS SLOT NE +//[OPname]: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN + +@:0 Piano1 (Acou.) +LFO: 0 0 0 0 0 +CH: 64 7 2 0 0 120 0 +M1: 31 24 4 4 2 41 0 3 4 0 0 +C1: 31 0 0 4 15 41 0 1 3 0 0 +M2: 31 7 0 1 11 43 0 2 3 0 0 +C2: 29 9 0 9 15 0 0 1 3 0 0 + +@:1 Piano2 (Brite) +LFO: 0 0 0 0 0 +CH: 64 7 2 0 0 120 0 +M1: 31 24 4 4 3 31 0 1 4 0 0 +C1: 31 0 0 4 15 39 0 3 3 0 0 +M2: 31 4 0 1 11 40 0 2 3 0 0 +C2: 29 9 0 9 15 0 0 1 3 0 0 + +@:2 Piano3 (EGrand) +LFO: 0 0 0 0 0 +CH: 64 7 2 0 0 120 0 +M1: 27 24 4 4 3 29 0 1 4 0 0 +C1: 31 12 0 4 15 35 0 1 3 0 0 +M2: 31 18 8 1 2 24 0 2 3 0 0 +C2: 29 9 0 9 15 0 0 1 3 0 0 + +@:3 EPiano1 (Rhodes) +LFO: 0 0 0 0 0 +CH: 64 0 4 0 0 120 0 +M1: 31 21 0 3 3 57 0 14 5 0 0 +C1: 31 9 0 10 15 23 0 1 0 0 0 +M2: 31 1 0 1 5 53 0 1 5 0 0 +C2: 31 8 0 9 15 2 0 1 0 0 0 + +@:4 EPiano2 (DX) +LFO: 0 0 0 0 0 +CH: 64 7 4 0 0 120 0 +M1: 31 0 0 3 15 38 0 11 3 0 0 +C1: 31 15 0 8 15 30 0 12 3 0 0 +M2: 31 6 0 1 11 33 0 1 3 0 0 +C2: 31 8 0 9 15 0 0 1 3 0 0 + +@:5 Harpsichord +LFO: 0 0 0 0 0 +CH: 64 7 0 0 0 120 0 +M1: 31 4 0 3 15 42 0 2 3 0 0 +C1: 31 28 6 4 1 21 0 5 3 0 0 +M2: 31 5 0 1 1 31 0 9 3 0 0 +C2: 31 9 0 9 15 0 0 1 3 0 0 + +@:6 Clavinet +LFO: 0 0 0 0 0 +CH: 64 7 0 0 0 120 0 +M1: 31 23 8 3 4 32 0 1 3 0 0 +C1: 31 0 0 4 15 22 0 1 3 0 0 +M2: 31 0 0 1 11 19 0 2 3 0 0 +C2: 31 9 0 10 15 0 0 1 3 0 0 + +@:7 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:8 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:9 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:10 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:11 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:12 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:13 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:14 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:15 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:16 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:17 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:18 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:19 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:20 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:21 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:22 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:23 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:24 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:25 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:26 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:27 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:28 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:29 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:30 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:31 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:32 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:33 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:34 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:35 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:36 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:37 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:38 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:39 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:40 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:41 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:42 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:43 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:44 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:45 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:46 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:47 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:48 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:49 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:50 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:51 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:52 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:53 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:54 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:55 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:56 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:57 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:58 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:59 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:60 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:61 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:62 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:63 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:64 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:65 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:66 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:67 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:68 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:69 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:70 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:71 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:72 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:73 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:74 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:75 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:76 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:77 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:78 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:79 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:80 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:81 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:82 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:83 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:84 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:85 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:86 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:87 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:88 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:89 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:90 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:91 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:92 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:93 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:94 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:95 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:96 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:97 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:98 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:99 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:100 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:101 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:102 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:103 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:104 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:105 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:106 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:107 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:108 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:109 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:110 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:111 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:112 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:113 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:114 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:115 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:116 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:117 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:118 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:119 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:120 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:121 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:122 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:123 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:124 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:125 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:126 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:127 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + diff --git a/instruments/FM/keys/PianoMD.fui b/instruments/FM/keys/PianoMD.fui new file mode 100644 index 00000000..3c2c9e92 Binary files /dev/null and b/instruments/FM/keys/PianoMD.fui differ diff --git a/instruments/FM/keys/Reed Organ or Bandoneon.dmp b/instruments/FM/keys/Reed Organ or Bandoneon.dmp new file mode 100644 index 00000000..04a27e10 Binary files /dev/null and b/instruments/FM/keys/Reed Organ or Bandoneon.dmp differ diff --git a/instruments/FM/keys/Rock Organ.dmp b/instruments/FM/keys/Rock Organ.dmp new file mode 100644 index 00000000..5b2e3692 Binary files /dev/null and b/instruments/FM/keys/Rock Organ.dmp differ diff --git a/instruments/FM/keys/Soft E Piano.dmp b/instruments/FM/keys/Soft E Piano.dmp new file mode 100644 index 00000000..09c0ee36 Binary files /dev/null and b/instruments/FM/keys/Soft E Piano.dmp differ diff --git a/instruments/FM/keys/Sparksichord.dmp b/instruments/FM/keys/Sparksichord.dmp new file mode 100644 index 00000000..e75439a5 Binary files /dev/null and b/instruments/FM/keys/Sparksichord.dmp differ diff --git a/instruments/FM/keys/YM2151_E-PIANO.fui b/instruments/FM/keys/YM2151_E-PIANO.fui new file mode 100644 index 00000000..5db164fd Binary files /dev/null and b/instruments/FM/keys/YM2151_E-PIANO.fui differ diff --git a/instruments/FM/keys/brickblock369 Church Organ.dmp b/instruments/FM/keys/brickblock369 Church Organ.dmp new file mode 100644 index 00000000..4bb90d2b Binary files /dev/null and b/instruments/FM/keys/brickblock369 Church Organ.dmp differ diff --git a/instruments/FM/keys/brickblock369 Harpsichord.dmp b/instruments/FM/keys/brickblock369 Harpsichord.dmp new file mode 100644 index 00000000..0f5ea587 Binary files /dev/null and b/instruments/FM/keys/brickblock369 Harpsichord.dmp differ diff --git a/instruments/FM/keys/fmpiano.fui b/instruments/FM/keys/fmpiano.fui new file mode 100644 index 00000000..5de7ead0 Binary files /dev/null and b/instruments/FM/keys/fmpiano.fui differ diff --git a/instruments/FM/keys/harpsisine.dmp b/instruments/FM/keys/harpsisine.dmp new file mode 100644 index 00000000..d5633cbb Binary files /dev/null and b/instruments/FM/keys/harpsisine.dmp differ diff --git a/instruments/FM/keys/nicco1690 Church Organ.dmp b/instruments/FM/keys/nicco1690 Church Organ.dmp new file mode 100644 index 00000000..543bf352 Binary files /dev/null and b/instruments/FM/keys/nicco1690 Church Organ.dmp differ diff --git a/instruments/FM/keys/organ_test/Organ Test - Notes.txt b/instruments/FM/keys/organ_test/Organ Test - Notes.txt new file mode 100644 index 00000000..794ac398 --- /dev/null +++ b/instruments/FM/keys/organ_test/Organ Test - Notes.txt @@ -0,0 +1,12 @@ +--- EXAMPLE OF LESLIE EFFECT MACRO FOR OVERALL VOLUME LEVEL, IN FURNACE--- +127 126 125 124 123 122 121 120 121 122 123 124 125 126 127 126 125 124 123 122 121 120 121 122 123 124 125 126 127 126 125 124 123 122 121 120 121 122 123 124 125 126 127 125 123 121 119 121 123 125 127 125 123 121 119 121 123 125 | 127 124 121 124 +For the pitch oscillation, use the vibrato command and gradually increase its depth and speed. + +--- 80'S ORGAN: YM2612 AND YM2151 DIFFERENCES --- +80's Organ was originally intended for YM2612, as this particular chip's specification causes a buzz for the high operator levels. +The YM2151 will simply make it play pure sine-waves. + +--- SPECIAL THANKS --- +Organ 3 Alt (Rock Organ Alt) - thanks to freq-mod for providing the parameters to make that sound! +Church Organ - mainly based off of the high score theme from Swords of Fury. +All the rest are chosen by me. \ No newline at end of file diff --git a/instruments/FM/keys/organ_test/Organ Test.opm b/instruments/FM/keys/organ_test/Organ Test.opm new file mode 100644 index 00000000..0ce75672 --- /dev/null +++ b/instruments/FM/keys/organ_test/Organ Test.opm @@ -0,0 +1,94 @@ +//LFO: LFRQ AMD PMD WF NFRQ +//@:[Num] [Name] +//CH: PAN FL CON AMS PMS SLOT NE +//OP: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN + +// vgm offset = 000001db, channels used = 123-5--- +@:0 DrawbarOrgan +LFO: 0 0 0 0 0 +CH: 64 0 7 0 0 120 0 +M1: 31 13 0 11 1 19 0 8 3 0 0 +C1: 31 13 0 11 1 17 0 4 3 0 0 +M2: 31 13 0 11 1 12 0 2 3 0 0 +C2: 31 13 0 11 1 5 0 1 3 0 0 + +// vgm offset = 00000a7b, channels used = 123----- +@:1 80's Organ +LFO: 0 0 0 0 0 +CH: 64 6 7 0 0 120 0 +M1: 31 13 0 11 0 18 0 1 3 0 0 +C1: 31 13 0 11 0 6 0 8 3 0 0 +M2: 31 13 0 11 0 0 0 4 3 0 0 +C2: 31 13 0 11 0 0 0 6 3 0 0 + +// vgm offset = 00001f36, channels used = 123----- +@:2 PercussiveOrgan +LFO: 0 0 0 0 0 +CH: 64 0 7 0 0 120 0 +M1: 31 16 0 11 5 16 0 6 3 0 0 +C1: 31 13 0 11 1 13 0 3 3 0 0 +M2: 31 13 0 11 1 9 0 2 3 0 0 +C2: 31 13 0 11 1 5 0 1 3 0 0 + +// vgm offset = 0000222d, channels used = 123----- +@:3 PercussiveOrgn2 +LFO: 0 0 0 0 0 +CH: 64 0 7 0 0 120 0 +M1: 31 18 0 11 15 5 0 6 3 0 0 +C1: 31 13 0 11 1 17 0 4 3 0 0 +M2: 31 13 0 11 1 12 0 2 3 0 0 +C2: 31 13 0 11 1 5 0 1 3 0 0 + +// vgm offset = 000025c3, channels used = 123-5--- +@:4 Rock Organ +LFO: 0 0 0 0 0 +CH: 64 0 2 0 0 120 0 +M1: 23 31 0 14 15 54 0 13 5 0 0 +C1: 31 0 0 4 15 30 0 2 0 0 0 +M2: 31 0 0 1 11 35 0 1 4 0 0 +C2: 31 0 0 11 15 2 0 1 3 0 0 + +// vgm offset = 00002fc9, channels used = 123-5--- +@:5 Rock Organ 2 +LFO: 0 0 0 0 0 +CH: 64 0 6 0 0 120 0 +M1: 31 14 0 4 3 15 1 1 4 0 0 +C1: 20 4 1 11 0 10 0 2 1 0 0 +M2: 20 15 4 11 2 10 1 4 2 0 0 +C2: 31 13 1 11 4 12 0 6 6 0 0 + +// vgm offset = 00003bbb, channels used = 123-5--- +@:6 Church Organ +LFO: 0 0 0 0 0 +CH: 64 7 4 0 0 120 0 +M1: 22 0 0 10 0 55 0 10 2 0 0 +C1: 15 15 0 8 0 17 2 2 0 0 0 +M2: 24 0 0 5 0 15 2 0 0 0 0 +C2: 13 0 0 8 0 6 2 0 6 0 0 + +// vgm offset = 00003fef, channels used = 123-56-- +@:7 ReedOr/Bndoneon +LFO: 0 0 0 0 0 +CH: 64 4 4 0 0 120 0 +M1: 31 0 0 3 0 29 0 4 3 0 0 +C1: 16 10 0 11 1 11 0 2 3 0 0 +M2: 31 0 0 1 0 16 0 1 3 0 0 +C2: 16 10 0 11 1 6 0 1 3 0 0 + +// vgm offset = 000045f8, channels used = 123-567- +@:8 Accordion +LFO: 0 0 0 0 0 +CH: 64 0 4 0 0 120 0 +M1: 31 0 0 3 0 19 0 1 6 0 0 +C1: 14 14 0 9 1 13 0 1 2 0 0 +M2: 31 0 0 1 0 35 0 4 2 0 0 +C2: 14 14 0 9 1 6 0 1 3 0 0 + +// vgm offset = 0000509e, channels used = 1------- +@:9 Harmonica +LFO: 0 0 0 0 0 +CH: 64 7 0 0 0 120 0 +M1: 31 0 0 6 0 50 0 2 3 0 0 +C1: 31 0 0 6 0 27 0 2 3 0 0 +M2: 31 0 0 6 0 22 0 1 3 0 0 +C2: 16 12 0 11 1 2 0 1 3 0 0 \ No newline at end of file diff --git a/instruments/FM/percussion/(CH3) Double Metalliphone.dmp b/instruments/FM/percussion/(CH3) Double Metalliphone.dmp new file mode 100644 index 00000000..f0b4237c Binary files /dev/null and b/instruments/FM/percussion/(CH3) Double Metalliphone.dmp differ diff --git a/instruments/FM/percussion/(CH3) Double Steel Drum.dmp b/instruments/FM/percussion/(CH3) Double Steel Drum.dmp new file mode 100644 index 00000000..25779c24 Binary files /dev/null and b/instruments/FM/percussion/(CH3) Double Steel Drum.dmp differ diff --git a/instruments/FM/percussion/(CH3) Double Xylophone.dmp b/instruments/FM/percussion/(CH3) Double Xylophone.dmp new file mode 100644 index 00000000..3586764c Binary files /dev/null and b/instruments/FM/percussion/(CH3) Double Xylophone.dmp differ diff --git a/instruments/FM/percussion/(GEN) Bongo.dmp b/instruments/FM/percussion/(GEN) Bongo.dmp new file mode 100644 index 00000000..386297e4 Binary files /dev/null and b/instruments/FM/percussion/(GEN) Bongo.dmp differ diff --git a/instruments/FM/percussion/(GEN) Chime.dmp b/instruments/FM/percussion/(GEN) Chime.dmp new file mode 100644 index 00000000..47981cd4 Binary files /dev/null and b/instruments/FM/percussion/(GEN) Chime.dmp differ diff --git a/instruments/FM/percussion/(GEN) Cowbell.dmp b/instruments/FM/percussion/(GEN) Cowbell.dmp new file mode 100644 index 00000000..cb75b8ca Binary files /dev/null and b/instruments/FM/percussion/(GEN) Cowbell.dmp differ diff --git a/instruments/FM/percussion/(GEN) Deep Marimba.dmp b/instruments/FM/percussion/(GEN) Deep Marimba.dmp new file mode 100644 index 00000000..9d550db9 Binary files /dev/null and b/instruments/FM/percussion/(GEN) Deep Marimba.dmp differ diff --git a/instruments/FM/percussion/(GEN) Marimba.dmp b/instruments/FM/percussion/(GEN) Marimba.dmp new file mode 100644 index 00000000..d6ddd79f Binary files /dev/null and b/instruments/FM/percussion/(GEN) Marimba.dmp differ diff --git a/instruments/FM/percussion/(GEN) Metalliphone.dmp b/instruments/FM/percussion/(GEN) Metalliphone.dmp new file mode 100644 index 00000000..5622fb6d Binary files /dev/null and b/instruments/FM/percussion/(GEN) Metalliphone.dmp differ diff --git a/instruments/FM/percussion/(GEN) Short Bongo.dmp b/instruments/FM/percussion/(GEN) Short Bongo.dmp new file mode 100644 index 00000000..2eb7bcc7 Binary files /dev/null and b/instruments/FM/percussion/(GEN) Short Bongo.dmp differ diff --git a/instruments/FM/percussion/(GEN) Space Chime.dmp b/instruments/FM/percussion/(GEN) Space Chime.dmp new file mode 100644 index 00000000..43bc3802 Binary files /dev/null and b/instruments/FM/percussion/(GEN) Space Chime.dmp differ diff --git a/instruments/FM/percussion/(GEN) Space Marimba.dmp b/instruments/FM/percussion/(GEN) Space Marimba.dmp new file mode 100644 index 00000000..503c643f Binary files /dev/null and b/instruments/FM/percussion/(GEN) Space Marimba.dmp differ diff --git a/instruments/FM/percussion/(GEN) Steel Drum.dmp b/instruments/FM/percussion/(GEN) Steel Drum.dmp new file mode 100644 index 00000000..52c34c0b Binary files /dev/null and b/instruments/FM/percussion/(GEN) Steel Drum.dmp differ diff --git a/instruments/FM/percussion/(GEN) VRC7 Chime.dmp b/instruments/FM/percussion/(GEN) VRC7 Chime.dmp new file mode 100644 index 00000000..6eb34b10 Binary files /dev/null and b/instruments/FM/percussion/(GEN) VRC7 Chime.dmp differ diff --git a/instruments/FM/percussion/(GEN) VRC7 Marimba.dmp b/instruments/FM/percussion/(GEN) VRC7 Marimba.dmp new file mode 100644 index 00000000..d6ddd79f Binary files /dev/null and b/instruments/FM/percussion/(GEN) VRC7 Marimba.dmp differ diff --git a/instruments/FM/percussion/(GEN) VRC7 Metalliphone.dmp b/instruments/FM/percussion/(GEN) VRC7 Metalliphone.dmp new file mode 100644 index 00000000..5083c5f6 Binary files /dev/null and b/instruments/FM/percussion/(GEN) VRC7 Metalliphone.dmp differ diff --git a/instruments/FM/percussion/(GEN) VRC7 Soft Hestian.dmp b/instruments/FM/percussion/(GEN) VRC7 Soft Hestian.dmp new file mode 100644 index 00000000..1ec29ac4 Binary files /dev/null and b/instruments/FM/percussion/(GEN) VRC7 Soft Hestian.dmp differ diff --git a/instruments/FM/percussion/(GEN) VRC7 Xylophone.dmp b/instruments/FM/percussion/(GEN) VRC7 Xylophone.dmp new file mode 100644 index 00000000..c0c72352 Binary files /dev/null and b/instruments/FM/percussion/(GEN) VRC7 Xylophone.dmp differ diff --git a/instruments/FM/percussion/(GEN) Xylophone 1.dmp b/instruments/FM/percussion/(GEN) Xylophone 1.dmp new file mode 100644 index 00000000..990f0684 Binary files /dev/null and b/instruments/FM/percussion/(GEN) Xylophone 1.dmp differ diff --git a/instruments/FM/percussion/(GEN) Xylophone 2.dmp b/instruments/FM/percussion/(GEN) Xylophone 2.dmp new file mode 100644 index 00000000..05e7c566 Binary files /dev/null and b/instruments/FM/percussion/(GEN) Xylophone 2.dmp differ diff --git a/instruments/FM/percussion/Agogo or Cowbell.dmp b/instruments/FM/percussion/Agogo or Cowbell.dmp new file mode 100644 index 00000000..4b3b2e35 Binary files /dev/null and b/instruments/FM/percussion/Agogo or Cowbell.dmp differ diff --git a/instruments/FM/percussion/BellMD.fui b/instruments/FM/percussion/BellMD.fui new file mode 100644 index 00000000..8d94d85e Binary files /dev/null and b/instruments/FM/percussion/BellMD.fui differ diff --git a/instruments/FM/percussion/Bell_Oct4-7.dmp b/instruments/FM/percussion/Bell_Oct4-7.dmp new file mode 100644 index 00000000..70974556 Binary files /dev/null and b/instruments/FM/percussion/Bell_Oct4-7.dmp differ diff --git a/instruments/FM/percussion/Chromatic Percussion.opm b/instruments/FM/percussion/Chromatic Percussion.opm new file mode 100644 index 00000000..76eb8450 --- /dev/null +++ b/instruments/FM/percussion/Chromatic Percussion.opm @@ -0,0 +1,1040 @@ +//LFO: LFRQ AMD PMD WF NFRQ +//@:[Num] [Name] +//CH: PAN FL CON AMS PMS SLOT NE +//OP: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN + +// vgm offset = 000001de, channels used = 12------ +@:0 Celesta (Low) +LFO: 0 0 0 0 0 +CH: 64 0 4 0 0 120 0 +M1: 31 27 0 12 15 40 0 6 3 0 0 +C1: 31 26 0 12 15 0 0 6 3 0 0 +M2: 31 11 0 1 11 44 0 6 3 0 0 +C2: 31 11 0 7 15 0 0 1 3 0 0 + +// vgm offset = 00000387, channels used = --3-567- +@:1 Celesta (High) +LFO: 0 0 0 0 0 +CH: 64 0 4 0 0 120 0 +M1: 31 26 0 11 15 40 0 6 5 0 0 +C1: 31 26 0 11 15 18 0 6 3 0 0 +M2: 31 11 0 1 11 60 0 6 3 0 0 +C2: 31 11 0 7 15 0 0 1 3 0 0 + +// vgm offset = 0000095c, channels used = 12------ +@:2 Glockenspiel +LFO: 0 0 0 0 0 +CH: 64 0 2 0 0 120 0 +M1: 31 27 0 14 15 28 0 9 3 0 0 +C1: 31 11 0 6 15 43 0 12 3 0 0 +M2: 31 10 0 5 15 35 0 6 3 0 0 +C2: 31 13 12 7 6 0 0 1 3 0 0 + +// vgm offset = 00000bd2, channels used = 123-567- +@:3 Music Box +LFO: 0 0 0 0 0 +CH: 64 4 4 0 0 120 0 +M1: 31 8 0 6 15 47 0 10 3 0 0 +C1: 14 10 0 7 15 16 0 2 3 0 0 +M2: 21 11 0 1 11 45 0 6 3 0 0 +C2: 31 10 0 7 15 1 0 1 3 0 0 + +// vgm offset = 00000f9f, channels used = 123-56-- +@:4 Vibraphone +LFO: 0 0 0 0 0 +CH: 64 0 4 0 0 120 0 +M1: 31 14 6 11 10 45 0 5 3 0 0 +C1: 31 13 16 11 15 10 0 4 3 0 0 +M2: 31 10 0 1 11 80 0 5 3 0 0 +C2: 31 10 0 8 15 4 0 1 3 0 0 + +// vgm offset = 00001450, channels used = 12------ +@:5 Marimba +LFO: 0 0 0 0 0 +CH: 64 0 4 0 0 120 0 +M1: 31 16 6 11 14 29 0 5 3 0 0 +C1: 31 18 16 11 15 16 0 4 3 0 0 +M2: 31 19 12 1 7 30 0 5 3 0 0 +C2: 31 12 14 8 4 4 0 1 3 0 0 + +// vgm offset = 00001802, channels used = 12------ +@:6 Xylophone +LFO: 0 0 0 0 0 +CH: 64 0 4 0 0 120 0 +M1: 31 20 6 12 14 19 0 5 3 0 0 +C1: 31 22 16 11 15 16 0 4 3 0 0 +M2: 31 19 15 1 4 28 0 6 3 0 0 +C2: 31 18 14 8 4 4 0 1 3 0 0 + +// vgm offset = 00001d9c, channels used = 12------ +@:7 Tubular Bells +LFO: 0 0 0 0 0 +CH: 64 4 4 0 0 120 0 +M1: 31 8 0 4 15 43 0 10 6 0 0 +C1: 31 10 0 6 15 10 0 8 2 0 0 +M2: 31 4 0 1 11 36 0 8 0 0 0 +C2: 31 9 0 6 15 6 0 5 5 0 0 + +// vgm offset = 00002066, channels used = 123----- +@:8 Dulcimer +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 120 0 +M1: 31 17 9 3 2 33 0 3 3 0 0 +C1: 31 7 0 3 15 27 0 3 3 0 0 +M2: 31 4 0 5 11 38 0 1 3 0 0 +C2: 31 9 0 5 15 2 0 1 3 0 0 + +// vgm offset = 00002783, channels used = 12------ +@:9 Timpani +LFO: 0 0 0 0 0 +CH: 64 0 2 0 0 120 0 +M1: 31 19 0 9 15 23 0 3 3 0 0 +C1: 31 14 14 4 6 18 0 1 0 0 0 +M2: 31 12 8 1 2 18 0 1 1 0 0 +C2: 31 12 0 6 15 2 0 1 3 0 0 + +// vgm offset = 00002bb8, channels used = 123-567- +@:10 Steel Drum +LFO: 0 0 0 0 0 +CH: 64 4 0 0 0 120 0 +M1: 31 8 0 3 15 42 0 5 5 0 0 +C1: 16 13 0 9 15 71 0 5 0 0 0 +M2: 16 11 10 7 3 33 0 3 6 0 0 +C2: 31 10 0 7 15 0 0 1 3 0 0 + +@:11 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:12 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:13 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:14 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:15 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:16 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:17 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:18 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:19 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:20 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:21 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:22 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:23 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:24 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:25 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:26 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:27 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:28 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:29 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:30 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:31 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:32 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:33 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:34 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:35 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:36 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:37 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:38 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:39 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:40 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:41 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:42 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:43 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:44 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:45 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:46 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:47 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:48 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:49 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:50 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:51 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:52 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:53 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:54 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:55 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:56 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:57 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:58 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:59 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:60 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:61 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:62 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:63 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:64 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:65 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:66 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:67 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:68 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:69 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:70 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:71 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:72 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:73 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:74 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:75 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:76 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:77 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:78 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:79 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:80 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:81 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:82 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:83 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:84 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:85 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:86 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:87 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:88 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:89 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:90 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:91 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:92 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:93 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:94 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:95 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:96 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:97 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:98 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:99 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:100 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:101 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:102 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:103 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:104 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:105 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:106 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:107 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:108 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:109 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:110 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:111 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:112 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:113 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:114 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:115 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:116 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:117 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:118 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:119 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:120 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:121 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:122 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:123 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:124 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:125 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:126 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + +@:127 no Name +LFO: 0 0 0 0 0 +CH: 64 0 0 0 0 64 0 +M1: 31 0 0 4 0 0 0 1 0 0 0 +C1: 31 0 0 4 0 0 0 1 0 0 0 +M2: 31 0 0 4 0 0 0 1 0 0 0 +C2: 31 0 0 4 0 0 0 1 0 0 0 + diff --git a/instruments/FM/percussion/ChurchBell.fui b/instruments/FM/percussion/ChurchBell.fui new file mode 100644 index 00000000..85a187b1 Binary files /dev/null and b/instruments/FM/percussion/ChurchBell.fui differ diff --git a/instruments/FM/percussion/Cowbell_F#5.dmp b/instruments/FM/percussion/Cowbell_F#5.dmp new file mode 100644 index 00000000..3ba4d1f7 Binary files /dev/null and b/instruments/FM/percussion/Cowbell_F#5.dmp differ diff --git a/instruments/FM/percussion/Dangerously Cheap Timpani.fui b/instruments/FM/percussion/Dangerously Cheap Timpani.fui new file mode 100644 index 00000000..45bc65db Binary files /dev/null and b/instruments/FM/percussion/Dangerously Cheap Timpani.fui differ diff --git a/instruments/FM/percussion/Dulcimer.dmp b/instruments/FM/percussion/Dulcimer.dmp new file mode 100644 index 00000000..76ddf97a Binary files /dev/null and b/instruments/FM/percussion/Dulcimer.dmp differ diff --git a/instruments/FM/percussion/Glockenspiel.dmp b/instruments/FM/percussion/Glockenspiel.dmp new file mode 100644 index 00000000..a33a224b Binary files /dev/null and b/instruments/FM/percussion/Glockenspiel.dmp differ diff --git a/instruments/FM/percussion/Kalimba.dmp b/instruments/FM/percussion/Kalimba.dmp new file mode 100644 index 00000000..b62ae7d4 Binary files /dev/null and b/instruments/FM/percussion/Kalimba.dmp differ diff --git a/instruments/FM/percussion/Kalimba.fui b/instruments/FM/percussion/Kalimba.fui new file mode 100644 index 00000000..128deca3 Binary files /dev/null and b/instruments/FM/percussion/Kalimba.fui differ diff --git a/instruments/FM/percussion/Marimba.dmp b/instruments/FM/percussion/Marimba.dmp new file mode 100644 index 00000000..8efcfc4e Binary files /dev/null and b/instruments/FM/percussion/Marimba.dmp differ diff --git a/instruments/FM/percussion/Marimba.fui b/instruments/FM/percussion/Marimba.fui new file mode 100644 index 00000000..faedbf8c Binary files /dev/null and b/instruments/FM/percussion/Marimba.fui differ diff --git a/instruments/FM/percussion/Music Box.dmp b/instruments/FM/percussion/Music Box.dmp new file mode 100644 index 00000000..1b65c6a1 Binary files /dev/null and b/instruments/FM/percussion/Music Box.dmp differ diff --git a/instruments/FM/percussion/MusicBox.fui b/instruments/FM/percussion/MusicBox.fui new file mode 100644 index 00000000..53512b99 Binary files /dev/null and b/instruments/FM/percussion/MusicBox.fui differ diff --git a/instruments/FM/percussion/OPN_-_Music_Box.fui b/instruments/FM/percussion/OPN_-_Music_Box.fui new file mode 100644 index 00000000..7bcda34f Binary files /dev/null and b/instruments/FM/percussion/OPN_-_Music_Box.fui differ diff --git a/instruments/FM/percussion/Peck.dmp b/instruments/FM/percussion/Peck.dmp new file mode 100644 index 00000000..451de73f Binary files /dev/null and b/instruments/FM/percussion/Peck.dmp differ diff --git a/instruments/FM/percussion/Rainbow_Bell.fui b/instruments/FM/percussion/Rainbow_Bell.fui new file mode 100644 index 00000000..7c2d7aa3 Binary files /dev/null and b/instruments/FM/percussion/Rainbow_Bell.fui differ diff --git a/instruments/FM/percussion/Realistic Bell.dmp b/instruments/FM/percussion/Realistic Bell.dmp new file mode 100644 index 00000000..9c44759e Binary files /dev/null and b/instruments/FM/percussion/Realistic Bell.dmp differ diff --git a/instruments/FM/percussion/Steel Drum.dmp b/instruments/FM/percussion/Steel Drum.dmp new file mode 100644 index 00000000..386c60ab Binary files /dev/null and b/instruments/FM/percussion/Steel Drum.dmp differ diff --git a/instruments/FM/percussion/Timpani.dmp b/instruments/FM/percussion/Timpani.dmp new file mode 100644 index 00000000..07974971 Binary files /dev/null and b/instruments/FM/percussion/Timpani.dmp differ diff --git a/instruments/FM/percussion/Tubular Bells.dmp b/instruments/FM/percussion/Tubular Bells.dmp new file mode 100644 index 00000000..bb1e55f0 Binary files /dev/null and b/instruments/FM/percussion/Tubular Bells.dmp differ diff --git a/instruments/FM/percussion/VRC7 Vibraphone.dmp b/instruments/FM/percussion/VRC7 Vibraphone.dmp new file mode 100644 index 00000000..56a05197 Binary files /dev/null and b/instruments/FM/percussion/VRC7 Vibraphone.dmp differ diff --git a/instruments/FM/percussion/Vibraphone.dmp b/instruments/FM/percussion/Vibraphone.dmp new file mode 100644 index 00000000..1a9aa387 Binary files /dev/null and b/instruments/FM/percussion/Vibraphone.dmp differ diff --git a/instruments/FM/percussion/Xylophone.dmp b/instruments/FM/percussion/Xylophone.dmp new file mode 100644 index 00000000..c3c02f8a Binary files /dev/null and b/instruments/FM/percussion/Xylophone.dmp differ diff --git a/instruments/FM/percussion/agogo.fui b/instruments/FM/percussion/agogo.fui new file mode 100644 index 00000000..ef42689b Binary files /dev/null and b/instruments/FM/percussion/agogo.fui differ diff --git a/instruments/FM/percussion/alg 7 bongoes.dmp b/instruments/FM/percussion/alg 7 bongoes.dmp new file mode 100644 index 00000000..b628c6e2 Binary files /dev/null and b/instruments/FM/percussion/alg 7 bongoes.dmp differ diff --git a/instruments/FM/percussion/bell_00_glockenspiel.fui b/instruments/FM/percussion/bell_00_glockenspiel.fui new file mode 100644 index 00000000..072100ae Binary files /dev/null and b/instruments/FM/percussion/bell_00_glockenspiel.fui differ diff --git a/instruments/FM/percussion/bell_01_glockenspiel_echoing.fui b/instruments/FM/percussion/bell_01_glockenspiel_echoing.fui new file mode 100644 index 00000000..d177ddec Binary files /dev/null and b/instruments/FM/percussion/bell_01_glockenspiel_echoing.fui differ diff --git a/instruments/FM/percussion/bell_02_glockenspiel_wobbly.fui b/instruments/FM/percussion/bell_02_glockenspiel_wobbly.fui new file mode 100644 index 00000000..2534f79c Binary files /dev/null and b/instruments/FM/percussion/bell_02_glockenspiel_wobbly.fui differ diff --git a/instruments/FM/percussion/brickblock369 Glockenspiel.dmp b/instruments/FM/percussion/brickblock369 Glockenspiel.dmp new file mode 100644 index 00000000..eae09ee9 Binary files /dev/null and b/instruments/FM/percussion/brickblock369 Glockenspiel.dmp differ diff --git a/instruments/FM/percussion/brickblock369 Steel Drum.dmp b/instruments/FM/percussion/brickblock369 Steel Drum.dmp new file mode 100644 index 00000000..a43ef62a Binary files /dev/null and b/instruments/FM/percussion/brickblock369 Steel Drum.dmp differ diff --git a/instruments/FM/percussion/brickblock369 Timpani.dmp b/instruments/FM/percussion/brickblock369 Timpani.dmp new file mode 100644 index 00000000..1e529749 Binary files /dev/null and b/instruments/FM/percussion/brickblock369 Timpani.dmp differ diff --git a/instruments/FM/percussion/fm_cowbell.fui b/instruments/FM/percussion/fm_cowbell.fui new file mode 100644 index 00000000..8a5c21d4 Binary files /dev/null and b/instruments/FM/percussion/fm_cowbell.fui differ diff --git a/instruments/FM/percussion/octave_8_bell.fui b/instruments/FM/percussion/octave_8_bell.fui new file mode 100644 index 00000000..4dc80b63 Binary files /dev/null and b/instruments/FM/percussion/octave_8_bell.fui differ diff --git a/instruments/FM/percussion/trashy handbells.dmp b/instruments/FM/percussion/trashy handbells.dmp new file mode 100644 index 00000000..4568da9f Binary files /dev/null and b/instruments/FM/percussion/trashy handbells.dmp differ diff --git a/instruments/FM/strings/(CH3) Double Viola.dmp b/instruments/FM/strings/(CH3) Double Viola.dmp new file mode 100644 index 00000000..1924f58c Binary files /dev/null and b/instruments/FM/strings/(CH3) Double Viola.dmp differ diff --git a/instruments/FM/strings/(GEN) Choir.dmp b/instruments/FM/strings/(GEN) Choir.dmp new file mode 100644 index 00000000..bf7ff11a Binary files /dev/null and b/instruments/FM/strings/(GEN) Choir.dmp differ diff --git a/instruments/FM/strings/(GEN) Fiddle.dmp b/instruments/FM/strings/(GEN) Fiddle.dmp new file mode 100644 index 00000000..ebffe070 Binary files /dev/null and b/instruments/FM/strings/(GEN) Fiddle.dmp differ diff --git a/instruments/FM/strings/(GEN) Rapid Fiddle.dmp b/instruments/FM/strings/(GEN) Rapid Fiddle.dmp new file mode 100644 index 00000000..a6c84ca2 Binary files /dev/null and b/instruments/FM/strings/(GEN) Rapid Fiddle.dmp differ diff --git a/instruments/FM/strings/(GEN) Space Violin.dmp b/instruments/FM/strings/(GEN) Space Violin.dmp new file mode 100644 index 00000000..24802321 Binary files /dev/null and b/instruments/FM/strings/(GEN) Space Violin.dmp differ diff --git a/instruments/FM/strings/(GEN) VRC7 Strings.dmp b/instruments/FM/strings/(GEN) VRC7 Strings.dmp new file mode 100644 index 00000000..79605935 Binary files /dev/null and b/instruments/FM/strings/(GEN) VRC7 Strings.dmp differ diff --git a/instruments/FM/strings/(GEN) VRC7 Viola.dmp b/instruments/FM/strings/(GEN) VRC7 Viola.dmp new file mode 100644 index 00000000..4bb4b369 Binary files /dev/null and b/instruments/FM/strings/(GEN) VRC7 Viola.dmp differ diff --git a/instruments/FM/strings/(GEN) Violin 1.dmp b/instruments/FM/strings/(GEN) Violin 1.dmp new file mode 100644 index 00000000..0650590f Binary files /dev/null and b/instruments/FM/strings/(GEN) Violin 1.dmp differ diff --git a/instruments/FM/strings/(GEN) Violin 2.dmp b/instruments/FM/strings/(GEN) Violin 2.dmp new file mode 100644 index 00000000..57e9a3fb Binary files /dev/null and b/instruments/FM/strings/(GEN) Violin 2.dmp differ diff --git a/instruments/FM/strings/Earthbound Strings.dmp b/instruments/FM/strings/Earthbound Strings.dmp new file mode 100644 index 00000000..edbb9343 Binary files /dev/null and b/instruments/FM/strings/Earthbound Strings.dmp differ diff --git a/instruments/FM/strings/Enchant.dmp b/instruments/FM/strings/Enchant.dmp new file mode 100644 index 00000000..88d1cb3d Binary files /dev/null and b/instruments/FM/strings/Enchant.dmp differ diff --git a/instruments/FM/strings/Ethereal Something.dmp b/instruments/FM/strings/Ethereal Something.dmp new file mode 100644 index 00000000..e7458064 Binary files /dev/null and b/instruments/FM/strings/Ethereal Something.dmp differ diff --git a/instruments/FM/strings/Fifths Pad.dmp b/instruments/FM/strings/Fifths Pad.dmp new file mode 100644 index 00000000..0ab2b61a Binary files /dev/null and b/instruments/FM/strings/Fifths Pad.dmp differ diff --git a/instruments/FM/strings/Fifths Type Beat.dmp b/instruments/FM/strings/Fifths Type Beat.dmp new file mode 100644 index 00000000..78af1f7a Binary files /dev/null and b/instruments/FM/strings/Fifths Type Beat.dmp differ diff --git a/instruments/FM/strings/OPNx_5thsPad.fui b/instruments/FM/strings/OPNx_5thsPad.fui new file mode 100644 index 00000000..ccc1afbe Binary files /dev/null and b/instruments/FM/strings/OPNx_5thsPad.fui differ diff --git a/instruments/FM/strings/OPNx_String.fui b/instruments/FM/strings/OPNx_String.fui new file mode 100644 index 00000000..63a14096 Binary files /dev/null and b/instruments/FM/strings/OPNx_String.fui differ diff --git a/instruments/FM/strings/Pad.dmp b/instruments/FM/strings/Pad.dmp new file mode 100644 index 00000000..9e6674f1 Binary files /dev/null and b/instruments/FM/strings/Pad.dmp differ diff --git a/instruments/FM/strings/SSGPad.dmp b/instruments/FM/strings/SSGPad.dmp new file mode 100644 index 00000000..6b37a3b0 Binary files /dev/null and b/instruments/FM/strings/SSGPad.dmp differ diff --git a/instruments/FM/strings/Sawsine pad.dmp b/instruments/FM/strings/Sawsine pad.dmp new file mode 100644 index 00000000..d03286c5 Binary files /dev/null and b/instruments/FM/strings/Sawsine pad.dmp differ diff --git a/instruments/FM/strings/Violin.fui b/instruments/FM/strings/Violin.fui new file mode 100644 index 00000000..2a560518 Binary files /dev/null and b/instruments/FM/strings/Violin.fui differ diff --git a/instruments/FM/strings/ViolinMD.fui b/instruments/FM/strings/ViolinMD.fui new file mode 100644 index 00000000..0b8eb5e6 Binary files /dev/null and b/instruments/FM/strings/ViolinMD.fui differ diff --git a/instruments/FM/strings/orchestal bullshit.dmp b/instruments/FM/strings/orchestal bullshit.dmp new file mode 100644 index 00000000..0c49c423 Binary files /dev/null and b/instruments/FM/strings/orchestal bullshit.dmp differ diff --git a/instruments/FM/strings/strings_00.fui b/instruments/FM/strings/strings_00.fui new file mode 100644 index 00000000..64a38f8b Binary files /dev/null and b/instruments/FM/strings/strings_00.fui differ diff --git a/instruments/FM/synth/(CH3) Double Ambience.dmp b/instruments/FM/synth/(CH3) Double Ambience.dmp new file mode 100644 index 00000000..335e026c Binary files /dev/null and b/instruments/FM/synth/(CH3) Double Ambience.dmp differ diff --git a/instruments/FM/synth/(CH3) Double Choir.dmp b/instruments/FM/synth/(CH3) Double Choir.dmp new file mode 100644 index 00000000..0d27f560 Binary files /dev/null and b/instruments/FM/synth/(CH3) Double Choir.dmp differ diff --git a/instruments/FM/synth/(CH3) Double Rounded Square.dmp b/instruments/FM/synth/(CH3) Double Rounded Square.dmp new file mode 100644 index 00000000..cbe498c8 Binary files /dev/null and b/instruments/FM/synth/(CH3) Double Rounded Square.dmp differ diff --git a/instruments/FM/synth/(CH3) Saw & Organ.dmp b/instruments/FM/synth/(CH3) Saw & Organ.dmp new file mode 100644 index 00000000..38692691 Binary files /dev/null and b/instruments/FM/synth/(CH3) Saw & Organ.dmp differ diff --git a/instruments/FM/synth/(CH3) Sine Chord.dmp b/instruments/FM/synth/(CH3) Sine Chord.dmp new file mode 100644 index 00000000..d8ece997 Binary files /dev/null and b/instruments/FM/synth/(CH3) Sine Chord.dmp differ diff --git a/instruments/FM/synth/(CH3) Synth with Light Snare.dmp b/instruments/FM/synth/(CH3) Synth with Light Snare.dmp new file mode 100644 index 00000000..2f077284 Binary files /dev/null and b/instruments/FM/synth/(CH3) Synth with Light Snare.dmp differ diff --git a/instruments/FM/synth/(GEN) 1 16 Pulse.dmp b/instruments/FM/synth/(GEN) 1 16 Pulse.dmp new file mode 100644 index 00000000..1549815d Binary files /dev/null and b/instruments/FM/synth/(GEN) 1 16 Pulse.dmp differ diff --git a/instruments/FM/synth/(GEN) 1 4 Pulse.dmp b/instruments/FM/synth/(GEN) 1 4 Pulse.dmp new file mode 100644 index 00000000..ca08407e Binary files /dev/null and b/instruments/FM/synth/(GEN) 1 4 Pulse.dmp differ diff --git a/instruments/FM/synth/(GEN) 1 8 Pulse.dmp b/instruments/FM/synth/(GEN) 1 8 Pulse.dmp new file mode 100644 index 00000000..66d86a1e Binary files /dev/null and b/instruments/FM/synth/(GEN) 1 8 Pulse.dmp differ diff --git a/instruments/FM/synth/(GEN) 3 16 Pulse.dmp b/instruments/FM/synth/(GEN) 3 16 Pulse.dmp new file mode 100644 index 00000000..15a469a7 Binary files /dev/null and b/instruments/FM/synth/(GEN) 3 16 Pulse.dmp differ diff --git a/instruments/FM/synth/(GEN) 3 8 Pulse.dmp b/instruments/FM/synth/(GEN) 3 8 Pulse.dmp new file mode 100644 index 00000000..4f516de9 Binary files /dev/null and b/instruments/FM/synth/(GEN) 3 8 Pulse.dmp differ diff --git a/instruments/FM/synth/(GEN) 5 16 Pulse.dmp b/instruments/FM/synth/(GEN) 5 16 Pulse.dmp new file mode 100644 index 00000000..1c91d1e2 Binary files /dev/null and b/instruments/FM/synth/(GEN) 5 16 Pulse.dmp differ diff --git a/instruments/FM/synth/(GEN) 7 16 Pulse.dmp b/instruments/FM/synth/(GEN) 7 16 Pulse.dmp new file mode 100644 index 00000000..498ec862 Binary files /dev/null and b/instruments/FM/synth/(GEN) 7 16 Pulse.dmp differ diff --git a/instruments/FM/synth/(GEN) Ambience.dmp b/instruments/FM/synth/(GEN) Ambience.dmp new file mode 100644 index 00000000..abee9ed1 Binary files /dev/null and b/instruments/FM/synth/(GEN) Ambience.dmp differ diff --git a/instruments/FM/synth/(GEN) Church Hestian.dmp b/instruments/FM/synth/(GEN) Church Hestian.dmp new file mode 100644 index 00000000..3617d281 Binary files /dev/null and b/instruments/FM/synth/(GEN) Church Hestian.dmp differ diff --git a/instruments/FM/synth/(GEN) Cutoff.dmp b/instruments/FM/synth/(GEN) Cutoff.dmp new file mode 100644 index 00000000..ad556e08 Binary files /dev/null and b/instruments/FM/synth/(GEN) Cutoff.dmp differ diff --git a/instruments/FM/synth/(GEN) Distorted Hestian.dmp b/instruments/FM/synth/(GEN) Distorted Hestian.dmp new file mode 100644 index 00000000..2068543b Binary files /dev/null and b/instruments/FM/synth/(GEN) Distorted Hestian.dmp differ diff --git a/instruments/FM/synth/(GEN) Hestian.dmp b/instruments/FM/synth/(GEN) Hestian.dmp new file mode 100644 index 00000000..98f85cf6 Binary files /dev/null and b/instruments/FM/synth/(GEN) Hestian.dmp differ diff --git a/instruments/FM/synth/(GEN) High Hestian.dmp b/instruments/FM/synth/(GEN) High Hestian.dmp new file mode 100644 index 00000000..2c5a3a2d Binary files /dev/null and b/instruments/FM/synth/(GEN) High Hestian.dmp differ diff --git a/instruments/FM/synth/(GEN) Hyperspeed Decay Sine.dmp b/instruments/FM/synth/(GEN) Hyperspeed Decay Sine.dmp new file mode 100644 index 00000000..e55628f9 Binary files /dev/null and b/instruments/FM/synth/(GEN) Hyperspeed Decay Sine.dmp differ diff --git a/instruments/FM/synth/(GEN) K.K. Slider Aah.dmp b/instruments/FM/synth/(GEN) K.K. Slider Aah.dmp new file mode 100644 index 00000000..29ff0e16 Binary files /dev/null and b/instruments/FM/synth/(GEN) K.K. Slider Aah.dmp differ diff --git a/instruments/FM/synth/(GEN) K.K. Slider Weh.dmp b/instruments/FM/synth/(GEN) K.K. Slider Weh.dmp new file mode 100644 index 00000000..a85e7f10 Binary files /dev/null and b/instruments/FM/synth/(GEN) K.K. Slider Weh.dmp differ diff --git a/instruments/FM/synth/(GEN) Low Hestian.dmp b/instruments/FM/synth/(GEN) Low Hestian.dmp new file mode 100644 index 00000000..bd68de84 Binary files /dev/null and b/instruments/FM/synth/(GEN) Low Hestian.dmp differ diff --git a/instruments/FM/synth/(GEN) NES Triangle.dmp b/instruments/FM/synth/(GEN) NES Triangle.dmp new file mode 100644 index 00000000..4d4a99d1 Binary files /dev/null and b/instruments/FM/synth/(GEN) NES Triangle.dmp differ diff --git a/instruments/FM/synth/(GEN) Near Perfect Saw Wave.dmp b/instruments/FM/synth/(GEN) Near Perfect Saw Wave.dmp new file mode 100644 index 00000000..d489f69c Binary files /dev/null and b/instruments/FM/synth/(GEN) Near Perfect Saw Wave.dmp differ diff --git a/instruments/FM/synth/(GEN) Near Perfect Square Wave.dmp b/instruments/FM/synth/(GEN) Near Perfect Square Wave.dmp new file mode 100644 index 00000000..47f59cdf Binary files /dev/null and b/instruments/FM/synth/(GEN) Near Perfect Square Wave.dmp differ diff --git a/instruments/FM/synth/(GEN) Perfect Saw Wave.dmp b/instruments/FM/synth/(GEN) Perfect Saw Wave.dmp new file mode 100644 index 00000000..7f4dbafc Binary files /dev/null and b/instruments/FM/synth/(GEN) Perfect Saw Wave.dmp differ diff --git a/instruments/FM/synth/(GEN) Perfect Square Wave.dmp b/instruments/FM/synth/(GEN) Perfect Square Wave.dmp new file mode 100644 index 00000000..0742fa53 Binary files /dev/null and b/instruments/FM/synth/(GEN) Perfect Square Wave.dmp differ diff --git a/instruments/FM/synth/(GEN) Perfect Triangle Wave.dmp b/instruments/FM/synth/(GEN) Perfect Triangle Wave.dmp new file mode 100644 index 00000000..09867025 Binary files /dev/null and b/instruments/FM/synth/(GEN) Perfect Triangle Wave.dmp differ diff --git a/instruments/FM/synth/(GEN) Rattling Hestian.dmp b/instruments/FM/synth/(GEN) Rattling Hestian.dmp new file mode 100644 index 00000000..f7ef159d Binary files /dev/null and b/instruments/FM/synth/(GEN) Rattling Hestian.dmp differ diff --git a/instruments/FM/synth/(GEN) Rough Hestian.dmp b/instruments/FM/synth/(GEN) Rough Hestian.dmp new file mode 100644 index 00000000..f3662fad Binary files /dev/null and b/instruments/FM/synth/(GEN) Rough Hestian.dmp differ diff --git a/instruments/FM/synth/(GEN) Rough Square.dmp b/instruments/FM/synth/(GEN) Rough Square.dmp new file mode 100644 index 00000000..c0586dfd Binary files /dev/null and b/instruments/FM/synth/(GEN) Rough Square.dmp differ diff --git a/instruments/FM/synth/(GEN) Rounded Square.dmp b/instruments/FM/synth/(GEN) Rounded Square.dmp new file mode 100644 index 00000000..c8d87e0f Binary files /dev/null and b/instruments/FM/synth/(GEN) Rounded Square.dmp differ diff --git a/instruments/FM/synth/(GEN) Saw Malleable.dmp b/instruments/FM/synth/(GEN) Saw Malleable.dmp new file mode 100644 index 00000000..6fa43a8d Binary files /dev/null and b/instruments/FM/synth/(GEN) Saw Malleable.dmp differ diff --git a/instruments/FM/synth/(GEN) Sharp Saw.dmp b/instruments/FM/synth/(GEN) Sharp Saw.dmp new file mode 100644 index 00000000..50910176 Binary files /dev/null and b/instruments/FM/synth/(GEN) Sharp Saw.dmp differ diff --git a/instruments/FM/synth/(GEN) Sine Growl.dmp b/instruments/FM/synth/(GEN) Sine Growl.dmp new file mode 100644 index 00000000..fefea8da Binary files /dev/null and b/instruments/FM/synth/(GEN) Sine Growl.dmp differ diff --git a/instruments/FM/synth/(GEN) Space Hestian.dmp b/instruments/FM/synth/(GEN) Space Hestian.dmp new file mode 100644 index 00000000..68c302ae Binary files /dev/null and b/instruments/FM/synth/(GEN) Space Hestian.dmp differ diff --git a/instruments/FM/synth/(GEN) Space Organ.dmp b/instruments/FM/synth/(GEN) Space Organ.dmp new file mode 100644 index 00000000..e7d796d4 Binary files /dev/null and b/instruments/FM/synth/(GEN) Space Organ.dmp differ diff --git a/instruments/FM/synth/(GEN) Space Saw.dmp b/instruments/FM/synth/(GEN) Space Saw.dmp new file mode 100644 index 00000000..fe36dafa Binary files /dev/null and b/instruments/FM/synth/(GEN) Space Saw.dmp differ diff --git a/instruments/FM/synth/(GEN) Sparkle.dmp b/instruments/FM/synth/(GEN) Sparkle.dmp new file mode 100644 index 00000000..d0ebfcdf Binary files /dev/null and b/instruments/FM/synth/(GEN) Sparkle.dmp differ diff --git a/instruments/FM/synth/(GEN) Spoopy.dmp b/instruments/FM/synth/(GEN) Spoopy.dmp new file mode 100644 index 00000000..6ba2222b Binary files /dev/null and b/instruments/FM/synth/(GEN) Spoopy.dmp differ diff --git a/instruments/FM/synth/(GEN) Square Wave.dmp b/instruments/FM/synth/(GEN) Square Wave.dmp new file mode 100644 index 00000000..67bd8a95 Binary files /dev/null and b/instruments/FM/synth/(GEN) Square Wave.dmp differ diff --git a/instruments/FM/synth/(GEN) Synth with Closed Hi-Hat.dmp b/instruments/FM/synth/(GEN) Synth with Closed Hi-Hat.dmp new file mode 100644 index 00000000..8f2e3389 Binary files /dev/null and b/instruments/FM/synth/(GEN) Synth with Closed Hi-Hat.dmp differ diff --git a/instruments/FM/synth/(GEN) Synth with Open Hi-Hat.dmp b/instruments/FM/synth/(GEN) Synth with Open Hi-Hat.dmp new file mode 100644 index 00000000..2a94410f Binary files /dev/null and b/instruments/FM/synth/(GEN) Synth with Open Hi-Hat.dmp differ diff --git a/instruments/FM/synth/(GEN) Twang EX.dmp b/instruments/FM/synth/(GEN) Twang EX.dmp new file mode 100644 index 00000000..10503c96 Binary files /dev/null and b/instruments/FM/synth/(GEN) Twang EX.dmp differ diff --git a/instruments/FM/synth/(GEN) Twang Whistle.dmp b/instruments/FM/synth/(GEN) Twang Whistle.dmp new file mode 100644 index 00000000..6e0a2bdc Binary files /dev/null and b/instruments/FM/synth/(GEN) Twang Whistle.dmp differ diff --git a/instruments/FM/synth/(GEN) VRC7 Choir.dmp b/instruments/FM/synth/(GEN) VRC7 Choir.dmp new file mode 100644 index 00000000..a0a4c873 Binary files /dev/null and b/instruments/FM/synth/(GEN) VRC7 Choir.dmp differ diff --git a/instruments/FM/synth/(GEN) VRC7 Hestian.dmp b/instruments/FM/synth/(GEN) VRC7 Hestian.dmp new file mode 100644 index 00000000..2f90a68e Binary files /dev/null and b/instruments/FM/synth/(GEN) VRC7 Hestian.dmp differ diff --git a/instruments/FM/synth/(GEN) Wahwah.dmp b/instruments/FM/synth/(GEN) Wahwah.dmp new file mode 100644 index 00000000..7b3dbbac Binary files /dev/null and b/instruments/FM/synth/(GEN) Wahwah.dmp differ diff --git a/instruments/FM/synth/Abduction.dmp b/instruments/FM/synth/Abduction.dmp new file mode 100644 index 00000000..600478b6 Binary files /dev/null and b/instruments/FM/synth/Abduction.dmp differ diff --git a/instruments/FM/synth/Altered Square.dmp b/instruments/FM/synth/Altered Square.dmp new file mode 100644 index 00000000..ca8e89d3 Binary files /dev/null and b/instruments/FM/synth/Altered Square.dmp differ diff --git a/instruments/FM/synth/Buttermilk.dmp b/instruments/FM/synth/Buttermilk.dmp new file mode 100644 index 00000000..9a9c4de7 Binary files /dev/null and b/instruments/FM/synth/Buttermilk.dmp differ diff --git a/instruments/FM/synth/Circuitbird Donk.dmp b/instruments/FM/synth/Circuitbird Donk.dmp new file mode 100644 index 00000000..c3b61824 Binary files /dev/null and b/instruments/FM/synth/Circuitbird Donk.dmp differ diff --git a/instruments/FM/synth/Cricket Chorus.fui b/instruments/FM/synth/Cricket Chorus.fui new file mode 100644 index 00000000..4dfaefee Binary files /dev/null and b/instruments/FM/synth/Cricket Chorus.fui differ diff --git a/instruments/FM/synth/Desert Synth.dmp b/instruments/FM/synth/Desert Synth.dmp new file mode 100644 index 00000000..0d188085 Binary files /dev/null and b/instruments/FM/synth/Desert Synth.dmp differ diff --git a/instruments/FM/synth/Ecco.dmp b/instruments/FM/synth/Ecco.dmp new file mode 100644 index 00000000..87c8520b Binary files /dev/null and b/instruments/FM/synth/Ecco.dmp differ diff --git a/instruments/FM/synth/FM_noname1.fui b/instruments/FM/synth/FM_noname1.fui new file mode 100644 index 00000000..bd739713 Binary files /dev/null and b/instruments/FM/synth/FM_noname1.fui differ diff --git a/instruments/FM/synth/Guess.dmp b/instruments/FM/synth/Guess.dmp new file mode 100644 index 00000000..be35eb8c Binary files /dev/null and b/instruments/FM/synth/Guess.dmp differ diff --git a/instruments/FM/synth/Klax.dmp b/instruments/FM/synth/Klax.dmp new file mode 100644 index 00000000..5ad6a10f Binary files /dev/null and b/instruments/FM/synth/Klax.dmp differ diff --git a/instruments/FM/synth/Kyd.dmp b/instruments/FM/synth/Kyd.dmp new file mode 100644 index 00000000..4506530e Binary files /dev/null and b/instruments/FM/synth/Kyd.dmp differ diff --git a/instruments/FM/synth/Love.dmp b/instruments/FM/synth/Love.dmp new file mode 100644 index 00000000..75ff867c Binary files /dev/null and b/instruments/FM/synth/Love.dmp differ diff --git a/instruments/FM/synth/Octave_Square.fui b/instruments/FM/synth/Octave_Square.fui new file mode 100644 index 00000000..446ad121 Binary files /dev/null and b/instruments/FM/synth/Octave_Square.fui differ diff --git a/instruments/FM/synth/Octave_Square2.fui b/instruments/FM/synth/Octave_Square2.fui new file mode 100644 index 00000000..f6fcea32 Binary files /dev/null and b/instruments/FM/synth/Octave_Square2.fui differ diff --git a/instruments/FM/synth/Reverby_Live_Chords.fui b/instruments/FM/synth/Reverby_Live_Chords.fui new file mode 100644 index 00000000..cd2f960c Binary files /dev/null and b/instruments/FM/synth/Reverby_Live_Chords.fui differ diff --git a/instruments/FM/synth/RustEcho.dmp b/instruments/FM/synth/RustEcho.dmp new file mode 100644 index 00000000..d4fb916b Binary files /dev/null and b/instruments/FM/synth/RustEcho.dmp differ diff --git a/instruments/FM/synth/Saw.dmp b/instruments/FM/synth/Saw.dmp new file mode 100644 index 00000000..e75a1603 Binary files /dev/null and b/instruments/FM/synth/Saw.dmp differ diff --git a/instruments/FM/synth/Signal.dmp b/instruments/FM/synth/Signal.dmp new file mode 100644 index 00000000..d5bfccee Binary files /dev/null and b/instruments/FM/synth/Signal.dmp differ diff --git a/instruments/FM/synth/Space Cowboy.dmp b/instruments/FM/synth/Space Cowboy.dmp new file mode 100644 index 00000000..c17037a2 Binary files /dev/null and b/instruments/FM/synth/Space Cowboy.dmp differ diff --git a/instruments/FM/synth/Space Shredder.dmp b/instruments/FM/synth/Space Shredder.dmp new file mode 100644 index 00000000..c9cbbc9d Binary files /dev/null and b/instruments/FM/synth/Space Shredder.dmp differ diff --git a/instruments/FM/synth/Square.dmp b/instruments/FM/synth/Square.dmp new file mode 100644 index 00000000..11e1f822 Binary files /dev/null and b/instruments/FM/synth/Square.dmp differ diff --git a/instruments/FM/synth/Square.fui b/instruments/FM/synth/Square.fui new file mode 100644 index 00000000..cf5d47bf Binary files /dev/null and b/instruments/FM/synth/Square.fui differ diff --git a/instruments/FM/synth/Squid.dmp b/instruments/FM/synth/Squid.dmp new file mode 100644 index 00000000..4635260a Binary files /dev/null and b/instruments/FM/synth/Squid.dmp differ diff --git a/instruments/FM/synth/Thin.dmp b/instruments/FM/synth/Thin.dmp new file mode 100644 index 00000000..b4ddac9e Binary files /dev/null and b/instruments/FM/synth/Thin.dmp differ diff --git a/instruments/FM/synth/Tri.dmp b/instruments/FM/synth/Tri.dmp new file mode 100644 index 00000000..37c05b3e Binary files /dev/null and b/instruments/FM/synth/Tri.dmp differ diff --git a/instruments/FM/synth/Twoah.dmp b/instruments/FM/synth/Twoah.dmp new file mode 100644 index 00000000..4059578a Binary files /dev/null and b/instruments/FM/synth/Twoah.dmp differ diff --git a/instruments/FM/synth/[1OPHigh]Saw.dmp b/instruments/FM/synth/[1OPHigh]Saw.dmp new file mode 100644 index 00000000..d62ce809 Binary files /dev/null and b/instruments/FM/synth/[1OPHigh]Saw.dmp differ diff --git a/instruments/FM/synth/[2OPHigh]Filtered Square.dmp b/instruments/FM/synth/[2OPHigh]Filtered Square.dmp new file mode 100644 index 00000000..cd2cbd3b Binary files /dev/null and b/instruments/FM/synth/[2OPHigh]Filtered Square.dmp differ diff --git a/instruments/FM/synth/ass-loud lead.dmp b/instruments/FM/synth/ass-loud lead.dmp new file mode 100644 index 00000000..328f7987 Binary files /dev/null and b/instruments/FM/synth/ass-loud lead.dmp differ diff --git a/instruments/FM/synth/cool leaed.dmp b/instruments/FM/synth/cool leaed.dmp new file mode 100644 index 00000000..4f226d63 Binary files /dev/null and b/instruments/FM/synth/cool leaed.dmp differ diff --git a/instruments/FM/synth/early 80s bs.dmp b/instruments/FM/synth/early 80s bs.dmp new file mode 100644 index 00000000..74e0440b Binary files /dev/null and b/instruments/FM/synth/early 80s bs.dmp differ diff --git a/instruments/FM/synth/fm_grinder.fui b/instruments/FM/synth/fm_grinder.fui new file mode 100644 index 00000000..21dea590 Binary files /dev/null and b/instruments/FM/synth/fm_grinder.fui differ diff --git a/instruments/FM/synth/fm_horror.fui b/instruments/FM/synth/fm_horror.fui new file mode 100644 index 00000000..029cfc5b Binary files /dev/null and b/instruments/FM/synth/fm_horror.fui differ diff --git a/instruments/FM/synth/fm_noname2.fui b/instruments/FM/synth/fm_noname2.fui new file mode 100644 index 00000000..6c5cae74 Binary files /dev/null and b/instruments/FM/synth/fm_noname2.fui differ diff --git a/instruments/FM/synth/fm_noname3.fui b/instruments/FM/synth/fm_noname3.fui new file mode 100644 index 00000000..faedb995 Binary files /dev/null and b/instruments/FM/synth/fm_noname3.fui differ diff --git a/instruments/FM/synth/fm_noname4.fui b/instruments/FM/synth/fm_noname4.fui new file mode 100644 index 00000000..68f0c66b Binary files /dev/null and b/instruments/FM/synth/fm_noname4.fui differ diff --git a/instruments/FM/synth/fm_noname5.fui b/instruments/FM/synth/fm_noname5.fui new file mode 100644 index 00000000..87ba4761 Binary files /dev/null and b/instruments/FM/synth/fm_noname5.fui differ diff --git a/instruments/FM/synth/fm_noname6.fui b/instruments/FM/synth/fm_noname6.fui new file mode 100644 index 00000000..35396974 Binary files /dev/null and b/instruments/FM/synth/fm_noname6.fui differ diff --git a/instruments/FM/synth/fm_noname7.fui b/instruments/FM/synth/fm_noname7.fui new file mode 100644 index 00000000..e88b4d34 Binary files /dev/null and b/instruments/FM/synth/fm_noname7.fui differ diff --git a/instruments/FM/synth/growl.fui b/instruments/FM/synth/growl.fui new file mode 100644 index 00000000..9be412ad Binary files /dev/null and b/instruments/FM/synth/growl.fui differ diff --git a/instruments/FM/synth/huh.dmp b/instruments/FM/synth/huh.dmp new file mode 100644 index 00000000..1124f9d5 Binary files /dev/null and b/instruments/FM/synth/huh.dmp differ diff --git a/instruments/FM/synth/mystic cave lead.dmp b/instruments/FM/synth/mystic cave lead.dmp new file mode 100644 index 00000000..86ec421c Binary files /dev/null and b/instruments/FM/synth/mystic cave lead.dmp differ diff --git a/instruments/FM/synth/saw + whatever the fuck.dmp b/instruments/FM/synth/saw + whatever the fuck.dmp new file mode 100644 index 00000000..6c23682c Binary files /dev/null and b/instruments/FM/synth/saw + whatever the fuck.dmp differ diff --git a/instruments/FM/synth/sawtooth.dmp b/instruments/FM/synth/sawtooth.dmp new file mode 100644 index 00000000..7841e21f Binary files /dev/null and b/instruments/FM/synth/sawtooth.dmp differ diff --git a/instruments/FM/synth/snes-ish square.dmp b/instruments/FM/synth/snes-ish square.dmp new file mode 100644 index 00000000..e984ed15 Binary files /dev/null and b/instruments/FM/synth/snes-ish square.dmp differ diff --git a/instruments/FM/synth/sonic spinball toxic caves thingy.dmp b/instruments/FM/synth/sonic spinball toxic caves thingy.dmp new file mode 100644 index 00000000..aa6cc88f Binary files /dev/null and b/instruments/FM/synth/sonic spinball toxic caves thingy.dmp differ diff --git a/instruments/FM/synth/whut.dmp b/instruments/FM/synth/whut.dmp new file mode 100644 index 00000000..94b3e55b Binary files /dev/null and b/instruments/FM/synth/whut.dmp differ diff --git a/instruments/FM/synth/yrwywryw.dmp b/instruments/FM/synth/yrwywryw.dmp new file mode 100644 index 00000000..15127fe6 Binary files /dev/null and b/instruments/FM/synth/yrwywryw.dmp differ diff --git a/instruments/FM/tfilib/banjo.tfi b/instruments/FM/tfilib/banjo.tfi new file mode 100644 index 00000000..7c170188 Binary files /dev/null and b/instruments/FM/tfilib/banjo.tfi differ diff --git a/instruments/FM/tfilib/bass.tfi b/instruments/FM/tfilib/bass.tfi new file mode 100644 index 00000000..fe5504bd Binary files /dev/null and b/instruments/FM/tfilib/bass.tfi differ diff --git a/instruments/FM/tfilib/bell.tfi b/instruments/FM/tfilib/bell.tfi new file mode 100644 index 00000000..1ecdb0e4 Binary files /dev/null and b/instruments/FM/tfilib/bell.tfi differ diff --git a/instruments/FM/tfilib/cymbal.tfi b/instruments/FM/tfilib/cymbal.tfi new file mode 100644 index 00000000..2abe7ac0 Binary files /dev/null and b/instruments/FM/tfilib/cymbal.tfi differ diff --git a/instruments/FM/tfilib/distbass.tfi b/instruments/FM/tfilib/distbass.tfi new file mode 100644 index 00000000..6e93bb77 Binary files /dev/null and b/instruments/FM/tfilib/distbass.tfi differ diff --git a/instruments/FM/tfilib/distguit.tfi b/instruments/FM/tfilib/distguit.tfi new file mode 100644 index 00000000..5929926f Binary files /dev/null and b/instruments/FM/tfilib/distguit.tfi differ diff --git a/instruments/FM/tfilib/distslap.tfi b/instruments/FM/tfilib/distslap.tfi new file mode 100644 index 00000000..44b4d1f0 Binary files /dev/null and b/instruments/FM/tfilib/distslap.tfi differ diff --git a/instruments/FM/tfilib/elecbass.tfi b/instruments/FM/tfilib/elecbass.tfi new file mode 100644 index 00000000..2d275444 Binary files /dev/null and b/instruments/FM/tfilib/elecbass.tfi differ diff --git a/instruments/FM/tfilib/fifths.tfi b/instruments/FM/tfilib/fifths.tfi new file mode 100644 index 00000000..f7a1a0b3 Binary files /dev/null and b/instruments/FM/tfilib/fifths.tfi differ diff --git a/instruments/FM/tfilib/flute.tfi b/instruments/FM/tfilib/flute.tfi new file mode 100644 index 00000000..d98b6847 Binary files /dev/null and b/instruments/FM/tfilib/flute.tfi differ diff --git a/instruments/FM/tfilib/guitar.tfi b/instruments/FM/tfilib/guitar.tfi new file mode 100644 index 00000000..27b6206c Binary files /dev/null and b/instruments/FM/tfilib/guitar.tfi differ diff --git a/instruments/FM/tfilib/harp.tfi b/instruments/FM/tfilib/harp.tfi new file mode 100644 index 00000000..c7fd62be Binary files /dev/null and b/instruments/FM/tfilib/harp.tfi differ diff --git a/instruments/FM/tfilib/harpsich.tfi b/instruments/FM/tfilib/harpsich.tfi new file mode 100644 index 00000000..a64a86ca Binary files /dev/null and b/instruments/FM/tfilib/harpsich.tfi differ diff --git a/instruments/FM/tfilib/hithat.tfi b/instruments/FM/tfilib/hithat.tfi new file mode 100644 index 00000000..67559890 Binary files /dev/null and b/instruments/FM/tfilib/hithat.tfi differ diff --git a/instruments/FM/tfilib/kick.tfi b/instruments/FM/tfilib/kick.tfi new file mode 100644 index 00000000..fe0ebba6 Binary files /dev/null and b/instruments/FM/tfilib/kick.tfi differ diff --git a/instruments/FM/tfilib/list.txt b/instruments/FM/tfilib/list.txt new file mode 100644 index 00000000..78d9c5b9 --- /dev/null +++ b/instruments/FM/tfilib/list.txt @@ -0,0 +1,42 @@ +Middle column is recommended octave range or pitch. +Octaves are counted from 1 (i.e. full range is 1~8) + +piano ...... 3~8 ... acoustic piano +tackpian ... 3~8 ... tack piano +sofpiano ... 3~8 ... soft synth piano +toypiano ... 3~8 ... toy piano +organ ...... 3~8 ... organ +marimba .... 4~7 ... marimba +harp ....... 5~8 ... harp +lyre ....... 5~8 ... lyre +harpsich ... 5~8 ... harpsichord +bell ....... 6~8 ... bell +synbell .... 6~8 ... synth bell +flute ...... 5~8 ... flute (recorder) +ocarina .... 5~8 ... ocarina +trumpet .... 4~6 ... trumpet +sax ........ 3~5 ... saxophone +guitar ..... 3~6 ... acoustic guitar +banjo ...... 3~6 ... banjo +bass ....... 3~4 ... acoustic bass +elecbass ... 3~4 ... electric bass +slapbass ... 3~4 ... slap bass +distguit ... 3~4 ... distortion guitar +distbass ... 3~4 ... distortion bass +distslap ... 3~4 ... distortion slap bass +square ..... 1~8 ... square wave (sharp) +softsqr .... 1~8 ... square wave (softer) +sawtooth ... 1~8 ... sawtooth wave (sharp) +softsaw .... 1~8 ... sawtooth wave (softer) +triangle ... 1~8 ... triangle wave +sine ....... 1~8 ... sine wave +neslike .... 1~8 ... "NES"-ish sound (25.0% square-like) +fifths ..... 1~8 ... perfect fifth synth +snare ...... c-2 ... snare drum +kick ....... c-2 ... kick drum +cymbal ..... a-6 ... cymbals +hithat ..... a-6 ... hit-hats +timpani1 ... c-1 ... timpani +timpani2 ... c-1 ... timpani (deeper) +wooddrum ... c-2 ... wooden drum +steldrum ... c-2 ... steel drum diff --git a/instruments/FM/tfilib/lyre.tfi b/instruments/FM/tfilib/lyre.tfi new file mode 100644 index 00000000..b984544c Binary files /dev/null and b/instruments/FM/tfilib/lyre.tfi differ diff --git a/instruments/FM/tfilib/marimba.tfi b/instruments/FM/tfilib/marimba.tfi new file mode 100644 index 00000000..c55c888a Binary files /dev/null and b/instruments/FM/tfilib/marimba.tfi differ diff --git a/instruments/FM/tfilib/neslike.tfi b/instruments/FM/tfilib/neslike.tfi new file mode 100644 index 00000000..663ae2d3 Binary files /dev/null and b/instruments/FM/tfilib/neslike.tfi differ diff --git a/instruments/FM/tfilib/ocarina.tfi b/instruments/FM/tfilib/ocarina.tfi new file mode 100644 index 00000000..739774a9 Binary files /dev/null and b/instruments/FM/tfilib/ocarina.tfi differ diff --git a/instruments/FM/tfilib/organ.tfi b/instruments/FM/tfilib/organ.tfi new file mode 100644 index 00000000..aa365241 Binary files /dev/null and b/instruments/FM/tfilib/organ.tfi differ diff --git a/instruments/FM/tfilib/piano.tfi b/instruments/FM/tfilib/piano.tfi new file mode 100644 index 00000000..fda5e974 Binary files /dev/null and b/instruments/FM/tfilib/piano.tfi differ diff --git a/instruments/FM/tfilib/sawtooth.tfi b/instruments/FM/tfilib/sawtooth.tfi new file mode 100644 index 00000000..a4263e52 Binary files /dev/null and b/instruments/FM/tfilib/sawtooth.tfi differ diff --git a/instruments/FM/tfilib/sax.tfi b/instruments/FM/tfilib/sax.tfi new file mode 100644 index 00000000..78ce0359 Binary files /dev/null and b/instruments/FM/tfilib/sax.tfi differ diff --git a/instruments/FM/tfilib/sine.tfi b/instruments/FM/tfilib/sine.tfi new file mode 100644 index 00000000..e41ef03f Binary files /dev/null and b/instruments/FM/tfilib/sine.tfi differ diff --git a/instruments/FM/tfilib/slapbass.tfi b/instruments/FM/tfilib/slapbass.tfi new file mode 100644 index 00000000..47eb03ed Binary files /dev/null and b/instruments/FM/tfilib/slapbass.tfi differ diff --git a/instruments/FM/tfilib/snare.tfi b/instruments/FM/tfilib/snare.tfi new file mode 100644 index 00000000..2148b239 Binary files /dev/null and b/instruments/FM/tfilib/snare.tfi differ diff --git a/instruments/FM/tfilib/sofpiano.tfi b/instruments/FM/tfilib/sofpiano.tfi new file mode 100644 index 00000000..462f88cf Binary files /dev/null and b/instruments/FM/tfilib/sofpiano.tfi differ diff --git a/instruments/FM/tfilib/softsaw.tfi b/instruments/FM/tfilib/softsaw.tfi new file mode 100644 index 00000000..18c1cf9d Binary files /dev/null and b/instruments/FM/tfilib/softsaw.tfi differ diff --git a/instruments/FM/tfilib/softsqr.tfi b/instruments/FM/tfilib/softsqr.tfi new file mode 100644 index 00000000..c5b87811 Binary files /dev/null and b/instruments/FM/tfilib/softsqr.tfi differ diff --git a/instruments/FM/tfilib/square.tfi b/instruments/FM/tfilib/square.tfi new file mode 100644 index 00000000..40777a94 Binary files /dev/null and b/instruments/FM/tfilib/square.tfi differ diff --git a/instruments/FM/tfilib/steldrum.tfi b/instruments/FM/tfilib/steldrum.tfi new file mode 100644 index 00000000..e953684a Binary files /dev/null and b/instruments/FM/tfilib/steldrum.tfi differ diff --git a/instruments/FM/tfilib/synbell.tfi b/instruments/FM/tfilib/synbell.tfi new file mode 100644 index 00000000..5be44e6b Binary files /dev/null and b/instruments/FM/tfilib/synbell.tfi differ diff --git a/instruments/FM/tfilib/tackpian.tfi b/instruments/FM/tfilib/tackpian.tfi new file mode 100644 index 00000000..9a98fe2a Binary files /dev/null and b/instruments/FM/tfilib/tackpian.tfi differ diff --git a/instruments/FM/tfilib/timpani1.tfi b/instruments/FM/tfilib/timpani1.tfi new file mode 100644 index 00000000..acfbf7cd Binary files /dev/null and b/instruments/FM/tfilib/timpani1.tfi differ diff --git a/instruments/FM/tfilib/timpani2.tfi b/instruments/FM/tfilib/timpani2.tfi new file mode 100644 index 00000000..8fe446f4 Binary files /dev/null and b/instruments/FM/tfilib/timpani2.tfi differ diff --git a/instruments/FM/tfilib/toypiano.tfi b/instruments/FM/tfilib/toypiano.tfi new file mode 100644 index 00000000..8bb51789 Binary files /dev/null and b/instruments/FM/tfilib/toypiano.tfi differ diff --git a/instruments/FM/tfilib/triangle.tfi b/instruments/FM/tfilib/triangle.tfi new file mode 100644 index 00000000..abf0ed31 Binary files /dev/null and b/instruments/FM/tfilib/triangle.tfi differ diff --git a/instruments/FM/tfilib/trumpet.tfi b/instruments/FM/tfilib/trumpet.tfi new file mode 100644 index 00000000..a5a9c6a8 Binary files /dev/null and b/instruments/FM/tfilib/trumpet.tfi differ diff --git a/instruments/FM/tfilib/wooddrum.tfi b/instruments/FM/tfilib/wooddrum.tfi new file mode 100644 index 00000000..a661547e Binary files /dev/null and b/instruments/FM/tfilib/wooddrum.tfi differ diff --git a/instruments/FM/wind/(CH3) Double Clarinet.dmp b/instruments/FM/wind/(CH3) Double Clarinet.dmp new file mode 100644 index 00000000..158b45a1 Binary files /dev/null and b/instruments/FM/wind/(CH3) Double Clarinet.dmp differ diff --git a/instruments/FM/wind/(CH3) Double Flute.dmp b/instruments/FM/wind/(CH3) Double Flute.dmp new file mode 100644 index 00000000..697ab487 Binary files /dev/null and b/instruments/FM/wind/(CH3) Double Flute.dmp differ diff --git a/instruments/FM/wind/(CH3) Double Woodwind.dmp b/instruments/FM/wind/(CH3) Double Woodwind.dmp new file mode 100644 index 00000000..d348a2c4 Binary files /dev/null and b/instruments/FM/wind/(CH3) Double Woodwind.dmp differ diff --git a/instruments/FM/wind/(CH3) Quadruple Ocarina.dmp b/instruments/FM/wind/(CH3) Quadruple Ocarina.dmp new file mode 100644 index 00000000..66d69334 Binary files /dev/null and b/instruments/FM/wind/(CH3) Quadruple Ocarina.dmp differ diff --git a/instruments/FM/wind/(GEN) Clarinet.dmp b/instruments/FM/wind/(GEN) Clarinet.dmp new file mode 100644 index 00000000..ad0ea8e6 Binary files /dev/null and b/instruments/FM/wind/(GEN) Clarinet.dmp differ diff --git a/instruments/FM/wind/(GEN) Didgeridoo.dmp b/instruments/FM/wind/(GEN) Didgeridoo.dmp new file mode 100644 index 00000000..dd510a4e Binary files /dev/null and b/instruments/FM/wind/(GEN) Didgeridoo.dmp differ diff --git a/instruments/FM/wind/(GEN) Flute.dmp b/instruments/FM/wind/(GEN) Flute.dmp new file mode 100644 index 00000000..dadfe964 Binary files /dev/null and b/instruments/FM/wind/(GEN) Flute.dmp differ diff --git a/instruments/FM/wind/(GEN) Forest Flute.dmp b/instruments/FM/wind/(GEN) Forest Flute.dmp new file mode 100644 index 00000000..98a711e6 Binary files /dev/null and b/instruments/FM/wind/(GEN) Forest Flute.dmp differ diff --git a/instruments/FM/wind/(GEN) Harmonica.dmp b/instruments/FM/wind/(GEN) Harmonica.dmp new file mode 100644 index 00000000..7b17b15c Binary files /dev/null and b/instruments/FM/wind/(GEN) Harmonica.dmp differ diff --git a/instruments/FM/wind/(GEN) Ocarina.dmp b/instruments/FM/wind/(GEN) Ocarina.dmp new file mode 100644 index 00000000..74527df5 Binary files /dev/null and b/instruments/FM/wind/(GEN) Ocarina.dmp differ diff --git a/instruments/FM/wind/(GEN) Saxophone EX.dmp b/instruments/FM/wind/(GEN) Saxophone EX.dmp new file mode 100644 index 00000000..acd8695d Binary files /dev/null and b/instruments/FM/wind/(GEN) Saxophone EX.dmp differ diff --git a/instruments/FM/wind/(GEN) Saxophone.dmp b/instruments/FM/wind/(GEN) Saxophone.dmp new file mode 100644 index 00000000..efe3e79c Binary files /dev/null and b/instruments/FM/wind/(GEN) Saxophone.dmp differ diff --git a/instruments/FM/wind/B'arinet.dmp b/instruments/FM/wind/B'arinet.dmp new file mode 100644 index 00000000..2d249ca6 Binary files /dev/null and b/instruments/FM/wind/B'arinet.dmp differ diff --git a/instruments/FM/wind/Clarinet.dmp b/instruments/FM/wind/Clarinet.dmp new file mode 100644 index 00000000..4fcc3c24 Binary files /dev/null and b/instruments/FM/wind/Clarinet.dmp differ diff --git a/instruments/FM/wind/English Horn.dmp b/instruments/FM/wind/English Horn.dmp new file mode 100644 index 00000000..71b17ce6 Binary files /dev/null and b/instruments/FM/wind/English Horn.dmp differ diff --git a/instruments/FM/wind/FM_4OP_-_Tenor_Sax.fui b/instruments/FM/wind/FM_4OP_-_Tenor_Sax.fui new file mode 100644 index 00000000..be9f5eeb Binary files /dev/null and b/instruments/FM/wind/FM_4OP_-_Tenor_Sax.fui differ diff --git a/instruments/FM/wind/Flute.dmp b/instruments/FM/wind/Flute.dmp new file mode 100644 index 00000000..94635894 Binary files /dev/null and b/instruments/FM/wind/Flute.dmp differ diff --git a/instruments/FM/wind/Harmonica.dmp b/instruments/FM/wind/Harmonica.dmp new file mode 100644 index 00000000..6514e259 Binary files /dev/null and b/instruments/FM/wind/Harmonica.dmp differ diff --git a/instruments/FM/wind/OPNx_BottleBlow.fui b/instruments/FM/wind/OPNx_BottleBlow.fui new file mode 100644 index 00000000..d4923ccf Binary files /dev/null and b/instruments/FM/wind/OPNx_BottleBlow.fui differ diff --git a/instruments/FM/wind/Pan Flute.dmp b/instruments/FM/wind/Pan Flute.dmp new file mode 100644 index 00000000..933c6129 Binary files /dev/null and b/instruments/FM/wind/Pan Flute.dmp differ diff --git a/instruments/FM/wind/bassoon.fui b/instruments/FM/wind/bassoon.fui new file mode 100644 index 00000000..7e71d1b6 Binary files /dev/null and b/instruments/FM/wind/bassoon.fui differ diff --git a/instruments/FM/wind/bassoon_clarinet.fui b/instruments/FM/wind/bassoon_clarinet.fui new file mode 100644 index 00000000..730dd323 Binary files /dev/null and b/instruments/FM/wind/bassoon_clarinet.fui differ diff --git a/instruments/FM/wind/fm_pipeflute.fui b/instruments/FM/wind/fm_pipeflute.fui new file mode 100644 index 00000000..d397017a Binary files /dev/null and b/instruments/FM/wind/fm_pipeflute.fui differ diff --git a/instruments/FM/wind/woodwind_00.fui b/instruments/FM/wind/woodwind_00.fui new file mode 100644 index 00000000..3f3f6db9 Binary files /dev/null and b/instruments/FM/wind/woodwind_00.fui differ diff --git a/instruments/OPL/4op Bass.fui b/instruments/OPL/4op Bass.fui new file mode 100644 index 00000000..0432505d Binary files /dev/null and b/instruments/OPL/4op Bass.fui differ diff --git a/instruments/OPL/Chorus Organ.fui b/instruments/OPL/Chorus Organ.fui new file mode 100644 index 00000000..14a418d0 Binary files /dev/null and b/instruments/OPL/Chorus Organ.fui differ diff --git a/instruments/OPL/Closed Hi-hat.fui b/instruments/OPL/Closed Hi-hat.fui new file mode 100644 index 00000000..26267896 Binary files /dev/null and b/instruments/OPL/Closed Hi-hat.fui differ diff --git a/instruments/OPL/Crystal.fui b/instruments/OPL/Crystal.fui new file mode 100644 index 00000000..490ba4b7 Binary files /dev/null and b/instruments/OPL/Crystal.fui differ diff --git a/instruments/OPL/DX7 Electric Piano.fui b/instruments/OPL/DX7 Electric Piano.fui new file mode 100644 index 00000000..743a1f0f Binary files /dev/null and b/instruments/OPL/DX7 Electric Piano.fui differ diff --git a/instruments/OPL/Distortion Guitar.fui b/instruments/OPL/Distortion Guitar.fui new file mode 100644 index 00000000..8bbe234a Binary files /dev/null and b/instruments/OPL/Distortion Guitar.fui differ diff --git a/instruments/OPL/Guitar 2.fui b/instruments/OPL/Guitar 2.fui new file mode 100644 index 00000000..78b590cc Binary files /dev/null and b/instruments/OPL/Guitar 2.fui differ diff --git a/instruments/OPL/Guitar.fui b/instruments/OPL/Guitar.fui new file mode 100644 index 00000000..09e0c423 Binary files /dev/null and b/instruments/OPL/Guitar.fui differ diff --git a/instruments/OPL/Low Overdriven Guitar (Sine Carrier).fui b/instruments/OPL/Low Overdriven Guitar (Sine Carrier).fui new file mode 100644 index 00000000..9fae9177 Binary files /dev/null and b/instruments/OPL/Low Overdriven Guitar (Sine Carrier).fui differ diff --git a/instruments/OPL/Low Overdriven Guitar (Square Carrier).fui b/instruments/OPL/Low Overdriven Guitar (Square Carrier).fui new file mode 100644 index 00000000..dfb5eba1 Binary files /dev/null and b/instruments/OPL/Low Overdriven Guitar (Square Carrier).fui differ diff --git a/instruments/OPL/OPL-ClickKik.fui b/instruments/OPL/OPL-ClickKik.fui new file mode 100644 index 00000000..979bb1f8 Binary files /dev/null and b/instruments/OPL/OPL-ClickKik.fui differ diff --git a/instruments/OPL/OPL-RhythmTemplate.fui b/instruments/OPL/OPL-RhythmTemplate.fui new file mode 100644 index 00000000..5586d4e9 Binary files /dev/null and b/instruments/OPL/OPL-RhythmTemplate.fui differ diff --git a/instruments/OPL/OPL-RollingDrum.fui b/instruments/OPL/OPL-RollingDrum.fui new file mode 100644 index 00000000..541b53df Binary files /dev/null and b/instruments/OPL/OPL-RollingDrum.fui differ diff --git a/instruments/OPL/OPL1_Ride_Closed.fui b/instruments/OPL/OPL1_Ride_Closed.fui new file mode 100644 index 00000000..3e9c139b Binary files /dev/null and b/instruments/OPL/OPL1_Ride_Closed.fui differ diff --git a/instruments/OPL/OPL2-TwinkleBlare.fui b/instruments/OPL/OPL2-TwinkleBlare.fui new file mode 100644 index 00000000..c6c22b2b Binary files /dev/null and b/instruments/OPL/OPL2-TwinkleBlare.fui differ diff --git a/instruments/OPL/OPL3-4opbrass11.fui b/instruments/OPL/OPL3-4opbrass11.fui new file mode 100644 index 00000000..b9e8f2aa Binary files /dev/null and b/instruments/OPL/OPL3-4opbrass11.fui differ diff --git a/instruments/OPL/OPL3-4opharmonica.fui b/instruments/OPL/OPL3-4opharmonica.fui new file mode 100644 index 00000000..2112f45a Binary files /dev/null and b/instruments/OPL/OPL3-4opharmonica.fui differ diff --git a/instruments/OPL/OPL3-4opsitar.fui b/instruments/OPL/OPL3-4opsitar.fui new file mode 100644 index 00000000..aec5fc60 Binary files /dev/null and b/instruments/OPL/OPL3-4opsitar.fui differ diff --git a/instruments/OPL/OPL3-FakeFalcomGuitar.fui b/instruments/OPL/OPL3-FakeFalcomGuitar.fui new file mode 100644 index 00000000..f18286ee Binary files /dev/null and b/instruments/OPL/OPL3-FakeFalcomGuitar.fui differ diff --git a/instruments/OPL/OPL3-FakeFalcomString.fui b/instruments/OPL/OPL3-FakeFalcomString.fui new file mode 100644 index 00000000..4ab83803 Binary files /dev/null and b/instruments/OPL/OPL3-FakeFalcomString.fui differ diff --git a/instruments/OPL/OPL3-GabberKick.fui b/instruments/OPL/OPL3-GabberKick.fui new file mode 100644 index 00000000..ab55ac47 Binary files /dev/null and b/instruments/OPL/OPL3-GabberKick.fui differ diff --git a/instruments/OPL/OPL3-GabberKick3-60hz-.fui b/instruments/OPL/OPL3-GabberKick3-60hz-.fui new file mode 100644 index 00000000..961039f5 Binary files /dev/null and b/instruments/OPL/OPL3-GabberKick3-60hz-.fui differ diff --git a/instruments/OPL/OPL3-MajorSquare.fui b/instruments/OPL/OPL3-MajorSquare.fui new file mode 100644 index 00000000..e5beed0f Binary files /dev/null and b/instruments/OPL/OPL3-MajorSquare.fui differ diff --git a/instruments/OPL/OPL3-PWM.fui b/instruments/OPL/OPL3-PWM.fui new file mode 100644 index 00000000..fd8f925a Binary files /dev/null and b/instruments/OPL/OPL3-PWM.fui differ diff --git a/instruments/OPL/OPL3-PeriodicNoise.fui b/instruments/OPL/OPL3-PeriodicNoise.fui new file mode 100644 index 00000000..9db8d477 Binary files /dev/null and b/instruments/OPL/OPL3-PeriodicNoise.fui differ diff --git a/instruments/OPL/OPL3-SegaBass.fui b/instruments/OPL/OPL3-SegaBass.fui new file mode 100644 index 00000000..3694ec42 Binary files /dev/null and b/instruments/OPL/OPL3-SegaBass.fui differ diff --git a/instruments/OPL/OPL3-SyncDuck.fui b/instruments/OPL/OPL3-SyncDuck.fui new file mode 100644 index 00000000..0f3b0a7e Binary files /dev/null and b/instruments/OPL/OPL3-SyncDuck.fui differ diff --git a/instruments/OPL/OPL3-TwinkleKey.fui b/instruments/OPL/OPL3-TwinkleKey.fui new file mode 100644 index 00000000..71add431 Binary files /dev/null and b/instruments/OPL/OPL3-TwinkleKey.fui differ diff --git a/instruments/OPL/OPL3-clap.fui b/instruments/OPL/OPL3-clap.fui new file mode 100644 index 00000000..882c9fb0 Binary files /dev/null and b/instruments/OPL/OPL3-clap.fui differ diff --git a/instruments/OPL/OPL3-easyhoova.fui b/instruments/OPL/OPL3-easyhoova.fui new file mode 100644 index 00000000..2f0aa153 Binary files /dev/null and b/instruments/OPL/OPL3-easyhoova.fui differ diff --git a/instruments/OPL/OPL3_-_C64_like_sqr_snare.fui b/instruments/OPL/OPL3_-_C64_like_sqr_snare.fui new file mode 100644 index 00000000..19d85f5a Binary files /dev/null and b/instruments/OPL/OPL3_-_C64_like_sqr_snare.fui differ diff --git a/instruments/OPL/OPL3_-_PWM_Lead.fui b/instruments/OPL/OPL3_-_PWM_Lead.fui new file mode 100644 index 00000000..b4bdf2f2 Binary files /dev/null and b/instruments/OPL/OPL3_-_PWM_Lead.fui differ diff --git a/instruments/OPL/OPL3_BigBass.fui b/instruments/OPL/OPL3_BigBass.fui new file mode 100644 index 00000000..dc477e4e Binary files /dev/null and b/instruments/OPL/OPL3_BigBass.fui differ diff --git a/instruments/OPL/OPL3_C64_Kick_or_Snare_Start.fui b/instruments/OPL/OPL3_C64_Kick_or_Snare_Start.fui new file mode 100644 index 00000000..f3f77153 Binary files /dev/null and b/instruments/OPL/OPL3_C64_Kick_or_Snare_Start.fui differ diff --git a/instruments/OPL/OPL3_C64_Snare_Carry.fui b/instruments/OPL/OPL3_C64_Snare_Carry.fui new file mode 100644 index 00000000..e0c755a3 Binary files /dev/null and b/instruments/OPL/OPL3_C64_Snare_Carry.fui differ diff --git a/instruments/OPL/OPL3_Slap_Bass.fui b/instruments/OPL/OPL3_Slap_Bass.fui new file mode 100644 index 00000000..4de8aac8 Binary files /dev/null and b/instruments/OPL/OPL3_Slap_Bass.fui differ diff --git a/instruments/OPL/OPL3_Springybass.fui b/instruments/OPL/OPL3_Springybass.fui new file mode 100644 index 00000000..8f3ce272 Binary files /dev/null and b/instruments/OPL/OPL3_Springybass.fui differ diff --git a/instruments/OPL/OPLL_Clickik.fui b/instruments/OPL/OPLL_Clickik.fui new file mode 100644 index 00000000..5cc7b3bb Binary files /dev/null and b/instruments/OPL/OPLL_Clickik.fui differ diff --git a/instruments/OPL/OPL_GoodPanFlute.fui b/instruments/OPL/OPL_GoodPanFlute.fui new file mode 100644 index 00000000..20b917b6 Binary files /dev/null and b/instruments/OPL/OPL_GoodPanFlute.fui differ diff --git a/instruments/OPL/OPL_KoreanSquare.fui b/instruments/OPL/OPL_KoreanSquare.fui new file mode 100644 index 00000000..99aa0be1 Binary files /dev/null and b/instruments/OPL/OPL_KoreanSquare.fui differ diff --git a/instruments/OPL/OPL_SupersawUnit.fui b/instruments/OPL/OPL_SupersawUnit.fui new file mode 100644 index 00000000..114ed507 Binary files /dev/null and b/instruments/OPL/OPL_SupersawUnit.fui differ diff --git a/instruments/OPL/OPL_horn.fui b/instruments/OPL/OPL_horn.fui new file mode 100644 index 00000000..38313a62 Binary files /dev/null and b/instruments/OPL/OPL_horn.fui differ diff --git a/instruments/OPL/OPM-lowpitch-fmclap.fui b/instruments/OPL/OPM-lowpitch-fmclap.fui new file mode 100644 index 00000000..427c337b Binary files /dev/null and b/instruments/OPL/OPM-lowpitch-fmclap.fui differ diff --git a/instruments/OPL/Open Hi-hat.fui b/instruments/OPL/Open Hi-hat.fui new file mode 100644 index 00000000..22b18f8d Binary files /dev/null and b/instruments/OPL/Open Hi-hat.fui differ diff --git a/instruments/OPL/Overdriven Guitar.fui b/instruments/OPL/Overdriven Guitar.fui new file mode 100644 index 00000000..20330f7a Binary files /dev/null and b/instruments/OPL/Overdriven Guitar.fui differ diff --git a/instruments/OPL/Snare Drum 2.fui b/instruments/OPL/Snare Drum 2.fui new file mode 100644 index 00000000..bdb3ed9a Binary files /dev/null and b/instruments/OPL/Snare Drum 2.fui differ diff --git a/instruments/OPL/Snare Drum.fui b/instruments/OPL/Snare Drum.fui new file mode 100644 index 00000000..470a78df Binary files /dev/null and b/instruments/OPL/Snare Drum.fui differ diff --git a/instruments/OPL/Synth Brass.fui b/instruments/OPL/Synth Brass.fui new file mode 100644 index 00000000..0bb75117 Binary files /dev/null and b/instruments/OPL/Synth Brass.fui differ diff --git a/instruments/OPL/Synth Guitar.fui b/instruments/OPL/Synth Guitar.fui new file mode 100644 index 00000000..0c7f7a40 Binary files /dev/null and b/instruments/OPL/Synth Guitar.fui differ diff --git a/instruments/OPL/Violin.fui b/instruments/OPL/Violin.fui new file mode 100644 index 00000000..f84bd6c4 Binary files /dev/null and b/instruments/OPL/Violin.fui differ diff --git a/instruments/OPL/opl3_4op_bass_1.fui b/instruments/OPL/opl3_4op_bass_1.fui new file mode 100644 index 00000000..bdc01cdf Binary files /dev/null and b/instruments/OPL/opl3_4op_bass_1.fui differ diff --git a/instruments/OPL/opl3_4op_bass_2.fui b/instruments/OPL/opl3_4op_bass_2.fui new file mode 100644 index 00000000..555cf369 Binary files /dev/null and b/instruments/OPL/opl3_4op_bass_2.fui differ diff --git a/instruments/OPL/opl3_4op_bass_3.fui b/instruments/OPL/opl3_4op_bass_3.fui new file mode 100644 index 00000000..2e8c8962 Binary files /dev/null and b/instruments/OPL/opl3_4op_bass_3.fui differ diff --git a/instruments/OPL/opl3_4op_drum_kick.fui b/instruments/OPL/opl3_4op_drum_kick.fui new file mode 100644 index 00000000..ed89055b Binary files /dev/null and b/instruments/OPL/opl3_4op_drum_kick.fui differ diff --git a/instruments/OPL/opl3_4op_drum_power_snare.fui b/instruments/OPL/opl3_4op_drum_power_snare.fui new file mode 100644 index 00000000..b351cf34 Binary files /dev/null and b/instruments/OPL/opl3_4op_drum_power_snare.fui differ diff --git a/instruments/OPL/opl3_4op_drum_snare.fui b/instruments/OPL/opl3_4op_drum_snare.fui new file mode 100644 index 00000000..7b3dac71 Binary files /dev/null and b/instruments/OPL/opl3_4op_drum_snare.fui differ diff --git a/instruments/OPL/opl3_4op_drum_tom.fui b/instruments/OPL/opl3_4op_drum_tom.fui new file mode 100644 index 00000000..437facdc Binary files /dev/null and b/instruments/OPL/opl3_4op_drum_tom.fui differ diff --git a/instruments/OPL/opl3_4op_growl_synth.fui b/instruments/OPL/opl3_4op_growl_synth.fui new file mode 100644 index 00000000..c91745b2 Binary files /dev/null and b/instruments/OPL/opl3_4op_growl_synth.fui differ diff --git a/instruments/OPL/opl3_4op_lead_guitar.fui b/instruments/OPL/opl3_4op_lead_guitar.fui new file mode 100644 index 00000000..fa390276 Binary files /dev/null and b/instruments/OPL/opl3_4op_lead_guitar.fui differ diff --git a/instruments/OPL/opl3_4op_piano_bell.fui b/instruments/OPL/opl3_4op_piano_bell.fui new file mode 100644 index 00000000..5157820a Binary files /dev/null and b/instruments/OPL/opl3_4op_piano_bell.fui differ diff --git a/instruments/OPL/opl3_4op_rhythm_guitar_1.fui b/instruments/OPL/opl3_4op_rhythm_guitar_1.fui new file mode 100644 index 00000000..38f02327 Binary files /dev/null and b/instruments/OPL/opl3_4op_rhythm_guitar_1.fui differ diff --git a/instruments/OPL/opl3_4op_rhythm_guitar_2.fui b/instruments/OPL/opl3_4op_rhythm_guitar_2.fui new file mode 100644 index 00000000..97c23288 Binary files /dev/null and b/instruments/OPL/opl3_4op_rhythm_guitar_2.fui differ diff --git a/instruments/OPL/opl3_4op_strings.fui b/instruments/OPL/opl3_4op_strings.fui new file mode 100644 index 00000000..d4f8450f Binary files /dev/null and b/instruments/OPL/opl3_4op_strings.fui differ diff --git a/instruments/OPLL/(SMSFM) Near Perfect Square Wave.dmp b/instruments/OPLL/(SMSFM) Near Perfect Square Wave.dmp new file mode 100644 index 00000000..392b8b59 Binary files /dev/null and b/instruments/OPLL/(SMSFM) Near Perfect Square Wave.dmp differ diff --git a/instruments/README.md b/instruments/README.md index fdfae2f3..40cad422 100644 --- a/instruments/README.md +++ b/instruments/README.md @@ -8,6 +8,37 @@ these are organized in the following categories: - **OPLL**: FM instruments for OPLL. - **other**: instruments for any other system. +# contributors + +- Abstract 64 +- A M 4 N +- Blast\_Brothers +- brickblock369 +- Deepfreeze +- dj.tuBIG/MaliceX +- dotHayabusa +- EpicTyphlosion +- freq-mod +- GENATARi +- jvsTSX +- Laggy +- LovelyA72 +- nicco1690 +- PacorexTheTrex +- paynspch +- pedipanol +- Sik +- Sonic 🦔 +- System64 +- Teuthida +- theloredev +- TheRealHedgehogSonic +- tildearrow +- Weeppiko +- Ygor G. +- YohananDiamond +- Yumetaro + # submit instruments you may submit your own instruments by creating a pull request or contacting me! the only guidelines are: diff --git a/instruments/other/(AY) Closed Hat.fui b/instruments/other/(AY) Closed Hat.fui new file mode 100644 index 00000000..237c8610 Binary files /dev/null and b/instruments/other/(AY) Closed Hat.fui differ diff --git a/instruments/other/(AY) Hat-EnvBass.fui b/instruments/other/(AY) Hat-EnvBass.fui new file mode 100644 index 00000000..00b661ab Binary files /dev/null and b/instruments/other/(AY) Hat-EnvBass.fui differ diff --git a/instruments/other/(AY) Kick E-1.fui b/instruments/other/(AY) Kick E-1.fui new file mode 100644 index 00000000..212289c9 Binary files /dev/null and b/instruments/other/(AY) Kick E-1.fui differ diff --git a/instruments/other/(AY) Open Hat.fui b/instruments/other/(AY) Open Hat.fui new file mode 100644 index 00000000..0466625b Binary files /dev/null and b/instruments/other/(AY) Open Hat.fui differ diff --git a/instruments/other/(AY) Snare A#3.fui b/instruments/other/(AY) Snare A#3.fui new file mode 100644 index 00000000..238199de Binary files /dev/null and b/instruments/other/(AY) Snare A#3.fui differ diff --git a/instruments/other/(S16) Computer Shutdown [C-6 0213].dmp b/instruments/other/(S16) Computer Shutdown [C-6 0213].dmp new file mode 100644 index 00000000..9ab5189b Binary files /dev/null and b/instruments/other/(S16) Computer Shutdown [C-6 0213].dmp differ diff --git a/instruments/other/(S16) Dog Whistle.dmp b/instruments/other/(S16) Dog Whistle.dmp new file mode 100644 index 00000000..c1627b34 Binary files /dev/null and b/instruments/other/(S16) Dog Whistle.dmp differ diff --git a/instruments/other/(S16) Donkey Kong Lead.dmp b/instruments/other/(S16) Donkey Kong Lead.dmp new file mode 100644 index 00000000..7532452c Binary files /dev/null and b/instruments/other/(S16) Donkey Kong Lead.dmp differ diff --git a/instruments/other/(S16) Hi-Hat Closed.dmp b/instruments/other/(S16) Hi-Hat Closed.dmp new file mode 100644 index 00000000..1091df53 Binary files /dev/null and b/instruments/other/(S16) Hi-Hat Closed.dmp differ diff --git a/instruments/other/(S16) Hi-Hat Open.dmp b/instruments/other/(S16) Hi-Hat Open.dmp new file mode 100644 index 00000000..04b91668 Binary files /dev/null and b/instruments/other/(S16) Hi-Hat Open.dmp differ diff --git a/instruments/other/(S16) Perfect Square Wave + Hi-Hat Closed.dmp b/instruments/other/(S16) Perfect Square Wave + Hi-Hat Closed.dmp new file mode 100644 index 00000000..1389c236 Binary files /dev/null and b/instruments/other/(S16) Perfect Square Wave + Hi-Hat Closed.dmp differ diff --git a/instruments/other/(S16) Perfect Square Wave + Hi-Hat Open.dmp b/instruments/other/(S16) Perfect Square Wave + Hi-Hat Open.dmp new file mode 100644 index 00000000..67b482bd Binary files /dev/null and b/instruments/other/(S16) Perfect Square Wave + Hi-Hat Open.dmp differ diff --git a/instruments/other/(S16) Portal Cast.dmp b/instruments/other/(S16) Portal Cast.dmp new file mode 100644 index 00000000..665c5622 Binary files /dev/null and b/instruments/other/(S16) Portal Cast.dmp differ diff --git a/instruments/other/(SMS) 2-Arp Chord High.dmp b/instruments/other/(SMS) 2-Arp Chord High.dmp new file mode 100644 index 00000000..845babb2 Binary files /dev/null and b/instruments/other/(SMS) 2-Arp Chord High.dmp differ diff --git a/instruments/other/(SMS) 2-Arp High.dmp b/instruments/other/(SMS) 2-Arp High.dmp new file mode 100644 index 00000000..cefb09cb Binary files /dev/null and b/instruments/other/(SMS) 2-Arp High.dmp differ diff --git a/instruments/other/(SMS) 2-Arp Major Low.dmp b/instruments/other/(SMS) 2-Arp Major Low.dmp new file mode 100644 index 00000000..f65623f0 Binary files /dev/null and b/instruments/other/(SMS) 2-Arp Major Low.dmp differ diff --git a/instruments/other/(SMS) 2-Arp Minor Low.dmp b/instruments/other/(SMS) 2-Arp Minor Low.dmp new file mode 100644 index 00000000..5345dd45 Binary files /dev/null and b/instruments/other/(SMS) 2-Arp Minor Low.dmp differ diff --git a/instruments/other/(SMS) 3-Arp High.dmp b/instruments/other/(SMS) 3-Arp High.dmp new file mode 100644 index 00000000..d0fab8e5 Binary files /dev/null and b/instruments/other/(SMS) 3-Arp High.dmp differ diff --git a/instruments/other/(SMS) 3-Arp Major.dmp b/instruments/other/(SMS) 3-Arp Major.dmp new file mode 100644 index 00000000..d9f9e3a7 Binary files /dev/null and b/instruments/other/(SMS) 3-Arp Major.dmp differ diff --git a/instruments/other/(SMS) 3-Arp Minor.dmp b/instruments/other/(SMS) 3-Arp Minor.dmp new file mode 100644 index 00000000..f36e80e9 Binary files /dev/null and b/instruments/other/(SMS) 3-Arp Minor.dmp differ diff --git a/instruments/other/(SMS) 5-Arp Major.dmp b/instruments/other/(SMS) 5-Arp Major.dmp new file mode 100644 index 00000000..7a929135 Binary files /dev/null and b/instruments/other/(SMS) 5-Arp Major.dmp differ diff --git a/instruments/other/(SMS) 5-Arp Minor.dmp b/instruments/other/(SMS) 5-Arp Minor.dmp new file mode 100644 index 00000000..d0bd56d1 Binary files /dev/null and b/instruments/other/(SMS) 5-Arp Minor.dmp differ diff --git a/instruments/other/(SMS) Arp Snare.dmp b/instruments/other/(SMS) Arp Snare.dmp new file mode 100644 index 00000000..71359542 Binary files /dev/null and b/instruments/other/(SMS) Arp Snare.dmp differ diff --git a/instruments/other/(SMS) Attack.dmp b/instruments/other/(SMS) Attack.dmp new file mode 100644 index 00000000..ac5d24ff Binary files /dev/null and b/instruments/other/(SMS) Attack.dmp differ diff --git a/instruments/other/(SMS) Buzz Noise.dmp b/instruments/other/(SMS) Buzz Noise.dmp new file mode 100644 index 00000000..d1bc3921 Binary files /dev/null and b/instruments/other/(SMS) Buzz Noise.dmp differ diff --git a/instruments/other/(SMS) Crash.dmp b/instruments/other/(SMS) Crash.dmp new file mode 100644 index 00000000..6f05ff80 Binary files /dev/null and b/instruments/other/(SMS) Crash.dmp differ diff --git a/instruments/other/(SMS) Cyclic Noise.dmp b/instruments/other/(SMS) Cyclic Noise.dmp new file mode 100644 index 00000000..cb84b309 Binary files /dev/null and b/instruments/other/(SMS) Cyclic Noise.dmp differ diff --git a/instruments/other/(SMS) Decay Noise.dmp b/instruments/other/(SMS) Decay Noise.dmp new file mode 100644 index 00000000..65e45cf8 Binary files /dev/null and b/instruments/other/(SMS) Decay Noise.dmp differ diff --git a/instruments/other/(SMS) Decay.dmp b/instruments/other/(SMS) Decay.dmp new file mode 100644 index 00000000..8c2688d1 Binary files /dev/null and b/instruments/other/(SMS) Decay.dmp differ diff --git a/instruments/other/(SMS) Down Slider.dmp b/instruments/other/(SMS) Down Slider.dmp new file mode 100644 index 00000000..cc13d5fa Binary files /dev/null and b/instruments/other/(SMS) Down Slider.dmp differ diff --git a/instruments/other/(SMS) Hi-Hat & Note.dmp b/instruments/other/(SMS) Hi-Hat & Note.dmp new file mode 100644 index 00000000..cff02382 Binary files /dev/null and b/instruments/other/(SMS) Hi-Hat & Note.dmp differ diff --git a/instruments/other/(SMS) Hi-Hat Closed.dmp b/instruments/other/(SMS) Hi-Hat Closed.dmp new file mode 100644 index 00000000..5e4e60a8 Binary files /dev/null and b/instruments/other/(SMS) Hi-Hat Closed.dmp differ diff --git a/instruments/other/(SMS) Hi-Hat Open.dmp b/instruments/other/(SMS) Hi-Hat Open.dmp new file mode 100644 index 00000000..ae6e88e7 Binary files /dev/null and b/instruments/other/(SMS) Hi-Hat Open.dmp differ diff --git a/instruments/other/(SMS) Kick Noise.dmp b/instruments/other/(SMS) Kick Noise.dmp new file mode 100644 index 00000000..b76fb886 Binary files /dev/null and b/instruments/other/(SMS) Kick Noise.dmp differ diff --git a/instruments/other/(SMS) Multi Slider.dmp b/instruments/other/(SMS) Multi Slider.dmp new file mode 100644 index 00000000..4f2bd3fc Binary files /dev/null and b/instruments/other/(SMS) Multi Slider.dmp differ diff --git a/instruments/other/(SMS) Obvious Crash.dmp b/instruments/other/(SMS) Obvious Crash.dmp new file mode 100644 index 00000000..0f54a85e Binary files /dev/null and b/instruments/other/(SMS) Obvious Crash.dmp differ diff --git a/instruments/other/(SMS) Record Scratch Down.dmp b/instruments/other/(SMS) Record Scratch Down.dmp new file mode 100644 index 00000000..b3f4b07f Binary files /dev/null and b/instruments/other/(SMS) Record Scratch Down.dmp differ diff --git a/instruments/other/(SMS) Record Scratch Up.dmp b/instruments/other/(SMS) Record Scratch Up.dmp new file mode 100644 index 00000000..a710de04 Binary files /dev/null and b/instruments/other/(SMS) Record Scratch Up.dmp differ diff --git a/instruments/other/(SMS) Retrig.dmp b/instruments/other/(SMS) Retrig.dmp new file mode 100644 index 00000000..b2822b8c Binary files /dev/null and b/instruments/other/(SMS) Retrig.dmp differ diff --git a/instruments/other/(SMS) Ride.dmp b/instruments/other/(SMS) Ride.dmp new file mode 100644 index 00000000..45c4b5fe Binary files /dev/null and b/instruments/other/(SMS) Ride.dmp differ diff --git a/instruments/other/(SMS) Snare.dmp b/instruments/other/(SMS) Snare.dmp new file mode 100644 index 00000000..d624b92a Binary files /dev/null and b/instruments/other/(SMS) Snare.dmp differ diff --git a/instruments/other/(SMS) Splash.dmp b/instruments/other/(SMS) Splash.dmp new file mode 100644 index 00000000..f3296072 Binary files /dev/null and b/instruments/other/(SMS) Splash.dmp differ diff --git a/instruments/other/(SMS) Thump & Note.dmp b/instruments/other/(SMS) Thump & Note.dmp new file mode 100644 index 00000000..3dccb56c Binary files /dev/null and b/instruments/other/(SMS) Thump & Note.dmp differ diff --git a/instruments/other/(SMS) Tim Follin 6-Arp Fast Major.dmp b/instruments/other/(SMS) Tim Follin 6-Arp Fast Major.dmp new file mode 100644 index 00000000..6440474c Binary files /dev/null and b/instruments/other/(SMS) Tim Follin 6-Arp Fast Major.dmp differ diff --git a/instruments/other/(SMS) Tim Follin 6-Arp Fast Minor.dmp b/instruments/other/(SMS) Tim Follin 6-Arp Fast Minor.dmp new file mode 100644 index 00000000..105421b6 Binary files /dev/null and b/instruments/other/(SMS) Tim Follin 6-Arp Fast Minor.dmp differ diff --git a/instruments/other/(SMS) Tim Follin 6-Arp Slow Major.dmp b/instruments/other/(SMS) Tim Follin 6-Arp Slow Major.dmp new file mode 100644 index 00000000..11eeafb4 Binary files /dev/null and b/instruments/other/(SMS) Tim Follin 6-Arp Slow Major.dmp differ diff --git a/instruments/other/(SMS) Tim Follin 6-Arp Slow Minor.dmp b/instruments/other/(SMS) Tim Follin 6-Arp Slow Minor.dmp new file mode 100644 index 00000000..3cbc3f38 Binary files /dev/null and b/instruments/other/(SMS) Tim Follin 6-Arp Slow Minor.dmp differ diff --git a/instruments/other/(SMS) Tim Follin Lead.dmp b/instruments/other/(SMS) Tim Follin Lead.dmp new file mode 100644 index 00000000..69ab7d40 Binary files /dev/null and b/instruments/other/(SMS) Tim Follin Lead.dmp differ diff --git a/instruments/other/(SMS) Tom A.dmp b/instruments/other/(SMS) Tom A.dmp new file mode 100644 index 00000000..813de521 Binary files /dev/null and b/instruments/other/(SMS) Tom A.dmp differ diff --git a/instruments/other/(SMS) Tom B.dmp b/instruments/other/(SMS) Tom B.dmp new file mode 100644 index 00000000..e06cccb8 Binary files /dev/null and b/instruments/other/(SMS) Tom B.dmp differ diff --git a/instruments/other/(SMS) Up Slider.dmp b/instruments/other/(SMS) Up Slider.dmp new file mode 100644 index 00000000..b57cfffe Binary files /dev/null and b/instruments/other/(SMS) Up Slider.dmp differ diff --git a/instruments/other/(SMS) Variable.dmp b/instruments/other/(SMS) Variable.dmp new file mode 100644 index 00000000..16a5c0f7 Binary files /dev/null and b/instruments/other/(SMS) Variable.dmp differ diff --git a/instruments/other/(SMS) Whistle.dmp b/instruments/other/(SMS) Whistle.dmp new file mode 100644 index 00000000..1ed679bb Binary files /dev/null and b/instruments/other/(SMS) Whistle.dmp differ diff --git a/instruments/other/2A03 Noise Hi-Hat Closed.fui b/instruments/other/2A03 Noise Hi-Hat Closed.fui new file mode 100644 index 00000000..a24dff83 Binary files /dev/null and b/instruments/other/2A03 Noise Hi-Hat Closed.fui differ diff --git a/instruments/other/2A03 Noise Hi-Hat Open.fui b/instruments/other/2A03 Noise Hi-Hat Open.fui new file mode 100644 index 00000000..26889b0a Binary files /dev/null and b/instruments/other/2A03 Noise Hi-Hat Open.fui differ diff --git a/instruments/other/2A03 Noise Kick.fui b/instruments/other/2A03 Noise Kick.fui new file mode 100644 index 00000000..7cf815e1 Binary files /dev/null and b/instruments/other/2A03 Noise Kick.fui differ diff --git a/instruments/other/2A03 Noise Snare.fui b/instruments/other/2A03 Noise Snare.fui new file mode 100644 index 00000000..0bcd5dac Binary files /dev/null and b/instruments/other/2A03 Noise Snare.fui differ diff --git a/instruments/other/2A03 Triangle Kick+Bass.fui b/instruments/other/2A03 Triangle Kick+Bass.fui new file mode 100644 index 00000000..9beaea86 Binary files /dev/null and b/instruments/other/2A03 Triangle Kick+Bass.fui differ diff --git a/instruments/other/2A03 Triangle Kick.fui b/instruments/other/2A03 Triangle Kick.fui new file mode 100644 index 00000000..f2263010 Binary files /dev/null and b/instruments/other/2A03 Triangle Kick.fui differ diff --git a/instruments/other/2A03 Triangle Snare+Bass.fui b/instruments/other/2A03 Triangle Snare+Bass.fui new file mode 100644 index 00000000..e91831c8 Binary files /dev/null and b/instruments/other/2A03 Triangle Snare+Bass.fui differ diff --git a/instruments/other/2A03 Triangle Snare.fui b/instruments/other/2A03 Triangle Snare.fui new file mode 100644 index 00000000..7ed4b8ac Binary files /dev/null and b/instruments/other/2A03 Triangle Snare.fui differ diff --git a/instruments/other/AY kick.fui b/instruments/other/AY kick.fui new file mode 100644 index 00000000..111f1da7 Binary files /dev/null and b/instruments/other/AY kick.fui differ diff --git a/instruments/other/AY snare.fui b/instruments/other/AY snare.fui new file mode 100644 index 00000000..b8c03a2d Binary files /dev/null and b/instruments/other/AY snare.fui differ diff --git a/instruments/other/C64_Snare.fui b/instruments/other/C64_Snare.fui new file mode 100644 index 00000000..28f1a2e7 Binary files /dev/null and b/instruments/other/C64_Snare.fui differ diff --git a/instruments/other/Castanets.fui b/instruments/other/Castanets.fui new file mode 100644 index 00000000..a81f93e1 Binary files /dev/null and b/instruments/other/Castanets.fui differ diff --git a/instruments/other/Closed_Hi-hat.fui b/instruments/other/Closed_Hi-hat.fui new file mode 100644 index 00000000..20cc2fce Binary files /dev/null and b/instruments/other/Closed_Hi-hat.fui differ diff --git a/instruments/other/Echo.fui b/instruments/other/Echo.fui new file mode 100644 index 00000000..65fe97f8 Binary files /dev/null and b/instruments/other/Echo.fui differ diff --git a/instruments/other/FDS-oneofthosejapaneseguitarsidkhowtheyarecalledyeah.fui b/instruments/other/FDS-oneofthosejapaneseguitarsidkhowtheyarecalledyeah.fui new file mode 100644 index 00000000..3dc7798a Binary files /dev/null and b/instruments/other/FDS-oneofthosejapaneseguitarsidkhowtheyarecalledyeah.fui differ diff --git a/instruments/other/FDS-overfuckingholyshitdriven-guitar.fui b/instruments/other/FDS-overfuckingholyshitdriven-guitar.fui new file mode 100644 index 00000000..eb2f8166 Binary files /dev/null and b/instruments/other/FDS-overfuckingholyshitdriven-guitar.fui differ diff --git a/instruments/other/FDS-slapassbass.fui b/instruments/other/FDS-slapassbass.fui new file mode 100644 index 00000000..058a8326 Binary files /dev/null and b/instruments/other/FDS-slapassbass.fui differ diff --git a/instruments/other/Flute.fui b/instruments/other/Flute.fui new file mode 100644 index 00000000..06119564 Binary files /dev/null and b/instruments/other/Flute.fui differ diff --git a/instruments/other/Follin Guitar 2.fui b/instruments/other/Follin Guitar 2.fui new file mode 100644 index 00000000..504c5870 Binary files /dev/null and b/instruments/other/Follin Guitar 2.fui differ diff --git a/instruments/other/Follin Guitar.fui b/instruments/other/Follin Guitar.fui new file mode 100644 index 00000000..3a3d292e Binary files /dev/null and b/instruments/other/Follin Guitar.fui differ diff --git a/instruments/other/GBkick.fui b/instruments/other/GBkick.fui new file mode 100644 index 00000000..d669277a Binary files /dev/null and b/instruments/other/GBkick.fui differ diff --git a/instruments/other/GBsnare.fui b/instruments/other/GBsnare.fui new file mode 100644 index 00000000..4731ea6b Binary files /dev/null and b/instruments/other/GBsnare.fui differ diff --git a/instruments/other/Guitar.fui b/instruments/other/Guitar.fui new file mode 100644 index 00000000..20f6f781 Binary files /dev/null and b/instruments/other/Guitar.fui differ diff --git a/instruments/other/Gunshot.fui b/instruments/other/Gunshot.fui new file mode 100644 index 00000000..09cb8fb8 Binary files /dev/null and b/instruments/other/Gunshot.fui differ diff --git a/instruments/other/NES_0-2-5_arp_lead.fui b/instruments/other/NES_0-2-5_arp_lead.fui new file mode 100644 index 00000000..493a167d Binary files /dev/null and b/instruments/other/NES_0-2-5_arp_lead.fui differ diff --git a/instruments/other/NES_0-3-5_arp_lead.fui b/instruments/other/NES_0-3-5_arp_lead.fui new file mode 100644 index 00000000..05a43de2 Binary files /dev/null and b/instruments/other/NES_0-3-5_arp_lead.fui differ diff --git a/instruments/other/Octave Arp.fui b/instruments/other/Octave Arp.fui new file mode 100644 index 00000000..a891ed9f Binary files /dev/null and b/instruments/other/Octave Arp.fui differ diff --git a/instruments/other/Open_Hi-hat.fui b/instruments/other/Open_Hi-hat.fui new file mode 100644 index 00000000..6054a5ca Binary files /dev/null and b/instruments/other/Open_Hi-hat.fui differ diff --git a/instruments/other/SMSPerc/SMSPerc About.txt b/instruments/other/SMSPerc/SMSPerc About.txt new file mode 100644 index 00000000..5964270c --- /dev/null +++ b/instruments/other/SMSPerc/SMSPerc About.txt @@ -0,0 +1,26 @@ +The SN7 noise channel can "steal" the pitch of the third square +channel for increased range, and this percussion set takes +advantage of that. The kick and snare drums use CH3 pitch for +two frames- CH3 is muted for the first frame and outputs a +square wave on the second. The hi-hats use CH3 pitch for only +one frame, during which CH3 is silent. + +In order for this to work, an instrument must be placed on both +CH3 and CH4, whenever a kick or snare is triggered. (Because +the hi-hats only need one frame of CH3 silence to sound +correct, and CH4 pitch macros take precedence over CH3 macros +in Furnace, you don't need to place an instrument in CH3 when +a hi-hat is playing by itself.) This is the "KickSnare CH3" +instrument in the folder. The "CH4" instruments are for CH4, +obviously. + +The "ExampleInst" instruments show a setup where a regular +melodic instrument is combined with the CH3 percussion +instruments, so that CH3 can play something useful with minimal +interruption from CH4. Of course, now you have to keep track of +three instruments for one timbre, and you can't have a note +sustain through a percussion sound, but I think it's worth it. + + +Note also that the CH4 instruments need to be played on either +C or D to sound correct. \ No newline at end of file diff --git a/instruments/other/SMSPerc/SMSPerc ExampleInst [Hat] CH3.fui b/instruments/other/SMSPerc/SMSPerc ExampleInst [Hat] CH3.fui new file mode 100644 index 00000000..950f7461 Binary files /dev/null and b/instruments/other/SMSPerc/SMSPerc ExampleInst [Hat] CH3.fui differ diff --git a/instruments/other/SMSPerc/SMSPerc ExampleInst [KickSnare] CH3.fui b/instruments/other/SMSPerc/SMSPerc ExampleInst [KickSnare] CH3.fui new file mode 100644 index 00000000..2c658579 Binary files /dev/null and b/instruments/other/SMSPerc/SMSPerc ExampleInst [KickSnare] CH3.fui differ diff --git a/instruments/other/SMSPerc/SMSPerc ExampleInst [Nothing] CH3.fui b/instruments/other/SMSPerc/SMSPerc ExampleInst [Nothing] CH3.fui new file mode 100644 index 00000000..d51578af Binary files /dev/null and b/instruments/other/SMSPerc/SMSPerc ExampleInst [Nothing] CH3.fui differ diff --git a/instruments/other/SMSPerc/SMSPerc Hat_Closed CH4 [D].fui b/instruments/other/SMSPerc/SMSPerc Hat_Closed CH4 [D].fui new file mode 100644 index 00000000..b3ec0f28 Binary files /dev/null and b/instruments/other/SMSPerc/SMSPerc Hat_Closed CH4 [D].fui differ diff --git a/instruments/other/SMSPerc/SMSPerc Hat_Open CH4 [D].fui b/instruments/other/SMSPerc/SMSPerc Hat_Open CH4 [D].fui new file mode 100644 index 00000000..81e93c17 Binary files /dev/null and b/instruments/other/SMSPerc/SMSPerc Hat_Open CH4 [D].fui differ diff --git a/instruments/other/SMSPerc/SMSPerc Kick CH4 [C].fui b/instruments/other/SMSPerc/SMSPerc Kick CH4 [C].fui new file mode 100644 index 00000000..3a8f1e85 Binary files /dev/null and b/instruments/other/SMSPerc/SMSPerc Kick CH4 [C].fui differ diff --git a/instruments/other/SMSPerc/SMSPerc KickSnare CH3.fui b/instruments/other/SMSPerc/SMSPerc KickSnare CH3.fui new file mode 100644 index 00000000..f35b0ef0 Binary files /dev/null and b/instruments/other/SMSPerc/SMSPerc KickSnare CH3.fui differ diff --git a/instruments/other/SMSPerc/SMSPerc Snare CH4 [D].fui b/instruments/other/SMSPerc/SMSPerc Snare CH4 [D].fui new file mode 100644 index 00000000..5569b2fc Binary files /dev/null and b/instruments/other/SMSPerc/SMSPerc Snare CH4 [D].fui differ diff --git a/instruments/other/SMS_0-2-5_arp_lead.fui b/instruments/other/SMS_0-2-5_arp_lead.fui new file mode 100644 index 00000000..c21c2d23 Binary files /dev/null and b/instruments/other/SMS_0-2-5_arp_lead.fui differ diff --git a/instruments/other/SMS_0-3-5_arp_lead.fui b/instruments/other/SMS_0-3-5_arp_lead.fui new file mode 100644 index 00000000..22fe1c3e Binary files /dev/null and b/instruments/other/SMS_0-3-5_arp_lead.fui differ diff --git a/instruments/other/Side_Stick.fui b/instruments/other/Side_Stick.fui new file mode 100644 index 00000000..783833d4 Binary files /dev/null and b/instruments/other/Side_Stick.fui differ diff --git a/instruments/other/Supersaw.fui b/instruments/other/Supersaw.fui new file mode 100644 index 00000000..e4747a80 Binary files /dev/null and b/instruments/other/Supersaw.fui differ diff --git a/instruments/other/VERA_0-2-5_arp_lead.fui b/instruments/other/VERA_0-2-5_arp_lead.fui new file mode 100644 index 00000000..9f52221b Binary files /dev/null and b/instruments/other/VERA_0-2-5_arp_lead.fui differ diff --git a/instruments/other/VERA_0-3-5_arp_lead.fui b/instruments/other/VERA_0-3-5_arp_lead.fui new file mode 100644 index 00000000..5f5c6194 Binary files /dev/null and b/instruments/other/VERA_0-3-5_arp_lead.fui differ diff --git a/instruments/other/VERA_Noise_Kick.fui b/instruments/other/VERA_Noise_Kick.fui new file mode 100644 index 00000000..2d25ab5a Binary files /dev/null and b/instruments/other/VERA_Noise_Kick.fui differ diff --git a/instruments/other/VERA_Noise_Snare.fui b/instruments/other/VERA_Noise_Snare.fui new file mode 100644 index 00000000..015d9a89 Binary files /dev/null and b/instruments/other/VERA_Noise_Snare.fui differ diff --git a/instruments/other/VIC20_kick.fui b/instruments/other/VIC20_kick.fui new file mode 100644 index 00000000..bc49b462 Binary files /dev/null and b/instruments/other/VIC20_kick.fui differ diff --git a/papers/doc/1-intro/README.md b/papers/doc/1-intro/README.md index c5729742..41cb32dc 100644 --- a/papers/doc/1-intro/README.md +++ b/papers/doc/1-intro/README.md @@ -13,10 +13,10 @@ 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 sound chip. + - 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 an Amiga/Sample instrument. +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 diff --git a/papers/doc/4-instrument/n163.md b/papers/doc/4-instrument/n163.md index 0bd91445..ca4dd2e7 100644 --- a/papers/doc/4-instrument/n163.md +++ b/papers/doc/4-instrument/n163.md @@ -2,7 +2,7 @@ Namco 163 instrument editor consists of two tabs: one controlling various parameters for waveform initialize and macro tab containing 10 macros. -## N163 +## Namco 163 - [Initial Waveform] - Determines the initial waveform for playing. - [Initial Waveform position in RAM] - Determines the initial waveform position will be load to RAM. - [Initial Waveform length in RAM] - Determines the initial waveform length will be load to RAM. diff --git a/papers/doc/4-instrument/pce.md b/papers/doc/4-instrument/pce.md index 50ac335b..ac0a6824 100644 --- a/papers/doc/4-instrument/pce.md +++ b/papers/doc/4-instrument/pce.md @@ -5,3 +5,5 @@ PCE instrument editor consists of only three macros, almost like TIA: - [Volume] - volume sequence - [Arpeggio] - pitch sequence - [Waveform] - spicifies wavetables sequence + +It also has wavetable synthesizer support, but unfortunately, it clicks a lot when in use on the HuC6280. diff --git a/papers/doc/4-instrument/vrc6.md b/papers/doc/4-instrument/vrc6.md index a4ea64a1..bd87e050 100644 --- a/papers/doc/4-instrument/vrc6.md +++ b/papers/doc/4-instrument/vrc6.md @@ -1,7 +1,15 @@ # VRC6 instrument editor -VRC6 instrument editor consists of only three macros: +The VRC6 (regular) instrument editor consists of only three macros: - [Volume] - volume sequence - [Arpeggio] - pitch sequence - [Duty cycle] - specifies duty cycle for pulse wave channels + +## VRC6 (saw) instrument editor + +This channel has its own instrument, a (currently, as of 0.6) one-of-a-kind thing in Furnace. + +The only differences from this instrument type from compared to the regular instrument are that it: + - has a volume range of 0-63 instead of 0-15. + - has no duty cycle range diff --git a/papers/doc/4-instrument/x1_010.md b/papers/doc/4-instrument/x1_010.md index 8f3691ee..c76cbcb5 100644 --- a/papers/doc/4-instrument/x1_010.md +++ b/papers/doc/4-instrument/x1_010.md @@ -8,4 +8,4 @@ X1-010 instrument editor consists of 7 macros. - [Envelope Mode] - allows shaping an envelope - [Envelope] - spicifies envelope shape sequence, it's also wavetable. - [Auto envelope numerator] - sets the envelope to the channel's frequency multiplied by numerator -- [Auto envelope denominator] - ets the envelope to the channel's frequency multiplied by denominator +- [Auto envelope denominator] - sets the envelope to the channel's frequency divided by denominator diff --git a/papers/doc/5-wave/README.md b/papers/doc/5-wave/README.md index 0668c0bf..b22e8699 100644 --- a/papers/doc/5-wave/README.md +++ b/papers/doc/5-wave/README.md @@ -1,5 +1,13 @@ # wavetable editor -Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy. +Wavetable synthesizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy. -Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS, Bubble System and N163, and 32-level height for PCE. If larger wave will be defined for these systems, it will be squashed to fit within the constraints of the system. +Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS, Bubble System and N163, and 32-level height for PCE. If a larger wave is defined for these systems, it will be squashed to fit within the constraints of the system. + +## wavetable synthesizer + +Furnace contains a mode for wavetable instruments that allows you to modulate or combine 1 or 2 waves to create unique "animated" sounds. Think of it like a VST or a plugin, as it's basically an extension of regular wavetable soundchips that still allow it to run on real hardware. + +This is accomplished by selecting a wave or two, a mode, and adjusting the settings as needed until you come up with a sound that you like, without taking up a load of space. This allows you to create unique sound effects or instruments, that, when used well, almost sound like they're Amiga samples. + +Unfortunately, on chips like the HuC6280, you cannot use the wavetable synth to animate waveforms and have them sound smooth, as the chip resets the channel's phase when a waveform is changed while the channel is playing. On certain frequencies, this can be avoided, but not on most, unfortunately. diff --git a/papers/doc/6-sample/README.md b/papers/doc/6-sample/README.md index 87ddf0f8..4fbb6c91 100644 --- a/papers/doc/6-sample/README.md +++ b/papers/doc/6-sample/README.md @@ -4,29 +4,63 @@ In the context of Furnace, a sound sample (usually just referred to as a sample) In Furnace, these samples can be generated by importing a .wav (think of it as an higher quality MP3) file. -## supported systems +## supported chips -As of Furnace 0.5.5, the following sound chips have sample support: - - NES/Ricoh 2A03 (PCM only, no DPCM, and only on channel 5) - - Sega Genesis/YM2612 (channel 6 only; but only if there exists a `1701` effect that gets played on or before a trigger for a sample) - - PC Engine/TurboGrafx 16/Huc6280 (same conditions as above) - - Amiga/Paula (on all channels AND resamplable, but you need to make an instrument with the Amiga format and tie it to a sample first) - - Arcade/SEGA PCM (same as above but you don't need to make an instrument for it and you have to use the `20xx` effect command to resample your samples) - - Neo Geo/Neo Geo EXT-Ch2 (on the last 7 channels only and can be resampled the same way as above) - - Seta/Allumer X1-010 (same as above, and both `1701` and `20xx` effect commands are affected on all 16 channels) +as of Furnace 0.6, the following sound chips have sample support: -Furnace also has a feature where you can make an Amiga formarted instrument on the YM2612 and Huc6280 to resample a sample you have in the module. +- NES/Ricoh 2A03 (with DPCM support and only on channel 5) +- Sega Genesis/YM2612 (channel 6 only) +- PC Engine/TurboGrafx-16/HuC6280 +- Amiga/Paula +- SegaPCM +- Neo Geo/Neo Geo CD/YM2610 (ADPCM channels only) +- Seta/Allumer X1-010 +- Atari Lynx +- MSM6258 and MSM6295 +- YMU759/MA-2 (last channel only) +- QSound +- ZX Spectrum 48k (1-bit) +- RF5C68 +- WonderSwan +- tildearrow Sound Unit +- VERA (last channel only) +- Y8950 (last channel only) +- a few more that I've forgotten to mention + +## compatible sample mode + +effect `17xx` enables/disables compatible sample mode whether supported (e.g. on Sega Genesis or PC Engine). + +in this mode, samples are mapped to notes in an octave from C to B, allowing you to use up to 12 samples. +if you need to use more samples, you may change the sample bank using effect `EBxx`. + +use of this mode is discouraged in favor of Sample type instruments. + +## notes + +due to limitations in some of those sound chips, some restrictions exist: + +- Amiga: sample lengths and loop will be set to an even number, and your sample can't be longer than 131070. +- NES: if on DPCM mode, only a limited selection of frequencies is available, and loop position isn't supported (only entire sample). +- SegaPCM: your sample can't be longer than 65535, and the maximum frequency is 31.25KHz. +- QSound: your sample can't be longer than 65535, and the loop length shall not be greater than 32767. +- Neo Geo (ADPCM-A): no looping supported. your samples will play at ~18.5KHz. +- Neo Geo (ADPCM-B): no loop position supported (only entire sample), and the maximum frequency is ~55KHz. +- YM2608: the maximum frequency is ~55KHz. +- MSM6258/MSM6295: no arbitrary frequency. +- ZX Spectrum Beeper: your sample can't be longer than 2048, and it always plays at ~55KHz. +- Seta/Allumer X1-010: frequency resolution is terrible in the lower end. your sample can't be longer than 131072. + +furthermore, many of these chips have a limited amount of sample memory. check memory usage in window > statistics. # the sample editor You can actually tweak your samples in Furnace's sample editor, which can be accessed by clicking on `window` (at the top of the screen) then clicking on `sample editor`. In there, you can modify certain data pertaining to your sample, such as the: - - volume of the sample (from 0% of the original to 200% of the original) - - pitch of the sample (from 1/6 of the original sample pitch to 6x the original sample pitch) - - and the sample rate of the sample (from 1KHz (1,000Hz) to 32KHz (32,000Hz)). + - volume of the sample in percentage, where 100% is the current level of the sample (note that you can distort it if you put it too high) + - the sample rate. + - what frequencies to filter, along with filter level/sweep and resonance options (much like the C64) + - and many more. -To apply the changes you made to a sample, just click the `apply` button at the bottom, near the preview button. - -# tips -If you have a sample you wanna use that is about 44100 or anything over 32000Hz downsample the sample to 32000Hz so that the pitch of the sample in Furnace stays like the original wav. You can do this in Audacity by going to the Bottom Left of audacity (If you see "Project Rate (Hz)" you are there) and changing the project rate to 32000Hz and save the file to wav in Audacity using "File -> Export -> Export as WAV". +The changes you make will be applied as soon as you've committed them to your sample, but they can be undoed and redoed, just like text. diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index d8551e1c..080e7269 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -22,10 +22,12 @@ this is a list of systems that Furnace supports, including each system's effects - [Philips SAA1099](saa1099.md) - [Microchip AY8930](ay8930.md) - [VERA](vera.md) +- [tildearrow Sound Unit](soundunit.md) - [Seta/Allumer X1-010](x1-010.md) - [WonderSwan](wonderswan.md) - [Bubble System WSG](bubblesystem.md) - [Namco 163](n163.md) +- [Namco WSG](namco.md) - [Yamaha OPL](opl.md) - [PC Speaker](pcspkr.md) - [Commodore PET](pet.md) diff --git a/papers/doc/7-systems/dac.md b/papers/doc/7-systems/dac.md new file mode 100644 index 00000000..856db2cd --- /dev/null +++ b/papers/doc/7-systems/dac.md @@ -0,0 +1,7 @@ +# Generic PCM DAC + +Realtek HD Audio's predecessor. It's just a 1/8/16-bit sample channel, with freely selectable rate and mono/stereo settings. With it, you can emulate PCM DACs found in Williams arcade boards, Sound Blasters, MSX TurboR, Atari STE, NEC PC-9801-86 etc. + +# effects + +none yet. diff --git a/papers/doc/7-systems/mmc5.md b/papers/doc/7-systems/mmc5.md index faf305c6..d483bd57 100644 --- a/papers/doc/7-systems/mmc5.md +++ b/papers/doc/7-systems/mmc5.md @@ -9,4 +9,4 @@ additionally, it offers an 8-bit DAC which can be used to play samples. only one # effects - `12xx`: set duty cycle or noise mode of channel. - - may be 0-3 for the pulse channels and 0-1 for the noise channel. + - may be 0-3 for the pulse channels. diff --git a/papers/doc/7-systems/n163.md b/papers/doc/7-systems/n163.md index 6057fa3c..efdc3c50 100644 --- a/papers/doc/7-systems/n163.md +++ b/papers/doc/7-systems/n163.md @@ -1,8 +1,8 @@ -# Namco C163 +# Namco 163 (also called N163, Namco C163, Namco 106 (sic), Namco 160 or Namco 129) -This is one of Namco's NES mappers, with up to 8 wavetable channels. It has also 128 byte of internal RAM, and both channel register and wavetables are stored here. Wavetables are variable size and freely allocable anywhere in RAM, it means it can use part of or continuously pre-loaded waveform and its sequences in RAM. But waveform RAM area becomes smaller as more channels are activated; as channel registers consumes 8 bytes for each channel. You must avoid conflict with channel register area and waveform for avoid broken channel playback. +This is one of Namco's NES mappers, with up to 8 wavetable channels. It has also 256 nibbles (128 bytes) of internal RAM, and both channel registers and wavetables are stored here. Wavetables are variable in size and freely allocable anywhere in RAM, it means it can use part of or continuously pre-loaded waveform and its sequences in RAM. At least 128 nibbles (64 bytes) can be dedicated to waves, with more available if not all channels are used - waveform RAM area becomes smaller as more channels are activated, since channel registers consume 8 bytes for each channel. You must avoid conflict with channel register area and waveform to avoid broken channel playback. -It outputs only a single channel at clock; so its sound quality gets more crunchy as more channels are activated. +Namco 163 uses time-division multiplexing for its output. this means that only one channel is output per sample (like OPLL and OPN2). therefore, its sound quality gets worse as more channels are activated. Furnace supports loading waveforms into RAM and waveform playback simultaneously, and channel limit is dynamically changeable with effect commands. You must load waveform to RAM first for playback, as its load behavior auto-updates when every waveform changes. diff --git a/papers/doc/7-systems/opz.md b/papers/doc/7-systems/opz.md index 61e93139..c7952a55 100644 --- a/papers/doc/7-systems/opz.md +++ b/papers/doc/7-systems/opz.md @@ -1,5 +1,7 @@ # Yamaha OPZ (YM2414) +**disclaimer: despite the name, this has nothing to do with teenage engineering's OP-Z synth!** + this is the YM2151's little-known successor, used in the Yamaha TX81Z and a few other Yamaha synthesizers. oh, and the Korg Z3 too. it adds these features on top of the YM2151: diff --git a/papers/doc/7-systems/sms.md b/papers/doc/7-systems/sms.md index 53560a97..882085f1 100644 --- a/papers/doc/7-systems/sms.md +++ b/papers/doc/7-systems/sms.md @@ -6,10 +6,13 @@ surely had better graphics than NES, but its sound (fairly weak, 4ch PSG with A- this console is powered by a derivative of the Texas Instruments SN76489. +the original iteration of the SN76489 used in the TI-99/4A computers was clocked much lower at 447 kHz, being able to play as low as 13.670 Hz (A -1). consequentially, pitch accuracy for higher notes is compromised. + + # effects - `20xy`: set noise mode. - - `x` controls whether to inherit frequency from hannel 3. + - `x` controls whether to inherit frequency from channel 3. - 0: use one of 3 preset frequencies (C: A-2; C#: A-3; D: A-4). - 1: use frequency of channel 3. - `y` controls whether to select noise or thin pulse. diff --git a/papers/doc/7-systems/soundunit.md b/papers/doc/7-systems/soundunit.md new file mode 100644 index 00000000..34cc16b4 --- /dev/null +++ b/papers/doc/7-systems/soundunit.md @@ -0,0 +1,45 @@ +# tildearrow Sound Unit +This is a fantasy sound chip, used in the specs2 fantasy computer designed by tildearrow. It includes native support for sample playback, but with only 8KB or 64KB of sample data, depending on the configuration used. Since 0.6pre1, this sound chip is no longer hidden by default and can be accessed through the module creation screen and can be added or removed. + +# effects + +- `10xx`: set waveform + - 0: pulse wave + - 1: sawtooth + - 2: sine wave + - 3: triangle wave + - 4: noise + - 5: periodic noise + - 6: XOR sine + - 7: XOR triangle +- `12xx`: set pulse width (0 to 7F) +- `13xx`: set resonance of filter (0 to FF) + - despite what the internal effects list says (0 to F), you can use a resonance value from 0 to FF (255) +- `14xx`: set filter mode and ringmod + - bit 0: ring mod + - bit 1: low pass + - bit 2: high pass + - bit 3: band pass +- `15xx`: set frequency sweep period low byte +- `16xx`: set frequency sweep period high byte +- `17xx`: set volume sweep period low byte +- `18xx`: set volume sweep period high byte +- `19xx`: set cutoff sweep period low byte +- `1Axx`: set cutoff sweep period high byte +- `1Bxx`: set frequency sweep boundary +- `1Cxx`: set volume sweep boundary +- `1Dxx`: set cutoff sweep boundary +- `1Exx`: set phase reset period low byte +- `1Fxx`: set phase reset period high byte +- `20xx`: toggle frequency sweep + - bit 0-6: speed + - bit 7: up direction +- `21xx`: toggle volume sweep + - bit 0-4: speed + - bit 5: up direction + - bit 6: loop + - bit 7: alternate +- `22xx`: toggle cutoff sweep + - bit 0-6: speed + - bit 7: up direction +- `4xxx`: set cutoff (0 to FFF) diff --git a/papers/doc/7-systems/x1-010.md b/papers/doc/7-systems/x1-010.md index 411afb3e..80b5b41a 100644 --- a/papers/doc/7-systems/x1-010.md +++ b/papers/doc/7-systems/x1-010.md @@ -8,9 +8,9 @@ Allumer rebadged it for their own arcade hardware. It has 16 channels, which can all be switched between PCM sample or wavetable playback mode. Wavetable playback needs to paired with envelope, similar to AY PSG, but shapes are stored in RAM and as such are user-definable. -In furnace, this chip can be configured for original arcade mono output or stereo output - it simulates early 'incorrect' emulation on some mono hardware, but it is also based on the assumption that each channel is connected to each output. +In Furnace, this chip can be configured for original arcade mono output or stereo output - it simulates early 'incorrect' emulation on some mono hardware, but it is also based on the assumption that each channel is connected to each output. -# waveform types +# Waveform types This chip supports 2 types of waveforms, needs to be paired to external 8 KB RAM to access these features: @@ -44,4 +44,4 @@ In furnace, you can enable the envelope shape split mode. When it is set, its wa - `y` is the denominator. - if `x` or `y` are 0 this will disable auto-envelope mode. -* PCM frequency: 255 step, fomula: `step * (Chip clock / 8192)`; 1.95KHz to 498KHz if Chip clock is 16MHz. +* PCM frequency: 255 step, formula: `step * (Chip clock / 8192)`; 1.95KHz to 498KHz if Chip clock is 16MHz. diff --git a/papers/doc/7-systems/zxbeep.md b/papers/doc/7-systems/zxbeep.md index 1c6cd74f..9e25284b 100644 --- a/papers/doc/7-systems/zxbeep.md +++ b/papers/doc/7-systems/zxbeep.md @@ -2,9 +2,12 @@ Rather than having a dedicated sound synthesizer, early ZX Spectrum models had one piezo beeper, controlled by Z80 CPU and ULA chip. It's capabilities should be on par with an IBM PC speaker... right? -Not really - very soon talented programmers found out ways to output much more than one square wave channel. A lot of ZX beeper routines do exist, as of 0.6pre1 Furnace supports only one - Follin-like engine with 6 channels of narrow pulse wave and click drums. +Not really - very soon talented programmers found out ways to output much more than one square wave channel. A lot of ZX beeper routines do exist, but as of 0.6pre1 Furnace supports only one - Follin-like engine with 6 channels of narrow pulse wave and click drums. # effects -- `12xx`: set pulse width -- `17xx`: trigger overlay drums. +- `12xx`: set pulse width. +- `17xx`: trigger overlay drum. + - `xx` is the sample number. + - overlay drums are 1-bit and always play at 55930Hz (NTSC) or 55420Hz (PAL). + - the maximum length is 2048! \ No newline at end of file diff --git a/papers/doc/README.md b/papers/doc/README.md index 86e522a1..771ea5f1 100644 --- a/papers/doc/README.md +++ b/papers/doc/README.md @@ -19,6 +19,7 @@ writers: - nicco1690 - DeMOSic - cam900 +- host12prog other: diff --git a/papers/export-tech.md b/papers/export-tech.md index 3bf71546..dbfadece 100644 --- a/papers/export-tech.md +++ b/papers/export-tech.md @@ -4,28 +4,23 @@ TODO -## pattern data +## macro data -read sequentially. +read length, loop and then release (1 byte). +if it is a 2-byte macro, read a dummy byte. -first byte determines what to read next: +then read data. + +## binary command stream + +read channel, command and values. + +if channel is 80 or higher, then it is a special command: ``` -NVI..EEE - -N: note -V: volume -I: instrument - -EEE: effect count (0-7) +fb xx xx xx xx: set tick rate +fc xx xx: wait xxxx ticks +fd xx: wait xx ticks +fe: wait one tick +ff: stop ``` - -if you read 0, end of pattern. -otherwise read in following order: - -1. note -2. volume -3. instrument -4. effect and effect value - -then read number of rows until next value, minus 1. diff --git a/papers/format.md b/papers/format.md index 013f2c5d..da06488f 100644 --- a/papers/format.md +++ b/papers/format.md @@ -25,10 +25,28 @@ the format has changed several times across versions. a `(>=VER)` indicates this furthermore, an `or reserved` indicates this field is always present, but is reserved when the version condition is not met. +the `size of this block` fields represent the size of a block excluding the ID and the aforementioned field. +these fields are 0 in format versions prior to 100 (0.6pre1). + # format versions the format versions are: +- 114: Furnace dev114 +- 113: Furnace dev113 +- 112: Furnace dev112 +- 111: Furnace dev111 +- 110: Furnace dev110 +- 109: Furnace dev109 +- 108: Furnace dev108 +- 107: Furnace dev107 +- 106: Furnace dev106 +- 105: Furnace dev105 +- 104: Furnace dev104 +- 103: Furnace dev103 +- 102: Furnace 0.6pre1 (dev102) +- 101: Furnace 0.6pre1 (dev101) +- 100: Furnace 0.6pre1 - 99: Furnace dev99 - 98: Furnace dev98 - 97: Furnace dev97 @@ -130,7 +148,7 @@ size | description size | description -----|------------------------------------ 4 | "INFO" block ID - 4 | reserved + 4 | size of this block 1 | time base (of first song) 1 | speed 1 (of first song) 1 | speed 2 (of first song) @@ -231,6 +249,14 @@ size | description | - 0xbc: reserved - 8 channels | - 0xbd: YM2612 extra features extended - 11 channels | - 0xbe: YM2612 extra features - 7 channels + | - 0xbf: T6W28 - 4 channels + | - 0xc0: PCM DAC - 1 channel + | - 0xc1: YM2612 CSM - 10 channels + | - 0xc2: Neo Geo CSM (YM2610) - 18 channels + | - 0xc3: OPN CSM - 10 channels + | - 0xc4: PC-98 CSM - 20 channels + | - 0xc5: YM2610B CSM - 20 channels + | - 0xc6: MSM5232 - 8 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - 0xfd: Dummy System - 8 channels @@ -248,7 +274,7 @@ size | description 4f | A-4 tuning 1 | limit slides (>=36) or reserved 1 | linear pitch (>=36) or reserved - | - 0: non-linaer + | - 0: non-linear | - 1: only pitch change (04xy/E5xx) linear | - 2: full linear (>=94) 1 | loop modality (>=36) or reserved @@ -300,7 +326,7 @@ size | description 1 | new ins affects envelope (Game Boy) (>=72) or reserved 1 | ExtCh channel state is shared (>=78) or reserved 1 | ignore DAC mode change outside of intended channel (>=83) or reserved - 1 | E1xx and E2xx also take priority over Slide00 (>=83) or reserved + 1 | E1xy and E2xy also take priority over Slide00 (>=83) or reserved 1 | new Sega PCM (with macros and proper vol/pan) (>=84) or reserved 1 | weird f-num/block-based chip pitch slides (>=85) or reserved 1 | SN duty macro always resets phase (>=86) or reserved @@ -311,7 +337,12 @@ size | description 1 | new volume scaling strategy (>=99) or reserved 1 | volume macro still applies after end (>=99) or reserved 1 | broken outVol (>=99) or reserved - 9 | reserved + 1 | E1xy and E2xy stop on same note (>=100) or reserved + 1 | broken initial position of porta after arp (>=101) or reserved + 1 | SN periods under 8 are treated as 1 (>=108) or reserved + 1 | cut/delay effect policy (>=110) or reserved + 1 | 0B/0D effect treatment (>=113) or reserved + 4 | reserved --- | **virtual tempo data** 2 | virtual tempo numerator of first song (>=96) or reserved 2 | virtual tempo denominator of first song (>=96) or reserved @@ -321,6 +352,13 @@ size | description 1 | number of additional subsongs 3 | reserved 4?? | pointers to subsong data + --- | **additional metadata** (>=103) + STR | system name + STR | album/category/game name + STR | song name (Japanese) + STR | song author (Japanese) + STR | system name (Japanese) + STR | album/category/game name (Japanese) ``` # subsong @@ -332,7 +370,7 @@ the way it's currently done is really weird, but it provides for some backwards size | description -----|------------------------------------ 4 | "SONG" block ID - 4 | reserved + 4 | size of this block 1 | time base 1 | speed 1 1 | speed 2 @@ -386,7 +424,7 @@ notes: size | description -----|------------------------------------ 4 | "INST" block ID - 4 | reserved + 4 | size of this block 2 | format version (see header) 1 | instrument type | - 0: standard @@ -421,6 +459,7 @@ size | description | - 29: SNES | - 30: Sound Unit | - 31: Namco WSG + | - 32: OPL (drums) 1 | reserved STR | instrument name --- | **FM instrument data** @@ -459,7 +498,8 @@ size | description 1 | vib 1 | ws 1 | ksr - 12 | reserved + 1 | operator enabled (>=114) or reserved + 11 | reserved --- | **Game Boy instrument data** 1 | volume 1 | direction @@ -512,7 +552,18 @@ size | description 4 | extra 1 macro loop (>=17) 4 | extra 2 macro loop (>=17) 4 | extra 3 macro loop (>=17) - 1 | arp macro mode + 1 | arp macro mode (<112) or reserved + | - treat this value in a special way. + | - before version 112, this byte indicates whether the arp macro mode is fixed or not. + | - from that version onwards, the fixed mode is part of the macro values. + | - to convert a <112 macro mode to a modern one, do the following: + | - is the macro mode set to fixed? + | - if yes, then: + | - set bit 30 of all arp macro values (this is the fixed mode bit) + | - does the macro loop? + | - if yes, then do nothing else + | - if no, then add one to the macro length, and set the last macro value to 0 + | - if no, then do nothing 1 | reserved (>=17) or volume macro height (>=15) or reserved 1 | reserved (>=17) or duty macro height (>=15) or reserved 1 | reserved (>=17) or wave macro height (>=15) or reserved @@ -520,6 +571,7 @@ size | description | - before version 87, if this is the C64 relative cutoff macro, its values were stored offset by 18. 4?? | arp macro | - before version 31, this macro's values were stored offset by 12. + | - from version 112 onward, bit 30 of a value indicates fixed mode. 4?? | duty macro | - before version 87, if this is the C64 relative duty macro, its values were stored offset by 12. 4?? | wave macro @@ -787,6 +839,148 @@ size | description 1 | vib depth 1 | am depth 23 | reserved + --- | **Sound Unit data** (>=104) + 1 | use sample + 1 | switch roles of phase reset timer and frequency + --- | **Game Boy envelope sequence** (>=105) + 1 | length + ??? | hardware sequence data + | size is length*3: + | 1 byte: command + | - 0: set envelope + | - 1: set sweep + | - 2: wait + | - 3: wait for release + | - 4: loop + | - 5: loop until release + | 2 bytes: data + | - for set envelope: + | - 1 byte: parameter + | - bit 4-7: volume + | - bit 3: direction + | - bit 0-2: length + | - 1 byte: sound length + | - for set sweep: + | - 1 byte: parameter + | - bit 4-6: length + | - bit 3: direction + | - bit 0-2: shift + | - 1 byte: nothing + | - for wait: + | - 1 byte: length (in ticks) + | - 1 byte: nothing + | - for wait for release: + | - 2 bytes: nothing + | - for loop/loop until release: + | - 2 bytes: position + --- | **Game Boy extra flags** (>=106) + 1 | use software envelope + 1 | always init hard env on new note + --- | **ES5506 data** (>=107) + 1 | filter mode + | - 0: HPK2_HPK2 + | - 1: HPK2_LPK1 + | - 2: LPK2_LPK2 + | - 3: LPK2_LPK1 + 2 | K1 + 2 | K2 + 2 | envelope count + 1 | left volume ramp + 1 | right volume ramp + 1 | K1 ramp + 1 | K2 ramp + 1 | K1 slow + 1 | K2 slow + --- | **SNES data** (>=109) + 1 | use envelope + 1 | gain mode + 1 | gain + 1 | attack + 1 | decay + 1 | sustain + 1 | release + --- | **macro speeds/delays** (>=111) + 1 | volume macro speed + 1 | arp macro speed + 1 | duty macro speed + 1 | wave macro speed + 1 | pitch macro speed + 1 | extra 1 macro speed + 1 | extra 2 macro speed + 1 | extra 3 macro speed + 1 | alg macro speed + 1 | fb macro speed + 1 | fms macro speed + 1 | ams macro speed + 1 | left panning macro speed + 1 | right panning macro speed + 1 | phase reset macro speed + 1 | extra 4 macro speed + 1 | extra 5 macro speed + 1 | extra 6 macro speed + 1 | extra 7 macro speed + 1 | extra 8 macro speed + 1 | volume macro delay + 1 | arp macro delay + 1 | duty macro delay + 1 | wave macro delay + 1 | pitch macro delay + 1 | extra 1 macro delay + 1 | extra 2 macro delay + 1 | extra 3 macro delay + 1 | alg macro delay + 1 | fb macro delay + 1 | fms macro delay + 1 | ams macro delay + 1 | left panning macro delay + 1 | right panning macro delay + 1 | phase reset macro delay + 1 | extra 4 macro delay + 1 | extra 5 macro delay + 1 | extra 6 macro delay + 1 | extra 7 macro delay + 1 | extra 8 macro delay + --- | **operator macro speeds/delay** × 4 (>=111) + 1 | AM macro speed + 1 | AR macro speed + 1 | DR macro speed + 1 | MULT macro speed + 1 | RR macro speed + 1 | SL macro speed + 1 | TL macro speed + 1 | DT2 macro speed + 1 | RS macro speed + 1 | DT macro speed + 1 | D2R macro speed + 1 | SSG-EG macro speed + 1 | DAM macro speed + 1 | DVB macro speed + 1 | EGT macro speed + 1 | KSL macro speed + 1 | SUS macro speed + 1 | VIB macro speed + 1 | WS macro speed + 1 | KSR macro speed + 1 | AM macro delay + 1 | AR macro delay + 1 | DR macro delay + 1 | MULT macro delay + 1 | RR macro delay + 1 | SL macro delay + 1 | TL macro delay + 1 | DT2 macro delay + 1 | RS macro delay + 1 | DT macro delay + 1 | D2R macro delay + 1 | SSG-EG macro delay + 1 | DAM macro delay + 1 | DVB macro delay + 1 | EGT macro delay + 1 | KSL macro delay + 1 | SUS macro delay + 1 | VIB macro delay + 1 | WS macro delay + 1 | KSR macro delay ``` # wavetable @@ -795,24 +989,64 @@ size | description size | description -----|------------------------------------ 4 | "WAVE" block ID - 4 | reserved + 4 | size of this block STR | wavetable name - 4 | wavetable size - 4 | wavetable min - 4 | wavetable max + 4 | wavetable width + 4 | reserved + 4 | wavetable height 4?? | wavetable data ``` -# sample +# sample (>=102) + +this is the new sample storage format used in Furnace dev102 and higher. + +``` +size | description +-----|------------------------------------ + 4 | "SMP2" block ID + 4 | size of this block + STR | sample name + 4 | length + 4 | compatibility rate + 4 | C-4 rate + 1 | depth + | - 0: ZX Spectrum overlay drum (1-bit) + | - 1: 1-bit NES DPCM (1-bit) + | - 3: YMZ ADPCM + | - 4: QSound ADPCM + | - 5: ADPCM-A + | - 6: ADPCM-B + | - 8: 8-bit PCM + | - 9: BRR (SNES) + | - 10: VOX + | - 16: 16-bit PCM + 3 | reserved + 4 | loop start + | - -1 means no loop + 4 | loop end + | - -1 means no loop + 16 | sample presence bitfields + | - for future use. + | - indicates whether the sample should be present in the memory of a system. + | - read 4 32-bit numbers (for 4 memory banks per system, e.g. YM2610 + | does ADPCM-A and ADPCM-B on separate memory banks). + ??? | sample data + | - size is length +``` + +# old sample (<102) + +this format is present when saving using previous Furnace versions. ``` size | description -----|------------------------------------ 4 | "SMPL" block ID - 4 | reserved + 4 | size of this block STR | sample name 4 | length - 4 | rate + 4 | compatibility rate 2 | volume (<58) or reserved 2 | pitch (<58) or reserved 1 | depth @@ -841,7 +1075,7 @@ size | description size | description -----|------------------------------------ 4 | "PATR" block ID - 4 | reserved + 4 | size of this block 2 | channel 2 | pattern index 2 | subsong (>=95) or reserved diff --git a/papers/newIns.md b/papers/newIns.md new file mode 100644 index 00000000..fcc5f966 --- /dev/null +++ b/papers/newIns.md @@ -0,0 +1,49 @@ +# possible new Furnace instrument format + +the main issue with Furnace instrument files is that they are too big, even if the instrument is nothing more than the FM setup... + +the aim of this new format is to greatly reduce the size of a resulting instrument. + +``` +size | description +-----|------------------------------------ + 6 | "FURINS" format magic + 2 | format version + 1 | instrument type + ??? | feature bits + 4 | instrument length (if wave/sample bits are on) +``` + +the "feature bits" field is a variable length bitfield. bit 7 in a byte indicates "read one more byte". + +the feature bits are: + +- 0: has wavetables +- 1: has samples +- 2: has name +- 3: FM data +- 4: FM data size (1: 2-op, 0: 4-op) +- 5: FM data includes OPL/OPZ data + - if off, only read an op until ssgEnv. + - if on, read everything else. +- 6: Game Boy data +- 7: (continue in next byte) +- 8: C64 data +- 9: Amiga data +- 10: standard data (macros) +- 11: operator macros +- 12: release points +- 13: op release points +- 14: extended op macros +- 15: (continue in next byte) +- 16: OPL drums mode data +- 17: Amiga sample map data +- 18: Namco 163 data +- 19: extra macros +- 20: FDS data +- 21: OPZ data +- 22: wavetable synth data +- 23: (continue in next byte) +- 24: additional macro modes +- 25: extra C64 data +- 26: MultiPCM data diff --git a/papers/screenshot2.png b/papers/screenshot2.png new file mode 100644 index 00000000..afda65b2 Binary files /dev/null and b/papers/screenshot2.png differ diff --git a/res/Info.plist b/res/Info.plist index d9528b15..7b8d0120 100644 --- a/res/Info.plist +++ b/res/Info.plist @@ -15,17 +15,17 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString - 0.6pre0 + 0.6pre1 CFBundleName Furnace CFBundlePackageType APPL CFBundleShortVersionString - 0.6pre0 + 0.6pre1 CFBundleSignature ???? CFBundleVersion - 0.6pre0 + 0.6pre1 NSHumanReadableCopyright NSHighResolutionCapable diff --git a/res/furnace.appdata.xml b/res/furnace.appdata.xml index f5393048..2214cb10 100644 --- a/res/furnace.appdata.xml +++ b/res/furnace.appdata.xml @@ -10,10 +10,13 @@

- this is a work-in-progress chiptune tracker which interacts with DefleMask module files (.dmf). + the biggest chiptune tracker ever made!

- it supports creating songs for Sega Genesis, Master System, Game Boy, PC Engine, NES, C64, YM2151/PCM and Neo Geo. featuring a clean-room design (zero reverse-engineered code and zero decompilation; using official DMF specs, guesswork and ABX tests only), bug/quirk implementation for increased playback accuracy, and accurate emulation cores whether possible (Nuked, MAME, SameBoy, Mednafen PCE, puNES, reSID and ymfm). + it allows you to create songs using a music tracker interface for several computer/game console/arcade sound chips. +

+

+ it also offers DefleMask compatibility, allowing you to import your songs and even export them back for interoperability.

diff --git a/scripts/Cross-Linux-armhf.cmake b/scripts/Cross-Linux-armhf.cmake new file mode 100644 index 00000000..3e490ae1 --- /dev/null +++ b/scripts/Cross-Linux-armhf.cmake @@ -0,0 +1,15 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR arm) + +set(TARGET_PREFIX arm-linux-gnueabihf) + +set(CMAKE_C_COMPILER ${TARGET_PREFIX}-gcc) +set(CMAKE_CXX_COMPILER ${TARGET_PREFIX}-g++) +set(PKG_CONFIG_EXECUTABLE ${TARGET_PREFIX}-pkg-config) + +set(CMAKE_FIND_ROOT_PATH /usr/${TARGET_PREFIX} /usr/lib/${TARGET_PREFIX}) + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) diff --git a/src/asm/6502/macroInt.s b/src/asm/6502/macroInt.s new file mode 100644 index 00000000..c115a807 --- /dev/null +++ b/src/asm/6502/macroInt.s @@ -0,0 +1,32 @@ +macroState=$50 ; pointer to state +macroAddr=$52 ; pointer to address + +; macro state takes 4 bytes +; macroPos bits: +; 7: had +; 6: will + +; x: macro +macroIntRun: + lda macroAddr,x + ora macroAddr+1,x + beq :+ + + ; do macro +: rts + +; set the macro address, then call +; x: macro +macroIntInit: + lda #0 + sta macroState,x + sta macroPos,x + txa + rol + tax + lda macroAddr,x + ora macroAddr+1,x + beq :+ + lda #$40 + sta macroState,x +: rts diff --git a/src/audio/abstract.cpp b/src/audio/abstract.cpp index e44fdf7e..942e3143 100644 --- a/src/audio/abstract.cpp +++ b/src/audio/abstract.cpp @@ -92,10 +92,12 @@ bool TAMidiOut::closeDevice() { } std::vector TAMidiIn::listDevices() { + logW("attempting to list devices of abstract TAMidiIn!"); return std::vector(); } std::vector TAMidiOut::listDevices() { + logW("attempting to list devices of abstract TAMidiOut!"); return std::vector(); } diff --git a/src/audio/jack.cpp b/src/audio/jack.cpp index 6e7b7bf1..f9502cf4 100644 --- a/src/audio/jack.cpp +++ b/src/audio/jack.cpp @@ -52,17 +52,30 @@ void TAAudioJACK::onBufferSize(jack_nframes_t bufsize) { } void TAAudioJACK::onProcess(jack_nframes_t nframes) { - if (audioProcCallback!=NULL) { - if (midiIn!=NULL) midiIn->gather(); - audioProcCallback(audioProcCallbackUser,inBufs,outBufs,desc.inChans,desc.outChans,desc.bufsize); - } for (int i=0; idesc.bufsize) { + delete[] inBufs[i]; + inBufs[i]=new float[nframes]; + } + memcpy(iInBufs[i],inBufs[i],nframes*sizeof(float)); + } + for (int i=0; idesc.bufsize) { + delete[] outBufs[i]; + outBufs[i]=new float[nframes]; + } + } + if (audioProcCallback!=NULL) { + if (midiIn!=NULL) midiIn->gather(); + audioProcCallback(audioProcCallbackUser,inBufs,outBufs,desc.inChans,desc.outChans,nframes); } for (int i=0; i msg; if (port==NULL) return false; - while (true) { - TAMidiMessage m; - double t=port->getMessage(&msg); - if (msg.empty()) break; + try { + while (true) { + TAMidiMessage m; + double t=port->getMessage(&msg); + if (msg.empty()) break; - // parse message - m.time=t; - m.type=msg[0]; - if (m.type!=TA_MIDI_SYSEX && msg.size()>1) { - memcpy(m.data,msg.data()+1,MIN(msg.size()-1,7)); - } else if (m.type==TA_MIDI_SYSEX) { - m.sysExData.reset(new unsigned char[msg.size()]); - m.sysExLen=msg.size(); - logD("got a SysEx of length %ld!",msg.size()); - memcpy(m.sysExData.get(),msg.data(),msg.size()); + // parse message + m.time=t; + m.type=msg[0]; + if (m.type!=TA_MIDI_SYSEX && msg.size()>1) { + memcpy(m.data,msg.data()+1,MIN(msg.size()-1,7)); + } else if (m.type==TA_MIDI_SYSEX) { + m.sysExData.reset(new unsigned char[msg.size()]); + m.sysExLen=msg.size(); + logD("got a SysEx of length %ld!",msg.size()); + memcpy(m.sysExData.get(),msg.data(),msg.size()); + } + queue.push(m); } - queue.push(m); + } catch (RtMidiError& e) { + logE("MIDI input error! %s",e.what()); + closeDevice(); + return false; } return true; } @@ -180,7 +186,12 @@ bool TAMidiOutRtMidi::send(const TAMidiMessage& what) { return false; } len=what.sysExLen; - port->sendMessage(what.sysExData.get(),len); + try { + port->sendMessage(what.sysExData.get(),len); + } catch (RtMidiError& e) { + logE("MIDI output error! %s",e.what()); + return false; + } return true; break; case TA_MIDI_MTC_FRAME: @@ -194,7 +205,12 @@ bool TAMidiOutRtMidi::send(const TAMidiMessage& what) { len=1; break; } - port->sendMessage((const unsigned char*)&what.type,len); + try { + port->sendMessage((const unsigned char*)&what.type,len); + } catch (RtMidiError& e) { + logE("MIDI output error! %s",e.what()); + return false; + } return true; } diff --git a/src/audio/sdl.cpp b/src/audio/sdlAudio.cpp similarity index 97% rename from src/audio/sdl.cpp rename to src/audio/sdlAudio.cpp index 3ef38d4f..f1d0e1e9 100644 --- a/src/audio/sdl.cpp +++ b/src/audio/sdlAudio.cpp @@ -17,11 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include #include #include #include "../ta-log.h" -#include "sdl.h" +#include "sdlAudio.h" void taSDLProcess(void* inst, unsigned char* buf, int nframes) { TAAudioSDL* in=(TAAudioSDL*)inst; @@ -111,7 +110,11 @@ bool TAAudioSDL::init(TAAudioDesc& request, TAAudioDesc& response) { desc.outFormat=TA_AUDIO_FORMAT_F32; ac.freq=desc.rate; +#ifdef TA_BIG_ENDIAN + ac.format=AUDIO_F32MSB; +#else ac.format=AUDIO_F32; +#endif ac.channels=desc.outChans; ac.samples=desc.bufsize; ac.callback=taSDLProcess; diff --git a/src/audio/sdl.h b/src/audio/sdlAudio.h similarity index 100% rename from src/audio/sdl.h rename to src/audio/sdlAudio.h diff --git a/src/check/check_dirent_type.c b/src/check/check_dirent_type.c new file mode 100644 index 00000000..e65a0d6b --- /dev/null +++ b/src/check/check_dirent_type.c @@ -0,0 +1,7 @@ +#include + +int main(int, char**) { + struct dirent deTest = { }; + unsigned char deType = deTest.d_type; + return 0; +} diff --git a/src/check/check_sysIO.c b/src/check/check_sysIO.c new file mode 100644 index 00000000..653721e6 --- /dev/null +++ b/src/check/check_sysIO.c @@ -0,0 +1,6 @@ +#include + +int main(int, char**) { + inb(0x61); + outb(0x00,0x61); +} diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp new file mode 100644 index 00000000..87bf76dd --- /dev/null +++ b/src/cli/cli.cpp @@ -0,0 +1,148 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "cli.h" +#include "../ta-log.h" + +bool cliQuit=false; + +#ifndef _WIN32 +static void handleTerm(int) { + cliQuit=true; +} +#endif + +void FurnaceCLI::bindEngine(DivEngine* eng) { + e=eng; +} + +bool FurnaceCLI::loop() { + bool escape=false; + bool escapeSecondStage=false; + while (!cliQuit) { +#ifdef _WIN32 + int c; + c=fgetc(stdin); + if (c==EOF) break; +#else + unsigned char c; + if (read(STDIN_FILENO,&c,1)<=0) continue; +#endif + if (escape) { + if (escapeSecondStage) { + switch (c) { + case 'C': // right + e->setOrder(e->getOrder()+1); + escape=false; + escapeSecondStage=false; + break; + case 'D': // left + e->setOrder(e->getOrder()-1); + escape=false; + escapeSecondStage=false; + break; + default: + escape=false; + escapeSecondStage=false; + break; + } + } else { + switch (c) { + case '[': case 'O': + escapeSecondStage=true; + break; + default: + escape=false; + break; + } + } + } else { + switch (c) { + case 0x1b: // + escape=true; + break; + case 'h': // left + e->setOrder(e->getOrder()-1); + break; + case 'l': // right + e->setOrder(e->getOrder()+1); + break; + case ' ': + if (e->isHalted()) { + e->resume(); + } else { + e->halt(); + } + break; + } + } + } + printf("\n"); + return true; +} + +bool FurnaceCLI::finish() { +#ifdef _WIN32 +#else + if (tcsetattr(0,TCSAFLUSH,&termpropold)!=0) { + logE("could not set console attributes!"); + logE("you may have to run `reset` on your terminal."); + return false; + } +#endif + return true; +} + +// blatantly copied from tildearrow/tfmxplay +bool FurnaceCLI::init() { +#ifdef _WIN32 + winin=GetStdHandle(STD_INPUT_HANDLE); + winout=GetStdHandle(STD_OUTPUT_HANDLE); + int termprop=0; + int termpropi=0; + GetConsoleMode(winout,(LPDWORD)&termprop); + GetConsoleMode(winin,(LPDWORD)&termpropi); + termprop|=ENABLE_VIRTUAL_TERMINAL_PROCESSING; + termpropi&=~ENABLE_LINE_INPUT; + SetConsoleMode(winout,termprop); + SetConsoleMode(winin,termpropi); +#else + sigemptyset(&intsa.sa_mask); + intsa.sa_flags=0; + intsa.sa_handler=handleTerm; + sigaction(SIGINT,&intsa,NULL); + + if (tcgetattr(0,&termprop)!=0) { + logE("could not get console attributes!"); + return false; + } + memcpy(&termpropold,&termprop,sizeof(struct termios)); + termprop.c_lflag&=~ECHO; + termprop.c_lflag&=~ICANON; + if (tcsetattr(0,TCSAFLUSH,&termprop)!=0) { + logE("could not set console attributes!"); + return false; + } +#endif + return true; +} + +FurnaceCLI::FurnaceCLI(): + e(NULL) { +} diff --git a/src/engine/platform/ym2203shared.h b/src/cli/cli.h similarity index 56% rename from src/engine/platform/ym2203shared.h rename to src/cli/cli.h index 5aec7916..55ad36b3 100644 --- a/src/engine/platform/ym2203shared.h +++ b/src/cli/cli.h @@ -17,29 +17,40 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -static unsigned short opOffs[4]={ - 0x00, 0x04, 0x08, 0x0c -}; -static bool isOutput[8][4]={ - // 1 3 2 4 - {false,false,false,true}, - {false,false,false,true}, - {false,false,false,true}, - {false,false,false,true}, - {false,false,true ,true}, - {false,true ,true ,true}, - {false,true ,true ,true}, - {true ,true ,true ,true}, -}; -static unsigned char dtTable[8]={ - 7,6,5,0,1,2,3,4 +#ifndef _FUR_CLI_H +#define _FUR_CLI_H + + +#include "../engine/engine.h" + +#include +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#endif + +class FurnaceCLI { + DivEngine* e; + +#ifdef _WIN32 + HANDLE winin; + HANDLE winout; +#else + struct sigaction intsa; + struct termios termprop; + struct termios termpropold; +#endif + + public: + void bindEngine(DivEngine* eng); + bool loop(); + bool finish(); + bool init(); + FurnaceCLI(); }; -static int orderedOps[4]={ - 0,2,1,3 -}; - -#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} -#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } - -#define CHIP_FREQBASE 4720270 +#endif diff --git a/src/engine/config.cpp b/src/engine/config.cpp index 92866a9f..38cb2b04 100644 --- a/src/engine/config.cpp +++ b/src/engine/config.cpp @@ -23,11 +23,92 @@ #include #ifdef _WIN32 +#include "winStuff.h" #define CONFIG_FILE "\\furnace.cfg" #else +#ifdef __HAIKU__ +#include +#include +#endif +#include +#include +#include #define CONFIG_FILE "/furnace.cfg" #endif +#ifdef IS_MOBILE +#ifdef HAVE_SDL2 +#include +#else +#error "Furnace mobile requires SDL2!" +#endif +#endif + +void DivEngine::initConfDir() { +#ifdef _WIN32 + // maybe move this function in here instead? + configPath=getWinConfigPath(); +#elif defined(IS_MOBILE) + configPath=SDL_GetPrefPath("tildearrow","furnace"); +#else +#ifdef __HAIKU__ + char userSettingsDir[PATH_MAX]; + status_t findUserDir = find_directory(B_USER_SETTINGS_DIRECTORY,0,true,userSettingsDir,PATH_MAX); + if (findUserDir==B_OK) { + configPath=userSettingsDir; + } else { + logW("unable to find/create user settings directory (%s)!",strerror(findUserDir)); + configPath="."; + return; + } +#else + // TODO this should check XDG_CONFIG_HOME first + char* home=getenv("HOME"); + if (home==NULL) { + int uid=getuid(); + struct passwd* entry=getpwuid(uid); + if (entry==NULL) { + logW("unable to determine home directory (%s)!",strerror(errno)); + configPath="."; + return; + } else { + configPath=entry->pw_dir; + } + } else { + configPath=home; + } +#ifdef __APPLE__ + configPath+="/Library/Application Support"; +#else + // FIXME this doesn't honour XDG_CONFIG_HOME *at all* + configPath+="/.config"; +#endif // __APPLE__ +#endif // __HAIKU__ +#ifdef __APPLE__ + configPath+="/Furnace"; +#else + configPath+="/furnace"; +#endif // __APPLE__ + struct stat st; + std::string pathSep="/"; + configPath+=pathSep; + size_t sepPos=configPath.find(pathSep,1); + while (sepPos!=std::string::npos) { + std::string subpath=configPath.substr(0,sepPos++); + if (stat(subpath.c_str(),&st)!=0) { + logI("creating config path element %s ...",subpath.c_str()); + if (mkdir(subpath.c_str(),0755)!=0) { + logW("could not create config path element %s! (%s)",subpath.c_str(),strerror(errno)); + configPath="."; + return; + } + } + sepPos=configPath.find(pathSep,sepPos); + } + configPath.resize(configPath.length()-pathSep.length()); +#endif // _WIN32 +} + bool DivEngine::saveConf() { configFile=configPath+String(CONFIG_FILE); FILE* f=ps_fopen(configFile.c_str(),"wb"); @@ -158,6 +239,10 @@ void DivEngine::setConf(String key, double value) { conf[key]=fmt::sprintf("%f",value); } +void DivEngine::setConf(String key, const char* value) { + conf[key]=String(value); +} + void DivEngine::setConf(String key, String value) { conf[key]=value; } diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 03bc23ca..2fde9add 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -55,6 +55,18 @@ enum DivDispatchCmds { DIV_CMD_PRE_PORTA, // (inPorta, isPortaOrSlide) DIV_CMD_PRE_NOTE, // used in C64 (note) + // these will be used in ROM export. + // do NOT implement! + DIV_CMD_HINT_VIBRATO, // (speed, depth) + DIV_CMD_HINT_VIBRATO_RANGE, // (range) + DIV_CMD_HINT_VIBRATO_SHAPE, // (shape) + DIV_CMD_HINT_PITCH, // (pitch) + DIV_CMD_HINT_ARPEGGIO, // (note1, note2) + DIV_CMD_HINT_VOLUME, // (vol) + DIV_CMD_HINT_VOL_SLIDE, // (amount, oneTick) + DIV_CMD_HINT_PORTA, // (target, speed) + DIV_CMD_HINT_LEGATO, // (note) + DIV_CMD_SAMPLE_MODE, // (enabled) DIV_CMD_SAMPLE_FREQ, // (frequency) DIV_CMD_SAMPLE_BANK, // (bank) @@ -400,11 +412,10 @@ class DivDispatch { virtual bool getDCOffRequired(); /** - * get a description of a dispatch-specific effect. - * @param effect the effect. - * @return the description, or NULL if effect is invalid. + * check whether PRE_NOTE command is desired. + * @return truth. */ - virtual const char* getEffectName(unsigned char effect); + virtual bool getWantPreNote(); /** * set the chip flags. diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 5437d6fc..1262d0a5 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -54,6 +54,7 @@ #include "platform/su.h" #include "platform/swan.h" #include "platform/lynx.h" +#include "platform/zxbeeper.h" #include "platform/bubsyswsg.h" #include "platform/n163.h" #include "platform/pet.h" @@ -65,9 +66,9 @@ #include "platform/ymz280b.h" #include "platform/rf5c68.h" #include "platform/snes.h" +#include "platform/pcmdac.h" #include "platform/dummy.h" #include "../ta-log.h" -#include "platform/zxbeeper.h" #include "song.h" void DivDispatchContainer::setRates(double gotRate) { @@ -218,10 +219,12 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do break; case DIV_SYSTEM_C64_6581: dispatch=new DivPlatformC64; + ((DivPlatformC64*)dispatch)->setFP(eng->getConfInt("c64Core",1)==1); ((DivPlatformC64*)dispatch)->setChipModel(true); break; case DIV_SYSTEM_C64_8580: dispatch=new DivPlatformC64; + ((DivPlatformC64*)dispatch)->setFP(eng->getConfInt("c64Core",1)==1); ((DivPlatformC64*)dispatch)->setChipModel(false); break; case DIV_SYSTEM_YM2151: @@ -399,6 +402,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_SNES: dispatch=new DivPlatformSNES; break; + case DIV_SYSTEM_PCM_DAC: + dispatch=new DivPlatformPCMDAC; + break; case DIV_SYSTEM_DUMMY: dispatch=new DivPlatformDummy; break; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 76b2cd11..bb03eda1 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -17,6 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "dispatch.h" +#include "song.h" #define _USE_MATH_DEFINES #include "engine.h" #include "instrument.h" @@ -24,20 +26,16 @@ #include "../ta-log.h" #include "../fileutils.h" #ifdef HAVE_SDL2 -#include "../audio/sdl.h" +#include "../audio/sdlAudio.h" #endif #include -#ifndef _WIN32 -#include -#include -#include -#endif #ifdef HAVE_JACK #include "../audio/jack.h" #endif #include +#include #ifdef HAVE_SNDFILE -#include +#include "sfWrapper.h" #endif #include @@ -127,8 +125,15 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul if ((effect&0xf0)==0x90) { return "9xxx: Set sample offset*256"; } else if (chan>=0 && changetEffectName(effect); - if (ret!=NULL) return ret; + DivSysDef* sysDef=sysDefs[sysOfChan[chan]]; + auto iter=sysDef->effectHandlers.find(effect); + if (iter!=sysDef->effectHandlers.end()) { + return iter->second.description; + } + iter=sysDef->postEffectHandlers.find(effect); + if (iter!=sysDef->postEffectHandlers.end()) { + return iter->second.description; + } } break; } @@ -142,37 +147,68 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) { int nextOrder=-1; int nextRow=0; int effectVal=0; + int lastSuspectedLoopEnd=-1; DivPattern* pat[DIV_MAX_CHANS]; + unsigned char wsWalked[8192]; + memset(wsWalked,0,8192); for (int i=0; iordersLen; i++) { for (int j=0; jord[j][i],false); } + if (i>lastSuspectedLoopEnd) { + lastSuspectedLoopEnd=i; + } for (int j=nextRow; jpatLen; j++) { nextRow=0; + bool changingOrder=false; + bool jumpingOrder=false; + if (wsWalked[((i<<5)+(j>>3))&8191]&(1<<(j&7))) { + loopOrder=i; + loopRow=j; + loopEnd=lastSuspectedLoopEnd; + return; + } for (int k=0; kdata[j][5+(l<<1)]; if (effectVal<0) effectVal=0; if (pat[k]->data[j][4+(l<<1)]==0x0d) { - if (nextOrder==-1 && (iordersLen-1 || !song.ignoreJumpAtEnd)) { - nextOrder=i+1; - nextRow=effectVal; + if (song.jumpTreatment==2) { + if ((iordersLen-1 || !song.ignoreJumpAtEnd)) { + nextOrder=i+1; + nextRow=effectVal; + jumpingOrder=true; + } + } else if (song.jumpTreatment==1) { + if (nextOrder==-1 && (iordersLen-1 || !song.ignoreJumpAtEnd)) { + nextOrder=i+1; + nextRow=effectVal; + jumpingOrder=true; + } + } else { + if ((iordersLen-1 || !song.ignoreJumpAtEnd)) { + if (!changingOrder) { + nextOrder=i+1; + } + jumpingOrder=true; + nextRow=effectVal; + } } } else if (pat[k]->data[j][4+(l<<1)]==0x0b) { - if (nextOrder==-1) { + if (nextOrder==-1 || song.jumpTreatment==0) { nextOrder=effectVal; - nextRow=0; + if (song.jumpTreatment==1 || song.jumpTreatment==2 || !jumpingOrder) { + nextRow=0; + } + changingOrder=true; } } } } + + wsWalked[((i<<5)+(j>>3))&8191]|=1<<(j&7); + if (nextOrder!=-1) { - if (nextOrder<=i) { - loopOrder=nextOrder; - loopRow=nextRow; - loopEnd=i; - return; - } i=nextOrder-1; nextOrder=-1; break; @@ -181,6 +217,308 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) { } } +#define EXPORT_BUFSIZE 2048 + +double DivEngine::benchmarkPlayback() { + float* outBuf[2]; + outBuf[0]=new float[EXPORT_BUFSIZE]; + outBuf[1]=new float[EXPORT_BUFSIZE]; + + curOrder=0; + prevOrder=0; + remainingLoops=1; + playSub(false); + + std::chrono::high_resolution_clock::time_point timeStart=std::chrono::high_resolution_clock::now(); + + // benchmark + while (playing) { + nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); + } + + std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now(); + + delete[] outBuf[0]; + delete[] outBuf[1]; + + double t=(double)(std::chrono::duration_cast(timeEnd-timeStart).count())/1000000.0; + printf("[RESULT] %fs\n",t); + return t; +} + +double DivEngine::benchmarkSeek() { + double t[20]; + curOrder=curSubSong->ordersLen-1; + prevOrder=curSubSong->ordersLen-1; + + // benchmark + for (int i=0; i<20; i++) { + std::chrono::high_resolution_clock::time_point timeStart=std::chrono::high_resolution_clock::now(); + playSub(false); + std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now(); + t[i]=(double)(std::chrono::duration_cast(timeEnd-timeStart).count())/1000000.0; + printf("[#%d] %fs\n",i+1,t[i]); + } + + double tMin=DBL_MAX; + double tMax=0.0; + double tAvg=0.0; + for (int i=0; i<20; i++) { + if (t[i]tMax) tMax=t[i]; + tAvg+=t[i]; + } + tAvg/=20.0; + + printf("[RESULT] min %fs max %fs average %fs\n",tMin,tMax,tAvg); + return tAvg; +} + +#define WRITE_TICK \ + if (!wroteTick) { \ + wroteTick=true; \ + if (binary) { \ + if (tick-lastTick>255) { \ + w->writeC(0xfc); \ + w->writeS(tick-lastTick); \ + } else if (tick-lastTick>1) { \ + w->writeC(0xfd); \ + w->writeC(tick-lastTick); \ + } else { \ + w->writeC(0xfe); \ + } \ + } else { \ + w->writeText(fmt::sprintf(">> TICK %d\n",tick)); \ + } \ + lastTick=tick; \ + } + +void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { + w->writeC(c.cmd); + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + case DIV_CMD_HINT_LEGATO: + if (c.value==DIV_NOTE_NULL) { + w->writeC(0xff); + } else { + w->writeC(c.value+60); + } + break; + case DIV_CMD_NOTE_OFF: + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + break; + case DIV_CMD_INSTRUMENT: + case DIV_CMD_HINT_VIBRATO_RANGE: + case DIV_CMD_HINT_VIBRATO_SHAPE: + case DIV_CMD_HINT_PITCH: + case DIV_CMD_HINT_VOLUME: + case DIV_CMD_SAMPLE_MODE: + case DIV_CMD_SAMPLE_FREQ: + case DIV_CMD_SAMPLE_BANK: + case DIV_CMD_SAMPLE_POS: + case DIV_CMD_SAMPLE_DIR: + case DIV_CMD_FM_HARD_RESET: + case DIV_CMD_FM_LFO: + case DIV_CMD_FM_LFO_WAVE: + case DIV_CMD_FM_FB: + case DIV_CMD_FM_EXTCH: + case DIV_CMD_FM_AM_DEPTH: + case DIV_CMD_FM_PM_DEPTH: + case DIV_CMD_STD_NOISE_FREQ: + case DIV_CMD_STD_NOISE_MODE: + case DIV_CMD_WAVE: + case DIV_CMD_GB_SWEEP_TIME: + case DIV_CMD_GB_SWEEP_DIR: + case DIV_CMD_PCE_LFO_MODE: + case DIV_CMD_PCE_LFO_SPEED: + case DIV_CMD_NES_DMC: + case DIV_CMD_C64_CUTOFF: + case DIV_CMD_C64_RESONANCE: + case DIV_CMD_C64_FILTER_MODE: + case DIV_CMD_C64_RESET_TIME: + case DIV_CMD_C64_RESET_MASK: + case DIV_CMD_C64_FILTER_RESET: + case DIV_CMD_C64_DUTY_RESET: + case DIV_CMD_C64_EXTENDED: + case DIV_CMD_AY_ENVELOPE_SET: + case DIV_CMD_AY_ENVELOPE_LOW: + case DIV_CMD_AY_ENVELOPE_HIGH: + case DIV_CMD_AY_ENVELOPE_SLIDE: + case DIV_CMD_AY_NOISE_MASK_AND: + case DIV_CMD_AY_NOISE_MASK_OR: + case DIV_CMD_AY_AUTO_ENVELOPE: + w->writeC(c.value); + break; + case DIV_CMD_PANNING: + case DIV_CMD_HINT_VIBRATO: + case DIV_CMD_HINT_ARPEGGIO: + case DIV_CMD_HINT_PORTA: + case DIV_CMD_FM_TL: + case DIV_CMD_FM_AM: + case DIV_CMD_FM_AR: + case DIV_CMD_FM_DR: + case DIV_CMD_FM_SL: + case DIV_CMD_FM_D2R: + case DIV_CMD_FM_RR: + case DIV_CMD_FM_DT: + case DIV_CMD_FM_DT2: + case DIV_CMD_FM_RS: + case DIV_CMD_FM_KSR: + case DIV_CMD_FM_VIB: + case DIV_CMD_FM_SUS: + case DIV_CMD_FM_WS: + case DIV_CMD_FM_SSG: + case DIV_CMD_FM_REV: + case DIV_CMD_FM_EG_SHIFT: + case DIV_CMD_FM_MULT: + case DIV_CMD_FM_FINE: + case DIV_CMD_AY_IO_WRITE: + case DIV_CMD_AY_AUTO_PWM: + w->writeC(c.value); + w->writeC(c.value2); + break; + case DIV_CMD_PRE_PORTA: + w->writeC((c.value?0x80:0)|(c.value2?0x40:0)); + break; + case DIV_CMD_HINT_VOL_SLIDE: + case DIV_CMD_C64_FINE_DUTY: + case DIV_CMD_C64_FINE_CUTOFF: + w->writeS(c.value); + break; + case DIV_CMD_FM_FIXFREQ: + w->writeS((c.value<<12)|(c.value2&0x7ff)); + break; + case DIV_CMD_NES_SWEEP: + w->writeC((c.value?8:0)|(c.value2&0x77)); + break; + default: + logW("unimplemented command %s!",cmdName[c.cmd]); + break; + } +} + +SafeWriter* DivEngine::saveCommand(bool binary) { + stop(); + repeatPattern=false; + setOrder(0); + BUSY_BEGIN_SOFT; + // determine loop point + int loopOrder=0; + int loopRow=0; + int loopEnd=0; + walkSong(loopOrder,loopRow,loopEnd); + logI("loop point: %d %d",loopOrder,loopRow); + + SafeWriter* w=new SafeWriter; + w->init(); + + // write header + if (binary) { + w->write("FCS",4); + } else { + w->writeText("# Furnace Command Stream\n\n"); + + w->writeText("[Information]\n"); + w->writeText(fmt::sprintf("name: %s\n",song.name)); + w->writeText(fmt::sprintf("author: %s\n",song.author)); + w->writeText(fmt::sprintf("category: %s\n",song.category)); + w->writeText(fmt::sprintf("system: %s\n",song.systemName)); + + w->writeText("\n"); + + w->writeText("[SubSongInformation]\n"); + w->writeText(fmt::sprintf("name: %s\n",curSubSong->name)); + w->writeText(fmt::sprintf("tickRate: %f\n",curSubSong->hz)); + + w->writeText("\n"); + + w->writeText("[SysDefinition]\n"); + // TODO + + w->writeText("\n"); + } + + // play the song ourselves + bool done=false; + playSub(false); + + if (!binary) { + w->writeText("[Stream]\n"); + } + int tick=0; + bool oldCmdStreamEnabled=cmdStreamEnabled; + cmdStreamEnabled=true; + double curDivider=divider; + int lastTick=0; + while (!done) { + if (nextTick(false,true) || !playing) { + done=true; + } + // get command stream + bool wroteTick=false; + if (curDivider!=divider) { + curDivider=divider; + WRITE_TICK; + if (binary) { + w->writeC(0xfb); + w->writeI((int)(curDivider*65536)); + } else { + w->writeText(fmt::sprintf(">> SET_RATE %f\n",curDivider)); + } + } + for (DivCommand& i: cmdStream) { + switch (i.cmd) { + // strip away hinted/useless commands + case DIV_ALWAYS_SET_VOLUME: + break; + case DIV_CMD_GET_VOLUME: + break; + case DIV_CMD_VOLUME: + break; + case DIV_CMD_NOTE_PORTA: + break; + case DIV_CMD_LEGATO: + break; + case DIV_CMD_PITCH: + break; + case DIV_CMD_PRE_NOTE: + break; + default: + WRITE_TICK; + if (binary) { + w->writeC(i.chan); + writePackedCommandValues(w,i); + } else { + w->writeText(fmt::sprintf(" %d: %s %d %d\n",i.chan,cmdName[i.cmd],i.value,i.value2)); + } + break; + } + } + cmdStream.clear(); + tick++; + } + cmdStreamEnabled=oldCmdStreamEnabled; + + if (binary) { + w->writeC(0xff); + } else { + if (!playing) { + w->writeText(">> END\n"); + } else { + w->writeText(">> LOOP 0\n"); + } + } + + remainingLoops=-1; + playing=false; + freelance=false; + extValuePresent=false; + BUSY_END; + + return w; +} + void _runExportThread(DivEngine* caller) { caller->runExportThread(); } @@ -189,8 +527,6 @@ bool DivEngine::isExporting() { return exporting; } -#define EXPORT_BUFSIZE 2048 - #ifdef HAVE_SNDFILE void DivEngine::runExportThread() { size_t fadeOutSamples=got.rate*exportFadeOut; @@ -200,11 +536,12 @@ void DivEngine::runExportThread() { case DIV_EXPORT_MODE_ONE: { SNDFILE* sf; SF_INFO si; + SFWrapper sfWrap; si.samplerate=got.rate; si.channels=2; si.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; - sf=sf_open(exportPath.c_str(),SFM_WRITE,&si); + sf=sfWrap.doOpen(exportPath.c_str(),SFM_WRITE,&si); if (sf==NULL) { logE("could not open file for writing! (%s)",sf_strerror(NULL)); exporting=false; @@ -259,7 +596,7 @@ void DivEngine::runExportThread() { delete[] outBuf[1]; delete[] outBuf[2]; - if (sf_close(sf)!=0) { + if (sfWrap.doClose()!=0) { logE("could not close audio file!"); } exporting=false; @@ -280,6 +617,7 @@ void DivEngine::runExportThread() { SNDFILE* sf[32]; SF_INFO si[32]; String fname[32]; + SFWrapper sfWrap[32]; for (int i=0; i=chans) break; if (getChannelType(i)!=5) break; + i++; } i--; } @@ -782,7 +1122,7 @@ void DivEngine::initSongWithDesc(const int* description) { } } -void DivEngine::createNew(const int* description) { +void DivEngine::createNew(const int* description, String sysName) { quitDispatch(); BUSY_BEGIN; saveLock.lock(); @@ -792,6 +1132,11 @@ void DivEngine::createNew(const int* description) { if (description!=NULL) { initSongWithDesc(description); } + if (sysName=="") { + song.systemName=getSongSystemLegacyName(song,!getConfInt("noMultiSystem",0)); + } else { + song.systemName=sysName; + } recalcChans(); saveLock.unlock(); BUSY_END; @@ -826,7 +1171,7 @@ void DivEngine::swapChannels(int src, int dest) { String prevChanName=curSubSong->chanName[src]; String prevChanShortName=curSubSong->chanShortName[src]; bool prevChanShow=curSubSong->chanShow[src]; - bool prevChanCollapse=curSubSong->chanCollapse[src]; + unsigned char prevChanCollapse=curSubSong->chanCollapse[src]; curSubSong->chanName[src]=curSubSong->chanName[dest]; curSubSong->chanShortName[src]=curSubSong->chanShortName[dest]; @@ -1073,6 +1418,134 @@ bool DivEngine::removeSystem(int index, bool preserveOrder) { return true; } +bool DivEngine::swapSystem(int src, int dest, bool preserveOrder) { + if (src==dest) { + lastError="source and destination are equal"; + return false; + } + if (src<0 || src>=song.systemLen) { + lastError="invalid source index"; + return false; + } + if (dest<0 || dest>=song.systemLen) { + lastError="invalid destination index"; + return false; + } + //int chanCount=chans; + quitDispatch(); + BUSY_BEGIN; + saveLock.lock(); + + if (!preserveOrder) { + // move channels + unsigned char unswappedChannels[DIV_MAX_CHANS]; + unsigned char swappedChannels[DIV_MAX_CHANS]; + std::vector> swapList; + std::vector chanList; + + int tchans=0; + + for (int i=0; i& i: swapList) { + for (int& j: i) { + swappedChannels[index++]=j; + } + } + + // swap channels + logV("swap list:"); + for (int i=0; i %d",unswappedChannels[i],swappedChannels[i]); + } + + for (size_t i=0; iorders; + DivPattern* prevPat[DIV_MAX_CHANS][256]; + unsigned char prevEffectCols[DIV_MAX_CHANS]; + String prevChanName[DIV_MAX_CHANS]; + String prevChanShortName[DIV_MAX_CHANS]; + bool prevChanShow[DIV_MAX_CHANS]; + unsigned char prevChanCollapse[DIV_MAX_CHANS]; + + for (int j=0; jpat[j].data[k]; + } + prevEffectCols[j]=song.subsong[i]->pat[j].effectCols; + + prevChanName[j]=song.subsong[i]->chanName[j]; + prevChanShortName[j]=song.subsong[i]->chanShortName[j]; + prevChanShow[j]=song.subsong[i]->chanShow[j]; + prevChanCollapse[j]=song.subsong[i]->chanCollapse[j]; + } + + for (int j=0; jorders.ord[j][k]=prevOrders.ord[swappedChannels[j]][k]; + song.subsong[i]->pat[j].data[k]=prevPat[swappedChannels[j]][k]; + } + + song.subsong[i]->pat[j].effectCols=prevEffectCols[swappedChannels[j]]; + song.subsong[i]->chanName[j]=prevChanName[swappedChannels[j]]; + song.subsong[i]->chanShortName[j]=prevChanShortName[swappedChannels[j]]; + song.subsong[i]->chanShow[j]=prevChanShow[swappedChannels[j]]; + song.subsong[i]->chanCollapse[j]=prevChanCollapse[swappedChannels[j]]; + } + } + } + + DivSystem srcSystem=song.system[src]; + + song.system[src]=song.system[dest]; + song.system[dest]=srcSystem; + + song.systemVol[src]^=song.systemVol[dest]; + song.systemVol[dest]^=song.systemVol[src]; + song.systemVol[src]^=song.systemVol[dest]; + + song.systemPan[src]^=song.systemPan[dest]; + song.systemPan[dest]^=song.systemPan[src]; + song.systemPan[src]^=song.systemPan[dest]; + + song.systemFlags[src]^=song.systemFlags[dest]; + song.systemFlags[dest]^=song.systemFlags[src]; + song.systemFlags[src]^=song.systemFlags[dest]; + + recalcChans(); + saveLock.unlock(); + BUSY_END; + initDispatch(); + BUSY_BEGIN; + renderSamples(); + reset(); + BUSY_END; + return true; +} + void DivEngine::poke(int sys, unsigned int addr, unsigned short val) { if (sys<0 || sys>=song.systemLen) return; BUSY_BEGIN; @@ -1216,6 +1689,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { speedAB=false; playing=true; skipping=true; + memset(walked,0,8192); for (int i=0; isetSkipRegisterWrites(true); while (playing && curOrder1)) { if (nextTick(preserveDrift)) { skipping=false; return; @@ -1348,6 +1822,15 @@ int DivEngine::calcFreq(int base, int pitch, bool period, int octave, int pitch2 base+((pitch*octave)>>1)+pitch2; } +int DivEngine::calcArp(int note, int arp, int offset) { + if (arp<0) { + if (!(arp&0x40000000)) return (arp|0x40000000)+offset; + } else { + if (arp&0x40000000) return (arp&(~0x40000000))+offset; + } + return note+arp; +} + int DivEngine::convertPanSplitToLinear(unsigned int val, unsigned char bits, int range) { int panL=val>>bits; int panR=val&((1<patLen; k++) { - if (curPat[i].data[j]->data[k][2]>index) { - curPat[i].data[j]->data[k][2]--; + for (size_t j=0; jpat[i].data[k]==NULL) continue; + for (int l=0; lpatLen; l++) { + if (song.subsong[j]->pat[i].data[k]->data[l][2]>index) { + song.subsong[j]->pat[i].data[k]->data[l][2]--; + } } } } @@ -1883,7 +2375,10 @@ void DivEngine::delInstrument(int index) { } int DivEngine::addWave() { - if (song.wave.size()>=256) return -1; + if (song.wave.size()>=256) { + lastError="too many wavetables!"; + return -1; + } BUSY_BEGIN; saveLock.lock(); DivWavetable* wave=new DivWavetable; @@ -1895,50 +2390,62 @@ int DivEngine::addWave() { return waveCount; } -bool DivEngine::addWaveFromFile(const char* path, bool addRaw) { +int DivEngine::addWavePtr(DivWavetable* which) { if (song.wave.size()>=256) { lastError="too many wavetables!"; - return false; + delete which; + return -1; } + BUSY_BEGIN; + saveLock.lock(); + int waveCount=(int)song.wave.size(); + song.wave.push_back(which); + song.waveLen=waveCount+1; + saveLock.unlock(); + BUSY_END; + return song.waveLen; +} + +DivWavetable* DivEngine::waveFromFile(const char* path, bool addRaw) { FILE* f=ps_fopen(path,"rb"); if (f==NULL) { lastError=fmt::sprintf("%s",strerror(errno)); - return false; + return NULL; } unsigned char* buf; ssize_t len; if (fseek(f,0,SEEK_END)!=0) { fclose(f); lastError=fmt::sprintf("could not seek to end: %s",strerror(errno)); - return false; + return NULL; } len=ftell(f); if (len<0) { fclose(f); lastError=fmt::sprintf("could not determine file size: %s",strerror(errno)); - return false; + return NULL; } if (len==(SIZE_MAX>>1)) { fclose(f); lastError="file size is invalid!"; - return false; + return NULL; } if (len==0) { fclose(f); lastError="file is empty"; - return false; + return NULL; } if (fseek(f,0,SEEK_SET)!=0) { fclose(f); lastError=fmt::sprintf("could not seek to beginning: %s",strerror(errno)); - return false; + return NULL; } buf=new unsigned char[len]; if (fread(buf,1,len,f)!=(size_t)len) { logW("did not read entire wavetable file buffer!"); delete[] buf; lastError=fmt::sprintf("could not read entire file: %s",strerror(errno)); - return false; + return NULL; } fclose(f); @@ -1966,7 +2473,7 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) { lastError="invalid wavetable header/data!"; delete wave; delete[] buf; - return false; + return NULL; } } else { try { @@ -2007,7 +2514,7 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) { } else { delete wave; delete[] buf; - return false; + return NULL; } } } catch (EndOfFileException& e) { @@ -2025,7 +2532,7 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) { } else { delete wave; delete[] buf; - return false; + return NULL; } } } @@ -2033,17 +2540,10 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) { delete wave; delete[] buf; lastError="premature end of file"; - return false; + return NULL; } - BUSY_BEGIN; - saveLock.lock(); - int waveCount=(int)song.wave.size(); - song.wave.push_back(wave); - song.waveLen=waveCount+1; - saveLock.unlock(); - BUSY_END; - return true; + return wave; } void DivEngine::delWave(int index) { @@ -2059,7 +2559,10 @@ void DivEngine::delWave(int index) { } int DivEngine::addSample() { - if (song.sample.size()>=256) return -1; + if (song.sample.size()>=256) { + lastError="too many samples!"; + return -1; + } BUSY_BEGIN; saveLock.lock(); DivSample* sample=new DivSample; @@ -2067,17 +2570,36 @@ int DivEngine::addSample() { sample->name=fmt::sprintf("Sample %d",sampleCount); song.sample.push_back(sample); song.sampleLen=sampleCount+1; + sPreview.sample=-1; + sPreview.pos=0; saveLock.unlock(); renderSamples(); BUSY_END; return sampleCount; } -int DivEngine::addSampleFromFile(const char* path) { +int DivEngine::addSamplePtr(DivSample* which) { if (song.sample.size()>=256) { lastError="too many samples!"; + delete which; return -1; } + int sampleCount=(int)song.sample.size(); + BUSY_BEGIN; + saveLock.lock(); + song.sample.push_back(which); + song.sampleLen=sampleCount+1; + saveLock.unlock(); + renderSamples(); + BUSY_END; + return sampleCount; +} + +DivSample* DivEngine::sampleFromFile(const char* path) { + if (song.sample.size()>=256) { + lastError="too many samples!"; + return NULL; + } BUSY_BEGIN; warnings=""; @@ -2110,7 +2632,6 @@ int DivEngine::addSampleFromFile(const char* path) { if (extS==".dmc") { // read as .dmc size_t len=0; DivSample* sample=new DivSample; - int sampleCount=(int)song.sample.size(); sample->name=stripPath; FILE* f=ps_fopen(path,"rb"); @@ -2118,7 +2639,7 @@ int DivEngine::addSampleFromFile(const char* path) { BUSY_END; lastError=fmt::sprintf("could not open file! (%s)",strerror(errno)); delete sample; - return -1; + return NULL; } if (fseek(f,0,SEEK_END)<0) { @@ -2126,7 +2647,7 @@ int DivEngine::addSampleFromFile(const char* path) { BUSY_END; lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno)); delete sample; - return -1; + return NULL; } len=ftell(f); @@ -2136,7 +2657,7 @@ int DivEngine::addSampleFromFile(const char* path) { BUSY_END; lastError="file is empty!"; delete sample; - return -1; + return NULL; } if (len==(SIZE_MAX>>1)) { @@ -2144,7 +2665,7 @@ int DivEngine::addSampleFromFile(const char* path) { BUSY_END; lastError="file is invalid!"; delete sample; - return -1; + return NULL; } if (fseek(f,0,SEEK_SET)<0) { @@ -2152,12 +2673,12 @@ int DivEngine::addSampleFromFile(const char* path) { BUSY_END; lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno)); delete sample; - return -1; + return NULL; } sample->rate=33144; sample->centerRate=33144; - sample->depth=1; + sample->depth=DIV_SAMPLE_DEPTH_1BIT_DPCM; sample->init(len*8); if (fread(sample->dataDPCM,1,len,f)==0) { @@ -2165,44 +2686,60 @@ int DivEngine::addSampleFromFile(const char* path) { BUSY_END; lastError=fmt::sprintf("could not read file! (%s)",strerror(errno)); delete sample; - return -1; + return NULL; } - - saveLock.lock(); - song.sample.push_back(sample); - song.sampleLen=sampleCount+1; - saveLock.unlock(); - renderSamples(); BUSY_END; - return sampleCount; + return sample; } } #ifndef HAVE_SNDFILE lastError="Furnace was not compiled with libsndfile!"; - return -1; + return NULL; #else SF_INFO si; - SNDFILE* f=sf_open(path,SFM_READ,&si); + SFWrapper sfWrap; + memset(&si,0,sizeof(SF_INFO)); + SNDFILE* f=sfWrap.doOpen(path,SFM_READ,&si); if (f==NULL) { BUSY_END; int err=sf_error(NULL); if (err==SF_ERR_SYSTEM) { lastError=fmt::sprintf("could not open file! (%s %s)",sf_error_number(err),strerror(errno)); } else { - lastError=fmt::sprintf("could not open file! (%s)",sf_error_number(err)); + lastError=fmt::sprintf("could not open file! (%s)\nif this is raw sample data, you may import it by right-clicking the Load Sample icon and selecting \"import raw\".",sf_error_number(err)); } - return -1; + return NULL; } if (si.frames>16777215) { lastError="this sample is too big! max sample size is 16777215."; - sf_close(f); + sfWrap.doClose(); BUSY_END; - return -1; + return NULL; } - short* buf=new short[si.channels*si.frames]; - if (sf_readf_short(f,buf,si.frames)!=si.frames) { - logW("sample read size mismatch!"); + void* buf=NULL; + sf_count_t sampleLen=sizeof(short); + if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) { + logD("sample is 8-bit unsigned"); + buf=new unsigned char[si.channels*si.frames]; + sampleLen=sizeof(unsigned char); + } else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) { + logD("sample is 32-bit float"); + buf=new float[si.channels*si.frames]; + sampleLen=sizeof(float); + } else { + logD("sample is 16-bit signed"); + buf=new short[si.channels*si.frames]; + sampleLen=sizeof(short); + } + if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8 || (si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) { + if (sf_read_raw(f,buf,si.frames*si.channels*sampleLen)!=(si.frames*si.channels*sampleLen)) { + logW("sample read size mismatch!"); + } + } else { + if (sf_read_short(f,(short*)buf,si.frames*si.channels)!=(si.frames*si.channels)) { + logW("sample read size mismatch!"); + } } DivSample* sample=new DivSample; int sampleCount=(int)song.sample.size(); @@ -2210,24 +2747,46 @@ int DivEngine::addSampleFromFile(const char* path) { int index=0; if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) { - sample->depth=8; + sample->depth=DIV_SAMPLE_DEPTH_8BIT; } else { - sample->depth=16; + sample->depth=DIV_SAMPLE_DEPTH_16BIT; } sample->init(si.frames); - for (int i=0; idata8[index++]=averaged; } - averaged/=si.channels; - if (((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8)) { - sample->data8[index++]=averaged>>8; - } else { + delete[] (unsigned char*)buf; + } else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) { + for (int i=0; i32767.0) averaged=32767.0; sample->data16[index++]=averaged; } + delete[] (float*)buf; + } else { + for (int i=0; idata16[index++]=averaged; + } + delete[] (short*)buf; } - delete[] buf; + sample->rate=si.samplerate; if (sample->rate<4000) sample->rate=4000; if (sample->rate>96000) sample->rate=96000; @@ -2245,6 +2804,7 @@ int DivEngine::addSampleFromFile(const char* path) { if(inst.loop_count && inst.loops[0].mode == SF_LOOP_FORWARD) { sample->loopStart=inst.loops[0].start; + sample->loopEnd=inst.loops[0].end; if(inst.loops[0].end < (unsigned int)sampleCount) sampleCount=inst.loops[0].end; } @@ -2252,19 +2812,186 @@ int DivEngine::addSampleFromFile(const char* path) { if (sample->centerRate<4000) sample->centerRate=4000; if (sample->centerRate>64000) sample->centerRate=64000; - sf_close(f); - saveLock.lock(); - song.sample.push_back(sample); - song.sampleLen=sampleCount+1; - saveLock.unlock(); - renderSamples(); + sfWrap.doClose(); BUSY_END; - return sampleCount; + return sample; #endif } +DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign) { + if (song.sample.size()>=256) { + lastError="too many samples!"; + return NULL; + } + if (channels<1) { + lastError="invalid channel count"; + return NULL; + } + if (depth!=DIV_SAMPLE_DEPTH_8BIT && depth!=DIV_SAMPLE_DEPTH_16BIT) { + if (channels!=1) { + lastError="channel count has to be 1 for non-8/16-bit format"; + return NULL; + } + } + BUSY_BEGIN; + warnings=""; + + const char* pathRedux=strrchr(path,DIR_SEPARATOR); + if (pathRedux==NULL) { + pathRedux=path; + } else { + pathRedux++; + } + String stripPath; + const char* pathReduxEnd=strrchr(pathRedux,'.'); + if (pathReduxEnd==NULL) { + stripPath=pathRedux; + } else { + for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) { + stripPath+=*i; + } + } + + size_t len=0; + size_t lenDivided=0; + DivSample* sample=new DivSample; + sample->name=stripPath; + + FILE* f=ps_fopen(path,"rb"); + if (f==NULL) { + BUSY_END; + lastError=fmt::sprintf("could not open file! (%s)",strerror(errno)); + delete sample; + return NULL; + } + + if (fseek(f,0,SEEK_END)<0) { + fclose(f); + BUSY_END; + lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno)); + delete sample; + return NULL; + } + + len=ftell(f); + + if (len==0) { + fclose(f); + BUSY_END; + lastError="file is empty!"; + delete sample; + return NULL; + } + + if (len==(SIZE_MAX>>1)) { + fclose(f); + BUSY_END; + lastError="file is invalid!"; + delete sample; + return NULL; + } + + if (fseek(f,0,SEEK_SET)<0) { + fclose(f); + BUSY_END; + lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno)); + delete sample; + return NULL; + } + + lenDivided=len/channels; + + unsigned int samples=lenDivided; + switch (depth) { + case DIV_SAMPLE_DEPTH_1BIT: + case DIV_SAMPLE_DEPTH_1BIT_DPCM: + samples=lenDivided*8; + break; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: + case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: + case DIV_SAMPLE_DEPTH_ADPCM_A: + case DIV_SAMPLE_DEPTH_ADPCM_B: + case DIV_SAMPLE_DEPTH_VOX: + samples=lenDivided*2; + break; + case DIV_SAMPLE_DEPTH_8BIT: + samples=lenDivided; + break; + case DIV_SAMPLE_DEPTH_BRR: + samples=16*((lenDivided+8)/9); + break; + case DIV_SAMPLE_DEPTH_16BIT: + samples=(lenDivided+1)/2; + break; + default: + break; + } + + if (samples>16777215) { + fclose(f); + BUSY_END; + lastError="this sample is too big! max sample size is 16777215."; + delete sample; + return NULL; + } + + sample->rate=32000; + sample->centerRate=32000; + sample->depth=depth; + sample->init(samples); + + unsigned char* buf=new unsigned char[len]; + if (fread(buf,1,len,f)==0) { + fclose(f); + BUSY_END; + lastError=fmt::sprintf("could not read file! (%s)",strerror(errno)); + delete[] buf; + delete sample; + return NULL; + } + + fclose(f); + + // import sample + size_t pos=0; + if (depth==DIV_SAMPLE_DEPTH_16BIT) { + for (unsigned int i=0; i=len) break; + if (bigEndian) { + accum+=(short)(((short)((buf[pos]<<8)|buf[pos+1]))^(unsign?0x8000:0)); + } else { + accum+=(short)(((short)(buf[pos]|(buf[pos+1]<<8)))^(unsign?0x8000:0)); + } + pos+=2; + } + accum/=channels; + sample->data16[i]=accum; + } + } else if (depth==DIV_SAMPLE_DEPTH_8BIT) { + for (unsigned int i=0; i=len) break; + accum+=(signed char)(buf[pos++]^(unsign?0x80:0)); + } + accum/=channels; + sample->data8[i]=accum; + } + } else { + memcpy(sample->getCurBuf(),buf,len); + } + delete[] buf; + + BUSY_END; + return sample; +} + void DivEngine::delSample(int index) { BUSY_BEGIN; + sPreview.sample=-1; + sPreview.pos=0; saveLock.lock(); if (index>=0 && index<(int)song.sample.size()) { delete song.sample[index]; @@ -2438,13 +3165,15 @@ void DivEngine::moveOrderDown() { void DivEngine::exchangeIns(int one, int two) { for (int i=0; ipatLen; k++) { - if (curPat[i].data[j]->data[k][2]==one) { - curPat[i].data[j]->data[k][2]=two; - } else if (curPat[i].data[j]->data[k][2]==two) { - curPat[i].data[j]->data[k][2]=one; + for (size_t j=0; jpat[i].data[k]==NULL) continue; + for (int l=0; lpatLen; l++) { + if (song.subsong[j]->pat[i].data[k]->data[l][2]==one) { + song.subsong[j]->pat[i].data[k]->data[l][2]=two; + } else if (song.subsong[j]->pat[i].data[k]->data[l][2]==two) { + song.subsong[j]->pat[i].data[k]->data[l][2]=one; + } } } } @@ -2479,6 +3208,8 @@ bool DivEngine::moveWaveUp(int which) { bool DivEngine::moveSampleUp(int which) { if (which<1 || which>=(int)song.sample.size()) return false; BUSY_BEGIN; + sPreview.sample=-1; + sPreview.pos=0; DivSample* prev=song.sample[which]; saveLock.lock(); song.sample[which]=song.sample[which-1]; @@ -2516,6 +3247,8 @@ bool DivEngine::moveWaveDown(int which) { bool DivEngine::moveSampleDown(int which) { if (which<0 || which>=((int)song.sample.size())-1) return false; BUSY_BEGIN; + sPreview.sample=-1; + sPreview.pos=0; DivSample* prev=song.sample[which]; saveLock.lock(); song.sample[which]=song.sample[which+1]; @@ -2528,7 +3261,7 @@ bool DivEngine::moveSampleDown(int which) { void DivEngine::noteOn(int chan, int ins, int note, int vol) { if (chan<0 || chan>=chans) return; BUSY_BEGIN; - pendingNotes.push(DivNoteEvent(chan,ins,note,vol,true)); + pendingNotes.push_back(DivNoteEvent(chan,ins,note,vol,true)); if (!playing) { reset(); freelance=true; @@ -2540,7 +3273,7 @@ void DivEngine::noteOn(int chan, int ins, int note, int vol) { void DivEngine::noteOff(int chan) { if (chan<0 || chan>=chans) return; BUSY_BEGIN; - pendingNotes.push(DivNoteEvent(chan,-1,-1,-1,false)); + pendingNotes.push_back(DivNoteEvent(chan,-1,-1,-1,false)); if (!playing) { reset(); freelance=true; @@ -2592,7 +3325,7 @@ void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) { if ((!midiPoly) || (isViable[finalChan] && chan[finalChan].midiNote==-1 && (insInst->type==DIV_INS_OPL || getChannelType(finalChan)==finalChanType || notInViableChannel))) { chan[finalChan].midiNote=note; chan[finalChan].midiAge=midiAgeCounter++; - pendingNotes.push(DivNoteEvent(finalChan,ins,note,vol,true)); + pendingNotes.push_back(DivNoteEvent(finalChan,ins,note,vol,true)); return; } if (++finalChan>=chans) { @@ -2613,7 +3346,7 @@ void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) { chan[candidate].midiNote=note; chan[candidate].midiAge=midiAgeCounter++; - pendingNotes.push(DivNoteEvent(candidate,ins,note,vol,true)); + pendingNotes.push_back(DivNoteEvent(candidate,ins,note,vol,true)); } void DivEngine::autoNoteOff(int ch, int note, int vol) { @@ -2625,7 +3358,7 @@ void DivEngine::autoNoteOff(int ch, int note, int vol) { //if (ch<0 || ch>=chans) return; for (int i=0; imidiIn->openDevice(inName)) { logW("could not open MIDI input device!"); } + } else { + logV("no MIDI input device selected."); } } if (output->midiOut) { @@ -2990,14 +3696,18 @@ bool DivEngine::initAudioBackend() { if (!output->midiOut->openDevice(outName)) { logW("could not open MIDI output device!"); } + } else { + logV("no MIDI output device selected."); } } return true; } -bool DivEngine::deinitAudioBackend() { +bool DivEngine::deinitAudioBackend(bool dueToSwitchMaster) { if (output!=NULL) { + logI("closing audio output."); + output->quit(); if (output->midiIn) { if (output->midiIn->isDeviceOpen()) { logI("closing MIDI input."); @@ -3011,53 +3721,21 @@ bool DivEngine::deinitAudioBackend() { } } output->quitMidi(); - output->quit(); delete output; output=NULL; - audioEngine=DIV_AUDIO_NULL; + if (dueToSwitchMaster) { + audioEngine=DIV_AUDIO_NULL; + } } return true; } -#ifdef _WIN32 -#include "winStuff.h" -#endif - bool DivEngine::init() { // register systems if (!systemsRegistered) registerSystems(); - + // init config -#ifdef _WIN32 - configPath=getWinConfigPath(); -#elif defined(IS_MOBILE) - configPath=SDL_GetPrefPath("tildearrow","furnace"); -#else - struct stat st; - char* home=getenv("HOME"); - if (home==NULL) { - int uid=getuid(); - struct passwd* entry=getpwuid(uid); - if (entry==NULL) { - logW("unable to determine config directory! (%s)",strerror(errno)); - configPath="."; - } else { - configPath=entry->pw_dir; -#ifdef __APPLE__ - CHECK_CONFIG_DIR_MAC(); -#else - CHECK_CONFIG_DIR(); -#endif - } - } else { - configPath=home; -#ifdef __APPLE__ - CHECK_CONFIG_DIR_MAC(); -#else - CHECK_CONFIG_DIR(); -#endif - } -#endif + initConfDir(); logD("config path: %s",configPath.c_str()); loadConf(); @@ -3073,6 +3751,12 @@ bool DivEngine::init() { preset.push_back(0); initSongWithDesc(preset.data()); } + String sysName=getConfString("initialSysName",""); + if (sysName=="") { + song.systemName=getSongSystemLegacyName(song,!getConfInt("noMultiSystem",0)); + } else { + song.systemName=sysName; + } hasLoadedSomething=true; } diff --git a/src/engine/engine.h b/src/engine/engine.h index dc16584d..3047a8af 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -32,7 +32,8 @@ #include #include #include -#include +#include +#include #define addWarning(x) \ if (warnings.empty()) { \ @@ -45,11 +46,14 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev99" -#define DIV_ENGINE_VERSION 99 - +#define DIV_VERSION "dev114" +#define DIV_ENGINE_VERSION 114 // for imports #define DIV_VERSION_MOD 0xff01 +#define DIV_VERSION_FC 0xff02 + +// "Namco C163" +#define DIV_C163_DEFAULT_NAME "Namco 163" enum DivStatusView { DIV_STATUS_NOTHING=0, @@ -60,8 +64,9 @@ enum DivStatusView { enum DivAudioEngines { DIV_AUDIO_JACK=0, DIV_AUDIO_SDL=1, - DIV_AUDIO_NULL=2, - DIV_AUDIO_DUMMY=3 + + DIV_AUDIO_NULL=126, + DIV_AUDIO_DUMMY=127 }; enum DivAudioExportModes { @@ -83,11 +88,11 @@ struct DivChannelState { int note, oldNote, lastIns, pitch, portaSpeed, portaNote; int volume, volSpeed, cut, rowDelay, volMax; int delayOrder, delayRow, retrigSpeed, retrigTick; - int vibratoDepth, vibratoRate, vibratoPos, vibratoDir, vibratoFine; + int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoDir, vibratoFine; int tremoloDepth, tremoloRate, tremoloPos; unsigned char arp, arpStage, arpTicks, panL, panR; bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff; - bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, noteOnInhibit, resetArp; + bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, wasShorthandPorta, noteOnInhibit, resetArp; int midiNote, curMidiNote, midiPitch; size_t midiAge; @@ -112,6 +117,7 @@ struct DivChannelState { vibratoDepth(0), vibratoRate(0), vibratoPos(0), + vibratoPosGiant(0), vibratoDir(0), vibratoFine(15), tremoloDepth(0), @@ -134,6 +140,7 @@ struct DivChannelState { inPorta(false), scheduledSlideReset(false), shorthandPorta(false), + wasShorthandPorta(false), noteOnInhibit(false), resetArp(false), midiNote(-1), @@ -157,7 +164,7 @@ struct DivNoteEvent { struct DivDispatchContainer { DivDispatch* dispatch; blip_buffer_t* bb[2]; - size_t bbInLen; + size_t bbInLen, runtotal, runLeft, runPos, lastAvail; int temp[2], prevSample[2]; short* bbIn[2]; short* bbOut[2]; @@ -175,6 +182,10 @@ struct DivDispatchContainer { dispatch(NULL), bb{NULL,NULL}, bbInLen(0), + runtotal(0), + runLeft(0), + runPos(0), + lastAvail(0), temp{0,0}, prevSample{0,0}, bbIn{NULL,NULL}, @@ -183,7 +194,29 @@ struct DivDispatchContainer { dcOffCompensation(false) {} }; -typedef std::function EffectProcess; +typedef int EffectValConversion(unsigned char,unsigned char); + +struct EffectHandler { + DivDispatchCmds dispatchCmd; + const char* description; + EffectValConversion* val; + EffectValConversion* val2; + EffectHandler( + DivDispatchCmds dispatchCmd_, + const char* description_, + EffectValConversion val_=NULL, + EffectValConversion val2_=NULL + ): + dispatchCmd(dispatchCmd_), + description(description_), + val(val_), + val2(val2_) {} +}; + +struct DivDoNotHandleEffect { +}; + +typedef std::unordered_map EffectHandlerMap; struct DivSysDef { const char* name; @@ -200,8 +233,8 @@ struct DivSysDef { // 0: primary // 1: alternate (usually PCM) DivInstrumentType chanInsType[DIV_MAX_CHANS][2]; - EffectProcess effectFunc; - EffectProcess postEffectFunc; + const EffectHandlerMap effectHandlers; + const EffectHandlerMap postEffectHandlers; DivSysDef( const char* sysName, const char* sysNameJ, unsigned char fileID, unsigned char fileID_DMF, int chans, bool isFMChip, bool isSTDChip, unsigned int vgmVer, bool compound, const char* desc, @@ -210,8 +243,8 @@ struct DivSysDef { std::initializer_list chTypes, std::initializer_list chInsType1, std::initializer_list chInsType2={}, - EffectProcess fxHandler=[](int,unsigned char,unsigned char) -> bool {return false;}, - EffectProcess postFxHandler=[](int,unsigned char,unsigned char) -> bool {return false;}): + const EffectHandlerMap fxHandlers_={}, + const EffectHandlerMap postFxHandlers_={}): name(sysName), nameJ(sysNameJ), description(desc), @@ -222,8 +255,8 @@ struct DivSysDef { isSTD(isSTDChip), isCompound(compound), vgmVersion(vgmVer), - effectFunc(fxHandler), - postEffectFunc(postFxHandler) { + effectHandlers(fxHandlers_), + postEffectHandlers(postFxHandlers_) { memset(chanNames,0,DIV_MAX_CHANS*sizeof(void*)); memset(chanShortNames,0,DIV_MAX_CHANS*sizeof(void*)); memset(chanTypes,0,DIV_MAX_CHANS*sizeof(int)); @@ -273,6 +306,8 @@ enum DivChanTypes { DIV_CH_OP=5 }; +extern const char* cmdName[]; + class DivEngine { DivDispatchContainer disCont[32]; TAAudio* output; @@ -294,6 +329,7 @@ class DivEngine { bool stopExport; bool halted; bool forceMono; + bool clampSamples; bool cmdStreamEnabled; bool softLocked; bool firstTick; @@ -320,7 +356,9 @@ class DivEngine { DivAudioExportModes exportMode; double exportFadeOut; std::map conf; - std::queue pendingNotes; + std::deque pendingNotes; + // bitfield + unsigned char walked[8192]; bool isMuted[DIV_MAX_CHANS]; std::mutex isBusy, saveLock; String configPath; @@ -352,6 +390,7 @@ class DivEngine { short vibTable[64]; int reversePitchTable[4096]; int pitchTable[4096]; + char c163NameCS[1024]; int midiBaseChan; bool midiPoly; size_t midiAgeCounter; @@ -393,6 +432,7 @@ class DivEngine { bool loadFur(unsigned char* file, size_t len); bool loadMod(unsigned char* file, size_t len); bool loadFTM(unsigned char* file, size_t len); + bool loadFC(unsigned char* file, size_t len); void loadDMP(SafeReader& reader, std::vector& ret, String& stripPath); void loadTFI(SafeReader& reader, std::vector& ret, String& stripPath); @@ -412,7 +452,7 @@ class DivEngine { int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret); bool initAudioBackend(); - bool deinitAudioBackend(); + bool deinitAudioBackend(bool dueToSwitchMaster=false); void registerSystems(); void initSongWithDesc(const int* description); @@ -450,7 +490,7 @@ class DivEngine { String encodeSysDesc(std::vector& desc); std::vector decodeSysDesc(String desc); // start fresh - void createNew(const int* description); + void createNew(const int* description, String sysName); // load a file. bool load(unsigned char* f, size_t length); // save as .dmf. @@ -462,7 +502,9 @@ class DivEngine { // specify system to build ROM for. SafeWriter* buildROM(int sys); // dump to VGM. - SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171); + SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false); + // dump command stream. + SafeWriter* saveCommand(bool binary=false); // export to an audio file bool saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime=0.0); // wait for audio export to finish @@ -474,9 +516,16 @@ class DivEngine { // notify wavetable change void notifyWaveChange(int wave); + // benchmark (returns time in seconds) + double benchmarkPlayback(); + double benchmarkSeek(); + // returns the minimum VGM version which may carry the specified system, or 0 if none. int minVGMVersion(DivSystem which); + // determine and setup config dir + void initConfDir(); + // save config bool saveConf(); @@ -495,6 +544,7 @@ class DivEngine { void setConf(String key, int value); void setConf(String key, float value); void setConf(String key, double value); + void setConf(String key, const char* value); void setConf(String key, String value); // calculate base frequency/period @@ -506,6 +556,9 @@ class DivEngine { // calculate frequency/period int calcFreq(int base, int pitch, bool period=false, int octave=0, int pitch2=0, double clock=1.0, double divider=1.0, int blockBits=0); + // calculate arpeggio + int calcArp(int note, int arp, int offset=0); + // convert panning formats int convertPanSplitToLinear(unsigned int val, unsigned char bits, int range); int convertPanSplitToLinearLR(unsigned char left, unsigned char right, int range); @@ -568,7 +621,7 @@ class DivEngine { DivInstrumentType getPreferInsSecondType(int ch); // get song system name - String getSongSystemName(bool isMultiSystemAcceptable=true); + String getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAcceptable=true); // get sys name const char* getSystemName(DivSystem sys); @@ -576,6 +629,9 @@ class DivEngine { // get japanese system name const char* getSystemNameJ(DivSystem sys); + // get sys definition + const DivSysDef* getSystemDef(DivSystem sys); + // convert sample rate format int fileToDivRate(int frate); int divToFileRate(int drate); @@ -652,6 +708,9 @@ class DivEngine { // is playing bool isPlaying(); + + // is running + bool isRunning(); // is stepping bool isStepping(); @@ -678,8 +737,11 @@ class DivEngine { // add wavetable int addWave(); - // add wavetable from file - bool addWaveFromFile(const char* path, bool loadRaw=true); + // add wavetable from pointer + int addWavePtr(DivWavetable* which); + + // get wavetable from file + DivWavetable* waveFromFile(const char* path, bool loadRaw=true); // delete wavetable void delWave(int index); @@ -687,8 +749,14 @@ class DivEngine { // add sample int addSample(); - // add sample from file - int addSampleFromFile(const char* path); + // add sample from pointer + int addSamplePtr(DivSample* which); + + // get sample from file + DivSample* sampleFromFile(const char* path); + + // get raw sample + DivSample* sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign); // delete sample void delSample(int index); @@ -845,6 +913,9 @@ class DivEngine { // remove system bool removeSystem(int index, bool preserveOrder=true); + + // move system + bool swapSystem(int src, int dest, bool preserveOrder=true); // write to register on system void poke(int sys, unsigned int addr, unsigned short val); @@ -1003,6 +1074,7 @@ class DivEngine { memset(reversePitchTable,0,4096*sizeof(int)); memset(pitchTable,0,4096*sizeof(int)); memset(sysDefs,0,256*sizeof(void*)); + memset(walked,0,8192); for (int i=0; i<256; i++) { sysFileMapFur[i]=DIV_SYSTEM_NULL; diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 637f7b24..10a4bcce 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -28,6 +28,8 @@ #define DIV_DMF_MAGIC ".DelekDefleMask." #define DIV_FUR_MAGIC "-Furnace module-" #define DIV_FTM_MAGIC "FamiTracker Module" +#define DIV_FC13_MAGIC "SMOD" +#define DIV_FC14_MAGIC "FC14" struct InflateBlock { unsigned char* buf; @@ -143,7 +145,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.loopModality=0; ds.properNoiseLayout=false; ds.waveDutyIsVol=false; - ds.resetMacroOnPorta=true; + // TODO: WHAT?! geodude.dmf fails when this is true + // but isn't that how Defle behaves??? + ds.resetMacroOnPorta=false; ds.legacyVolumeSlides=true; ds.compatibleArpeggio=true; ds.noteOffResetsSlides=true; @@ -171,6 +175,11 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.newVolumeScaling=false; ds.volMacroLinger=false; ds.brokenOutVol=true; // ??? + ds.e1e2StopOnSameNote=true; + ds.brokenPortaArp=false; + ds.snNoLowPeriods=true; + ds.delayBehavior=0; + ds.jumpTreatment=2; // 1.1 compat flags if (ds.version>24) { @@ -501,6 +510,14 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { for (int j=0; jstd.arpMacro.len; j++) { ins->std.arpMacro.val[j]-=12; } + } else { + ins->std.arpMacro.mode=0; + for (int j=0; jstd.arpMacro.len; j++) { + ins->std.arpMacro.val[j]^=0x40000000; + } + if (ins->std.arpMacro.loop==255 && ins->std.arpMacro.len<255) { + ins->std.arpMacro.val[ins->std.arpMacro.len++]=0; + } } ins->std.dutyMacro.len=reader.readC(); @@ -586,7 +603,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { logD("GB data: vol %d dir %d len %d sl %d",ins->gb.envVol,ins->gb.envDir,ins->gb.envLen,ins->gb.soundLen); } else if (ds.system[0]==DIV_SYSTEM_GB) { - // try to convert macro to envelope + // set software envelope flag + ins->gb.softEnv=true; + // try to convert macro to envelope in case the user decides to switch to them if (ins->std.volMacro.len>0) { ins->gb.envVol=ins->std.volMacro.val[0]; if (ins->std.volMacro.val[0]std.volMacro.val[1]) { @@ -596,7 +615,6 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ins->gb.soundLen=ins->std.volMacro.len*2; } } - addWarning("Game Boy volume macros converted to envelopes. may not be perfect!"); } } @@ -797,17 +815,17 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { sample->rate=ymuSampleRate*400; } if (ds.version>0x15) { - sample->depth=reader.readC(); - if (sample->depth!=8 && sample->depth!=16) { + sample->depth=(DivSampleDepth)reader.readC(); + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) { logW("%d: sample depth is wrong! (%d)",i,sample->depth); - sample->depth=16; + sample->depth=DIV_SAMPLE_DEPTH_16BIT; } } else { if (ds.version>0x08) { - sample->depth=16; + sample->depth=DIV_SAMPLE_DEPTH_16BIT; } else { // it appears samples were stored as ADPCM back then - sample->depth=3; + sample->depth=DIV_SAMPLE_DEPTH_YMZ_ADPCM; } } if (length>0) { @@ -820,6 +838,13 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { data=new short[length]; reader.read(data,length*2); } + +#ifdef TA_BIG_ENDIAN + // convert to big-endian + for (int pos=0; pos>8)); + } +#endif if (pitch!=5) { logD("%d: scaling from %d...",i,pitch); @@ -836,7 +861,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { if (k>=sample->samples) { break; } - if (sample->depth==8) { + if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { float next=(float)(data[(unsigned int)j]-0x80)*mult; sample->data8[k++]=fmin(fmax(next,-128),127); } else { @@ -905,6 +930,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.system[1]=DIV_SYSTEM_FDS; } + ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0)); + if (active) quitDispatch(); BUSY_BEGIN_SOFT; saveLock.lock(); @@ -1043,6 +1070,21 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { ds.volMacroLinger=false; ds.brokenOutVol=true; } + if (ds.version<100) { + ds.e1e2StopOnSameNote=false; + } + if (ds.version<101) { + ds.brokenPortaArp=true; + } + if (ds.version<108) { + ds.snNoLowPeriods=true; + } + if (ds.version<110) { + ds.delayBehavior=1; + } + if (ds.version<113) { + ds.jumpTreatment=1; + } ds.isDMF=false; reader.readS(); // reserved @@ -1310,9 +1352,15 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } // pointers - reader.read(insPtr,ds.insLen*4); - reader.read(wavePtr,ds.waveLen*4); - reader.read(samplePtr,ds.sampleLen*4); + for (int i=0; iordersLen); @@ -1439,7 +1487,32 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { reader.readC(); reader.readC(); } - for (int i=0; i<9; i++) { + if (ds.version>=100) { + ds.e1e2StopOnSameNote=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=101) { + ds.brokenPortaArp=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=108) { + ds.snNoLowPeriods=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=110) { + ds.delayBehavior=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=113) { + ds.jumpTreatment=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<4; i++) { reader.readC(); } } @@ -1464,7 +1537,22 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { for (int i=0; i=103) { + ds.systemName=reader.readString(); + ds.category=reader.readString(); + ds.nameJ=reader.readString(); + ds.authorJ=reader.readString(); + ds.systemNameJ=reader.readString(); + ds.categoryJ=reader.readString(); + } else { + ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0)); + } + + // read subsongs + if (ds.version>=95) { for (int i=0; iname=reader.readString(); sample->samples=reader.readI(); + if (!isNewSample) { + sample->loopEnd=sample->samples; + } sample->rate=reader.readI(); - if (ds.version<58) { - vol=reader.readS(); - pitch=reader.readS(); - } else { - reader.readI(); - } - sample->depth=reader.readC(); - // reserved - reader.readC(); + if (isNewSample) { + sample->centerRate=reader.readI(); + sample->depth=(DivSampleDepth)reader.readC(); - // while version 32 stored this value, it was unused. - if (ds.version>=38) { - sample->centerRate=(unsigned short) reader.readS(); - } else { - reader.readS(); - } + // reserved + reader.readC(); + reader.readC(); + reader.readC(); - if (ds.version>=19) { sample->loopStart=reader.readI(); + sample->loopEnd=reader.readI(); + + for (int i=0; i<4; i++) { + reader.readI(); + } } else { - reader.readI(); + if (ds.version<58) { + vol=reader.readS(); + pitch=reader.readS(); + } else { + reader.readI(); + } + sample->depth=(DivSampleDepth)reader.readC(); + + // reserved + reader.readC(); + + // while version 32 stored this value, it was unused. + if (ds.version>=38) { + sample->centerRate=(unsigned short)reader.readS(); + } else { + reader.readS(); + } + + if (ds.version>=19) { + sample->loopStart=reader.readI(); + } else { + reader.readI(); + } } if (ds.version>=58) { // modern sample sample->init(sample->samples); reader.read(sample->getCurBuf(),sample->getCurBufLen()); +#ifdef TA_BIG_ENDIAN + // convert 16-bit samples to big-endian + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { + unsigned char* sampleBuf=(unsigned char*)sample->getCurBuf(); + size_t sampleBufLen=sample->getCurBufLen(); + for (size_t pos=0; possamples; short* data=new short[length]; reader.read(data,2*length); +#ifdef TA_BIG_ENDIAN + // convert 16-bit samples to big-endian + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { + unsigned char* sampleBuf=(unsigned char*)sample->getCurBuf(); + size_t sampleBufLen=sample->getCurBufLen(); + for (size_t pos=0; posdepth!=8 && sample->depth!=16) { + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) { logW("%d: sample depth is wrong! (%d)",i,sample->depth); - sample->depth=16; + sample->depth=DIV_SAMPLE_DEPTH_16BIT; } sample->samples=(double)sample->samples/samplePitches[pitch]; sample->init(sample->samples); @@ -1665,7 +1801,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (k>=sample->samples) { break; } - if (sample->depth==8) { + if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { float next=(float)(data[(unsigned int)j]-0x80)*mult; sample->data8[k++]=fmin(fmax(next,-128),127); } else { @@ -1809,6 +1945,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { ds.noSlidesOnFirstTick=true; ds.rowResetsArpPos=true; ds.ignoreJumpAtEnd=false; + ds.delayBehavior=0; int insCount=31; bool bypassLimits=false; @@ -1824,26 +1961,33 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { } if (memcmp(magic,"M.K.",4)==0 || memcmp(magic,"M!K!",4)==0 || memcmp(magic,"M&K!",4)==0) { logD("detected a ProTracker module"); + ds.systemName="Amiga"; chCount=4; } else if (memcmp(magic,"CD81",4)==0 || memcmp(magic,"OKTA",4)==0 || memcmp(magic,"OCTA",4)==0) { logD("detected an Oktalyzer/Octalyzer/OctaMED module"); + ds.systemName="Amiga (8-channel)"; chCount=8; } else if (memcmp(magic+1,"CHN",3)==0 && magic[0]>='1' && magic[0]<='9') { logD("detected a FastTracker module"); + ds.systemName="PC"; chCount=magic[0]-'0'; } else if (memcmp(magic,"FLT",3)==0 && magic[3]>='1' && magic[3]<='9') { logD("detected a Fairlight module"); + ds.systemName="Amiga"; chCount=magic[3]-'0'; } else if (memcmp(magic,"TDZ",3)==0 && magic[3]>='1' && magic[3]<='9') { logD("detected a TakeTracker module"); + ds.systemName="PC"; chCount=magic[3]-'0'; } else if ((memcmp(magic+2,"CH",2)==0 || memcmp(magic+2,"CN",2)==0) && (magic[0]>='1' && magic[0]<='9' && magic[1]>='0' && magic[1]<='9')) { logD("detected a Fast/TakeTracker module"); + ds.systemName="PC"; chCount=((magic[0]-'0')*10)+(magic[1]-'0'); } else { insCount=15; logD("possibly a Soundtracker module"); + ds.systemName="Amiga"; chCount=4; } @@ -1859,7 +2003,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { logD("reading samples... (%d)",insCount); for (int i=0; idepth=8; + sample->depth=DIV_SAMPLE_DEPTH_8BIT; sample->name=reader.readString(22); logD("%d: %s",i+1,sample->name); int slen=((unsigned short)reader.readS_BE())*2; @@ -1879,8 +2023,8 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { loopLen=0; } if (loopLen>=2) { - if (loopEndloopStart=loopStart; + sample->loopEnd=loopEnd; } sample->init(slen); ds.sample.push_back(sample); @@ -2194,6 +2338,687 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { return success; } +unsigned char fcXORTriangle[32]={ + 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, + 0xc0, 0xb8, 0xb0, 0xa8, 0xa0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x98, 0xa0, 0xa8, 0xb0, 0xb8 +}; + +unsigned char fcCustom1[32]={ + 0x45, 0x45, 0x79, 0x7d, 0x7a, 0x77, 0x70, 0x66, 0x61, 0x58, 0x53, 0x4d, 0x2c, 0x20, 0x18, 0x12, + 0x04, 0xdb, 0xd3, 0xcd, 0xc6, 0xbc, 0xb5, 0xae, 0xa8, 0xa3, 0x9d, 0x99, 0x93, 0x8e, 0x8b, 0x8a +}; + +unsigned char fcCustom2[32]={ + 0x45, 0x45, 0x79, 0x7d, 0x7a, 0x77, 0x70, 0x66, 0x5b, 0x4b, 0x43, 0x37, 0x2c, 0x20, 0x18, 0x12, + 0x04, 0xf8, 0xe8, 0xdb, 0xcf, 0xc6, 0xbe, 0xb0, 0xa8, 0xa4, 0x9e, 0x9a, 0x95, 0x94, 0x8d, 0x83 +}; + +unsigned char fcTinyTriangle[16]={ + 0x00, 0x00, 0x40, 0x60, 0x7f, 0x60, 0x40, 0x20, 0x00, 0xe0, 0xc0, 0xa0, 0x80, 0xa0, 0xc0, 0xe0 +}; + +void generateFCPresetWave(int index, DivWavetable* wave) { + wave->max=255; + wave->len=32; + + switch (index) { + case 0x00: case 0x01: case 0x02: case 0x03: + case 0x04: case 0x05: case 0x06: case 0x07: + case 0x08: case 0x09: case 0x0a: case 0x0b: + case 0x0c: case 0x0d: case 0x0e: case 0x0f: + // XOR triangle + for (int i=0; i<32; i++) { + wave->data[i]=(unsigned char)((fcXORTriangle[i]^0x80)^(((index+15)data[i]=(index>i)?0x01:0xff; + } + break; + case 0x20: case 0x21: case 0x22: case 0x23: + case 0x24: case 0x25: case 0x26: case 0x27: + // tiny pulse + for (int i=0; i<32; i++) { + wave->data[i]=((index-0x18)>(i&15))?0x01:0xff; + } + break; + case 0x28: + case 0x2e: + // saw + for (int i=0; i<32; i++) { + wave->data[i]=i<<3; + } + break; + case 0x29: + case 0x2f: + // tiny saw + for (int i=0; i<32; i++) { + wave->data[i]=(i<<4)&0xff; + } + break; + case 0x2a: + // custom 1 + for (int i=0; i<32; i++) { + wave->data[i]=fcCustom1[i]^0x80; + } + break; + case 0x2b: + // custom 2 + for (int i=0; i<32; i++) { + wave->data[i]=fcCustom2[i]^0x80; + } + break; + case 0x2c: case 0x2d: + // tiny triangle + for (int i=0; i<32; i++) { + wave->data[i]=fcTinyTriangle[i&15]^0x80; + } + break; + default: + for (int i=0; i<32; i++) { + wave->data[i]=i; + } + break; + } +} + +bool DivEngine::loadFC(unsigned char* file, size_t len) { + struct InvalidHeaderException {}; + bool success=false; + char magic[4]={0,0,0,0}; + SafeReader reader=SafeReader(file,len); + warnings=""; + bool isFC14=false; + unsigned int patPtr, freqMacroPtr, volMacroPtr, samplePtr, wavePtr; + unsigned int seqLen, patLen, freqMacroLen, volMacroLen, sampleLen; + + unsigned char waveLen[80]; + //unsigned char waveLoopLen[40]; + + struct FCSequence { + unsigned char pat[4]; + signed char transpose[4]; + signed char offsetIns[4]; + unsigned char speed; + }; + std::vector seq; + struct FCPattern { + unsigned char note[32]; + unsigned char val[32]; + }; + std::vector pat; + struct FCMacro { + unsigned char val[64]; + }; + std::vector freqMacros; + std::vector volMacros; + + struct FCSample { + unsigned short loopLen, len, loopStart; + } sample[10]; + + try { + DivSong ds; + ds.tuning=436.0; + ds.version=DIV_VERSION_FC; + //ds.linearPitch=0; + //ds.pitchMacroIsLinear=false; + //ds.noSlidesOnFirstTick=true; + //ds.rowResetsArpPos=true; + ds.pitchSlideSpeed=8; + ds.ignoreJumpAtEnd=false; + + // load here + if (!reader.seek(0,SEEK_SET)) { + logE("premature end of file!"); + lastError="incomplete file"; + delete[] file; + return false; + } + reader.read(magic,4); + + if (memcmp(magic,DIV_FC13_MAGIC,4)==0) { + isFC14=false; + } else if (memcmp(magic,DIV_FC14_MAGIC,4)==0) { + isFC14=true; + } else { + logW("the magic isn't complete"); + throw EndOfFileException(&reader,reader.tell()); + } + + ds.systemLen=1; + ds.system[0]=DIV_SYSTEM_AMIGA; + ds.systemVol[0]=64; + ds.systemPan[0]=0; + ds.systemFlags[0]=1|(80<<8); // PAL + ds.systemName="Amiga"; + + seqLen=reader.readI_BE(); + if (seqLen%13) { + logW("sequence length is not multiple of 13 (%d)",seqLen); + //throw EndOfFileException(&reader,reader.tell()); + } + patPtr=reader.readI_BE(); + patLen=reader.readI_BE(); + if (patLen%64) { + logW("pattern length is not multiple of 64 (%d)",patLen); + throw EndOfFileException(&reader,reader.tell()); + } + freqMacroPtr=reader.readI_BE(); + freqMacroLen=reader.readI_BE(); + if (freqMacroLen%64) { + logW("freq sequence length is not multiple of 64 (%d)",freqMacroLen); + //throw EndOfFileException(&reader,reader.tell()); + } + volMacroPtr=reader.readI_BE(); + volMacroLen=reader.readI_BE(); + if (volMacroLen%64) { + logW("vol sequence length is not multiple of 64 (%d)",volMacroLen); + //throw EndOfFileException(&reader,reader.tell()); + } + samplePtr=reader.readI_BE(); + if (isFC14) { + wavePtr=reader.readI_BE(); // wave len + sampleLen=0; + } else { + sampleLen=reader.readI_BE(); + wavePtr=0; + } + + logD("patPtr: %x",patPtr); + logD("patLen: %d",patLen); + logD("freqMacroPtr: %x",freqMacroPtr); + logD("freqMacroLen: %d",freqMacroLen); + logD("volMacroPtr: %x",volMacroPtr); + logD("volMacroLen: %d",volMacroLen); + logD("samplePtr: %x",samplePtr); + if (isFC14) { + logD("wavePtr: %x",wavePtr); + } else { + logD("sampleLen: %d",sampleLen); + } + + // sample info + logD("samples: (%x)",reader.tell()); + for (int i=0; i<10; i++) { + sample[i].len=reader.readS_BE(); + sample[i].loopStart=reader.readS_BE(); + sample[i].loopLen=reader.readS_BE(); + + logD("- %d: %d (%d, %d)",i,sample[i].len,sample[i].loopStart,sample[i].loopLen); + } + + // wavetable lengths + if (isFC14) { + logD("wavetables:"); + for (int i=0; i<80; i++) { + waveLen[i]=(unsigned char)reader.readC(); + + logD("- %d: %.4x",i,waveLen[i]); + } + } + + // sequences + seqLen/=13; + logD("reading sequences... (%d)",seqLen); + for (unsigned int i=0; idepth=DIV_SAMPLE_DEPTH_8BIT; + if (sample[i].len>0) { + s->init(sample[i].len*2); + } + s->name=fmt::sprintf("Sample %d",i+1); + if (sample[i].loopLen>1) { + s->loopStart=sample[i].loopStart; + s->loopEnd=sample[i].loopStart+(sample[i].loopLen*2); + } + reader.read(s->data8,sample[i].len*2); + ds.sample.push_back(s); + } + ds.sampleLen=(int)ds.sample.size(); + + // wavetables + if (isFC14) { + if (!reader.seek(wavePtr,SEEK_SET)) { + logE("premature end of file!"); + lastError="incomplete file"; + delete[] file; + return false; + } + logD("reading wavetables..."); + for (int i=0; i<80; i++) { + DivWavetable* w=new DivWavetable; + w->min=0; + w->max=255; + w->len=MIN(256,waveLen[i]*2); + + for (int i=0; i<256; i++) { + w->data[i]=128; + } + + if (waveLen[i]>0) { + signed char* waveArray=new signed char[waveLen[i]*2]; + reader.read(waveArray,waveLen[i]*2); + int howMany=waveLen[i]*2; + if (howMany>256) howMany=256; + for (int i=0; idata[i]=waveArray[i]+128; + } + delete[] waveArray; + } else { + logV("empty wave %d",i); + generateFCPresetWave(i,w); + } + + ds.wave.push_back(w); + } + } else { + // generate preset waves + for (int i=0; i<48; i++) { + DivWavetable* w=new DivWavetable; + generateFCPresetWave(i,w); + ds.wave.push_back(w); + } + } + ds.waveLen=(int)ds.wave.size(); + + // convert + ds.subsong[0]->ordersLen=seqLen; + ds.subsong[0]->patLen=32; + ds.subsong[0]->hz=50; + ds.subsong[0]->pal=true; + ds.subsong[0]->customTempo=true; + ds.subsong[0]->pat[3].effectCols=3; + ds.subsong[0]->speed1=3; + ds.subsong[0]->speed2=3; + + int lastIns[4]; + int lastNote[4]; + signed char lastTranspose[4]; + bool isSliding[4]; + + memset(lastIns,-1,4*sizeof(int)); + memset(lastNote,-1,4*sizeof(int)); + memset(lastTranspose,0,4); + memset(isSliding,0,4*sizeof(bool)); + + for (unsigned int i=0; iorders.ord[j][i]=i; + DivPattern* p=ds.subsong[0]->pat[j].getPattern(i,true); + if (j==3 && seq[i].speed) { + p->data[0][6]=0x09; + p->data[0][7]=seq[i].speed; + p->data[0][8]=0x0f; + p->data[0][9]=seq[i].speed; + } + + bool ignoreNext=false; + + for (int k=0; k<32; k++) { + FCPattern& fp=pat[seq[i].pat[j]]; + if (fp.note[k]>0 && fp.note[k]<0x49) { + lastNote[j]=fp.note[k]; + short note=(fp.note[k]+seq[i].transpose[j])%12; + short octave=2+((fp.note[k]+seq[i].transpose[j])/12); + if (fp.note[k]>=0x3d) octave-=6; + if (note==0) { + note=12; + octave--; + } + octave&=0xff; + p->data[k][0]=note; + p->data[k][1]=octave; + if (isSliding[j]) { + isSliding[j]=false; + p->data[k][4]=2; + p->data[k][5]=0; + } + } else if (fp.note[k]==0x49) { + if (k>0) { + p->data[k-1][4]=0x0d; + p->data[k-1][5]=0; + } + } else if (k==0 && lastTranspose[j]!=seq[i].transpose[j]) { + p->data[0][2]=lastIns[j]; + p->data[0][4]=0x03; + p->data[0][5]=0xff; + lastTranspose[j]=seq[i].transpose[j]; + + short note=(lastNote[j]+seq[i].transpose[j])%12; + short octave=2+((lastNote[j]+seq[i].transpose[j])/12); + if (lastNote[j]>=0x3d) octave-=6; + if (note==0) { + note=12; + octave--; + } + octave&=0xff; + p->data[k][0]=note; + p->data[k][1]=octave; + } + if (fp.val[k]) { + if (ignoreNext) { + ignoreNext=false; + } else { + if (fp.val[k]==0xf0) { + p->data[k][0]=100; + p->data[k][1]=0; + p->data[k][2]=-1; + } else if (fp.val[k]&0xe0) { + if (fp.val[k]&0x40) { + p->data[k][4]=2; + p->data[k][5]=0; + isSliding[j]=false; + } else if (fp.val[k]&0x80) { + isSliding[j]=true; + if (k<31) { + if (fp.val[k+1]&0x20) { + p->data[k][4]=2; + p->data[k][5]=fp.val[k+1]&0x1f; + } else { + p->data[k][4]=1; + p->data[k][5]=fp.val[k+1]&0x1f; + } + ignoreNext=true; + } else { + p->data[k][4]=2; + p->data[k][5]=0; + } + } + } else { + p->data[k][2]=(fp.val[k]+seq[i].offsetIns[j])&0x3f; + lastIns[j]=p->data[k][2]; + } + } + } else if (fp.note[k]>0 && fp.note[k]<0x49) { + p->data[k][2]=seq[i].offsetIns[j]; + lastIns[j]=p->data[k][2]; + } + } + } + } + + // convert instruments + for (unsigned int i=0; itype=DIV_INS_AMIGA; + ins->name=fmt::sprintf("Instrument %d",i); + ins->amiga.useWave=true; + unsigned char seqSpeed=m.val[0]; + unsigned char freqMacro=m.val[1]; + unsigned char vibSpeed=m.val[2]; + unsigned char vibDepth=m.val[3]; + unsigned char vibDelay=m.val[4]; + + unsigned char lastVal=m.val[5]; + + signed char loopMap[64]; + memset(loopMap,-1,64); + + signed char loopMapFreq[64]; + memset(loopMapFreq,-1,64); + + signed char loopMapWave[64]; + memset(loopMapWave,-1,64); + + // volume sequence + ins->std.volMacro.len=0; + for (int j=5; j<64; j++) { + loopMap[j]=ins->std.volMacro.len; + if (m.val[j]==0xe1) { // end + break; + } else if (m.val[j]==0xe0) { // loop + if (++j>=64) break; + ins->std.volMacro.loop=loopMap[m.val[j]&63]; + break; + } else if (m.val[j]==0xe8) { // sustain + if (++j>=64) break; + unsigned char susTime=m.val[j]; + // TODO: <= or std.volMacro.val[ins->std.volMacro.len]=lastVal; + if (++ins->std.volMacro.len>=255) break; + } + if (ins->std.volMacro.len>=255) break; + } else if (m.val[j]==0xe9 || m.val[j]==0xea) { // volume slide + if (++j>=64) break; + signed char slideStep=m.val[j]; + if (++j>=64) break; + unsigned char slideTime=m.val[j]; + // TODO: <= or 0) { + lastVal+=slideStep; + if (lastVal>63) lastVal=63; + } else { + if (-slideStep>lastVal) { + lastVal=0; + } else { + lastVal-=slideStep; + } + } + ins->std.volMacro.val[ins->std.volMacro.len]=lastVal; + if (++ins->std.volMacro.len>=255) break; + } + } else { + // TODO: replace with upcoming macro speed + for (int k=0; kstd.volMacro.val[ins->std.volMacro.len]=m.val[j]; + lastVal=m.val[j]; + if (++ins->std.volMacro.len>=255) break; + } + if (ins->std.volMacro.len>=255) break; + } + } + + // frequency sequence + lastVal=0; + ins->amiga.initSample=-1; + if (freqMacrostd.arpMacro.len; + loopMapWave[j]=ins->std.waveMacro.len; + if (fm.val[j]==0xe1) { + break; + } else if (fm.val[j]==0xe2 || fm.val[j]==0xe4) { + if (++j>=64) break; + unsigned char wave=fm.val[j]; + if (wave<10) { // sample + if (ins->amiga.initSample==-1) { + ins->amiga.initSample=wave; + ins->amiga.useWave=false; + } + } else { // waveform + ins->std.waveMacro.val[ins->std.waveMacro.len]=wave-10; + ins->std.waveMacro.open=true; + lastVal=wave; + //if (++ins->std.arpMacro.len>=255) break; + } + } else if (fm.val[j]==0xe0) { + if (++j>=64) break; + ins->std.arpMacro.loop=loopMapFreq[fm.val[j]&63]; + ins->std.waveMacro.loop=loopMapWave[fm.val[j]&63]; + break; + } else if (fm.val[j]==0xe3) { + logV("unhandled vibrato!"); + } else if (fm.val[j]==0xe8) { + logV("unhandled sustain!"); + } else if (fm.val[j]==0xe7) { + if (++j>=64) break; + fm=freqMacros[MIN(fm.val[j],freqMacros.size()-1)]; + j=0; + } else if (fm.val[j]==0xe9) { + logV("unhandled pack!"); + } else if (fm.val[j]==0xea) { + logV("unhandled pitch!"); + } else { + if (fm.val[j]>0x80) { + ins->std.arpMacro.val[ins->std.arpMacro.len]=(fm.val[j]-0x80+24)^0x40000000; + } else { + ins->std.arpMacro.val[ins->std.arpMacro.len]=fm.val[j]; + } + if (lastVal>=10) { + ins->std.waveMacro.val[ins->std.waveMacro.len]=lastVal-10; + } + ins->std.arpMacro.open=true; + if (++ins->std.arpMacro.len>=255) break; + if (++ins->std.waveMacro.len>=255) break; + } + } + } + + // waveform width + if (lastVal>=10 && (unsigned int)(lastVal-10)amiga.waveLen=ds.wave[lastVal-10]->len-1; + } + + // vibrato + for (int j=0; j<=vibDelay; j++) { + ins->std.pitchMacro.val[ins->std.pitchMacro.len]=0; + if (++ins->std.pitchMacro.len>=255) break; + } + int vibPos=0; + ins->std.pitchMacro.loop=ins->std.pitchMacro.len; + do { + vibPos+=vibSpeed; + if (vibPos>vibDepth) vibPos=vibDepth; + ins->std.pitchMacro.val[ins->std.pitchMacro.len]=vibPos*32; + if (++ins->std.pitchMacro.len>=255) break; + } while (vibPosstd.pitchMacro.val[ins->std.pitchMacro.len]=vibPos*32; + if (++ins->std.pitchMacro.len>=255) break; + } while (vibPos>-vibDepth); + do { + vibPos+=vibSpeed; + if (vibPos>0) vibPos=0; + ins->std.pitchMacro.val[ins->std.pitchMacro.len]=vibPos*32; + if (++ins->std.pitchMacro.len>=255) break; + } while (vibPos<0); + + ds.ins.push_back(ins); + } + ds.insLen=(int)ds.ins.size(); + + // optimize + ds.subsong[0]->optimizePatterns(); + ds.subsong[0]->rearrangePatterns(); + + if (active) quitDispatch(); + BUSY_BEGIN_SOFT; + saveLock.lock(); + song.unload(); + song=ds; + changeSong(0); + recalcChans(); + saveLock.unlock(); + BUSY_END; + if (active) { + initDispatch(); + BUSY_BEGIN; + renderSamples(); + reset(); + BUSY_END; + } + success=true; + } catch (EndOfFileException& e) { + //logE("premature end of file!"); + lastError="incomplete file"; + } catch (InvalidHeaderException& e) { + //logE("invalid header!"); + lastError="invalid header!"; + } + return success; +} + #define CHECK_BLOCK_VERSION(x) \ if (blockVersion>x) { \ logE("incompatible block version %d for %s!",blockVersion,blockName); \ @@ -2427,8 +3252,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { const int dpcmNotes=(blockVersion>=2)?96:72; for (int j=0; jamiga.noteMap[j]=(short)((unsigned char)reader.readC())-1; - ins->amiga.noteFreq[j]=(unsigned char)reader.readC(); + ins->amiga.noteMap[j].map=(short)((unsigned char)reader.readC())-1; + ins->amiga.noteMap[j].freq=(unsigned char)reader.readC(); if (blockVersion>=6) { reader.readC(); // DMC value } @@ -2483,6 +3308,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { ins->std.arpMacro.len=reader.readC(); ins->std.arpMacro.loop=reader.readI(); ins->std.arpMacro.rel=reader.readI(); + // TODO: get rid ins->std.arpMacro.mode=reader.readI(); for (int j=0; jstd.arpMacro.len; j++) { ins->std.arpMacro.val[j]=reader.readC(); @@ -2666,6 +3492,8 @@ bool DivEngine::load(unsigned char* f, size_t slen) { return loadFTM(file,len); } else if (memcmp(file,DIV_FUR_MAGIC,16)==0) { return loadFur(file,len); + } else if (memcmp(file,DIV_FC13_MAGIC,4)==0 || memcmp(file,DIV_FC14_MAGIC,4)==0) { + return loadFC(file,len); } // step 3: try loading as .mod @@ -2696,7 +3524,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { std::vector wavePtr; std::vector samplePtr; std::vector patPtr; - size_t ptrSeek, subSongPtrSeek; + size_t ptrSeek, subSongPtrSeek, blockStartSeek, blockEndSeek; size_t subSongIndex=0; DivSubSong* subSong=song.subsong[subSongIndex]; warnings=""; @@ -2760,21 +3588,34 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { // high short is channel // low short is pattern number std::vector patsToWrite; - bool alreadyAdded[256]; - for (int i=0; iordersLen; k++) { - if (alreadyAdded[subs->orders.ord[i][k]]) continue; - patsToWrite.push_back(PatToWrite(j,i,subs->orders.ord[i][k])); - alreadyAdded[subs->orders.ord[i][k]]=true; + if (getConfInt("saveUnusedPatterns",0)==1) { + for (int i=0; ipat[i].data[k]==NULL) continue; + patsToWrite.push_back(PatToWrite(j,i,k)); + } + } + } + } else { + bool alreadyAdded[256]; + for (int i=0; iordersLen; k++) { + if (alreadyAdded[subs->orders.ord[i][k]]) continue; + patsToWrite.push_back(PatToWrite(j,i,subs->orders.ord[i][k])); + alreadyAdded[subs->orders.ord[i][k]]=true; + } } } } /// SONG INFO w->write("INFO",4); + blockStartSeek=w->tell(); w->writeI(0); w->writeC(subSong->timeBase); @@ -2911,7 +3752,12 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeC(song.newVolumeScaling); w->writeC(song.volMacroLinger); w->writeC(song.brokenOutVol); - for (int i=0; i<9; i++) { + w->writeC(song.e1e2StopOnSameNote); + w->writeC(song.brokenPortaArp); + w->writeC(song.snNoLowPeriods); + w->writeC(song.delayBehavior); + w->writeC(song.jumpTreatment); + for (int i=0; i<4; i++) { w->writeC(0); } @@ -2932,11 +3778,25 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeI(0); } + // additional metadata + w->writeString(song.systemName,false); + w->writeString(song.category,false); + w->writeString(song.nameJ,false); + w->writeString(song.authorJ,false); + w->writeString(song.systemNameJ,false); + w->writeString(song.categoryJ,false); + + blockEndSeek=w->tell(); + w->seek(blockStartSeek,SEEK_SET); + w->writeI(blockEndSeek-blockStartSeek-4); + w->seek(0,SEEK_END); + /// SUBSONGS for (subSongIndex=1; subSongIndextell()); w->write("SONG",4); + blockStartSeek=w->tell(); w->writeI(0); w->writeC(subSong->timeBase); @@ -2979,6 +3839,11 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { for (int i=0; iwriteString(subSong->chanShortName[i],false); } + + blockEndSeek=w->tell(); + w->seek(blockStartSeek,SEEK_SET); + w->writeI(blockEndSeek-blockStartSeek-4); + w->seek(0,SEEK_END); } /// INSTRUMENT @@ -2999,19 +3864,45 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { for (int i=0; itell()); - w->write("SMPL",4); + w->write("SMP2",4); + blockStartSeek=w->tell(); w->writeI(0); w->writeString(sample->name,false); w->writeI(sample->samples); w->writeI(sample->rate); - w->writeI(0); // reserved (for now) + w->writeI(sample->centerRate); w->writeC(sample->depth); + w->writeC(0); // reserved + w->writeC(0); w->writeC(0); - w->writeS(sample->centerRate); w->writeI(sample->loopStart); + w->writeI(sample->loopEnd); + for (int i=0; i<4; i++) { + w->writeI(0xffffffff); + } + +#ifdef TA_BIG_ENDIAN + // store 16-bit samples as little-endian + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { + unsigned char* sampleBuf=(unsigned char*)sample->getCurBuf(); + size_t bufLen=sample->getCurBufLen(); + for (size_t i=0; iwriteC(sampleBuf[i+1]); + w->writeC(sampleBuf[i]); + } + } else { + w->write(sample->getCurBuf(),sample->getCurBufLen()); + } +#else w->write(sample->getCurBuf(),sample->getCurBufLen()); +#endif + + blockEndSeek=w->tell(); + w->seek(blockStartSeek,SEEK_SET); + w->writeI(blockEndSeek-blockStartSeek-4); + w->seek(0,SEEK_END); } /// PATTERN @@ -3019,6 +3910,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { DivPattern* pat=song.subsong[i.subsong]->pat[i.chan].getPattern(i.pat,false); patPtr.push_back(w->tell()); w->write("PATR",4); + blockStartSeek=w->tell(); w->writeI(0); w->writeS(i.chan); @@ -3032,10 +3924,21 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeS(pat->data[j][1]); // octave w->writeS(pat->data[j][2]); // instrument w->writeS(pat->data[j][3]); // volume +#ifdef TA_BIG_ENDIAN + for (int k=0; kpat[i.chan].effectCols*2; k++) { + w->writeS(pat->data[j][4+k]); + } +#else w->write(&pat->data[j][4],2*song.subsong[i.subsong]->pat[i.chan].effectCols*2); // effects +#endif } w->writeString(pat->name,false); + + blockEndSeek=w->tell(); + w->seek(blockStartSeek,SEEK_SET); + w->writeI(blockEndSeek-blockStartSeek-4); + w->seek(0,SEEK_END); } /// POINTERS @@ -3262,7 +4165,25 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { // safety check if (!isFMSystem(sys) && i->type!=DIV_INS_STD && i->type!=DIV_INS_FDS) { - i->type=DIV_INS_STD; + switch (song.system[0]) { + case DIV_SYSTEM_GB: + i->type=DIV_INS_GB; + break; + case DIV_SYSTEM_C64_6581: + case DIV_SYSTEM_C64_8580: + i->type=DIV_INS_C64; + break; + case DIV_SYSTEM_PCE: + i->type=DIV_INS_PCE; + break; + case DIV_SYSTEM_YM2610: + case DIV_SYSTEM_YM2610_EXT: + i->type=DIV_INS_AY; + break; + default: + i->type=DIV_INS_STD; + break; + } } if (!isSTDSystem(sys) && i->type!=DIV_INS_FM) { i->type=DIV_INS_FM; @@ -3303,47 +4224,79 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { } } else { // STD if (sys!=DIV_SYSTEM_GB) { - w->writeC(i->std.volMacro.len); + int realVolMacroLen=i->std.volMacro.len; + if (realVolMacroLen>127) realVolMacroLen=127; + w->writeC(realVolMacroLen); if ((sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) && i->c64.volIsCutoff) { - for (int j=0; jstd.volMacro.len; j++) { + for (int j=0; jwriteI(i->std.volMacro.val[j]+18); } } else { - w->write(i->std.volMacro.val,4*i->std.volMacro.len); + for (int j=0; jwriteI(i->std.volMacro.val[j]); + } } - if (i->std.volMacro.len>0) { + if (realVolMacroLen>0) { w->writeC(i->std.volMacro.loop); } } + // TODO: take care of new arp macro format w->writeC(i->std.arpMacro.len); - if (i->std.arpMacro.mode) { - w->write(i->std.arpMacro.val,4*i->std.arpMacro.len); + bool arpMacroMode=false; + int arpMacroHowManyFixed=0; + int realArpMacroLen=i->std.arpMacro.len; + for (int j=0; jstd.arpMacro.len; j++) { + if ((i->std.arpMacro.val[j]&0xc0000000)==0x40000000 || (i->std.arpMacro.val[j]&0xc0000000)==0x80000000) { + arpMacroHowManyFixed++; + } + } + if (arpMacroHowManyFixed>=i->std.arpMacro.len-1) { + arpMacroMode=true; + } + if (i->std.arpMacro.len>0) { + if (arpMacroMode && i->std.arpMacro.val[i->std.arpMacro.len-1]==0 && i->std.arpMacro.loop>=i->std.arpMacro.len) { + realArpMacroLen--; + } + } + + if (arpMacroMode) { + for (int j=0; jwriteI(i->std.arpMacro.val[j]); + } } else { - for (int j=0; jstd.arpMacro.len; j++) { + for (int j=0; jwriteI(i->std.arpMacro.val[j]+12); } } - if (i->std.arpMacro.len>0) { + if (realArpMacroLen>0) { w->writeC(i->std.arpMacro.loop); } - w->writeC(i->std.arpMacro.mode); + w->writeC(arpMacroMode); - w->writeC(i->std.dutyMacro.len); + int realDutyMacroLen=i->std.dutyMacro.len; + if (realDutyMacroLen>127) realDutyMacroLen=127; + w->writeC(realDutyMacroLen); if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) { - for (int j=0; jstd.dutyMacro.len; j++) { + for (int j=0; jwriteI(i->std.dutyMacro.val[j]+12); } } else { - w->write(i->std.dutyMacro.val,4*i->std.dutyMacro.len); + for (int j=0; jwriteI(i->std.dutyMacro.val[j]); + } } - if (i->std.dutyMacro.len>0) { + if (realDutyMacroLen>0) { w->writeC(i->std.dutyMacro.loop); } - w->writeC(i->std.waveMacro.len); - w->write(i->std.waveMacro.val,4*i->std.waveMacro.len); - if (i->std.waveMacro.len>0) { + int realWaveMacroLen=i->std.waveMacro.len; + if (realWaveMacroLen>127) realWaveMacroLen=127; + w->writeC(realWaveMacroLen); + for (int j=0; jwriteI(i->std.waveMacro.val[j]); + } + if (realWaveMacroLen>0) { w->writeC(i->std.waveMacro.loop); } @@ -3393,20 +4346,39 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { w->writeI(i->data[j]>>2); } } else { - w->write(i->data,4*i->len); + for (int j=0; jlen; j++) { + w->writeI(i->data[j]); + } } } + bool relWarning=false; + for (int i=0; iwriteC(curPat[i].effectCols); for (int j=0; jordersLen; j++) { DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][j],false); for (int k=0; kpatLen; k++) { - w->writeS(pat->data[k][0]); // note - w->writeS(pat->data[k][1]); // octave + if ((pat->data[k][0]==101 || pat->data[k][0]==102) && pat->data[k][1]==0) { + w->writeS(100); + w->writeS(0); + if (!relWarning) { + relWarning=true; + addWarning("note/macro release will be converted to note off!"); + } + } else { + w->writeS(pat->data[k][0]); // note + w->writeS(pat->data[k][1]); // octave + } w->writeS(pat->data[k][3]); // volume +#ifdef TA_BIG_ENDIAN + for (int l=0; lwriteS(pat->data[k][4+l]); + } +#else w->write(&pat->data[k][4],2*curPat[i].effectCols*2); // effects +#endif w->writeS(pat->data[k][2]); // instrument } } @@ -3425,7 +4397,15 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { w->writeC(50); // i'm too lazy to deal with .dmf's weird way of storing 8-bit samples w->writeC(16); + // well I can't be lazy if it's on a big-endian system +#ifdef TA_BIG_ENDIAN + for (unsigned int j=0; jlength16; j++) { + w->writeC(((unsigned short)i->data16[j])&0xff); + w->writeC(((unsigned short)i->data16[j])>>8); + } +#else w->write(i->data16,i->length16); +#endif } saveLock.unlock(); diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index bf83a2dc..83361130 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -21,6 +21,7 @@ #include "../ta-log.h" #include "../fileutils.h" #include +#include enum DivInsFormats { DIV_INSFORMAT_DMP, @@ -149,9 +150,13 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St ins->type=DIV_INS_FM; logD("instrument type is Arcade"); break; + case 9: // Neo Geo + ins->type=DIV_INS_FM; + logD("instrument type is Neo Geo"); + break; default: logD("instrument type is unknown"); - lastError="unknown instrument type!"; + lastError=fmt::sprintf("unknown instrument type %d!",sys); delete ins; return; break; @@ -170,11 +175,21 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St mode=reader.readC(); logD("instrument mode is %d",mode); if (mode==0) { - if (version<11) { - ins->type=DIV_INS_STD; + if (ins->type==DIV_INS_FM) { + if (sys==9) { + ins->type=DIV_INS_AY; + } else { + ins->type=DIV_INS_STD; + } } } else { - ins->type=DIV_INS_FM; + if (sys==3 || sys==6) { + ins->type=DIV_INS_OPLL; + } else if (sys==1) { + ins->type=DIV_INS_OPL; + } else { + ins->type=DIV_INS_FM; + } } } else { ins->type=DIV_INS_FM; @@ -192,7 +207,13 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St ins->fm.ops=reader.readC()?4:2; } } else { - ins->fm.ops=reader.readC()?2:4; + // HELP + if (reader.size()==49) { + ins->fm.ops=4; + reader.readC(); + } else { + ins->fm.ops=reader.readC()?2:4; + } } } else { ins->fm.ops=4; @@ -225,12 +246,23 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St ins->fm.op[j].dvb=reader.readC(); ins->fm.op[j].dam=reader.readC(); } else { - ins->fm.op[j].rs=reader.readC(); - ins->fm.op[j].dt=reader.readC(); - ins->fm.op[j].dt2=ins->fm.op[j].dt>>4; - ins->fm.op[j].dt&=15; - ins->fm.op[j].d2r=reader.readC(); - ins->fm.op[j].ssgEnv=reader.readC(); + if (sys==3 || sys==6) { // OPLL/VRC7 + ins->fm.op[j].ksr=reader.readC()?1:0; + ins->fm.op[j].vib=reader.readC(); + if (j==0) { + ins->fm.opllPreset=ins->fm.op[j].vib>>4; + } + ins->fm.op[j].vib=ins->fm.op[j].vib?1:0; + ins->fm.op[j].ksl=reader.readC()?1:0; + ins->fm.op[j].ssgEnv=reader.readC(); + } else { + ins->fm.op[j].rs=reader.readC(); + ins->fm.op[j].dt=reader.readC(); + ins->fm.op[j].dt2=ins->fm.op[j].dt>>4; + ins->fm.op[j].dt&=15; + ins->fm.op[j].d2r=reader.readC(); + ins->fm.op[j].ssgEnv=reader.readC(); + } } } } else { // STD @@ -240,6 +272,9 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St if (version>5) { for (int i=0; istd.volMacro.len; i++) { ins->std.volMacro.val[i]=reader.readI(); + if (ins->std.volMacro.val[i]>15 && sys==6) { // FDS + ins->type=DIV_INS_FDS; + } } } else { for (int i=0; istd.volMacro.len; i++) { @@ -726,6 +761,8 @@ void DivEngine::loadOPLI(SafeReader& reader, std::vector& ret, S ins = new DivInstrument; ins->type = DIV_INS_OPL; ins->name = fmt::sprintf("%s (2)", insName); + ins->fm.alg = (feedConnect2nd & 0x1); + ins->fm.fb = ((feedConnect2nd >> 1) & 0xF); for (int i : {1,0}) { readOpliOp(reader, ins->fm.op[i]); } @@ -796,7 +833,7 @@ void DivEngine::loadOPNI(SafeReader& reader, std::vector& ret, S op.mult = dtMul & 0xF; op.dt = ((dtMul >> 4) & 0x7); - op.tl = totalLevel & 0x3F; + op.tl = totalLevel & 0x7F; op.rs = ((arRateScale >> 6) & 0x3); op.ar = arRateScale & 0x1F; op.dr = drAmpEnable & 0x1F; @@ -835,8 +872,9 @@ void DivEngine::loadY12(SafeReader& reader, std::vector& ret, St DivInstrumentFM::Operator& insOp = ins->fm.op[i]; uint8_t tmp = reader.readC(); insOp.mult = tmp & 0xF; - insOp.dt = ((tmp >> 4) & 0x7); - insOp.tl = (reader.readC() & 0x3F); + // ??? + insOp.dt = ((3 + (tmp >> 4)) & 0x7); + insOp.tl = (reader.readC() & 0x7F); tmp = reader.readC(); insOp.rs = ((tmp >> 6) & 0x3); insOp.ar = tmp & 0x1F; @@ -1255,7 +1293,7 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St patchNameRead = lfoRead = characteristicRead = m1Read = c1Read = m2Read = c2Read = false; newPatch = NULL; }; - auto readIntStrWithinRange = [](String&& input, int limitLow, int limitHigh) -> int { + auto readIntStrWithinRange = [](String&& input, int limitLow = INT_MIN, int limitHigh = INT_MAX) -> int { int x = std::stoi(input.c_str()); if (x > limitHigh || x < limitLow) { throw std::invalid_argument(fmt::sprintf("%s is out of bounds of range [%d..%d]", input, limitLow, limitHigh)); @@ -1273,7 +1311,7 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St op.mult = readIntStrWithinRange(reader.readStringToken(), 0, 15); op.dt = fmDtRegisterToFurnace(readIntStrWithinRange(reader.readStringToken(), 0, 7)); op.dt2 = readIntStrWithinRange(reader.readStringToken(), 0, 3); - op.am = readIntStrWithinRange(reader.readStringToken(), 0, 1); + op.am = readIntStrWithinRange(reader.readStringToken(), 0) > 0 ? 1 : 0; }; auto seekGroupValStart = [](SafeReader& reader, int pos) { // Seek to position then move to next ':' character @@ -1490,6 +1528,8 @@ void DivEngine::loadWOPL(SafeReader& reader, std::vector& ret, S ins = new DivInstrument; ins->type = DIV_INS_OPL; ins->name = fmt::sprintf("%s (2)", insName); + ins->fm.alg = (feedConnect2nd & 0x1); + ins->fm.fb = ((feedConnect2nd >> 1) & 0xF); for (int i : {1,0}) { patchSum += readWoplOp(reader, ins->fm.op[i]); } @@ -1631,7 +1671,7 @@ void DivEngine::loadWOPN(SafeReader& reader, std::vector& ret, S total += (op.mult = dtMul & 0xF); total += (op.dt = ((dtMul >> 4) & 0x7)); - total += (op.tl = totalLevel & 0x3F); + total += (op.tl = totalLevel & 0x7F); total += (op.rs = ((arRateScale >> 6) & 0x3)); total += (op.ar = arRateScale & 0x1F); total += (op.dr = drAmpEnable & 0x1F); diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 14daae8b..22cdddf4 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -24,7 +24,10 @@ #include "../fileutils.h" void DivInstrument::putInsData(SafeWriter* w) { + size_t blockStartSeek, blockEndSeek; + w->write("INST",4); + blockStartSeek=w->tell(); w->writeI(0); w->writeS(DIV_ENGINE_VERSION); @@ -68,8 +71,10 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(op.ws); w->writeC(op.ksr); + w->writeC(op.enable); + // reserved - for (int k=0; k<12; k++) { + for (int k=0; k<11; k++) { w->writeC(0); } } @@ -129,7 +134,7 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeI(std.ex1Macro.loop); w->writeI(std.ex2Macro.loop); w->writeI(std.ex3Macro.loop); - w->writeC(std.arpMacro.mode); + w->writeC(0); // this was arp macro mode w->writeC(0); // reserved w->writeC(0); w->writeC(0); @@ -384,8 +389,12 @@ void DivInstrument::putInsData(SafeWriter* w) { // sample map w->writeC(amiga.useNoteMap); if (amiga.useNoteMap) { - w->write(amiga.noteFreq,120*sizeof(unsigned int)); - w->write(amiga.noteMap,120*sizeof(short)); + for (int note=0; note<120; note++) { + w->writeI(amiga.noteMap[note].freq); + } + for (int note=0; note<120; note++) { + w->writeS(amiga.noteMap[note].map); + } } // N163 @@ -520,8 +529,142 @@ void DivInstrument::putInsData(SafeWriter* w) { for (int j=0; j<23; j++) { // reserved w->writeC(0); } + + // Sound Unit + w->writeC(su.useSample); + w->writeC(su.switchRoles); + + // GB hardware sequence + w->writeC(gb.hwSeqLen); + for (int i=0; iwriteC(gb.hwSeq[i].cmd); + w->writeS(gb.hwSeq[i].data); + } + + // GB additional flags + w->writeC(gb.softEnv); + w->writeC(gb.alwaysInit); + + // ES5506 + w->writeC(es5506.filter.mode); + w->writeS(es5506.filter.k1); + w->writeS(es5506.filter.k2); + w->writeS(es5506.envelope.ecount); + w->writeC(es5506.envelope.lVRamp); + w->writeC(es5506.envelope.rVRamp); + w->writeC(es5506.envelope.k1Ramp); + w->writeC(es5506.envelope.k2Ramp); + w->writeC(es5506.envelope.k1Slow); + w->writeC(es5506.envelope.k2Slow); + + // SNES + w->writeC(snes.useEnv); + w->writeC(snes.gainMode); + w->writeC(snes.gain); + w->writeC(snes.a); + w->writeC(snes.d); + w->writeC(snes.s); + w->writeC(snes.r); + + // macro speed/delay + w->writeC(std.volMacro.speed); + w->writeC(std.arpMacro.speed); + w->writeC(std.dutyMacro.speed); + w->writeC(std.waveMacro.speed); + w->writeC(std.pitchMacro.speed); + w->writeC(std.ex1Macro.speed); + w->writeC(std.ex2Macro.speed); + w->writeC(std.ex3Macro.speed); + w->writeC(std.algMacro.speed); + w->writeC(std.fbMacro.speed); + w->writeC(std.fmsMacro.speed); + w->writeC(std.amsMacro.speed); + w->writeC(std.panLMacro.speed); + w->writeC(std.panRMacro.speed); + w->writeC(std.phaseResetMacro.speed); + w->writeC(std.ex4Macro.speed); + w->writeC(std.ex5Macro.speed); + w->writeC(std.ex6Macro.speed); + w->writeC(std.ex7Macro.speed); + w->writeC(std.ex8Macro.speed); + + w->writeC(std.volMacro.delay); + w->writeC(std.arpMacro.delay); + w->writeC(std.dutyMacro.delay); + w->writeC(std.waveMacro.delay); + w->writeC(std.pitchMacro.delay); + w->writeC(std.ex1Macro.delay); + w->writeC(std.ex2Macro.delay); + w->writeC(std.ex3Macro.delay); + w->writeC(std.algMacro.delay); + w->writeC(std.fbMacro.delay); + w->writeC(std.fmsMacro.delay); + w->writeC(std.amsMacro.delay); + w->writeC(std.panLMacro.delay); + w->writeC(std.panRMacro.delay); + w->writeC(std.phaseResetMacro.delay); + w->writeC(std.ex4Macro.delay); + w->writeC(std.ex5Macro.delay); + w->writeC(std.ex6Macro.delay); + w->writeC(std.ex7Macro.delay); + w->writeC(std.ex8Macro.delay); + + // op macro speed/delay + for (int i=0; i<4; i++) { + DivInstrumentSTD::OpMacro& op=std.opMacros[i]; + + w->writeC(op.amMacro.speed); + w->writeC(op.arMacro.speed); + w->writeC(op.drMacro.speed); + w->writeC(op.multMacro.speed); + w->writeC(op.rrMacro.speed); + w->writeC(op.slMacro.speed); + w->writeC(op.tlMacro.speed); + w->writeC(op.dt2Macro.speed); + w->writeC(op.rsMacro.speed); + w->writeC(op.dtMacro.speed); + w->writeC(op.d2rMacro.speed); + w->writeC(op.ssgMacro.speed); + w->writeC(op.damMacro.speed); + w->writeC(op.dvbMacro.speed); + w->writeC(op.egtMacro.speed); + w->writeC(op.kslMacro.speed); + w->writeC(op.susMacro.speed); + w->writeC(op.vibMacro.speed); + w->writeC(op.wsMacro.speed); + w->writeC(op.ksrMacro.speed); + + w->writeC(op.amMacro.delay); + w->writeC(op.arMacro.delay); + w->writeC(op.drMacro.delay); + w->writeC(op.multMacro.delay); + w->writeC(op.rrMacro.delay); + w->writeC(op.slMacro.delay); + w->writeC(op.tlMacro.delay); + w->writeC(op.dt2Macro.delay); + w->writeC(op.rsMacro.delay); + w->writeC(op.dtMacro.delay); + w->writeC(op.d2rMacro.delay); + w->writeC(op.ssgMacro.delay); + w->writeC(op.damMacro.delay); + w->writeC(op.dvbMacro.delay); + w->writeC(op.egtMacro.delay); + w->writeC(op.kslMacro.delay); + w->writeC(op.susMacro.delay); + w->writeC(op.vibMacro.delay); + w->writeC(op.wsMacro.delay); + w->writeC(op.ksrMacro.delay); + } + + blockEndSeek=w->tell(); + w->seek(blockStartSeek,SEEK_SET); + w->writeI(blockEndSeek-blockStartSeek-4); + w->seek(0,SEEK_END); } +#define READ_MACRO_VALS(x,y) \ + for (int macroValPos=0; macroValPos=114) { + op.enable=reader.readC(); + } else { + reader.readC(); + } + // reserved - for (int k=0; k<12; k++) reader.readC(); + for (int k=0; k<11; k++) reader.readC(); } // GB @@ -647,10 +796,10 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { int oldVolHeight=reader.readC(); int oldDutyHeight=reader.readC(); reader.readC(); // oldWaveHeight - reader.read(std.volMacro.val,4*std.volMacro.len); - reader.read(std.arpMacro.val,4*std.arpMacro.len); - reader.read(std.dutyMacro.val,4*std.dutyMacro.len); - reader.read(std.waveMacro.val,4*std.waveMacro.len); + READ_MACRO_VALS(std.volMacro.val,std.volMacro.len); + READ_MACRO_VALS(std.arpMacro.val,std.arpMacro.len); + READ_MACRO_VALS(std.dutyMacro.val,std.dutyMacro.len); + READ_MACRO_VALS(std.waveMacro.val,std.waveMacro.len); if (version<31) { if (!std.arpMacro.mode) for (int j=0; j=17) { - reader.read(std.pitchMacro.val,4*std.pitchMacro.len); - reader.read(std.ex1Macro.val,4*std.ex1Macro.len); - reader.read(std.ex2Macro.val,4*std.ex2Macro.len); - reader.read(std.ex3Macro.val,4*std.ex3Macro.len); + READ_MACRO_VALS(std.pitchMacro.val,std.pitchMacro.len); + READ_MACRO_VALS(std.ex1Macro.val,std.ex1Macro.len); + READ_MACRO_VALS(std.ex2Macro.val,std.ex2Macro.len); + READ_MACRO_VALS(std.ex3Macro.val,std.ex3Macro.len); } else { if (type==DIV_INS_STD) { if (oldVolHeight==31) { @@ -703,10 +852,10 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { std.fmsMacro.open=reader.readC(); std.amsMacro.open=reader.readC(); - reader.read(std.algMacro.val,4*std.algMacro.len); - reader.read(std.fbMacro.val,4*std.fbMacro.len); - reader.read(std.fmsMacro.val,4*std.fmsMacro.len); - reader.read(std.amsMacro.val,4*std.amsMacro.len); + READ_MACRO_VALS(std.algMacro.val,std.algMacro.len); + READ_MACRO_VALS(std.fbMacro.val,std.fbMacro.len); + READ_MACRO_VALS(std.fmsMacro.val,std.fmsMacro.len); + READ_MACRO_VALS(std.amsMacro.val,std.amsMacro.len); for (int i=0; i<4; i++) { DivInstrumentSTD::OpMacro& op=std.opMacros[i]; @@ -909,23 +1058,27 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { // clear noise macro if PCE instrument and version<63 if (version<63 && type==DIV_INS_PCE) { std.dutyMacro.len=0; - std.dutyMacro.loop=-1; - std.dutyMacro.rel=-1; + std.dutyMacro.loop=255; + std.dutyMacro.rel=255; } // clear wave macro if OPLL instrument and version<70 if (version<70 && type==DIV_INS_OPLL) { std.waveMacro.len=0; - std.waveMacro.loop=-1; - std.waveMacro.rel=-1; + std.waveMacro.loop=255; + std.waveMacro.rel=255; } // sample map if (version>=67) { amiga.useNoteMap=reader.readC(); if (amiga.useNoteMap) { - reader.read(amiga.noteFreq,120*sizeof(unsigned int)); - reader.read(amiga.noteMap,120*sizeof(short)); + for (int note=0; note<120; note++) { + amiga.noteMap[note].freq=reader.readI(); + } + for (int note=0; note<120; note++) { + amiga.noteMap[note].map=reader.readS(); + } } } @@ -976,14 +1129,14 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { std.ex7Macro.open=reader.readC(); std.ex8Macro.open=reader.readC(); - reader.read(std.panLMacro.val,4*std.panLMacro.len); - reader.read(std.panRMacro.val,4*std.panRMacro.len); - reader.read(std.phaseResetMacro.val,4*std.phaseResetMacro.len); - reader.read(std.ex4Macro.val,4*std.ex4Macro.len); - reader.read(std.ex5Macro.val,4*std.ex5Macro.len); - reader.read(std.ex6Macro.val,4*std.ex6Macro.len); - reader.read(std.ex7Macro.val,4*std.ex7Macro.len); - reader.read(std.ex8Macro.val,4*std.ex8Macro.len); + READ_MACRO_VALS(std.panLMacro.val,std.panLMacro.len); + READ_MACRO_VALS(std.panRMacro.val,std.panRMacro.len); + READ_MACRO_VALS(std.phaseResetMacro.val,std.phaseResetMacro.len); + READ_MACRO_VALS(std.ex4Macro.val,std.ex4Macro.len); + READ_MACRO_VALS(std.ex5Macro.val,std.ex5Macro.len); + READ_MACRO_VALS(std.ex6Macro.val,std.ex6Macro.len); + READ_MACRO_VALS(std.ex7Macro.val,std.ex7Macro.len); + READ_MACRO_VALS(std.ex8Macro.val,std.ex8Macro.len); // FDS fds.modSpeed=reader.readI(); @@ -1059,6 +1212,157 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { for (int k=0; k<23; k++) reader.readC(); } + // Sound Unit + if (version>=104) { + su.useSample=reader.readC(); + su.switchRoles=reader.readC(); + } + + // GB hardware sequence + if (version>=105) { + gb.hwSeqLen=reader.readC(); + for (int i=0; i=106) { + gb.softEnv=reader.readC(); + gb.alwaysInit=reader.readC(); + } + + // ES5506 + if (version>=107) { + es5506.filter.mode=(DivInstrumentES5506::Filter::FilterMode)reader.readC(); + es5506.filter.k1=reader.readS(); + es5506.filter.k2=reader.readS(); + es5506.envelope.ecount=reader.readS(); + es5506.envelope.lVRamp=reader.readC(); + es5506.envelope.rVRamp=reader.readC(); + es5506.envelope.k1Ramp=reader.readC(); + es5506.envelope.k2Ramp=reader.readC(); + es5506.envelope.k1Slow=reader.readC(); + es5506.envelope.k2Slow=reader.readC(); + } + + // SNES + if (version>=109) { + snes.useEnv=reader.readC(); + snes.gainMode=(DivInstrumentSNES::GainMode)reader.readC(); + snes.gain=reader.readC(); + snes.a=reader.readC(); + snes.d=reader.readC(); + snes.s=reader.readC(); + snes.r=reader.readC(); + } + + // macro speed/delay + if (version>=111) { + std.volMacro.speed=reader.readC(); + std.arpMacro.speed=reader.readC(); + std.dutyMacro.speed=reader.readC(); + std.waveMacro.speed=reader.readC(); + std.pitchMacro.speed=reader.readC(); + std.ex1Macro.speed=reader.readC(); + std.ex2Macro.speed=reader.readC(); + std.ex3Macro.speed=reader.readC(); + std.algMacro.speed=reader.readC(); + std.fbMacro.speed=reader.readC(); + std.fmsMacro.speed=reader.readC(); + std.amsMacro.speed=reader.readC(); + std.panLMacro.speed=reader.readC(); + std.panRMacro.speed=reader.readC(); + std.phaseResetMacro.speed=reader.readC(); + std.ex4Macro.speed=reader.readC(); + std.ex5Macro.speed=reader.readC(); + std.ex6Macro.speed=reader.readC(); + std.ex7Macro.speed=reader.readC(); + std.ex8Macro.speed=reader.readC(); + + std.volMacro.delay=reader.readC(); + std.arpMacro.delay=reader.readC(); + std.dutyMacro.delay=reader.readC(); + std.waveMacro.delay=reader.readC(); + std.pitchMacro.delay=reader.readC(); + std.ex1Macro.delay=reader.readC(); + std.ex2Macro.delay=reader.readC(); + std.ex3Macro.delay=reader.readC(); + std.algMacro.delay=reader.readC(); + std.fbMacro.delay=reader.readC(); + std.fmsMacro.delay=reader.readC(); + std.amsMacro.delay=reader.readC(); + std.panLMacro.delay=reader.readC(); + std.panRMacro.delay=reader.readC(); + std.phaseResetMacro.delay=reader.readC(); + std.ex4Macro.delay=reader.readC(); + std.ex5Macro.delay=reader.readC(); + std.ex6Macro.delay=reader.readC(); + std.ex7Macro.delay=reader.readC(); + std.ex8Macro.delay=reader.readC(); + + // op macro speed/delay + for (int i=0; i<4; i++) { + DivInstrumentSTD::OpMacro& op=std.opMacros[i]; + + op.amMacro.speed=reader.readC(); + op.arMacro.speed=reader.readC(); + op.drMacro.speed=reader.readC(); + op.multMacro.speed=reader.readC(); + op.rrMacro.speed=reader.readC(); + op.slMacro.speed=reader.readC(); + op.tlMacro.speed=reader.readC(); + op.dt2Macro.speed=reader.readC(); + op.rsMacro.speed=reader.readC(); + op.dtMacro.speed=reader.readC(); + op.d2rMacro.speed=reader.readC(); + op.ssgMacro.speed=reader.readC(); + op.damMacro.speed=reader.readC(); + op.dvbMacro.speed=reader.readC(); + op.egtMacro.speed=reader.readC(); + op.kslMacro.speed=reader.readC(); + op.susMacro.speed=reader.readC(); + op.vibMacro.speed=reader.readC(); + op.wsMacro.speed=reader.readC(); + op.ksrMacro.speed=reader.readC(); + + op.amMacro.delay=reader.readC(); + op.arMacro.delay=reader.readC(); + op.drMacro.delay=reader.readC(); + op.multMacro.delay=reader.readC(); + op.rrMacro.delay=reader.readC(); + op.slMacro.delay=reader.readC(); + op.tlMacro.delay=reader.readC(); + op.dt2Macro.delay=reader.readC(); + op.rsMacro.delay=reader.readC(); + op.dtMacro.delay=reader.readC(); + op.d2rMacro.delay=reader.readC(); + op.ssgMacro.delay=reader.readC(); + op.damMacro.delay=reader.readC(); + op.dvbMacro.delay=reader.readC(); + op.egtMacro.delay=reader.readC(); + op.kslMacro.delay=reader.readC(); + op.susMacro.delay=reader.readC(); + op.vibMacro.delay=reader.readC(); + op.wsMacro.delay=reader.readC(); + op.ksrMacro.delay=reader.readC(); + } + } + + // old arp macro format + if (version<112) { + if (std.arpMacro.mode) { + std.arpMacro.mode=0; + for (int i=0; i=std.arpMacro.len || (std.arpMacro.rel>std.arpMacro.loop && std.arpMacro.relfinish(); return true; } + +bool DivInstrument::saveDMP(const char* path) { + SafeWriter* w=new SafeWriter(); + w->init(); + + // write version + w->writeC(11); + + // guess the system + switch (type) { + case DIV_INS_FM: + // we can't tell between Genesis, Neo Geo and Arcade ins type yet + w->writeC(0x02); + w->writeC(1); + break; + case DIV_INS_STD: + // we can't tell between SMS and NES ins type yet + w->writeC(0x03); + w->writeC(0); + break; + case DIV_INS_GB: + w->writeC(0x04); + w->writeC(0); + break; + case DIV_INS_C64: + w->writeC(0x07); + w->writeC(0); + break; + case DIV_INS_PCE: + w->writeC(0x06); + w->writeC(0); + break; + case DIV_INS_OPLL: + // ??? + w->writeC(0x13); + w->writeC(1); + break; + case DIV_INS_OPZ: + // data will be lost + w->writeC(0x08); + w->writeC(1); + break; + case DIV_INS_FDS: + // ??? + w->writeC(0x06); + w->writeC(0); + break; + default: + // not supported by .dmp + w->finish(); + return false; + } + + if (type==DIV_INS_FM || type==DIV_INS_OPLL || type==DIV_INS_OPZ) { + w->writeC(fm.fms); + w->writeC(fm.fb); + w->writeC(fm.alg); + w->writeC(fm.ams); + + // TODO: OPLL params + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=fm.op[i]; + w->writeC(op.mult); + w->writeC(op.tl); + w->writeC(op.ar); + w->writeC(op.dr); + w->writeC(op.sl); + w->writeC(op.rr); + w->writeC(op.am); + w->writeC(op.rs); + w->writeC(op.dt|(op.dt2<<4)); + w->writeC(op.d2r); + w->writeC(op.ssgEnv); + } + } else { + if (type!=DIV_INS_GB) { + w->writeC(std.volMacro.len); + for (int i=0; iwriteI(std.volMacro.val[i]); + } + if (std.volMacro.len>0) w->writeC(std.volMacro.loop); + } + + w->writeC(std.arpMacro.len); + for (int i=0; iwriteI(std.arpMacro.val[i]+12); + } + if (std.arpMacro.len>0) w->writeC(std.arpMacro.loop); + w->writeC(std.arpMacro.mode); + + w->writeC(std.dutyMacro.len); + for (int i=0; iwriteI(std.dutyMacro.val[i]+12); + } + if (std.dutyMacro.len>0) w->writeC(std.dutyMacro.loop); + + w->writeC(std.waveMacro.len); + for (int i=0; iwriteI(std.waveMacro.val[i]+12); + } + if (std.waveMacro.len>0) w->writeC(std.waveMacro.loop); + + if (type==DIV_INS_C64) { + w->writeC(c64.triOn); + w->writeC(c64.sawOn); + w->writeC(c64.pulseOn); + w->writeC(c64.noiseOn); + w->writeC(c64.a); + w->writeC(c64.d); + w->writeC(c64.s); + w->writeC(c64.r); + w->writeC((c64.duty*100)/4095); + w->writeC(c64.ringMod); + w->writeC(c64.oscSync); + w->writeC(c64.toFilter); + w->writeC(c64.volIsCutoff); + w->writeC(c64.initFilter); + w->writeC(c64.res); + w->writeC((c64.cut*100)/2047); + w->writeC(c64.hp); + w->writeC(c64.lp); + w->writeC(c64.bp); + w->writeC(c64.ch3off); + } + if (type==DIV_INS_GB) { + w->writeC(gb.envVol); + w->writeC(gb.envDir); + w->writeC(gb.envLen); + w->writeC(gb.soundLen); + } + } + + FILE* outFile=ps_fopen(path,"wb"); + if (outFile==NULL) { + logE("could not save instrument: %s!",strerror(errno)); + w->finish(); + return false; + } + if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { + logW("did not write entire instrument!"); + } + fclose(outFile); + w->finish(); + return true; +} diff --git a/src/engine/instrument.h b/src/engine/instrument.h index c4af6f04..e5372933 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -165,21 +165,20 @@ struct DivInstrumentMacro { int val[256]; unsigned int mode; bool open; - unsigned char len; - signed char loop; - signed char rel; + unsigned char len, delay, speed, loop, rel; // the following variables are used by the GUI and not saved in the file int vScroll, vZoom; - explicit DivInstrumentMacro(const String& n, bool initOpen=false): name(n), mode(0), open(initOpen), len(0), - loop(-1), - rel(-1), + delay(0), + speed(1), + loop(255), + rel(255), vScroll(0), vZoom(-1) { memset(val,0,256*sizeof(int)); @@ -261,12 +260,32 @@ struct DivInstrumentSTD { }; struct DivInstrumentGB { - unsigned char envVol, envDir, envLen, soundLen; + unsigned char envVol, envDir, envLen, soundLen, hwSeqLen; + bool softEnv, alwaysInit; + enum HWSeqCommands: unsigned char { + DIV_GB_HWCMD_ENVELOPE=0, + DIV_GB_HWCMD_SWEEP, + DIV_GB_HWCMD_WAIT, + DIV_GB_HWCMD_WAIT_REL, + DIV_GB_HWCMD_LOOP, + DIV_GB_HWCMD_LOOP_REL, + + DIV_GB_HWCMD_MAX + }; + struct HWSeqCommand { + unsigned char cmd; + unsigned short data; + } hwSeq[256]; DivInstrumentGB(): envVol(15), envDir(0), envLen(2), - soundLen(64) {} + soundLen(64), + hwSeqLen(0), + softEnv(false), + alwaysInit(false) { + memset(hwSeq,0,256*sizeof(int)); + } }; struct DivInstrumentC64 { @@ -306,12 +325,18 @@ struct DivInstrumentC64 { }; struct DivInstrumentAmiga { + struct SampleMap { + int freq; + short map; + SampleMap(int f=0, short m=-1): + freq(f), + map(m) {} + }; short initSample; bool useNoteMap; bool useWave; unsigned char waveLen; - int noteFreq[120]; - short noteMap[120]; + SampleMap noteMap[120]; /** * get the sample at specified note. @@ -321,7 +346,7 @@ struct DivInstrumentAmiga { if (useNoteMap) { if (note<0) note=0; if (note>119) note=119; - return noteMap[note]; + return noteMap[note].map; } return initSample; } @@ -334,7 +359,7 @@ struct DivInstrumentAmiga { if (useNoteMap) { if (note<0) note=0; if (note>119) note=119; - return noteFreq[note]; + return noteMap[note].freq; } return -1; } @@ -344,8 +369,9 @@ struct DivInstrumentAmiga { useNoteMap(false), useWave(false), waveLen(31) { - memset(noteMap,-1,120*sizeof(short)); - memset(noteFreq,0,120*sizeof(int)); + for (SampleMap& elem: noteMap) { + elem=SampleMap(); + } } }; @@ -430,6 +456,72 @@ struct DivInstrumentWaveSynth { param4(0) {} }; +struct DivInstrumentSoundUnit { + bool useSample; + bool switchRoles; + DivInstrumentSoundUnit(): + useSample(false), + switchRoles(false) {} +}; + +struct DivInstrumentES5506 { + struct Filter { + enum FilterMode: unsigned char { // filter mode for pole 4,3 + FILTER_MODE_HPK2_HPK2=0, + FILTER_MODE_HPK2_LPK1, + FILTER_MODE_LPK2_LPK2, + FILTER_MODE_LPK2_LPK1, + }; + FilterMode mode; + unsigned short k1, k2; + Filter(): + mode(FILTER_MODE_LPK2_LPK1), + k1(0xffff), + k2(0xffff) {} + }; + struct Envelope { + unsigned short ecount; + signed char lVRamp, rVRamp; + signed char k1Ramp, k2Ramp; + bool k1Slow, k2Slow; + Envelope(): + ecount(0), + lVRamp(0), + rVRamp(0), + k1Ramp(0), + k2Ramp(0), + k1Slow(false), + k2Slow(false) {} + }; + Filter filter; + Envelope envelope; + DivInstrumentES5506(): + filter(Filter()), + envelope(Envelope()) {} +}; + +struct DivInstrumentSNES { + enum GainMode: unsigned char { + GAIN_MODE_DIRECT=0, + GAIN_MODE_DEC_LINEAR=4, + GAIN_MODE_DEC_LOG=5, + GAIN_MODE_INC_LINEAR=6, + GAIN_MODE_INC_INVLOG=7 + }; + bool useEnv; + GainMode gainMode; + unsigned char gain; + unsigned char a, d, s, r; + DivInstrumentSNES(): + useEnv(true), + gainMode(GAIN_MODE_DIRECT), + gain(127), + a(15), + d(7), + s(7), + r(0) {} +}; + struct DivInstrument { String name; bool mode; @@ -443,6 +535,9 @@ struct DivInstrument { DivInstrumentFDS fds; DivInstrumentMultiPCM multipcm; DivInstrumentWaveSynth ws; + DivInstrumentSoundUnit su; + DivInstrumentES5506 es5506; + DivInstrumentSNES snes; /** * save the instrument to a SafeWriter. @@ -464,6 +559,13 @@ struct DivInstrument { * @return whether it was successful. */ bool save(const char* path); + + /** + * save this instrument to a file in .dmp format. + * @param path file path. + * @return whether it was successful. + */ + bool saveDMP(const char* path); DivInstrument(): name(""), type(DIV_INS_FM) { diff --git a/src/engine/macroInt.cpp b/src/engine/macroInt.cpp index 06f04682..36850818 100644 --- a/src/engine/macroInt.cpp +++ b/src/engine/macroInt.cpp @@ -32,6 +32,18 @@ void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tic had=false; return; } + if (delay>0) { + delay--; + return; + } + if (began && source.delay>0) { + delay=source.delay; + } else { + delay=source.speed-1; + } + if (began) { + began=false; + } if (finished) { finished=false; } @@ -41,16 +53,17 @@ void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tic actualHad=has; had=actualHad; if (has) { + lastPos=pos; val=source.val[pos++]; - if (source.rel>=0 && pos>source.rel && !released) { - if (source.loop=0 && source.loopsource.rel && !released) { + if (source.loop=source.len) { - if (source.loop=0 && (source.loop>=source.rel || source.rel>=source.len)) { + if (source.loop=source.rel || source.rel>=source.len)) { pos=source.loop; } else if (linger) { pos--; @@ -239,7 +252,7 @@ void DivMacroInt::init(DivInstrument* which) { for (size_t i=0; iprepare(*macroSource[i],e); - hasRelease=(macroSource[i]->rel>=0 && macroSource[i]->rellen); + hasRelease=(macroSource[i]->rellen); } else { hasRelease=false; } @@ -251,3 +264,110 @@ void DivMacroInt::notifyInsDeletion(DivInstrument* which) { init(NULL); } } + +// randomly-generated +constexpr unsigned int hashTable[256]={ + 0x0718657, 0xe904eb33, 0x14b2da2b, 0x0ef67ca9, + 0x0f0559a, 0x4142065a, 0x4d9ab4ba, 0x3cdd601a, + 0x6635aca, 0x2c41ab72, 0xf98e8d31, 0x1003ee63, + 0x3fd9fb5, 0x30734d16, 0xe8964431, 0x29bb9b79, + 0x817f580, 0xfe083b9e, 0x974b5e85, 0x3b5729c2, + 0x2afea96, 0xf1573b4b, 0x308a1024, 0xaa94b92d, + 0x693fa93, 0x547ba3da, 0xac4f206c, 0x93f72ea9, + 0xcc44001, 0x37e27670, 0xf35a63d0, 0xd1cdbb92, + 0x7c7ee24, 0xfa267ee9, 0xf9cd9956, 0x6a6768d4, + 0x9e6a108, 0xf6ca4bd0, 0xa53cba9f, 0x526a523a, + 0xf46f0c8, 0xf131bd4c, 0x82800d48, 0xabff9214, + 0x40eabd4, 0xea0ef8f7, 0xdc3968d6, 0x54c3cb63, + 0x8855023, 0xaab73861, 0xff0bea2c, 0x139b9765, + 0x4a21279, 0x6b2aa29a, 0xf147cc3f, 0xc42edc1a, + 0xfe2f86f, 0x6d352047, 0xd3cac3e4, 0x35e5c389, + 0xe923727, 0x12fe3b32, 0x204295c5, 0x254a8b7a, + 0xc1d995d, 0x26a512d2, 0xa3e34033, 0x9a968df0, + 0x53447ed, 0x36cf4077, 0x189b03a7, 0x558790e8, + 0x01f921a, 0x840f260c, 0x93dd2b86, 0x12f69cb0, + 0x117d93a, 0xcb2cbc2b, 0xd41e3aed, 0x5ff6ec75, + 0x607290d, 0xd41adb92, 0x64f94ba7, 0xaff720f7, + 0x6bf1d5d, 0xc8e36c6d, 0x7095bab5, 0xdfbf7b0d, + 0x01ddeea, 0xe8f262da, 0xf589512f, 0xc2ecac5d, + 0xbe29d98, 0xff8b5a2e, 0x18e7279e, 0x6ad24dcb, + 0x2b3b9b1, 0x6f5227d8, 0x076d7553, 0x6c5856e2, + 0x995f655, 0xe9fcf5a6, 0x83671b70, 0xaf3aed1e, + 0xac340f0, 0x5c7008b4, 0x14651282, 0x8bf855b9, + 0x4a933af, 0x829b87f1, 0x9a673070, 0xb19da64f, + 0x77d8f36, 0x584c9fdc, 0xa9e52c0d, 0x6da5e13d, + 0xae1051f, 0xe85e976f, 0xfeac2d9a, 0x19c46754, + 0x1cba6f3, 0xaf21bc31, 0x16b6a8d4, 0xe08b0fdb, + 0x97e6e54, 0x5da499ae, 0xab472e19, 0xc2491f2e, + 0xc08c563, 0xe91b131b, 0xc8e22451, 0x6995c8fe, + 0x7042718, 0x01043738, 0xc7d88b28, 0x2d9f330f, + 0x4b3aae5, 0xf1e705ba, 0xc5b8ee59, 0xa8ba4e8f, + 0x55f65a2, 0xa1899e41, 0x296243c8, 0x1e502bf2, + 0x20080de, 0x841d2239, 0x37b082af, 0xbdd7f7da, + 0x4075090, 0x1dc7dc49, 0x5cd3c69a, 0x7fb13b62, + 0xb382bf1, 0xa0cfbc2f, 0x9eca4dc1, 0xb9355453, + 0x5d0dd24, 0x834f4d8e, 0xe9b136b2, 0xe7b8738d, + 0x1c91d41, 0x8cb3ddb5, 0xdc600590, 0x607cff55, + 0x2ca7675, 0x4622a8e4, 0x9340e414, 0xcb44928a, + 0xa9e791c, 0x68849920, 0xc5b5fcd8, 0xbc352269, + 0x3ab13cf, 0xaa3cbbd0, 0x1abacc64, 0x623b5b49, + 0xcc8c4c3, 0x3c8f2f70, 0x3e584a28, 0x9316d24d, + 0xfe315a2, 0x10f0ba7a, 0xed15a523, 0x4f987369, + 0x7aa4a4a, 0x90eaf98f, 0xcf0af610, 0x1b38f4e7, + 0x19df72d, 0xd8306808, 0xd54e25ac, 0x76b79c6d, + 0x58110cf, 0x06a3e5f2, 0x873a6039, 0xf52684e3, + 0xecf39c3, 0x7cbb2759, 0xe280d361, 0x91e8471a, + 0xa67cdd3, 0x17cac3be, 0xfc9eff1f, 0x71abdf49, + 0x6168624, 0xb68f86f7, 0x67a8e72a, 0xe746911d, + 0xca48fd7, 0x8f3cc436, 0x3a3851a8, 0x30a7e26e, + 0xca49308, 0xb598ef74, 0x49ef167a, 0xa9e17632, + 0x0f7308a, 0xf156efed, 0xcf799645, 0xbae4b85a, + 0xecba3fe, 0xd97f861d, 0xc164af62, 0xb1aca42f, + 0xf249576, 0x83d1bf4e, 0x2f486a9c, 0xd3b53cc2, + 0x17d7c26, 0xd95ddae1, 0x76c1a2f5, 0xf8af6782, + 0xdbaece4, 0x010b2b53, 0x049be200, 0xd9fd0d1a, + 0x37d7e6c, 0x5b848651, 0x203c98c7, 0x669681b0, + 0x683086f, 0xdd0ee8ab, 0x5dbe008b, 0xe5d0690d, + 0x23dd758, 0x6b34acbc, 0x4b2b3e65, 0xcc7b56c1, + 0x196b0a0, 0x7b065105, 0xb731b01a, 0xd37daa16, + 0xf77816b, 0x3c9fa546, 0x81dfadb8, 0x39b1fb8b +}; + +constexpr unsigned int NAME_HASH(const char* name) { + unsigned int nameHash=0xffffffff; + for (const char* i=name; *i; i++) { + nameHash=(nameHash>>8)^hashTable[(unsigned char)*i]; + } + return nameHash; +} + +#define CONSIDER(x) case NAME_HASH(#x): return &x; break; + +DivMacroStruct* DivMacroInt::structByName(const String& name) { + unsigned int hash=NAME_HASH(name.c_str()); + + switch (hash) { + CONSIDER(vol) + CONSIDER(arp) + CONSIDER(duty) + CONSIDER(wave) + CONSIDER(pitch) + CONSIDER(ex1) + CONSIDER(ex2) + CONSIDER(ex3) + CONSIDER(alg) + CONSIDER(fb) + CONSIDER(fms) + CONSIDER(ams) + CONSIDER(panL) + CONSIDER(panR) + CONSIDER(phaseReset) + CONSIDER(ex4) + CONSIDER(ex5) + CONSIDER(ex6) + CONSIDER(ex7) + CONSIDER(ex8) + } + + return NULL; +} diff --git a/src/engine/macroInt.h b/src/engine/macroInt.h index 83962555..5208dc54 100644 --- a/src/engine/macroInt.h +++ b/src/engine/macroInt.h @@ -25,21 +25,24 @@ class DivEngine; struct DivMacroStruct { - int pos; + int pos, lastPos, delay; int val; - bool has, had, actualHad, finished, will, linger; + bool has, had, actualHad, finished, will, linger, began; unsigned int mode; void doMacro(DivInstrumentMacro& source, bool released, bool tick); void init() { - pos=mode=0; + pos=lastPos=mode=delay=0; has=had=actualHad=will=false; linger=false; + began=true; // TODO: test whether this breaks anything? val=0; } void prepare(DivInstrumentMacro& source, DivEngine* e); DivMacroStruct(): pos(0), + lastPos(0), + delay(0), val(0), has(false), had(false), @@ -47,6 +50,7 @@ struct DivMacroStruct { finished(false), will(false), linger(false), + began(true), mode(0) {} }; @@ -127,6 +131,13 @@ class DivMacroInt { */ void notifyInsDeletion(DivInstrument* which); + /** + * get DivMacroStruct by macro name. + * @param which the macro name. + * @return a DivMacroStruct, or NULL if none found. + */ + DivMacroStruct* structByName(const String& name); + DivMacroInt(): e(NULL), ins(NULL), diff --git a/src/engine/pattern.cpp b/src/engine/pattern.cpp index 0a561376..77255e08 100644 --- a/src/engine/pattern.cpp +++ b/src/engine/pattern.cpp @@ -18,6 +18,7 @@ */ #include "engine.h" +#include "../ta-log.h" static DivPattern emptyPat; @@ -40,6 +41,44 @@ DivPattern* DivChannelData::getPattern(int index, bool create) { return data[index]; } +std::vector> DivChannelData::optimize() { + std::vector> ret; + for (int i=0; i<256; i++) { + if (data[i]!=NULL) { + // compare + for (int j=0; j<256; j++) { + if (j==i) continue; + if (data[j]==NULL) continue; + if (memcmp(data[i]->data,data[j]->data,256*32*sizeof(short))==0) { + delete data[j]; + data[j]=NULL; + logV("%d == %d",i,j); + ret.push_back(std::pair(j,i)); + } + } + } + } + return ret; +} + +std::vector> DivChannelData::rearrange() { + std::vector> ret; + for (int i=0; i<256; i++) { + if (data[i]==NULL) { + for (int j=i; j<256; j++) { + if (data[j]!=NULL) { + data[i]=data[j]; + data[j]=NULL; + logV("%d -> %d",j,i); + ret.push_back(std::pair(j,i)); + if (++i>=256) break; + } + } + } + } + return ret; +} + void DivChannelData::wipePatterns() { for (int i=0; i<256; i++) { if (data[i]!=NULL) { @@ -54,81 +93,6 @@ void DivPattern::copyOn(DivPattern* dest) { memcpy(dest->data,data,sizeof(data)); } -SafeReader* DivPattern::compile(int len, int fxRows) { - SafeWriter w; - w.init(); - short lastNote, lastOctave, lastInstr, lastVolume, lastEffect[8], lastEffectVal[8]; - unsigned char rows=0; - - lastNote=0; - lastOctave=0; - lastInstr=-1; - lastVolume=-1; - memset(lastEffect,-1,8*sizeof(short)); - memset(lastEffectVal,-1,8*sizeof(short)); - - for (int i=0; i struct DivPattern { String name; @@ -28,14 +29,6 @@ struct DivPattern { * @param dest the destination pattern. */ void copyOn(DivPattern* dest); - - /** - * don't use yet! - * @param len the pattern length - * @param fxRows number of effect ...columns - * @return a SafeReader. - */ - SafeReader* compile(int len=256, int fxRows=1); DivPattern(); }; @@ -59,6 +52,20 @@ struct DivChannelData { */ DivPattern* getPattern(int index, bool create); + /** + * optimize pattern data. + * not thread-safe! use a mutex! + * @return a list of From -> To pairs + */ + std::vector> optimize(); + + /** + * re-arrange NULLs. + * not thread-safe! use a mutex! + * @return a list of From -> To pairs + */ + std::vector> rearrange(); + /** * destroy all patterns on this DivChannelData. */ diff --git a/src/engine/platform/abstract.cpp b/src/engine/platform/abstract.cpp index 54366e1b..358e54aa 100644 --- a/src/engine/platform/abstract.cpp +++ b/src/engine/platform/abstract.cpp @@ -90,8 +90,8 @@ bool DivDispatch::getDCOffRequired() { return false; } -const char* DivDispatch::getEffectName(unsigned char effect) { - return NULL; +bool DivDispatch::getWantPreNote() { + return false; } void DivDispatch::setFlags(unsigned int flags) { diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 8037feaf..ed71d76a 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -64,21 +64,6 @@ const char** DivPlatformAmiga::getRegisterSheet() { return regCheatSheetAmiga; } -const char* DivPlatformAmiga::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Toggle filter (0 disables; 1 enables)"; - break; - case 0x11: - return "11xx: Toggle AM with next channel"; - break; - case 0x12: - return "12xx: Toggle period modulation with next channel"; - break; - } - return NULL; -} - #define writeAudDat(x) \ chan[i].audDat=x; \ if (i<3 && chan[i].useV) { \ @@ -114,12 +99,10 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le if (chan[i].audPossamples) { writeAudDat(s->data8[chan[i].audPos++]); } - if (chan[i].audPos>=s->samples || chan[i].audPos>=131071) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - chan[i].audPos=s->loopStart; - } else { - chan[i].sample=-1; - } + if (s->isLoopable() && chan[i].audPos>=MIN(131071,s->getEndPosition())) { + chan[i].audPos=s->loopStart; + } else if (chan[i].audPos>=MIN(131071,s->samples)) { + chan[i].sample=-1; } } else { chan[i].sample=-1; @@ -180,19 +163,9 @@ void DivPlatformAmiga::tick(bool sysTick) { } } if (chan[i].std.arp.had) { - if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(chan[i].std.arp.val)); - } else { - chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(chan[i].note+chan[i].std.arp.val)); - } - } + // TODO: why the off mult? this may be a bug! + chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(parent->calcArp(chan[i].note,chan[i].std.arp.val))); chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(chan[i].note)); - chan[i].freqChanged=true; - } } if (chan[i].useWave && chan[i].std.wave.had) { if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { @@ -355,6 +328,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_SAMPLE_POS: diff --git a/src/engine/platform/amiga.h b/src/engine/platform/amiga.h index e7372e63..59617ec3 100644 --- a/src/engine/platform/amiga.h +++ b/src/engine/platform/amiga.h @@ -105,7 +105,6 @@ class DivPlatformAmiga: public DivDispatch { void notifyWaveChange(int wave); void notifyInsDeletion(void* ins); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); }; diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 87c402e0..6e925c9a 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -22,38 +22,6 @@ #include #include -#include "fmshared_OPM.h" - -static unsigned short chanOffs[8]={ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 -}; -static unsigned short opOffs[4]={ - 0x00, 0x08, 0x10, 0x18 -}; -static bool isOutput[8][4]={ - // 1 3 2 4 - {false,false,false,true}, - {false,false,false,true}, - {false,false,false,true}, - {false,false,false,true}, - {false,false,true ,true}, - {false,true ,true ,true}, - {false,true ,true ,true}, - {true ,true ,true ,true}, -}; -static unsigned char dtTable[8]={ - 7,6,5,0,1,2,3,4 -}; - -static int orderedOps[4]={ - 0,2,1,3 -}; - -#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} -#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } - -#define NOTE_LINEAR(x) (((x)<<6)+baseFreqOff+log2(parent->song.tuning/440.0)*12.0*64.0) - const char* regCheatSheetOPM[]={ "Test", "00", "NoteCtl", "08", @@ -82,111 +50,6 @@ const char** DivPlatformArcade::getRegisterSheet() { return regCheatSheetOPM; } -const char* DivPlatformArcade::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Set noise frequency (xx: value; 0 disables noise)"; - break; - case 0x11: - return "11xx: Set feedback (0 to 7)"; - break; - case 0x12: - return "12xx: Set level of operator 1 (0 highest, 7F lowest)"; - break; - case 0x13: - return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; - break; - case 0x14: - return "14xx: Set level of operator 3 (0 highest, 7F lowest)"; - break; - case 0x15: - return "15xx: Set level of operator 4 (0 highest, 7F lowest)"; - break; - case 0x16: - return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; - break; - case 0x17: - return "17xx: Set LFO speed"; - break; - case 0x18: - return "18xx: Set LFO waveform (0 saw, 1 square, 2 triangle, 3 noise)"; - break; - case 0x19: - return "19xx: Set attack of all operators (0 to 1F)"; - break; - case 0x1a: - return "1Axx: Set attack of operator 1 (0 to 1F)"; - break; - case 0x1b: - return "1Bxx: Set attack of operator 2 (0 to 1F)"; - break; - case 0x1c: - return "1Cxx: Set attack of operator 3 (0 to 1F)"; - break; - case 0x1d: - return "1Dxx: Set attack of operator 4 (0 to 1F)"; - break; - case 0x1e: - return "1Exx: Set AM depth (0 to 7F)"; - break; - case 0x1f: - return "1Fxx: Set PM depth (0 to 7F)"; - break; - case 0x30: - return "30xx: Toggle hard envelope reset on new notes"; - break; - case 0x50: - return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; - break; - case 0x51: - return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; - break; - case 0x52: - return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; - break; - case 0x53: - return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)"; - break; - case 0x54: - return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)"; - break; - case 0x55: - return "55xy: Set detune 2 (x: operator from 1 to 4 (0 for all ops); y: detune from 0 to 3)"; - break; - case 0x56: - return "56xx: Set decay of all operators (0 to 1F)"; - break; - case 0x57: - return "57xx: Set decay of operator 1 (0 to 1F)"; - break; - case 0x58: - return "58xx: Set decay of operator 2 (0 to 1F)"; - break; - case 0x59: - return "59xx: Set decay of operator 3 (0 to 1F)"; - break; - case 0x5a: - return "5Axx: Set decay of operator 4 (0 to 1F)"; - break; - case 0x5b: - return "5Bxx: Set decay 2 of all operators (0 to 1F)"; - break; - case 0x5c: - return "5Cxx: Set decay 2 of operator 1 (0 to 1F)"; - break; - case 0x5d: - return "5Dxx: Set decay 2 of operator 2 (0 to 1F)"; - break; - case 0x5e: - return "5Exx: Set decay 2 of operator 3 (0 to 1F)"; - break; - case 0x5f: - return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; - break; - } - return NULL; -} - void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) { static int o[2]; @@ -198,13 +61,13 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si OPM_Write(&fm,1,w.val); regPool[w.addr&0xff]=w.val; //printf("write: %x = %.2x\n",w.addr,w.val); - writes.pop(); + writes.pop_front(); } else { OPM_Write(&fm,0,w.addr); w.addrOrVal=true; } } - + OPM_Clock(&fm,NULL,NULL,NULL,NULL); OPM_Clock(&fm,NULL,NULL,NULL,NULL); OPM_Clock(&fm,NULL,NULL,NULL,NULL); @@ -214,13 +77,13 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si for (int i=0; i<8; i++) { oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]; } - + if (o[0]<-32768) o[0]=-32768; if (o[0]>32767) o[0]=32767; if (o[1]<-32768) o[1]=-32768; if (o[1]>32767) o[1]=32767; - + bufL[h]=o[0]; bufR[h]=o[1]; } @@ -239,11 +102,11 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz fm_ymfm->write(0x0+((w.addr>>8)<<1),w.addr); fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val); regPool[w.addr&0xff]=w.val; - writes.pop(); + writes.pop_front(); delay=1; } } - + fm_ymfm->generate(&out_ymfm); for (int i=0; i<8; i++) { @@ -257,7 +120,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz os[1]=out_ymfm.data[1]; if (os[1]<-32768) os[1]=-32768; if (os[1]>32767) os[1]=32767; - + bufL[h]=os[0]; bufR[h]=os[1]; } @@ -298,18 +161,9 @@ void DivPlatformArcade::tick(bool sysTick) { if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_LINEAR(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_LINEAR(chan[i].note+(signed char)chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_LINEAR(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_LINEAR(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.duty.had) { @@ -401,6 +255,10 @@ void DivPlatformArcade::tick(bool sysTick) { chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3)); } + if (chan[i].std.ex4.had && chan[i].active) { + chan[i].opMask=chan[i].std.ex4.val&15; + chan[i].opMaskChanged=true; + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -493,8 +351,9 @@ void DivPlatformArcade::tick(bool sysTick) { immWrite(i+0x30,chan[i].freq<<2); chan[i].freqChanged=false; } - if (chan[i].keyOn) { - immWrite(0x08,0x78|i); + if (chan[i].keyOn || chan[i].opMaskChanged) { + immWrite(0x08,(chan[i].opMask<<3)|i); + chan[i].opMaskChanged=false; chan[i].keyOn=false; } } @@ -516,6 +375,11 @@ int DivPlatformArcade::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; + chan[c.chan].opMask= + (chan[c.chan].state.op[0].enable?1:0)| + (chan[c.chan].state.op[2].enable?2:0)| + (chan[c.chan].state.op[1].enable?4:0)| + (chan[c.chan].state.op[3].enable?8:0); } chan[c.chan].macroInit(ins); @@ -648,6 +512,12 @@ int DivPlatformArcade::dispatch(DivCommand c) { break; } case DIV_CMD_FM_LFO: { + if(c.value==0) { + rWrite(0x01,0x02); + } + else { + rWrite(0x01,0x00); + } rWrite(0x18,c.value); break; } @@ -859,6 +729,7 @@ int DivPlatformArcade::dispatch(DivCommand c) { return 127; break; case DIV_CMD_PRE_PORTA: + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_LINEAR(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_PRE_NOTE: @@ -938,7 +809,7 @@ void DivPlatformArcade::poke(std::vector& wlist) { } void DivPlatformArcade::reset() { - while (!writes.empty()) writes.pop(); + while (!writes.empty()) writes.pop_front(); memset(regPool,0,256); if (useYMFM) { fm_ymfm->reset(); @@ -970,6 +841,8 @@ void DivPlatformArcade::reset() { pmDepth=0x7f; //rWrite(0x18,0x10); + immWrite(0x01,0x02); // LFO Off + immWrite(0x18,0x00); // LFO Freq Off immWrite(0x19,amDepth); immWrite(0x19,0x80|pmDepth); //rWrite(0x1b,0x00); @@ -978,15 +851,20 @@ void DivPlatformArcade::reset() { } void DivPlatformArcade::setFlags(unsigned int flags) { - if (flags==2) { - chipClock=4000000.0; - baseFreqOff=-122; - } else if (flags==1) { - chipClock=COLOR_PAL*4.0/5.0; - baseFreqOff=12; - } else { - chipClock=COLOR_NTSC; - baseFreqOff=0; + switch (flags&0xff) { + default: + case 0: + chipClock=COLOR_NTSC; + baseFreqOff=0; + break; + case 1: + chipClock=COLOR_PAL*4.0/5.0; + baseFreqOff=12; + break; + case 2: + chipClock=4000000.0; + baseFreqOff=-122; + break; } rate=chipClock/64; for (int i=0; i<8; i++) { diff --git a/src/engine/platform/arcade.h b/src/engine/platform/arcade.h index e73c0618..2a9c2b40 100644 --- a/src/engine/platform/arcade.h +++ b/src/engine/platform/arcade.h @@ -19,19 +19,23 @@ #ifndef _ARCADE_H #define _ARCADE_H -#include "../dispatch.h" +#include "fmshared_OPM.h" +#include "../macroInt.h" #include "../instrument.h" #include #include "../../../extern/opm/opm.h" #include "sound/ymfm/ymfm_opm.h" -#include "../macroInt.h" class DivArcadeInterface: public ymfm::ymfm_interface { }; -class DivPlatformArcade: public DivDispatch { +class DivPlatformArcade: public DivPlatformOPM { protected: + const unsigned short chanOffs[8]={ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 + }; + struct Channel { DivInstrumentFM state; DivMacroInt std; @@ -39,9 +43,9 @@ class DivPlatformArcade: public DivDispatch { int freq, baseFreq, pitch, pitch2, note; int ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset, opMaskChanged; int vol, outVol; - unsigned char chVolL, chVolR; + unsigned char chVolL, chVolR, opMask; void macroInit(DivInstrument* which) { std.init(which); pitch2=0; @@ -64,38 +68,27 @@ class DivPlatformArcade: public DivDispatch { portaPause(false), furnacePCM(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(0), chVolL(127), - chVolR(127) {} + chVolR(127), + opMask(15) {} }; Channel chan[8]; DivDispatchOscBuffer* oscBuf[8]; - struct QueuedWrite { - unsigned short addr; - unsigned char val; - bool addrOrVal; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} - }; - std::queue writes; opm_t fm; - int delay, baseFreqOff; + int baseFreqOff; int pcmL, pcmR, pcmCycles; - unsigned char lastBusy; unsigned char amDepth, pmDepth; ymfm::ym2151* fm_ymfm; ymfm::ym2151::output_data out_ymfm; DivArcadeInterface iface; - unsigned char regPool[256]; - bool extMode, useYMFM; bool isMuted[8]; - - short oldWrites[256]; - short pendingWrites[256]; int octave(int freq); int toFreq(int freq); @@ -124,7 +117,6 @@ class DivPlatformArcade: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformArcade(); diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 0bca2766..23f20c2a 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -27,7 +27,7 @@ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(regRemap(a),v); if (dumpWrites) {addWrite(regRemap(a),v);} } -#define CHIP_DIVIDER ((sunsoft||clockSel)?16:8) +#define CHIP_DIVIDER (extMode?extDiv:((sunsoft||clockSel)?16:8)) const char* regCheatSheetAY[]={ "FreqL_A", "0", @@ -69,46 +69,18 @@ const char* regCheatSheetAY8914[]={ NULL }; +// taken from ay8910.cpp +const int sunsoftVolTable[32]={ + 103350, 73770, 52657, 37586, 32125, 27458, 24269, 21451, + 18447, 15864, 14009, 12371, 10506, 8922, 7787, 6796, + 5689, 4763, 4095, 3521, 2909, 2403, 2043, 1737, + 1397, 1123, 925, 762, 578, 438, 332, 251 +}; + const char** DivPlatformAY8910::getRegisterSheet() { return intellivision?regCheatSheetAY8914:regCheatSheetAY; } -const char* DivPlatformAY8910::getEffectName(unsigned char effect) { - switch (effect) { - case 0x20: - return "20xx: Set channel mode (bit 0: square; bit 1: noise; bit 2: envelope)"; - break; - case 0x21: - return "21xx: Set noise frequency (0 to 1F)"; - break; - case 0x22: - return "22xy: Set envelope mode (x: shape, y: enable for this channel)"; - break; - case 0x23: - return "23xx: Set envelope period low byte"; - break; - case 0x24: - return "24xx: Set envelope period high byte"; - break; - case 0x25: - return "25xx: Envelope slide up"; - break; - case 0x26: - return "26xx: Envelope slide down"; - break; - case 0x29: - return "29xy: Set auto-envelope (x: numerator; y: denominator)"; - break; - case 0x2e: - return "2Exx: Write to I/O port A"; - break; - case 0x2f: - return "2Fxx: Write to I/O port B"; - break; - } - return NULL; -} - void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t len) { if (ayBufLensound_stream_update(ayBuf,len); if (sunsoft) { for (size_t i=0; isound_stream_update(ayBuf,1); + bufL[i+start]=ayBuf[0][0]; bufR[i+start]=bufL[i+start]; - } - } else if (stereo) { - for (size_t i=0; idata[oscBuf[0]->needle++]=sunsoftVolTable[31-(ay->lastIndx&31)]>>3; + oscBuf[1]->data[oscBuf[1]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>5)&31)]>>3; + oscBuf[2]->data[oscBuf[2]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>10)&31)]>>3; } } else { - for (size_t i=0; isound_stream_update(ayBuf,len); + if (stereo) { + for (size_t i=0; idata[oscBuf[ch]->needle++]=ayBuf[ch][i]; + for (int ch=0; ch<3; ch++) { + for (size_t i=0; idata[oscBuf[ch]->needle++]=ayBuf[ch][i]; + } } } } @@ -195,18 +173,9 @@ void DivPlatformAY8910::tick(bool sysTick) { } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.duty.had) { rWrite(0x06,31-chan[i].std.duty.val); @@ -471,9 +440,12 @@ int DivPlatformAY8910::dispatch(DivCommand c) { return 15; break; case DIV_CMD_PRE_PORTA: + // TODO: FIX wtr_envelope.dmf + // the brokenPortaArp update broke it if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AY)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_PRE_NOTE: @@ -489,9 +461,9 @@ void DivPlatformAY8910::muteChannel(int ch, bool mute) { isMuted[ch]=mute; if (isMuted[ch]) { rWrite(0x08+ch,0); - } else if (intellivision && (chan[ch].psgMode&4)) { + } else if (intellivision && (chan[ch].psgMode&4) && chan[ch].active) { rWrite(0x08+ch,(chan[ch].vol&0xc)<<2); - } else { + } else if (chan[ch].active) { rWrite(0x08+ch,(chan[ch].outVol&15)|((chan[ch].psgMode&4)<<2)); } } @@ -565,8 +537,6 @@ void DivPlatformAY8910::reset() { delay=0; - extMode=false; - ioPortA=false; ioPortB=false; portAVal=0; @@ -595,50 +565,69 @@ void DivPlatformAY8910::poke(std::vector& wlist) { for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); } -void DivPlatformAY8910::setFlags(unsigned int flags) { - clockSel=(flags>>7)&1; - switch (flags&15) { - case 1: - chipClock=COLOR_PAL*2.0/5.0; - break; - case 2: - chipClock=1750000; - break; - case 3: - chipClock=2000000; - break; - case 4: - chipClock=1500000; - break; - case 5: - chipClock=1000000; - break; - case 6: - chipClock=COLOR_NTSC/4.0; - break; - case 7: - chipClock=COLOR_PAL*3.0/8.0; - break; - case 8: - chipClock=COLOR_PAL*3.0/16.0; - break; - case 9: - chipClock=COLOR_PAL/4.0; - break; - case 10: - chipClock=2097152; - break; - case 11: - chipClock=COLOR_NTSC; - break; - case 12: - chipClock=3600000; - break; - default: - chipClock=COLOR_NTSC/2.0; - break; +void DivPlatformAY8910::setExtClockDiv(unsigned int eclk, unsigned char ediv) { + if (extMode) { + extClock=eclk; + extDiv=ediv; + } +} + +void DivPlatformAY8910::setFlags(unsigned int flags) { + if (extMode) { + chipClock=extClock; + rate=chipClock/extDiv; + } else { + clockSel=(flags>>7)&1; + switch (flags&15) { + default: + case 0: + chipClock=COLOR_NTSC/2.0; + break; + case 1: + chipClock=COLOR_PAL*2.0/5.0; + break; + case 2: + chipClock=1750000; + break; + case 3: + chipClock=2000000; + break; + case 4: + chipClock=1500000; + break; + case 5: + chipClock=1000000; + break; + case 6: + chipClock=COLOR_NTSC/4.0; + break; + case 7: + chipClock=COLOR_PAL*3.0/8.0; + break; + case 8: + chipClock=COLOR_PAL*3.0/16.0; + break; + case 9: + chipClock=COLOR_PAL/4.0; + break; + case 10: + chipClock=2097152; + break; + case 11: + chipClock=COLOR_NTSC; + break; + case 12: + chipClock=3600000; + break; + case 13: + chipClock=20000000/16; + break; + case 14: + chipClock=1536000; + break; + } + rate=chipClock/8; } - rate=chipClock/8; for (int i=0; i<3; i++) { oscBuf[i]->rate=rate; } diff --git a/src/engine/platform/ay.h b/src/engine/platform/ay.h index 0b05eb44..4b7ce106 100644 --- a/src/engine/platform/ay.h +++ b/src/engine/platform/ay.h @@ -70,6 +70,9 @@ class DivPlatformAY8910: public DivDispatch { int delay; bool extMode; + unsigned int extClock; + unsigned char extDiv; + bool stereo, sunsoft, intellivision, clockSel; bool ioPortA, ioPortB; unsigned char portAVal, portBVal; @@ -88,6 +91,7 @@ class DivPlatformAY8910: public DivDispatch { friend void putDispatchChan(void*,int,int); public: + void setExtClockDiv(unsigned int eclk=COLOR_NTSC, unsigned char ediv=8); void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); @@ -108,8 +112,12 @@ class DivPlatformAY8910: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); + DivPlatformAY8910(bool useExtMode=false, unsigned int eclk=COLOR_NTSC, unsigned char ediv=8): + DivDispatch(), + extMode(useExtMode), + extClock(eclk), + extDiv(ediv) {} }; #endif diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index a92f185c..d77457d6 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -77,54 +77,6 @@ const char** DivPlatformAY8930::getRegisterSheet() { return regCheatSheetAY8930; } -const char* DivPlatformAY8930::getEffectName(unsigned char effect) { - switch (effect) { - case 0x12: - return "12xx: Set duty cycle (0 to 8)"; - break; - case 0x20: - return "20xx: Set channel mode (bit 0: square; bit 1: noise; bit 2: envelope)"; - break; - case 0x21: - return "21xx: Set noise frequency (0 to 1F)"; - break; - case 0x22: - return "22xy: Set envelope mode (x: shape, y: enable for this channel)"; - break; - case 0x23: - return "23xx: Set envelope period low byte"; - break; - case 0x24: - return "24xx: Set envelope period high byte"; - break; - case 0x25: - return "25xx: Envelope slide up"; - break; - case 0x26: - return "26xx: Envelope slide down"; - break; - case 0x27: - return "27xx: Set noise AND mask"; - break; - case 0x28: - return "28xx: Set noise OR mask"; - break; - case 0x29: - return "29xy: Set auto-envelope (x: numerator; y: denominator)"; - break; - case 0x2d: - return "2Dxx: NOT TO BE EMPLOYED BY THE COMPOSER"; - break; - case 0x2e: - return "2Exx: Write to I/O port A"; - break; - case 0x2f: - return "2Fxx: Write to I/O port B"; - break; - } - return NULL; -} - void DivPlatformAY8930::acquire(short* bufL, short* bufR, size_t start, size_t len) { if (ayBufLencalcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.duty.had) { rWrite(0x06,chan[i].std.duty.val); @@ -506,6 +449,7 @@ int DivPlatformAY8930::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AY8930)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_PRE_NOTE: @@ -521,7 +465,7 @@ void DivPlatformAY8930::muteChannel(int ch, bool mute) { isMuted[ch]=mute; if (isMuted[ch]) { rWrite(0x08+ch,0); - } else { + } else if (chan[ch].active) { rWrite(0x08+ch,(chan[ch].outVol&31)|((chan[ch].psgMode&4)<<3)); } } diff --git a/src/engine/platform/ay8930.h b/src/engine/platform/ay8930.h index 10ac7736..81995f2d 100644 --- a/src/engine/platform/ay8930.h +++ b/src/engine/platform/ay8930.h @@ -103,7 +103,6 @@ class DivPlatformAY8930: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); }; diff --git a/src/engine/platform/bubsyswsg.cpp b/src/engine/platform/bubsyswsg.cpp index 48a07880..89503cce 100644 --- a/src/engine/platform/bubsyswsg.cpp +++ b/src/engine/platform/bubsyswsg.cpp @@ -39,15 +39,6 @@ const char** DivPlatformBubSysWSG::getRegisterSheet() { return regCheatSheetBubSysWSG; } -const char* DivPlatformBubSysWSG::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Change waveform"; - break; - } - return NULL; -} - void DivPlatformBubSysWSG::acquire(short* bufL, short* bufR, size_t start, size_t len) { int chanOut=0; for (size_t h=start; hcalcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.wave.had) { if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { @@ -250,6 +232,7 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SCC)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/bubsyswsg.h b/src/engine/platform/bubsyswsg.h index e288493c..cfc41dc1 100644 --- a/src/engine/platform/bubsyswsg.h +++ b/src/engine/platform/bubsyswsg.h @@ -85,7 +85,6 @@ class DivPlatformBubSysWSG: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformBubSysWSG(); diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 3372c820..ad111347 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -19,9 +19,10 @@ #include "c64.h" #include "../engine.h" +#include "sound/c64_fp/siddefs-fp.h" #include -#define rWrite(a,v) if (!skipRegisterWrites) {sid.write(a,v); regPool[(a)&0x1f]=v; if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {if (isFP) {sid_fp.write(a,v);} else {sid.write(a,v);}; regPool[(a)&0x1f]=v; if (dumpWrites) {addWrite(a,v);} } #define CHIP_FREQBASE 524288 @@ -62,61 +63,26 @@ const char** DivPlatformC64::getRegisterSheet() { return regCheatSheetSID; } -const char* DivPlatformC64::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Set waveform (bit 0: triangle; bit 1: saw; bit 2: pulse; bit 3: noise)"; - break; - case 0x11: - return "11xx: Set coarse cutoff (not recommended; use 4xxx instead)"; - break; - case 0x12: - return "12xx: Set coarse pulse width (not recommended; use 3xxx instead)"; - break; - case 0x13: - return "13xx: Set resonance (0 to F)"; - break; - case 0x14: - return "14xx: Set filter mode (bit 0: low pass; bit 1: band pass; bit 2: high pass)"; - break; - case 0x15: - return "15xx: Set envelope reset time"; - break; - case 0x1a: - return "1Axx: Disable envelope reset for this channel (1 disables; 0 enables)"; - break; - case 0x1b: - return "1Bxy: Reset cutoff (x: on new note; y: now)"; - break; - case 0x1c: - return "1Cxy: Reset pulse width (x: on new note; y: now)"; - break; - case 0x1e: - return "1Exy: Change additional parameters"; - break; - case 0x30: case 0x31: case 0x32: case 0x33: - case 0x34: case 0x35: case 0x36: case 0x37: - case 0x38: case 0x39: case 0x3a: case 0x3b: - case 0x3c: case 0x3d: case 0x3e: case 0x3f: - return "3xxx: Set pulse width (0 to FFF)"; - break; - case 0x40: case 0x41: case 0x42: case 0x43: - case 0x44: case 0x45: case 0x46: case 0x47: - return "4xxx: Set cutoff (0 to 7FF)"; - break; - } - return NULL; -} - void DivPlatformC64::acquire(short* bufL, short* bufR, size_t start, size_t len) { + int dcOff=isFP?0:sid.get_dc(0); for (size_t i=start; i=8) { - writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=sid.last_chan_out[0]>>5; - oscBuf[1]->data[oscBuf[1]->needle++]=sid.last_chan_out[1]>>5; - oscBuf[2]->data[oscBuf[2]->needle++]=sid.last_chan_out[2]>>5; + if (isFP) { + sid_fp.clock(4,&bufL[i]); + if (++writeOscBuf>=4) { + writeOscBuf=0; + oscBuf[0]->data[oscBuf[0]->needle++]=(sid_fp.lastChanOut[0]-dcOff)>>5; + oscBuf[1]->data[oscBuf[1]->needle++]=(sid_fp.lastChanOut[1]-dcOff)>>5; + oscBuf[2]->data[oscBuf[2]->needle++]=(sid_fp.lastChanOut[2]-dcOff)>>5; + } + } else { + sid.clock(); + bufL[i]=sid.output(); + if (++writeOscBuf>=16) { + writeOscBuf=0; + oscBuf[0]->data[oscBuf[0]->needle++]=(sid.last_chan_out[0]-dcOff)>>5; + oscBuf[1]->data[oscBuf[1]->needle++]=(sid.last_chan_out[1]-dcOff)>>5; + oscBuf[2]->data[oscBuf[2]->needle++]=(sid.last_chan_out[2]-dcOff)>>5; + } } } } @@ -149,18 +115,9 @@ void DivPlatformC64::tick(bool sysTick) { } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.duty.had) { DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_C64); @@ -368,6 +325,7 @@ int DivPlatformC64::dispatch(DivCommand c) { chan[c.chan].keyOn=true; } } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_PRE_NOTE: @@ -458,7 +416,11 @@ int DivPlatformC64::dispatch(DivCommand c) { void DivPlatformC64::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - sid.set_is_muted(ch,mute); + if (isFP) { + sid_fp.mute(ch,mute); + } else { + sid.set_is_muted(ch,mute); + } } void DivPlatformC64::forceIns() { @@ -511,13 +473,25 @@ bool DivPlatformC64::getDCOffRequired() { return true; } +bool DivPlatformC64::getWantPreNote() { + return true; +} + +float DivPlatformC64::getPostAmp() { + return isFP?3.0f:1.0f; +} + void DivPlatformC64::reset() { for (int i=0; i<3; i++) { chan[i]=DivPlatformC64::Channel(); chan[i].std.setEngine(parent); } - sid.reset(); + if (isFP) { + sid_fp.reset(); + } else { + sid.reset(); + } memset(regPool,0,32); rWrite(0x18,0x0f); @@ -539,12 +513,24 @@ void DivPlatformC64::poke(std::vector& wlist) { void DivPlatformC64::setChipModel(bool is6581) { if (is6581) { - sid.set_chip_model(MOS6581); + if (isFP) { + sid_fp.setChipModel(reSIDfp::MOS6581); + } else { + sid.set_chip_model(MOS6581); + } } else { - sid.set_chip_model(MOS8580); + if (isFP) { + sid_fp.setChipModel(reSIDfp::MOS8580); + } else { + sid.set_chip_model(MOS8580); + } } } +void DivPlatformC64::setFP(bool fp) { + isFP=fp; +} + void DivPlatformC64::setFlags(unsigned int flags) { switch (flags&0xf) { case 0x0: // NTSC C64 @@ -562,6 +548,10 @@ void DivPlatformC64::setFlags(unsigned int flags) { for (int i=0; i<3; i++) { oscBuf[i]->rate=rate/16; } + if (isFP) { + rate/=4; + sid_fp.setSamplingParameters(chipClock,reSIDfp::DECIMATE,rate,0); + } } int DivPlatformC64::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { diff --git a/src/engine/platform/c64.h b/src/engine/platform/c64.h index d9dc0804..7587730b 100644 --- a/src/engine/platform/c64.h +++ b/src/engine/platform/c64.h @@ -23,6 +23,7 @@ #include "../dispatch.h" #include "../macroInt.h" #include "sound/c64/sid.h" +#include "sound/c64_fp/SID.h" class DivPlatformC64: public DivDispatch { struct Channel { @@ -76,12 +77,17 @@ class DivPlatformC64: public DivDispatch { unsigned char filtControl, filtRes, vol; unsigned char writeOscBuf; int filtCut, resetTime; + bool isFP; SID sid; + reSIDfp::SID sid_fp; unsigned char regPool[32]; friend void putDispatchChan(void*,int,int); + void acquire_classic(short* bufL, short* bufR, size_t start, size_t len); + void acquire_fp(short* bufL, short* bufR, size_t start, size_t len); + void updateFilter(); public: void acquire(short* bufL, short* bufR, size_t start, size_t len); @@ -97,14 +103,16 @@ class DivPlatformC64: public DivDispatch { void setFlags(unsigned int flags); void notifyInsChange(int ins); bool getDCOffRequired(); + bool getWantPreNote(); + float getPostAmp(); DivMacroInt* getChanMacroInt(int ch); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void setChipModel(bool is6581); + void setFP(bool fp); void quit(); ~DivPlatformC64(); }; diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp index ea4b83e9..7622a099 100644 --- a/src/engine/platform/fds.cpp +++ b/src/engine/platform/fds.cpp @@ -55,30 +55,6 @@ const char** DivPlatformFDS::getRegisterSheet() { return regCheatSheetFDS; } -const char* DivPlatformFDS::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Change waveform"; - break; - case 0x11: - return "11xx: Set modulation depth"; - break; - case 0x12: - return "12xy: Set modulation speed high byte (x: enable; y: value)"; - break; - case 0x13: - return "13xx: Set modulation speed low byte"; - break; - case 0x14: - return "14xx: Set modulator position"; - break; - case 0x15: - return "15xx: Set modulator table to waveform"; - break; - } - return NULL; -} - void DivPlatformFDS::acquire_puNES(short* bufL, short* bufR, size_t start, size_t len) { for (size_t i=start; icalcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); - chan[i].freqChanged=true; - } } /* if (chan[i].std.duty.had) { @@ -406,6 +373,7 @@ int DivPlatformFDS::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FDS)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/fds.h b/src/engine/platform/fds.h index f655a777..2bd98329 100644 --- a/src/engine/platform/fds.h +++ b/src/engine/platform/fds.h @@ -104,7 +104,6 @@ class DivPlatformFDS: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformFDS(); diff --git a/src/engine/platform/fmshared_OPM.h b/src/engine/platform/fmshared_OPM.h index 0ab05344..9b838c19 100644 --- a/src/engine/platform/fmshared_OPM.h +++ b/src/engine/platform/fmshared_OPM.h @@ -17,13 +17,32 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#define ADDR_MULT_DT 0x40 -#define ADDR_TL 0x60 -#define ADDR_RS_AR 0x80 -#define ADDR_AM_DR 0xa0 -#define ADDR_DT2_D2R 0xc0 -#define ADDR_SL_RR 0xe0 -#define ADDR_NOTE 0x28 -#define ADDR_KF 0x30 -#define ADDR_FMS_AMS 0x38 -#define ADDR_LR_FB_ALG 0x20 \ No newline at end of file +#ifndef _FMSHARED_OPM_H +#define _FMSHARED_OPM_H + +#include "fmsharedbase.h" + +#define NOTE_LINEAR(x) (((x)<<6)+baseFreqOff+log2(parent->song.tuning/440.0)*12.0*64.0) + +class DivPlatformOPM: public DivPlatformFMBase { + protected: + const unsigned short ADDR_MULT_DT=0x40; + const unsigned short ADDR_TL=0x60; + const unsigned short ADDR_RS_AR=0x80; + const unsigned short ADDR_AM_DR=0xa0; + const unsigned short ADDR_DT2_D2R=0xc0; + const unsigned short ADDR_SL_RR=0xe0; + const unsigned short ADDR_NOTE=0x28; + const unsigned short ADDR_KF=0x30; + const unsigned short ADDR_FMS_AMS=0x38; + const unsigned short ADDR_LR_FB_ALG=0x20; + + const unsigned short opOffs[4]={ + 0x00, 0x08, 0x10, 0x18 + }; + + DivPlatformOPM(): + DivPlatformFMBase() {} +}; + +#endif diff --git a/src/engine/platform/fmshared_OPN.h b/src/engine/platform/fmshared_OPN.h index a709aa1a..406d7281 100644 --- a/src/engine/platform/fmshared_OPN.h +++ b/src/engine/platform/fmshared_OPN.h @@ -20,17 +20,7 @@ #ifndef _FMSHARED_OPN_H #define _FMSHARED_OPN_H -#define ADDR_MULT_DT 0x30 -#define ADDR_TL 0x40 -#define ADDR_RS_AR 0x50 -#define ADDR_AM_DR 0x60 -#define ADDR_DT2_D2R 0x70 -#define ADDR_SL_RR 0x80 -#define ADDR_SSG 0x90 -#define ADDR_FREQ 0xa0 -#define ADDR_FREQH 0xa4 -#define ADDR_FB_ALG 0xb0 -#define ADDR_LRAF 0xb4 +#include "fmsharedbase.h" #define PLEASE_HELP_ME(_targetChan) \ int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); \ @@ -93,4 +83,36 @@ return 2; \ } -#endif \ No newline at end of file +class DivPlatformOPN: public DivPlatformFMBase { + protected: + const unsigned short ADDR_MULT_DT=0x30; + const unsigned short ADDR_TL=0x40; + const unsigned short ADDR_RS_AR=0x50; + const unsigned short ADDR_AM_DR=0x60; + const unsigned short ADDR_DT2_D2R=0x70; + const unsigned short ADDR_SL_RR=0x80; + const unsigned short ADDR_SSG=0x90; + const unsigned short ADDR_FREQ=0xa0; + const unsigned short ADDR_FREQH=0xa4; + const unsigned short ADDR_FB_ALG=0xb0; + const unsigned short ADDR_LRAF=0xb4; + + const unsigned short opOffs[4]={ + 0x00, 0x04, 0x08, 0x0c + }; + + double fmFreqBase; + unsigned int fmDivBase; + unsigned int ayDiv; + bool extSys; + + DivPlatformOPN(double f=9440540.0, unsigned int d=72, unsigned int a=32, bool isExtSys=false): + DivPlatformFMBase(), + fmFreqBase(f), + fmDivBase(d), + ayDiv(a), + extSys(isExtSys) {} + +}; + +#endif diff --git a/src/engine/platform/fmsharedbase.h b/src/engine/platform/fmsharedbase.h new file mode 100644 index 00000000..64099739 --- /dev/null +++ b/src/engine/platform/fmsharedbase.h @@ -0,0 +1,96 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _FMSHARED_BASE_H +#define _FMSHARED_BASE_H + +#include "../dispatch.h" +#include + +class DivPlatformFMBase: public DivDispatch { + protected: + const bool isOutput[8][4]={ + // 1 3 2 4 + {false,false,false,true}, + {false,false,false,true}, + {false,false,false,true}, + {false,false,false,true}, + {false,false,true ,true}, + {false,true ,true ,true}, + {false,true ,true ,true}, + {true ,true ,true ,true}, + }; + const unsigned char dtTable[8]={ + 7,6,5,0,1,2,3,4 + }; + + const int orderedOps[4]={ + 0,2,1,3 + }; + + struct QueuedWrite { + unsigned short addr; + unsigned char val; + bool addrOrVal; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + }; + std::deque writes; + + unsigned char lastBusy; + int delay; + + unsigned char regPool[512]; + short oldWrites[512]; + short pendingWrites[512]; + + inline void rWrite(unsigned short a, short v) { + if (!skipRegisterWrites) { + pendingWrites[a]=v; + } + } + inline void immWrite(unsigned short a, unsigned char v) { + if (!skipRegisterWrites) { + writes.push_back(QueuedWrite(a,v)); + if (dumpWrites) { + addWrite(a,v); + } + } + } + inline void urgentWrite(unsigned short a, unsigned char v) { + if (!skipRegisterWrites) { + if (writes.empty()) { + writes.push_back(QueuedWrite(a,v)); + } else if (writes.size()>16 || writes.front().addrOrVal) { + writes.push_back(QueuedWrite(a,v)); + } else { + writes.push_front(QueuedWrite(a,v)); + } + if (dumpWrites) { + addWrite(a,v); + } + } + } + + DivPlatformFMBase(): + DivDispatch(), + lastBusy(0), + delay(0) {} +}; + +#endif diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 999d31b9..fea89c71 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -21,8 +21,8 @@ #include "../engine.h" #include -#define rWrite(a,v) if (!skipRegisterWrites) {GB_apu_write(gb,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } -#define immWrite(a,v) {GB_apu_write(gb,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } +#define immWrite(a,v) {writes.emplace(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } #define CHIP_DIVIDER 16 @@ -61,29 +61,14 @@ const char** DivPlatformGB::getRegisterSheet() { return regCheatSheetGB; } -const char* DivPlatformGB::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Change waveform"; - break; - case 0x11: - return "11xx: Set noise length (0: long; 1: short)"; - break; - case 0x12: - return "12xx: Set duty cycle (0 to 3)"; - break; - case 0x13: - return "13xy: Setup sweep (x: time; y: shift)"; - break; - case 0x14: - return "14xx: Set sweep direction (0: up; 1: down)"; - break; - } - return NULL; -} - void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t i=start; iapu_output.final_sample.left; bufR[i]=gb->apu_output.final_sample.right; @@ -97,10 +82,11 @@ void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len) void DivPlatformGB::updateWave() { rWrite(0x1a,0); for (int i=0; i<16; i++) { - int nibble1=15-ws.output[i<<1]; - int nibble2=15-ws.output[1+(i<<1)]; + int nibble1=15-ws.output[((i<<1)+antiClickWavePos-1)&31]; + int nibble2=15-ws.output[((1+(i<<1))+antiClickWavePos-1)&31]; rWrite(0x30+i,(nibble1<<4)|nibble2); } + antiClickWavePos&=31; } static unsigned char chanMuteMask[4]={ @@ -151,39 +137,47 @@ static unsigned char noiseTable[256]={ }; void DivPlatformGB::tick(bool sysTick) { + if (antiClickEnabled && sysTick && chan[2].freq>0) { + antiClickPeriodCount+=((chipClock>>1)/MAX(parent->getCurHz(),1.0f)); + antiClickWavePos+=antiClickPeriodCount/chan[2].freq; + antiClickPeriodCount%=chan[2].freq; + } + for (int i=0; i<4; i++) { chan[i].std.next(); + if (chan[i].softEnv) { + if (chan[i].std.vol.had) { + chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15); + if (chan[i].outVol<0) chan[i].outVol=0; + + if (i==2) { + rWrite(16+i*5+2,gbVolMap[chan[i].outVol]); + chan[i].soundLen=64; + } else { + chan[i].envLen=0; + chan[i].envDir=1; + chan[i].envVol=chan[i].outVol; + chan[i].soundLen=64; + + if (!chan[i].keyOn) chan[i].killIt=true; + } + } + } if (chan[i].std.arp.had) { if (i==3) { // noise - if (chan[i].std.arp.mode) { - chan[i].baseFreq=chan[i].std.arp.val+24; - } else { - chan[i].baseFreq=chan[i].note+chan[i].std.arp.val; - } + chan[i].baseFreq=parent->calcArp(chan[i].note,chan[i].std.arp.val,24); if (chan[i].baseFreq>255) chan[i].baseFreq=255; if (chan[i].baseFreq<0) chan[i].baseFreq=0; } else { - if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val+24); - } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); - } - } + chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val,24)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.duty.had) { chan[i].duty=chan[i].std.duty.val; - DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB); if (i!=2) { - rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); - } else { + rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63))); + } else if (!chan[i].softEnv) { if (parent->song.waveDutyIsVol) { rWrite(16+i*5+2,gbVolMap[(chan[i].std.duty.val&3)<<2]); } @@ -213,6 +207,10 @@ void DivPlatformGB::tick(bool sysTick) { if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1) { chan[i].keyOn=true; + if (i==2) { + antiClickWavePos=0; + antiClickPeriodCount=0; + } } } if (i==2) { @@ -223,14 +221,64 @@ void DivPlatformGB::tick(bool sysTick) { } } } + // run hardware sequence + if (chan[i].active) { + if (--chan[i].hwSeqDelay<=0) { + chan[i].hwSeqDelay=0; + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB); + int hwSeqCount=0; + while (chan[i].hwSeqPosgb.hwSeqLen && hwSeqCount<4) { + bool leave=false; + unsigned short data=ins->gb.hwSeq[chan[i].hwSeqPos].data; + switch (ins->gb.hwSeq[chan[i].hwSeqPos].cmd) { + case DivInstrumentGB::DIV_GB_HWCMD_ENVELOPE: + if (!chan[i].softEnv) { + chan[i].envLen=data&7; + chan[i].envDir=(data&8)?1:0; + chan[i].envVol=(data>>4)&15; + chan[i].soundLen=data>>8; + chan[i].keyOn=true; + } + break; + case DivInstrumentGB::DIV_GB_HWCMD_SWEEP: + chan[i].sweep=data; + chan[i].sweepChanged=true; + break; + case DivInstrumentGB::DIV_GB_HWCMD_WAIT: + chan[i].hwSeqDelay=data+1; + leave=true; + break; + case DivInstrumentGB::DIV_GB_HWCMD_WAIT_REL: + if (!chan[i].released) { + chan[i].hwSeqPos--; + leave=true; + } + break; + case DivInstrumentGB::DIV_GB_HWCMD_LOOP: + chan[i].hwSeqPos=data-1; + break; + case DivInstrumentGB::DIV_GB_HWCMD_LOOP_REL: + if (!chan[i].released) { + chan[i].hwSeqPos=data-1; + } + break; + } + + chan[i].hwSeqPos++; + if (leave) break; + hwSeqCount++; + } + } + } + if (chan[i].sweepChanged) { chan[i].sweepChanged=false; if (i==0) { rWrite(16+i*5,chan[i].sweep); } } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB); if (i==3) { // noise int ntPos=chan[i].baseFreq; if (ntPos<0) ntPos=0; @@ -244,10 +292,11 @@ void DivPlatformGB::tick(bool sysTick) { if (chan[i].keyOn) { if (i==2) { // wave rWrite(16+i*5,0x80); - rWrite(16+i*5+2,gbVolMap[chan[i].vol]); + rWrite(16+i*5+2,gbVolMap[chan[i].outVol]); } else { - rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); - rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3)); + rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63))); + rWrite(16+i*5+2,((chan[i].envVol<<4))|(chan[i].envLen&7)|((chan[i].envDir&1)<<3)); + chan[i].lastKill=chan[i].envVol; } } if (chan[i].keyOff) { @@ -259,15 +308,35 @@ void DivPlatformGB::tick(bool sysTick) { } if (i==3) { // noise rWrite(16+i*5+3,(chan[i].freq&0xff)|(chan[i].duty?8:0)); - rWrite(16+i*5+4,((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((ins->gb.soundLen<64)<<6)); + rWrite(16+i*5+4,((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((chan[i].soundLen<64)<<6)); } else { rWrite(16+i*5+3,(2048-chan[i].freq)&0xff); - rWrite(16+i*5+4,(((2048-chan[i].freq)>>8)&7)|((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((ins->gb.soundLen<63)<<6)); + rWrite(16+i*5+4,(((2048-chan[i].freq)>>8)&7)|((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((chan[i].soundLen<63)<<6)); } if (chan[i].keyOn) chan[i].keyOn=false; if (chan[i].keyOff) chan[i].keyOff=false; chan[i].freqChanged=false; } + if (chan[i].killIt) { + if (i!=2) { + //rWrite(16+i*5+2,8); + int killDelta=chan[i].lastKill-chan[i].outVol+1; + if (killDelta<0) killDelta+=16; + chan[i].lastKill=chan[i].outVol; + + if (killDelta!=1) { + rWrite(16+i*5+2,((chan[i].envVol<<4))|8); + for (int j=0; jgb.softEnv; chan[c.chan].macroInit(ins); if (c.chan==2) { if (chan[c.chan].wave<0) { @@ -299,17 +372,35 @@ int DivPlatformGB::dispatch(DivCommand c) { } ws.init(ins,32,15,chan[c.chan].insChanged); } + if ((chan[c.chan].insChanged || ins->gb.alwaysInit) && !chan[c.chan].softEnv) { + if (!chan[c.chan].soManyHacksToMakeItDefleCompatible && c.chan!=2) { + chan[c.chan].envVol=ins->gb.envVol; + } + chan[c.chan].envLen=ins->gb.envLen; + chan[c.chan].envDir=ins->gb.envDir; + chan[c.chan].soundLen=ins->gb.soundLen; + if (!chan[c.chan].soManyHacksToMakeItDefleCompatible && c.chan!=2) { + chan[c.chan].vol=chan[c.chan].envVol; + chan[c.chan].outVol=chan[c.chan].envVol; + } + } + if (c.chan==2 && chan[c.chan].softEnv) { + chan[c.chan].soundLen=64; + } chan[c.chan].insChanged=false; break; } case DIV_CMD_NOTE_OFF: chan[c.chan].active=false; chan[c.chan].keyOff=true; + chan[c.chan].hwSeqPos=0; + chan[c.chan].hwSeqDelay=0; chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: chan[c.chan].std.release(); + chan[c.chan].released=true; break; case DIV_CMD_INSTRUMENT: if (chan[c.chan].ins!=c.value || c.value2==1) { @@ -317,17 +408,33 @@ int DivPlatformGB::dispatch(DivCommand c) { chan[c.chan].insChanged=true; if (c.chan!=2) { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_GB); - chan[c.chan].vol=ins->gb.envVol; - if (parent->song.gbInsAffectsEnvelope) { - rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3)); + if (!ins->gb.softEnv) { + chan[c.chan].envVol=ins->gb.envVol; + chan[c.chan].envLen=ins->gb.envLen; + chan[c.chan].envDir=ins->gb.envDir; + chan[c.chan].soundLen=ins->gb.soundLen; + chan[c.chan].vol=chan[c.chan].envVol; + chan[c.chan].outVol=chan[c.chan].vol; + if (parent->song.gbInsAffectsEnvelope) { + rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(chan[c.chan].envLen&7)|((chan[c.chan].envDir&1)<<3)); + } } } } break; case DIV_CMD_VOLUME: chan[c.chan].vol=c.value; + chan[c.chan].outVol=c.value; if (c.chan==2) { - rWrite(16+c.chan*5+2,gbVolMap[chan[c.chan].vol]); + rWrite(16+c.chan*5+2,gbVolMap[chan[c.chan].outVol]); + } + if (!chan[c.chan].softEnv) { + chan[c.chan].envVol=chan[c.chan].vol; + chan[c.chan].soManyHacksToMakeItDefleCompatible=true; + } else if (c.chan!=2) { + chan[c.chan].envVol=chan[c.chan].vol; + if (!chan[c.chan].keyOn) chan[c.chan].killIt=true; + chan[c.chan].freqChanged=true; } break; case DIV_CMD_GET_VOLUME: @@ -393,6 +500,7 @@ int DivPlatformGB::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_GB)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GB_SWEEP_DIR: @@ -461,7 +569,7 @@ void DivPlatformGB::reset() { } memset(gb,0,sizeof(GB_gameboy_t)); memset(regPool,0,128); - gb->model=GB_MODEL_DMG_B; + gb->model=model; GB_apu_init(gb); GB_set_sample_rate(gb,rate); // enable all channels @@ -470,12 +578,23 @@ void DivPlatformGB::reset() { lastPan=0xff; immWrite(0x25,procMute()); immWrite(0x24,0x77); + + antiClickPeriodCount=0; + antiClickWavePos=0; +} + +int DivPlatformGB::getPortaFloor(int ch) { + return 24; } bool DivPlatformGB::isStereo() { return true; } +bool DivPlatformGB::getDCOffRequired() { + return (model==GB_MODEL_AGB); +} + void DivPlatformGB::notifyInsChange(int ins) { for (int i=0; i<4; i++) { if (chan[i].ins==ins) { @@ -488,7 +607,7 @@ void DivPlatformGB::notifyWaveChange(int wave) { if (chan[2].wave==wave) { ws.changeWave1(wave); updateWave(); - if (!chan[2].keyOff) chan[2].keyOn=true; + if (!chan[2].keyOff && chan[2].active) chan[2].keyOn=true; } } @@ -506,6 +625,24 @@ void DivPlatformGB::poke(std::vector& wlist) { for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); } +void DivPlatformGB::setFlags(unsigned int flags) { + antiClickEnabled=!(flags&8); + switch (flags&3) { + case 0: + model=GB_MODEL_DMG_B; + break; + case 1: + model=GB_MODEL_CGB_C; + break; + case 2: + model=GB_MODEL_CGB_E; + break; + case 3: + model=GB_MODEL_AGB; + break; + } +} + int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { chipClock=4194304; rate=chipClock/16; @@ -517,7 +654,9 @@ int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int fl parent=p; dumpWrites=false; skipRegisterWrites=false; + model=GB_MODEL_DMG_B; gb=new GB_gameboy_t; + setFlags(flags); reset(); return 4; } diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h index fe2f6e51..516e7d9f 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -24,13 +24,18 @@ #include "../macroInt.h" #include "../waveSynth.h" #include "sound/gb/gb.h" +#include class DivPlatformGB: public DivDispatch { struct Channel { int freq, baseFreq, pitch, pitch2, note, ins; unsigned char duty, sweep; - bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta; - signed char vol, outVol, wave; + bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, released, softEnv, killIt; + bool soManyHacksToMakeItDefleCompatible; + signed char vol, outVol, wave, lastKill; + unsigned char envVol, envDir, envLen, soundLen; + unsigned short hwSeqPos; + short hwSeqDelay; DivMacroInt std; void macroInit(DivInstrument* which) { std.init(which); @@ -52,17 +57,38 @@ class DivPlatformGB: public DivDispatch { keyOn(false), keyOff(false), inPorta(false), + released(false), + softEnv(false), + killIt(false), + soManyHacksToMakeItDefleCompatible(false), vol(15), outVol(15), - wave(-1) {} + wave(-1), + lastKill(0), + envVol(0), + envDir(0), + envLen(0), + soundLen(0), + hwSeqPos(0), + hwSeqDelay(0) {} }; Channel chan[4]; DivDispatchOscBuffer* oscBuf[4]; bool isMuted[4]; + bool antiClickEnabled; unsigned char lastPan; DivWaveSynth ws; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + }; + std::queue writes; + + int antiClickPeriodCount, antiClickWavePos; GB_gameboy_t* gb; + GB_model_t model; unsigned char regPool[128]; unsigned char procMute(); @@ -80,14 +106,16 @@ class DivPlatformGB: public DivDispatch { void forceIns(); void tick(bool sysTick=true); void muteChannel(int ch, bool mute); + int getPortaFloor(int ch); bool isStereo(); + bool getDCOffRequired(); void notifyInsChange(int ins); void notifyWaveChange(int wave); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); + void setFlags(unsigned int flags); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformGB(); diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 46e28ccb..515f0c0a 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -22,119 +22,11 @@ #include #include -#include "genesisshared.h" +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase #define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6]))) -static unsigned char konOffs[6]={ - 0, 1, 2, 4, 5, 6 -}; - -#define CHIP_DIVIDER 72 -#define CHIP_FREQBASE 9440540 - -const char* DivPlatformGenesis::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xy: Setup LFO (x: enable; y: speed)"; - break; - case 0x11: - return "11xx: Set feedback (0 to 7)"; - break; - case 0x12: - return "12xx: Set level of operator 1 (0 highest, 7F lowest)"; - break; - case 0x13: - return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; - break; - case 0x14: - return "14xx: Set level of operator 3 (0 highest, 7F lowest)"; - break; - case 0x15: - return "15xx: Set level of operator 4 (0 highest, 7F lowest)"; - break; - case 0x16: - return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; - break; - case 0x17: - return "17xx: Enable channel 6 DAC"; - break; - case 0x18: - return "18xx: Toggle extended channel 3 mode"; - break; - case 0x19: - return "19xx: Set attack of all operators (0 to 1F)"; - break; - case 0x1a: - return "1Axx: Set attack of operator 1 (0 to 1F)"; - break; - case 0x1b: - return "1Bxx: Set attack of operator 2 (0 to 1F)"; - break; - case 0x1c: - return "1Cxx: Set attack of operator 3 (0 to 1F)"; - break; - case 0x1d: - return "1Dxx: Set attack of operator 4 (0 to 1F)"; - break; - case 0x30: - return "30xx: Toggle hard envelope reset on new notes"; - break; - case 0x50: - return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; - break; - case 0x51: - return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; - break; - case 0x52: - return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; - break; - case 0x53: - return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)"; - break; - case 0x54: - return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)"; - break; - case 0x55: - return "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)"; - break; - case 0x56: - return "56xx: Set decay of all operators (0 to 1F)"; - break; - case 0x57: - return "57xx: Set decay of operator 1 (0 to 1F)"; - break; - case 0x58: - return "58xx: Set decay of operator 2 (0 to 1F)"; - break; - case 0x59: - return "59xx: Set decay of operator 3 (0 to 1F)"; - break; - case 0x5a: - return "5Axx: Set decay of operator 4 (0 to 1F)"; - break; - case 0x5b: - return "5Bxx: Set decay 2 of all operators (0 to 1F)"; - break; - case 0x5c: - return "5Cxx: Set decay 2 of operator 1 (0 to 1F)"; - break; - case 0x5d: - return "5Dxx: Set decay 2 of operator 2 (0 to 1F)"; - break; - case 0x5e: - return "5Exx: Set decay 2 of operator 3 (0 to 1F)"; - break; - case 0x5f: - return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; - break; - case 0xdf: - return "DFxx: Set sample playback direction (0: normal; 1: reverse)"; - break; - } - return NULL; -} - void DivPlatformGenesis::processDAC() { if (softPCM) { softPCMTimer+=chipClock/576; @@ -159,14 +51,13 @@ void DivPlatformGenesis::processDAC() { if (chan[i].dacPeriod>=(chipClock/576)) { if (s->samples>0) { while (chan[i].dacPeriod>=(chipClock/576)) { - if (++chan[i].dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples && !chan[i].dacDirection) { - chan[i].dacPos=s->loopStart; - } else { - chan[i].dacSample=-1; - chan[i].dacPeriod=0; - break; - } + ++chan[i].dacPos; + if (!chan[i].dacDirection && (s->isLoopable() && chan[i].dacPos>=s->getEndPosition())) { + chan[i].dacPos=s->loopStart; + } else if (chan[i].dacPos>=s->samples) { + chan[i].dacSample=-1; + chan[i].dacPeriod=0; + break; } chan[i].dacPeriod-=(chipClock/576); } @@ -206,14 +97,13 @@ void DivPlatformGenesis::processDAC() { chan[5].dacReady=false; } } - if (++chan[5].dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples && !chan[5].dacDirection) { - chan[5].dacPos=s->loopStart; - } else { - chan[5].dacSample=-1; - if (parent->song.brokenDACMode) { - rWrite(0x2b,0); - } + chan[5].dacPos++; + if (!chan[5].dacDirection && (s->isLoopable() && chan[5].dacPos>=s->getEndPosition())) { + chan[5].dacPos=s->loopStart; + } else if (chan[5].dacPos>=s->samples) { + chan[5].dacSample=-1; + if (parent->song.brokenDACMode) { + rWrite(0x2b,0); } } while (chan[5].dacPeriod>=rate) chan[5].dacPeriod-=rate; @@ -348,13 +238,17 @@ void DivPlatformGenesis::acquire(short* bufL, short* bufR, size_t start, size_t } void DivPlatformGenesis::tick(bool sysTick) { - for (int i=0; i<6; i++) { + for (int i=0; i<(softPCM?7:6); i++) { if (i==2 && extMode) continue; chan[i].std.next(); if (chan[i].std.vol.had) { - chan[i].outVol=VOL_SCALE_LOG(chan[i].vol,MIN(127,chan[i].std.vol.val),127); - for (int j=0; j<4; j++) { + int inVol=chan[i].std.vol.val; + if (chan[i].furnaceDac && inVol>0) { + inVol+=63; + } + chan[i].outVol=VOL_SCALE_LOG(chan[i].vol,MIN(127,inVol),127); + if (i<6) for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; if (isMuted[i]) { @@ -369,25 +263,41 @@ void DivPlatformGenesis::tick(bool sysTick) { } } - if (chan[i].std.arp.had) { - if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11); - } else { - chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11); + if (i>=5 && chan[i].furnaceDac) { + if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + chan[i].baseFreq=parent->calcBaseFreq(1,1,parent->calcArp(chan[i].note,chan[i].std.arp.val),false); } + chan[i].freqChanged=true; } - chan[i].freqChanged=true; } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11); + if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + chan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(chan[i].note,chan[i].std.arp.val),11); + } chan[i].freqChanged=true; } } - if (chan[i].std.panL.had) { - chan[i].pan=chan[i].std.panL.val&3; - rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (i>=5 && chan[i].furnaceDac) { + if (chan[i].std.panL.had) { + chan[5].pan&=1; + chan[5].pan|=chan[i].std.panL.val?2:0; + } + if (chan[i].std.panR.had) { + chan[5].pan&=2; + chan[5].pan|=chan[i].std.panR.val?1:0; + } + if (chan[i].std.panL.had || chan[i].std.panR.had) { + rWrite(chanOffs[5]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[5].pan<<6))|(chan[5].state.fms&7)|((chan[5].state.ams&3)<<4)); + } + } else { + if (chan[i].std.panL.had) { + chan[i].pan=chan[i].std.panL.val&3; + if (i<6) { + rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + } } if (chan[i].std.pitch.had) { @@ -400,6 +310,8 @@ void DivPlatformGenesis::tick(bool sysTick) { chan[i].freqChanged=true; } + if (i>=6) continue; + if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1 && chan[i].active) { chan[i].keyOn=true; @@ -435,6 +347,10 @@ void DivPlatformGenesis::tick(bool sysTick) { chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } + if (chan[i].std.ex4.had && chan[i].active) { + chan[i].opMask=chan[i].std.ex4.val&15; + chan[i].opMaskChanged=true; + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -567,8 +483,9 @@ void DivPlatformGenesis::tick(bool sysTick) { } chan[i].freqChanged=false; } - if (chan[i].keyOn) { - if (i<6) immWrite(0x28,0xf0|konOffs[i]); + if (chan[i].keyOn || chan[i].opMaskChanged) { + if (i<6) immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); + chan[i].opMaskChanged=false; chan[i].keyOn=false; } } @@ -640,9 +557,20 @@ int DivPlatformGenesis::dispatch(DivCommand c) { chan[c.chan].dacPeriod=0; if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false); + chan[c.chan].portaPause=false; + chan[c.chan].note=c.value; chan[c.chan].freqChanged=true; } chan[c.chan].furnaceDac=true; + + chan[c.chan].macroInit(ins); + if (!chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + + // ??? + //chan[c.chan].keyOn=true; + chan[c.chan].active=true; } else { // compatible mode if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; @@ -668,6 +596,11 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; + chan[c.chan].opMask= + (chan[c.chan].state.op[0].enable?1:0)| + (chan[c.chan].state.op[2].enable?2:0)| + (chan[c.chan].state.op[1].enable?4:0)| + (chan[c.chan].state.op[3].enable?8:0); } chan[c.chan].macroInit(ins); @@ -892,6 +825,13 @@ int DivPlatformGenesis::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; } + case DIV_CMD_FM_EXTCH: { + if (extSys) { + extMode=c.value; + immWrite(0x27,extMode?0x40:0); + } + break; + } case DIV_CMD_FM_LFO: { if (c.chan>=6) break; lfoValue=(c.value&7)|((c.value>>4)<<3); @@ -1247,12 +1187,13 @@ void DivPlatformGenesis::setSoftPCM(bool value) { } void DivPlatformGenesis::setFlags(unsigned int flags) { - switch (flags) { + switch (flags&(~0x80000000)) { + default: + case 0: chipClock=COLOR_NTSC*15.0/7.0; break; case 1: chipClock=COLOR_PAL*12.0/7.0; break; case 2: chipClock=8000000.0; break; case 3: chipClock=COLOR_NTSC*12.0/7.0; break; case 4: chipClock=COLOR_NTSC*9.0/4.0; break; - default: chipClock=COLOR_NTSC*15.0/7.0; break; } ladder=flags&0x80000000; OPN2_SetChipType(ladder?ym3438_mode_ym2612:0); diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index 14257409..8588b21d 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -19,28 +19,35 @@ #ifndef _GENESIS_H #define _GENESIS_H -#include "../dispatch.h" -#include +#include "fmshared_OPN.h" +#include "../macroInt.h" #include "../../../extern/Nuked-OPN2/ym3438.h" #include "sound/ymfm/ymfm_opn.h" -#include "sms.h" class DivYM2612Interface: public ymfm::ymfm_interface { }; -class DivPlatformGenesis: public DivDispatch { +class DivPlatformGenesis: public DivPlatformOPN { protected: + const unsigned short chanOffs[6]={ + 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 + }; + + const unsigned char konOffs[6]={ + 0, 1, 2, 4, 5, 6 + }; + struct Channel { DivInstrumentFM state; DivMacroInt std; unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, note; int ins; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset, opMaskChanged; int vol, outVol; - unsigned char pan; + unsigned char pan, opMask; bool dacMode; int dacPeriod; @@ -75,9 +82,11 @@ class DivPlatformGenesis: public DivDispatch { furnaceDac(false), inPorta(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(0), pan(3), + opMask(15), dacMode(false), dacPeriod(0), dacRate(0), @@ -92,21 +101,11 @@ class DivPlatformGenesis: public DivDispatch { Channel chan[10]; DivDispatchOscBuffer* oscBuf[10]; bool isMuted[10]; - struct QueuedWrite { - unsigned short addr; - unsigned char val; - bool addrOrVal; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} - }; - std::deque writes; ym3438_t fm; - int delay; - unsigned char lastBusy; ymfm::ym2612* fm_ymfm; ymfm::ym2612::output_data out_ymfm; DivYM2612Interface iface; - unsigned char regPool[512]; unsigned char lfoValue; @@ -115,9 +114,6 @@ class DivPlatformGenesis: public DivDispatch { bool extMode, softPCM, useYMFM; bool ladder; - short oldWrites[512]; - short pendingWrites[512]; - unsigned char dacVolTable[128]; friend void putDispatchChan(void*,int,int); @@ -150,9 +146,10 @@ class DivPlatformGenesis: public DivDispatch { int getPortaFloor(int ch); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); + DivPlatformGenesis(): + DivPlatformOPN(9440540.0, 72, 32) {} ~DivPlatformGenesis(); }; #endif diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index f7fdaf44..a2df33c4 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -21,10 +21,11 @@ #include "../engine.h" #include -#include "genesisshared.h" +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase -#define CHIP_DIVIDER 72 -#define CHIP_FREQBASE 9440540 +#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6]))) +#define IS_EXTCH_MUTED (isOpMuted[0] && isOpMuted[1] && isOpMuted[2] && isOpMuted[3]) int DivPlatformGenesisExt::dispatch(DivCommand c) { if (c.chan<2) { @@ -36,6 +37,10 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { } int ch=c.chan-2; int ordch=orderedOps[ch]; + if (!extMode) { + c.chan=2; + return DivPlatformGenesis::dispatch(c); + } switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); @@ -65,10 +70,11 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { rWrite(baseAddr+0x70,op.d2r&31); rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); rWrite(baseAddr+0x90,op.ssgEnv&15); + opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? rWrite(chanOffs[2]+0xb0,(chan[2].state.alg&7)|(chan[2].state.fb<<3)); - rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); + rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); } opChan[ch].insChanged=false; @@ -119,7 +125,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { opChan[i].pan=opChan[ch].pan; } } - rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); + rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); break; } case DIV_CMD_PITCH: { @@ -175,6 +181,11 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { opChan[ch].freqChanged=true; break; } + case DIV_CMD_FM_EXTCH: { + extMode=c.value; + immWrite(0x27,extMode?0x40:0); + break; + } case DIV_CMD_FM_LFO: { lfoValue=(c.value&7)|((c.value>>4)<<3); rWrite(0x22,lfoValue); @@ -388,6 +399,8 @@ void DivPlatformGenesisExt::muteChannel(int ch, bool mute) { rWrite(baseAddr+0x40,op.tl); immWrite(baseAddr+0x40,op.tl); } + + rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); } static int opChanOffsL[4]={ @@ -403,7 +416,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -411,6 +424,16 @@ void DivPlatformGenesisExt::tick(bool sysTick) { } } if (writeSomething) { + if (chan[7].active) { // CSM + writeMask^=0xf0; + } + /*printf( + "Mask: %c %c %c %c\n", + (writeMask&0x10)?'1':'-', + (writeMask&0x20)?'2':'-', + (writeMask&0x40)?'3':'-', + (writeMask&0x80)?'4':'-' + );*/ immWrite(0x28,writeMask); } } @@ -440,10 +463,12 @@ void DivPlatformGenesisExt::tick(bool sysTick) { immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); } - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn) { writeNoteOn=true; - writeMask|=1<<(4+i); + if (opChan[i].mask) { + writeMask|=1<<(4+i); + } opChan[i].keyOn=false; } } @@ -471,6 +496,13 @@ void DivPlatformGenesisExt::tick(bool sysTick) { if (chan[7].active) { // CSM writeMask^=0xf0; } + /*printf( + "Mask: %c %c %c %c\n", + (writeMask&0x10)?'1':'-', + (writeMask&0x20)?'2':'-', + (writeMask&0x40)?'3':'-', + (writeMask&0x80)?'4':'-' + );*/ immWrite(0x28,writeMask); } @@ -491,7 +523,7 @@ void DivPlatformGenesisExt::forceIns() { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; - if (i==2) { // extended channel + if (i==2 && extMode) { // extended channel if (isOpMuted[j]) { rWrite(baseAddr+0x40,127); } else if (isOutput[chan[i].state.alg][j]) { @@ -518,7 +550,11 @@ void DivPlatformGenesisExt::forceIns() { rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (i==2) { + rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[0].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } else { + rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } if (chan[i].active) { chan[i].keyOn=true; chan[i].freqChanged=true; @@ -535,6 +571,11 @@ void DivPlatformGenesisExt::forceIns() { opChan[i].freqChanged=true; } } + if (extMode && softPCM && chan[7].active) { // CSM + chan[7].insChanged=true; + chan[7].freqChanged=true; + chan[7].keyOn=true; + } } void* DivPlatformGenesisExt::getChanState(int ch) { @@ -594,6 +635,7 @@ int DivPlatformGenesisExt::init(DivEngine* parent, int channels, int sugRate, un for (int i=0; i<4; i++) { isOpMuted[i]=false; } + extSys=true; reset(); return 13; diff --git a/src/engine/platform/genesisext.h b/src/engine/platform/genesisext.h index 07e0d5cc..d4dd93e7 100644 --- a/src/engine/platform/genesisext.h +++ b/src/engine/platform/genesisext.h @@ -27,7 +27,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask; int vol; unsigned char pan; OpChannel(): @@ -46,6 +46,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis { keyOff(false), portaPause(false), inPorta(false), + mask(true), vol(0), pan(3) {} }; diff --git a/src/engine/platform/genesisshared.h b/src/engine/platform/genesisshared.h deleted file mode 100644 index d58d339e..00000000 --- a/src/engine/platform/genesisshared.h +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Furnace Tracker - multi-system chiptune tracker - * Copyright (C) 2021-2022 tildearrow and contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -static unsigned short chanOffs[6]={ - 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 -}; -static unsigned short opOffs[4]={ - 0x00, 0x04, 0x08, 0x0c -}; -static bool isOutput[8][4]={ - // 1 3 2 4 - {false,false,false,true}, - {false,false,false,true}, - {false,false,false,true}, - {false,false,false,true}, - {false,false,true ,true}, - {false,true ,true ,true}, - {false,true ,true ,true}, - {true ,true ,true ,true}, -}; -static unsigned char dtTable[8]={ - 7,6,5,0,1,2,3,4 -}; - -static int orderedOps[4]={ - 0,2,1,3 -}; - -#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} -#define immWrite(a,v) if (!skipRegisterWrites) {writes.push_back(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } -#define urgentWrite(a,v) if (!skipRegisterWrites) { \ - if (writes.empty()) { \ - writes.push_back(QueuedWrite(a,v)); \ - } else if (writes.size()>16 || writes.front().addrOrVal) { \ - writes.push_back(QueuedWrite(a,v)); \ - } else { \ - writes.push_front(QueuedWrite(a,v)); \ - } \ - if (dumpWrites) { \ - addWrite(a,v); \ - } \ -} - -#include "fmshared_OPN.h" diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index 4fd30db9..f771be33 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -129,19 +129,6 @@ const char** DivPlatformLynx::getRegisterSheet() { return regCheatSheetLynx; } -const char* DivPlatformLynx::getEffectName(unsigned char effect) { - switch (effect) - { - case 0x30: case 0x31: case 0x32: case 0x33: - case 0x34: case 0x35: case 0x36: case 0x37: - case 0x38: case 0x39: case 0x3a: case 0x3b: - case 0x3c: case 0x3d: case 0x3e: case 0x3f: - return "3xxx: Load LFSR (0 to FFF)"; - break; - } - return NULL; -} - void DivPlatformLynx::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t h=start; hdata8[chan[i].samplePos++]*chan[i].outVol)>>7); } - if (chan[i].samplePos>=(int)s->samples) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - chan[i].samplePos=s->loopStart; - } else { - chan[i].sample=-1; - } + if (s->isLoopable() && chan[i].samplePos>=(int)s->getEndPosition()) { + chan[i].samplePos=s->loopStart; + } else if (chan[i].samplePos>=(int)s->samples) { + chan[i].sample=-1; } } } @@ -187,22 +172,9 @@ void DivPlatformLynx::tick(bool sysTick) { } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); - if (chan[i].pcm) chan[i].sampleBaseFreq=parent->calcBaseFreq(1.0,1.0,chan[i].std.arp.val,false); - chan[i].actualNote=chan[i].std.arp.val; - } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); - if (chan[i].pcm) chan[i].sampleBaseFreq=parent->calcBaseFreq(1.0,1.0,chan[i].note+chan[i].std.arp.val,false); - chan[i].actualNote=chan[i].note+chan[i].std.arp.val; - } - chan[i].freqChanged=true; - } - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); - if (chan[i].pcm) chan[i].sampleBaseFreq=parent->calcBaseFreq(1.0,1.0,chan[i].note,false); - chan[i].actualNote=chan[i].note; + chan[i].actualNote=parent->calcArp(chan[i].note,chan[i].std.arp.val); + chan[i].baseFreq=NOTE_PERIODIC(chan[i].actualNote); + if (chan[i].pcm) chan[i].sampleBaseFreq=parent->calcBaseFreq(1.0,1.0,chan[i].actualNote,false); chan[i].freqChanged=true; } } @@ -387,6 +359,7 @@ int DivPlatformLynx::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_MIKEY)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/lynx.h b/src/engine/platform/lynx.h index 849c3182..1d789521 100644 --- a/src/engine/platform/lynx.h +++ b/src/engine/platform/lynx.h @@ -104,7 +104,6 @@ class DivPlatformLynx: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName( unsigned char effect ); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformLynx(); diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp index 2b97f46f..0a3bfb9e 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -43,15 +43,6 @@ const char** DivPlatformMMC5::getRegisterSheet() { return regCheatSheetMMC5; } -const char* DivPlatformMMC5::getEffectName(unsigned char effect) { - switch (effect) { - case 0x12: - return "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)"; - break; - } - return NULL; -} - void DivPlatformMMC5::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t i=start; idata8[dacPos]+0x80)); } - if (++dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - dacPos=s->loopStart; - } else { - dacSample=-1; - } + dacPos++; + if (s->isLoopable() && dacPos>=s->getEndPosition()) { + dacPos=s->loopStart; + } else if (dacPos>=s->samples) { + dacSample=-1; } dacPeriod-=rate; } else { @@ -111,20 +101,12 @@ void DivPlatformMMC5::tick(bool sysTick) { if (chan[i].outVol<0) chan[i].outVol=0; rWrite(0x5000+i*4,0x30|chan[i].outVol|((chan[i].duty&3)<<6)); } + // TODO: arp macros on NES PCM? if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.duty.had) { chan[i].duty=chan[i].std.duty.val; @@ -172,7 +154,7 @@ void DivPlatformMMC5::tick(bool sysTick) { // PCM if (chan[2].freqChanged) { - chan[2].freq=parent->calcFreq(chan[2].baseFreq,chan[2].pitch,false); + chan[2].freq=parent->calcFreq(chan[2].baseFreq,chan[2].pitch,false,0,chan[2].pitch2,1,1); if (chan[2].furnaceDac) { double off=1.0; if (dacSample>=0 && dacSamplesong.sampleLen) { @@ -202,7 +184,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) { } dacPos=0; dacPeriod=0; - chan[c.chan].baseFreq=parent->song.tuning*pow(2.0f,((float)(c.value+3)/12.0f)); + chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; @@ -283,7 +265,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; case DIV_CMD_NOTE_PORTA: { - int destFreq=NOTE_PERIODIC(c.value2); + int destFreq=(c.chan==2)?(parent->calcBaseFreq(1,1,c.value2,false)):(NOTE_PERIODIC(c.value2)); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { chan[c.chan].baseFreq+=c.value; @@ -316,7 +298,11 @@ int DivPlatformMMC5::dispatch(DivCommand c) { } break; case DIV_CMD_LEGATO: - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); + if (c.chan==2) { + chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)),false); + } else { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); + } chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; @@ -324,6 +310,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/mmc5.h b/src/engine/platform/mmc5.h index f09092d5..90da3eaa 100644 --- a/src/engine/platform/mmc5.h +++ b/src/engine/platform/mmc5.h @@ -89,7 +89,6 @@ class DivPlatformMMC5: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformMMC5(); diff --git a/src/engine/platform/msm6258.cpp b/src/engine/platform/msm6258.cpp index db1848cf..0ba3ae0b 100644 --- a/src/engine/platform/msm6258.cpp +++ b/src/engine/platform/msm6258.cpp @@ -30,18 +30,6 @@ const char** DivPlatformMSM6258::getRegisterSheet() { return NULL; } -const char* DivPlatformMSM6258::getEffectName(unsigned char effect) { - switch (effect) { - case 0x20: - return "20xx: Set frequency divider (0-2)"; - break; - case 0x21: - return "21xx: Select clock rate (0: full; 1: half)"; - break; - } - return NULL; -} - void DivPlatformMSM6258::acquire(short* bufL, short* bufR, size_t start, size_t len) { short* outs[2]={ &msmOut, @@ -121,6 +109,7 @@ int DivPlatformMSM6258::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; } sample=ins->amiga.getSample(c.value); + samplePos=0; if (sample>=0 && samplesong.sampleLen) { //DivSample* s=parent->getSample(chan[c.chan].sample); if (c.value!=DIV_NOTE_NULL) { @@ -144,8 +133,8 @@ int DivPlatformMSM6258::dispatch(DivCommand c) { //DivSample* s=parent->getSample(12*sampleBank+c.value%12); sample=12*sampleBank+c.value%12; samplePos=0; - msm->ctrl_w(1); - msm->ctrl_w(2); + rWrite(0,1); + rWrite(0,2); } break; } @@ -380,7 +369,7 @@ void DivPlatformMSM6258::setFlags(unsigned int flags) { chipClock=4000000; break; } - rate=chipClock/128; + rate=chipClock/256; for (int i=0; i<1; i++) { oscBuf[i]->rate=rate; } diff --git a/src/engine/platform/msm6258.h b/src/engine/platform/msm6258.h index e3b50a17..f6a351b5 100644 --- a/src/engine/platform/msm6258.h +++ b/src/engine/platform/msm6258.h @@ -26,10 +26,6 @@ class DivPlatformMSM6258: public DivDispatch { protected: - const unsigned short chanOffs[6]={ - 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 - }; - struct Channel { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; @@ -77,12 +73,10 @@ class DivPlatformMSM6258: public DivDispatch { struct QueuedWrite { unsigned short addr; unsigned char val; - bool addrOrVal; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {} }; std::queue writes; okim6258_device* msm; - unsigned char regPool[512]; unsigned char lastBusy; unsigned char* adpcmMem; @@ -93,11 +87,6 @@ class DivPlatformMSM6258: public DivDispatch { int delay, updateOsc, sample, samplePos; - bool extMode; - - short oldWrites[512]; - short pendingWrites[512]; - friend void putDispatchChan(void*,int,int); public: @@ -120,7 +109,6 @@ class DivPlatformMSM6258: public DivDispatch { void poke(std::vector& wlist); void setFlags(unsigned int flags); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); const void* getSampleMem(int index); size_t getSampleMemCapacity(int index); size_t getSampleMemUsage(int index); diff --git a/src/engine/platform/msm6295.cpp b/src/engine/platform/msm6295.cpp index aeb29dd3..0a76a2c9 100644 --- a/src/engine/platform/msm6295.cpp +++ b/src/engine/platform/msm6295.cpp @@ -24,22 +24,17 @@ #include #define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define rWriteDelay(a,v,d) if (!skipRegisterWrites) {writes.emplace(a,v,d); if (dumpWrites) {addWrite(a,v);} } const char** DivPlatformMSM6295::getRegisterSheet() { return NULL; } -const char* DivPlatformMSM6295::getEffectName(unsigned char effect) { - switch (effect) { - case 0x20: - return "20xx: Set chip output rate (0: clock/132; 1: clock/165)"; - break; +u8 DivPlatformMSM6295::read_byte(u32 address) { + if (adpcmMem==NULL || address>=getSampleMemCapacity(0)) { + return 0; } - return NULL; -} - -u8 DivMSM6295Interface::read_byte(u32 address) { - return adpcmMem[address&0xffff]; + return adpcmMem[address&0x3ffff]; } void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t len) { @@ -49,7 +44,7 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t QueuedWrite& w=writes.front(); switch (w.addr) { case 0: // command - msm->command_w(w.val); + msm.command_w(w.val); break; case 8: // chip clock select (VGM) case 9: @@ -57,7 +52,7 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t case 11: break; case 12: // rate select - msm->ss_w(!w.val); + msm.ss_w(!w.val); break; case 14: // enable bankswitch break; @@ -70,21 +65,21 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t break; } writes.pop(); - delay=32; + delay=w.delay; } } else { delay--; } - msm->tick(); + msm.tick(); - bufL[h]=msm->out()<<4; + bufL[h]=msm.out()<<4; if (++updateOsc>=22) { updateOsc=0; // TODO: per-channel osc for (int i=0; i<4; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=msm->m_voice[i].m_muted?0:(msm->m_voice[i].m_out<<6); + oscBuf[i]->data[oscBuf[i]->needle++]=msm.m_voice[i].m_muted?0:(msm.m_voice[i].m_out<<6); } } } @@ -118,7 +113,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - rWrite(0,(8<getSample(12*sampleBank+c.value%12); chan[c.chan].sample=12*sampleBank+c.value%12; - rWrite(0,(8<(parent->song.sample.size()/12)) { sampleBank=parent->song.sample.size()/12; } - iface.sampleBank=sampleBank; break; case DIV_CMD_LEGATO: { break; @@ -212,7 +206,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) { void DivPlatformMSM6295::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - msm->m_voice[ch].m_muted=mute; + msm.m_voice[ch].m_muted=mute; } void DivPlatformMSM6295::forceIns() { @@ -253,8 +247,8 @@ void DivPlatformMSM6295::poke(std::vector& wlist) { void DivPlatformMSM6295::reset() { while (!writes.empty()) writes.pop(); - msm->reset(); - msm->ss_w(false); + msm.reset(); + msm.ss_w(rateSelInit); if (dumpWrites) { addWrite(0xffffffff,0); } @@ -268,7 +262,8 @@ void DivPlatformMSM6295::reset() { } sampleBank=0; - rateSel=false; + rateSel=rateSelInit; + rWrite(12,!rateSelInit); delay=0; } @@ -343,7 +338,9 @@ void DivPlatformMSM6295::renderSamples() { } void DivPlatformMSM6295::setFlags(unsigned int flags) { - switch (flags) { + rateSelInit=(flags>>7)&1; + switch (flags&0x7f) { + default: case 0: chipClock=4000000/4; break; @@ -383,22 +380,27 @@ void DivPlatformMSM6295::setFlags(unsigned int flags) { case 12: chipClock=1500000; break; - default: - chipClock=4000000/4; + case 13: + chipClock=3000000; + break; + case 14: + chipClock=COLOR_NTSC/3.0; break; } rate=chipClock/3; for (int i=0; i<4; i++) { oscBuf[i]->rate=rate/22; } + if (rateSel!=rateSelInit) { + rWrite(12,!rateSelInit); + rateSel=rateSelInit; + } } int DivPlatformMSM6295::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; adpcmMem=new unsigned char[getSampleMemCapacity(0)]; adpcmMemLen=0; - iface.adpcmMem=adpcmMem; - iface.sampleBank=0; dumpWrites=false; skipRegisterWrites=false; updateOsc=0; @@ -406,7 +408,6 @@ int DivPlatformMSM6295::init(DivEngine* p, int channels, int sugRate, unsigned i isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - msm=new msm6295_core(iface); setFlags(flags); reset(); return 4; @@ -416,7 +417,6 @@ void DivPlatformMSM6295::quit() { for (int i=0; i<4; i++) { delete oscBuf[i]; } - delete msm; delete[] adpcmMem; } diff --git a/src/engine/platform/msm6295.h b/src/engine/platform/msm6295.h index 3e019208..0953bd33 100644 --- a/src/engine/platform/msm6295.h +++ b/src/engine/platform/msm6295.h @@ -24,60 +24,31 @@ #include #include "sound/oki/msm6295.hpp" -class DivMSM6295Interface: public vgsound_emu_mem_intf { - public: - unsigned char* adpcmMem; - int sampleBank; - u8 read_byte(u32 address); - DivMSM6295Interface(): adpcmMem(NULL), sampleBank(0) {} -}; - -class DivPlatformMSM6295: public DivDispatch { +class DivPlatformMSM6295: public DivDispatch, public vgsound_emu_mem_intf { protected: - const unsigned short chanOffs[6]={ - 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 - }; - struct Channel { - unsigned char freqH, freqL; - int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; - unsigned char psgMode, autoEnvNum, autoEnvDen; + int note, ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, furnacePCM, hardReset; int vol, outVol; int sample; - unsigned char pan; DivMacroInt std; void macroInit(DivInstrument* which) { std.init(which); - pitch2=0; } Channel(): - freqH(0), - freqL(0), - freq(0), - baseFreq(0), - pitch(0), - pitch2(0), - portaPauseFreq(0), note(0), ins(-1), - psgMode(1), - autoEnvNum(0), - autoEnvDen(0), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), - portaPause(false), - inPorta(false), furnacePCM(false), hardReset(false), vol(0), outVol(15), - sample(-1), - pan(3) {} + sample(-1) {} }; Channel chan[4]; DivDispatchOscBuffer* oscBuf[4]; @@ -85,57 +56,58 @@ class DivPlatformMSM6295: public DivDispatch { struct QueuedWrite { unsigned short addr; unsigned char val; - bool addrOrVal; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + unsigned short delay; + QueuedWrite(unsigned short a, unsigned char v, unsigned short d=32): + addr(a), + val(v), + delay(d) {} }; std::queue writes; - msm6295_core* msm; - unsigned char regPool[512]; + msm6295_core msm; unsigned char lastBusy; unsigned char* adpcmMem; size_t adpcmMemLen; - DivMSM6295Interface iface; unsigned char sampleBank; int delay, updateOsc; - bool extMode; - bool rateSel; + bool rateSel=false, rateSelInit=false; - short oldWrites[512]; - short pendingWrites[512]; - friend void putDispatchChan(void*,int,int); public: - void acquire(short* bufL, short* bufR, size_t start, size_t len); - int dispatch(DivCommand c); - void* getChanState(int chan); - DivMacroInt* getChanMacroInt(int ch); - DivDispatchOscBuffer* getOscBuffer(int chan); - unsigned char* getRegisterPool(); - int getRegisterPoolSize(); - void reset(); - void forceIns(); - void tick(bool sysTick=true); - void muteChannel(int ch, bool mute); - bool keyOffAffectsArp(int ch); - float getPostAmp(); - void notifyInsChange(int ins); - void notifyInsDeletion(void* ins); - void poke(unsigned int addr, unsigned short val); - void poke(std::vector& wlist); - void setFlags(unsigned int flags); - const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); - const void* getSampleMem(int index); - size_t getSampleMemCapacity(int index); - size_t getSampleMemUsage(int index); - void renderSamples(); - - int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); - void quit(); + virtual u8 read_byte(u32 address) override; + virtual void acquire(short* bufL, short* bufR, size_t start, size_t len) override; + virtual int dispatch(DivCommand c) override; + virtual void* getChanState(int chan) override; + virtual DivMacroInt* getChanMacroInt(int ch) override; + virtual DivDispatchOscBuffer* getOscBuffer(int chan) override; + virtual unsigned char* getRegisterPool() override; + virtual int getRegisterPoolSize() override; + virtual void reset() override; + virtual void forceIns() override; + virtual void tick(bool sysTick=true) override; + virtual void muteChannel(int ch, bool mute) override; + virtual bool keyOffAffectsArp(int ch) override; + virtual float getPostAmp() override; + virtual void notifyInsChange(int ins) override; + virtual void notifyInsDeletion(void* ins) override; + virtual void poke(unsigned int addr, unsigned short val) override; + virtual void poke(std::vector& wlist) override; + virtual void setFlags(unsigned int flags) override; + virtual const char** getRegisterSheet() override; + virtual const void* getSampleMem(int index) override; + virtual size_t getSampleMemCapacity(int index) override; + virtual size_t getSampleMemUsage(int index) override; + virtual void renderSamples() override; + + virtual int init(DivEngine* parent, int channels, int sugRate, unsigned int flags) override; + virtual void quit() override; + DivPlatformMSM6295(): + DivDispatch(), + vgsound_emu_mem_intf(), + msm(*this) {} ~DivPlatformMSM6295(); }; #endif diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 50f9d836..bc985b42 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -108,51 +108,6 @@ const char** DivPlatformN163::getRegisterSheet() { return regCheatSheetN163; } -const char* DivPlatformN163::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Select waveform"; - break; - case 0x11: - return "11xx: Set waveform position in RAM (single nibble unit)"; - break; - case 0x12: - return "12xx: Set waveform length in RAM (04 to FC, 4 nibble unit)"; - break; - case 0x13: - return "130x: Change waveform update mode (0: off, bit 0: update now, bit 1: update when every waveform changes)"; - break; - case 0x14: - return "14xx: Select waveform for load to RAM"; - break; - case 0x15: - return "15xx: Set waveform position for load to RAM (single nibble unit)"; - break; - case 0x16: - return "16xx: Set waveform length for load to RAM (04 to FC, 4 nibble unit)"; - break; - case 0x17: - return "170x: Change waveform load mode (0: off, bit 0: load now, bit 1: load when every waveform changes)"; - break; - case 0x18: - return "180x: Change channel limits (0 to 7, x + 1)"; - break; - case 0x20: - return "20xx: (Global) Select waveform for load to RAM"; - break; - case 0x21: - return "21xx: (Global) Set waveform position for load to RAM (single nibble unit)"; - break; - case 0x22: - return "22xx: (Global) Set waveform length for load to RAM (04 to FC, 4 nibble unit)"; - break; - case 0x23: - return "230x: (Global) Change waveform load mode (0: off, bit 0: load now, bit 1: load when every waveform changes)"; - break; - } - return NULL; -} - void DivPlatformN163::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t i=start; icalcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.duty.had) { if (chan[i].wavePos!=chan[i].std.duty.val) { @@ -562,6 +508,7 @@ int DivPlatformN163::dispatch(DivCommand c) { chan[c.chan].keyOn=true; } } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: @@ -695,6 +642,9 @@ void DivPlatformN163::setFlags(unsigned int flags) { for (int i=0; i<8; i++) { oscBuf[i]->rate=rate/(initChanMax+1); } + + // needed to make sure changing channel count won't trigger glitches + reset(); } int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { diff --git a/src/engine/platform/n163.h b/src/engine/platform/n163.h index 5e44d3dd..c77510f5 100644 --- a/src/engine/platform/n163.h +++ b/src/engine/platform/n163.h @@ -110,7 +110,6 @@ class DivPlatformN163: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformN163(); diff --git a/src/engine/platform/namcowsg.cpp b/src/engine/platform/namcowsg.cpp index 1e8ff46d..236790e3 100644 --- a/src/engine/platform/namcowsg.cpp +++ b/src/engine/platform/namcowsg.cpp @@ -151,22 +151,7 @@ const char** DivPlatformNamcoWSG::getRegisterSheet() { return regCheatSheetNamcoWSG; } -const char* DivPlatformNamcoWSG::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Change waveform"; - break; - case 0x11: - return "11xx: Toggle noise mode"; - break; - } - return NULL; -} - void DivPlatformNamcoWSG::acquire(short* bufL, short* bufR, size_t start, size_t len) { - short* buf[2]={ - bufL+start, bufR+start - }; while (!writes.empty()) { QueuedWrite w=writes.front(); switch (devType) { @@ -186,7 +171,15 @@ void DivPlatformNamcoWSG::acquire(short* bufL, short* bufR, size_t start, size_t regPool[w.addr&0x3f]=w.val; writes.pop(); } - namco->sound_stream_update(buf,len); + for (size_t h=start; hsound_stream_update(buf,1); + for (int i=0; idata[oscBuf[i]->needle++]=namco->m_channel_list[i].last_out*chans; + } + } } void DivPlatformNamcoWSG::updateWave(int ch) { @@ -213,18 +206,9 @@ void DivPlatformNamcoWSG::tick(bool sysTick) { } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.wave.had) { if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { @@ -317,7 +301,7 @@ void DivPlatformNamcoWSG::tick(bool sysTick) { } rWrite((i<<3)+0x04,chan[i].freq&0xff); rWrite((i<<3)+0x05,(chan[i].freq>>8)&0xff); - rWrite((i<<3)+0x06,((chan[i].freq>>15)&15)|(i<<4)); + rWrite((i<<3)+0x06,((chan[i].freq>>16)&15)|(i<<4)); } break; case 30: @@ -331,7 +315,7 @@ void DivPlatformNamcoWSG::tick(bool sysTick) { } rWrite((i<<3)+0x103,chan[i].freq&0xff); rWrite((i<<3)+0x102,(chan[i].freq>>8)&0xff); - rWrite((i<<3)+0x101,((chan[i].freq>>15)&15)|(i<<4)); + rWrite((i<<3)+0x101,((chan[i].freq>>16)&15)|(i<<4)); } break; } @@ -437,6 +421,7 @@ int DivPlatformNamcoWSG::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/namcowsg.h b/src/engine/platform/namcowsg.h index 4ab81bdc..ede25e8e 100644 --- a/src/engine/platform/namcowsg.h +++ b/src/engine/platform/namcowsg.h @@ -96,7 +96,6 @@ class DivPlatformNamcoWSG: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformNamcoWSG(); diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 50e548ea..50fcd5ca 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -62,27 +62,6 @@ const char** DivPlatformNES::getRegisterSheet() { return regCheatSheetNES; } -const char* DivPlatformNES::getEffectName(unsigned char effect) { - switch (effect) { - case 0x11: - return "Write to delta modulation counter (0 to 7F)"; - break; - case 0x12: - return "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)"; - break; - case 0x13: - return "13xy: Sweep up (x: time; y: shift)"; - break; - case 0x14: - return "14xy: Sweep down (x: time; y: shift)"; - break; - case 0x18: - return "18xx: Select PCM/DPCM mode (0: PCM; 1: DPCM)"; - break; - } - return NULL; -} - void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) { if (useNP) { nes1_NP->Write(addr,data); @@ -108,12 +87,11 @@ void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) { rWrite(0x4011,next); \ } \ } \ - if (++dacPos>=s->samples) { \ - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { \ - dacPos=s->loopStart; \ - } else { \ - dacSample=-1; \ - } \ + dacPos++; \ + if (s->isLoopable() && dacPos>=s->getEndPosition()) { \ + dacPos=s->loopStart; \ + } else if (dacPos>=s->samples) { \ + dacSample=-1; \ } \ dacPeriod-=rate; \ } else { \ @@ -137,11 +115,11 @@ void DivPlatformNES::acquire_puNES(short* bufL, short* bufR, size_t start, size_ bufL[i]=sample; if (++writeOscBuf>=32) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=nes->S1.output<<11; - oscBuf[1]->data[oscBuf[1]->needle++]=nes->S2.output<<11; - oscBuf[2]->data[oscBuf[2]->needle++]=nes->TR.output<<11; - oscBuf[3]->data[oscBuf[3]->needle++]=nes->NS.output<<11; - oscBuf[4]->data[oscBuf[4]->needle++]=nes->DMC.output<<8; + oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:(nes->S1.output<<11); + oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:(nes->S2.output<<11); + oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:(nes->TR.output<<11); + oscBuf[3]->data[oscBuf[3]->needle++]=isMuted[3]?0:(nes->NS.output<<11); + oscBuf[4]->data[oscBuf[4]->needle++]=isMuted[4]?0:(nes->DMC.output<<8); } } } @@ -241,28 +219,15 @@ void DivPlatformNES::tick(bool sysTick) { } if (chan[i].std.arp.had) { if (i==3) { // noise - if (chan[i].std.arp.mode) { - chan[i].baseFreq=chan[i].std.arp.val; - } else { - chan[i].baseFreq=chan[i].note+chan[i].std.arp.val; - } + chan[i].baseFreq=parent->calcArp(chan[i].note,chan[i].std.arp.val); if (chan[i].baseFreq>255) chan[i].baseFreq=255; if (chan[i].baseFreq<0) chan[i].baseFreq=0; } else { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.duty.had) { chan[i].duty=chan[i].std.duty.val; @@ -573,6 +538,7 @@ int DivPlatformNES::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/nes.h b/src/engine/platform/nes.h index 35c51df7..c0000330 100644 --- a/src/engine/platform/nes.h +++ b/src/engine/platform/nes.h @@ -106,7 +106,6 @@ class DivPlatformNES: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); const void* getSampleMem(int index); size_t getSampleMemCapacity(int index); size_t getSampleMemUsage(int index); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index fbf91448..2dd6d1dc 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -152,98 +152,6 @@ const int orderedOpsL[4]={ #define ADDR_FREQH 0xb0 #define ADDR_LR_FB_ALG 0xc0 -const char* DivPlatformOPL::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Set global AM depth (0: 1dB, 1: 4.8dB)"; - break; - case 0x11: - return "11xx: Set feedback (0 to 7)"; - break; - case 0x12: - return "12xx: Set level of operator 1 (0 highest, 3F lowest)"; - break; - case 0x13: - return "13xx: Set level of operator 2 (0 highest, 3F lowest)"; - break; - case 0x14: - return "14xx: Set level of operator 3 (0 highest, 3F lowest; 4-op only)"; - break; - case 0x15: - return "15xx: Set level of operator 4 (0 highest, 3F lowest; 4-op only)"; - break; - case 0x16: - return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; - break; - case 0x17: - return "17xx: Set global vibrato depth (0: normal, 1: double)"; - break; - case 0x18: - if (properDrumsSys) { - return "18xx: Toggle drums mode (1: enabled; 0: disabled)"; - } - break; - case 0x19: - return "19xx: Set attack of all operators (0 to F)"; - break; - case 0x1a: - return "1Axx: Set attack of operator 1 (0 to F)"; - break; - case 0x1b: - return "1Bxx: Set attack of operator 2 (0 to F)"; - break; - case 0x1c: - return "1Cxx: Set attack of operator 3 (0 to F; 4-op only)"; - break; - case 0x1d: - return "1Dxx: Set attack of operator 4 (0 to F; 4-op only)"; - break; - case 0x2a: - return "2Axy: Set waveform (x: operator from 1 to 4 (0 for all ops); y: waveform from 0 to 3 in OPL2 and 0 to 7 in OPL3)"; - break; - case 0x30: - return "30xx: Toggle hard envelope reset on new notes"; - break; - case 0x50: - return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; - break; - case 0x51: - return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; - break; - case 0x52: - return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; - break; - case 0x53: - return "53xy: Set vibrato (x: operator from 1 to 4 (0 for all ops); y: enabled)"; - break; - case 0x54: - return "54xy: Set key scale level (x: operator from 1 to 4 (0 for all ops); y: level from 0 to 3)"; - break; - case 0x55: - return "55xy: Set envelope sustain (x: operator from 1 to 4 (0 for all ops); y: enabled)"; - break; - case 0x56: - return "56xx: Set decay of all operators (0 to F)"; - break; - case 0x57: - return "57xx: Set decay of operator 1 (0 to F)"; - break; - case 0x58: - return "58xx: Set decay of operator 2 (0 to F)"; - break; - case 0x59: - return "59xx: Set decay of operator 3 (0 to F; 4-op only)"; - break; - case 0x5a: - return "5Axx: Set decay of operator 4 (0 to F; 4-op only)"; - break; - case 0x5b: - return "5Bxy: Set whether key will scale envelope (x: operator from 1 to 4 (0 for all ops); y: enabled)"; - break; - } - return NULL; -} - void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) { static short o[2]; static int os[2]; @@ -277,8 +185,13 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_ regPool[w.addr&511]=w.val; writes.pop(); } - - OPL3_Generate(&fm,o); os[0]+=o[0]; os[1]+=o[1]; + + if (downsample) { + OPL3_GenerateResampled(&fm,o); + } else { + OPL3_Generate(&fm,o); + } + os[0]+=o[0]; os[1]+=o[1]; if (adpcmChan>=0) { adpcmB->clock(); @@ -288,24 +201,45 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_ if (!isMuted[adpcmChan]) { os[0]-=aOut.data[0]>>3; os[1]-=aOut.data[0]>>3; - oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]+=aOut.data[0]; + oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=aOut.data[0]; } else { oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=0; } } - for (int i=0; idata[oscBuf[i]->needle]=0; - if (fm.channel[i].out[0]!=NULL) { - oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[0]; + if (fm.rhy&0x20) { + for (int i=0; idata[oscBuf[i]->needle]=0; + if (fm.channel[i].out[0]!=NULL) { + oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[0]; + } + if (fm.channel[i].out[1]!=NULL) { + oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[1]; + } + oscBuf[i]->data[oscBuf[i]->needle]<<=1; + oscBuf[i]->needle++; } - if (fm.channel[i].out[1]!=NULL) { - oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[1]; + // special + oscBuf[melodicChans+1]->data[oscBuf[melodicChans+1]->needle++]=fm.slot[16].out*6; + oscBuf[melodicChans+2]->data[oscBuf[melodicChans+2]->needle++]=fm.slot[14].out*6; + oscBuf[melodicChans+3]->data[oscBuf[melodicChans+3]->needle++]=fm.slot[17].out*6; + oscBuf[melodicChans+4]->data[oscBuf[melodicChans+4]->needle++]=fm.slot[13].out*6; + } else { + for (int i=0; idata[oscBuf[i]->needle]=0; + if (fm.channel[i].out[0]!=NULL) { + oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[0]; + } + if (fm.channel[i].out[1]!=NULL) { + oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[1]; + } + oscBuf[i]->data[oscBuf[i]->needle]<<=1; + oscBuf[i]->needle++; } - oscBuf[i]->data[oscBuf[i]->needle]<<=1; - oscBuf[i]->needle++; } if (os[0]<-32768) os[0]=-32768; @@ -315,7 +249,9 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_ if (os[1]>32767) os[1]=32767; bufL[h]=os[0]; - bufR[h]=os[1]; + if (oplType==3 || oplType==759) { + bufR[h]=os[1]; + } } } @@ -363,18 +299,9 @@ void DivPlatformOPL::tick(bool sysTick) { if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); - chan[i].freqChanged=true; - } } if (oplType==3 && chan[i].std.panL.had) { @@ -557,18 +484,9 @@ void DivPlatformOPL::tick(bool sysTick) { if (chan[adpcmChan].std.arp.had) { if (!chan[adpcmChan].inPorta) { - if (chan[adpcmChan].std.arp.mode) { - chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].std.arp.val); - } else { - chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].note+(signed char)chan[adpcmChan].std.arp.val); - } + chan[adpcmChan].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[adpcmChan].note,chan[adpcmChan].std.arp.val)); } chan[adpcmChan].freqChanged=true; - } else { - if (chan[adpcmChan].std.arp.mode && chan[adpcmChan].std.arp.finished) { - chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].note); - chan[adpcmChan].freqChanged=true; - } } } if (chan[adpcmChan].freqChanged) { @@ -596,8 +514,9 @@ void DivPlatformOPL::tick(bool sysTick) { if (chan[i].freqChanged) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)*2,chan[i].pitch2,chipClock,CHIP_FREQBASE); if (chan[i].fixedFreq>0) chan[i].freq=chan[i].fixedFreq; + if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>131071) chan[i].freq=131071; - int freqt=toFreq(chan[i].freq)+chan[i].pitch2; + int freqt=toFreq(chan[i].freq); chan[i].freqH=freqt>>8; chan[i].freqL=freqt&0xff; immWrite(chanMap[i]+ADDR_FREQ,chan[i].freqL); @@ -677,6 +596,9 @@ void DivPlatformOPL::muteChannel(int ch, bool mute) { fm.channel[outChanMap[ch]].muted=mute; } int ops=(slots[3][ch]!=255 && chan[ch].state.ops==4 && oplType==3)?4:2; + if (ch&1 && ch<12) { + if (chan[ch-1].fourOp) return; + } chan[ch].fourOp=(ops==4); update4OpMask=true; for (int i=0; ioffB+s->lengthB-1; immWrite(11,(end>>2)&0xff); immWrite(12,(end>>10)&0xff); - immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat + immWrite(7,(s->isLoopable())?0xb0:0xa0); // start/repeat if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); @@ -777,8 +699,8 @@ int DivPlatformOPL::dispatch(DivCommand c) { int end=s->offB+s->lengthB-1; immWrite(11,(end>>2)&0xff); immWrite(12,(end>>10)&0xff); - immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat - int freq=(65536.0*(double)s->rate)/(double)rate; + immWrite(7,(s->isLoopable())?0xb0:0xa0); // start/repeat + int freq=(65536.0*(double)s->rate)/(double)chipRateBase; immWrite(16,freq&0xff); immWrite(17,(freq>>8)&0xff); } @@ -842,6 +764,13 @@ int DivPlatformOPL::dispatch(DivCommand c) { int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; chan[c.chan].fourOp=(ops==4); if (chan[c.chan].fourOp) { + /* + if (chan[c.chan+1].active) { + chan[c.chan+1].keyOff=true; + chan[c.chan+1].keyOn=false; + chan[c.chan+1].active=false; + }*/ + chan[c.chan+1].insChanged=true; chan[c.chan+1].macroInit(NULL); } update4OpMask=true; @@ -1404,6 +1333,9 @@ int DivPlatformOPL::dispatch(DivCommand c) { return 63; break; case DIV_CMD_PRE_PORTA: + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) { + chan[c.chan].baseFreq=(c.chan==adpcmChan)?(NOTE_ADPCMB(chan[c.chan].note)):(NOTE_FREQUENCY(chan[c.chan].note)); + } chan[c.chan].inPorta=c.value; break; case DIV_CMD_PRE_NOTE: @@ -1508,7 +1440,12 @@ void DivPlatformOPL::reset() { fm_ymfm->reset(); } */ - OPL3_Reset(&fm,rate); + if (downsample) { + const unsigned int downsampledRate=(unsigned int)((double)rate*rate/chipRateBase); + OPL3_Reset(&fm,downsampledRate); + } else { + OPL3_Reset(&fm,rate); + } if (dumpWrites) { addWrite(0xffffffff,0); } @@ -1585,7 +1522,7 @@ void DivPlatformOPL::reset() { } bool DivPlatformOPL::isStereo() { - return true; + return (oplType==3 || oplType==759); } bool DivPlatformOPL::keyOffAffectsArp(int ch) { @@ -1625,6 +1562,7 @@ void DivPlatformOPL::setYMFM(bool use) { void DivPlatformOPL::setOPLType(int type, bool drums) { pretendYMU=false; + downsample=false; adpcmChan=-1; switch (type) { case 1: case 2: case 8950: @@ -1633,7 +1571,7 @@ void DivPlatformOPL::setOPLType(int type, bool drums) { slots=drums?slotsDrums:slotsNonDrums; chanMap=drums?chanMapOPL2Drums:chanMapOPL2; outChanMap=outChanMapOPL2; - chipFreqBase=9440540*0.25; + chipFreqBase=32768*72; chans=9; melodicChans=drums?6:9; totalChans=drums?11:9; @@ -1641,23 +1579,27 @@ void DivPlatformOPL::setOPLType(int type, bool drums) { adpcmChan=drums?11:9; } break; - case 3: case 759: + case 3: case 4: case 759: slotsNonDrums=slotsOPL3; slotsDrums=slotsOPL3Drums; slots=drums?slotsDrums:slotsNonDrums; chanMap=drums?chanMapOPL3Drums:chanMapOPL3; outChanMap=outChanMapOPL3; - chipFreqBase=9440540; + chipFreqBase=32768*288; chans=18; melodicChans=drums?15:18; totalChans=drums?20:18; if (type==759) { pretendYMU=true; adpcmChan=16; + } else if (type==4) { + chipFreqBase=32768*684; + downsample=true; } break; } - if (type==759) { + chipType=type; + if (type==759 || type==4) { oplType=3; } else if (type==8950) { oplType=1; @@ -1692,20 +1634,76 @@ void DivPlatformOPL::setFlags(unsigned int flags) { rate=chipClock/36; }*/ - if (oplType==3) { - chipClock=COLOR_NTSC*4.0; - rate=chipClock/288; - } else { - chipClock=COLOR_NTSC; - rate=chipClock/72; + switch (chipType) { + default: + case 1: case 2: case 8950: + switch (flags&0xff) { + case 0x01: + chipClock=COLOR_PAL*4.0/5.0; + break; + case 0x02: + chipClock=4000000.0; + break; + case 0x03: + chipClock=3000000.0; + break; + case 0x04: + chipClock=38400*13*8; // 31948800/8 + break; + case 0x05: + chipClock=3500000.0; + break; + default: + chipClock=COLOR_NTSC; + break; + } + rate=chipClock/72; + chipRateBase=rate; + break; + case 3: + switch (flags&0xff) { + case 0x01: + chipClock=COLOR_PAL*16.0/5.0; + break; + case 0x02: + chipClock=14000000.0; + break; + case 0x03: + chipClock=16000000.0; + break; + case 0x04: + chipClock=15000000.0; + break; + default: + chipClock=COLOR_NTSC*4.0; + break; + } + rate=chipClock/288; + chipRateBase=rate; + break; + case 4: + switch (flags&0xff) { + case 0x01: + chipClock=COLOR_PAL*32.0/5.0; + break; + case 0x02: + chipClock=33868800.0; + break; + default: + chipClock=COLOR_NTSC*8.0; + break; + } + rate=chipClock/768; + chipRateBase=chipClock/684; + break; + case 759: + rate=48000; + chipRateBase=rate; + chipClock=rate*288; + break; } - if (pretendYMU) { - rate=48000; - chipClock=rate*288; - } - - for (int i=0; i<18; i++) { + for (int i=0; i<20; i++) { oscBuf[i]->rate=rate; } } @@ -1756,7 +1754,7 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int f for (int i=0; i<20; i++) { isMuted[i]=false; } - for (int i=0; i<18; i++) { + for (int i=0; i<20; i++) { oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); @@ -1774,7 +1772,7 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int f } void DivPlatformOPL::quit() { - for (int i=0; i<18; i++) { + for (int i=0; i<20; i++) { delete oscBuf[i]; } if (adpcmChan>=0) { diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index a051074a..e3b16679 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -75,7 +75,7 @@ class DivPlatformOPL: public DivDispatch { } }; Channel chan[20]; - DivDispatchOscBuffer* oscBuf[18]; + DivDispatchOscBuffer* oscBuf[20]; bool isMuted[20]; struct QueuedWrite { unsigned short addr; @@ -95,8 +95,8 @@ class DivPlatformOPL: public DivDispatch { const unsigned char** slots; const unsigned short* chanMap; const unsigned char* outChanMap; - double chipFreqBase; - int delay, oplType, chans, melodicChans, totalChans, adpcmChan, sampleBank; + int chipFreqBase, chipRateBase; + int delay, chipType, oplType, chans, melodicChans, totalChans, adpcmChan, sampleBank; unsigned char lastBusy; unsigned char drumState; unsigned char drumVol[5]; @@ -107,7 +107,7 @@ class DivPlatformOPL: public DivDispatch { unsigned char lfoValue; - bool useYMFM, update4OpMask, pretendYMU; + bool useYMFM, update4OpMask, pretendYMU, downsample; short oldWrites[512]; short pendingWrites[512]; @@ -145,7 +145,6 @@ class DivPlatformOPL: public DivDispatch { int getPortaFloor(int ch); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); - const char* getEffectName(unsigned char effect); const void* getSampleMem(int index); size_t getSampleMemCapacity(int index); size_t getSampleMemUsage(int index); diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 6d77ca1f..9485551a 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -27,68 +27,6 @@ #define CHIP_FREQBASE 1180068 -const char* DivPlatformOPLL::getEffectName(unsigned char effect) { - switch (effect) { - case 0x11: - return "11xx: Set feedback (0 to 7)"; - break; - case 0x12: - return "12xx: Set level of operator 1 (0 highest, 3F lowest)"; - break; - case 0x13: - return "13xx: Set level of operator 2 (0 highest, F lowest)"; - break; - case 0x16: - return "16xy: Set operator multiplier (x: operator from 1 to 2; y: multiplier)"; - break; - case 0x18: - if (properDrumsSys) { - return "18xx: Toggle drums mode (1: enabled; 0: disabled)"; - } - break; - case 0x19: - return "19xx: Set attack of all operators (0 to F)"; - break; - case 0x1a: - return "1Axx: Set attack of operator 1 (0 to F)"; - break; - case 0x1b: - return "1Bxx: Set attack of operator 2 (0 to F)"; - break; - case 0x50: - return "50xy: Set AM (x: operator from 1 to 2 (0 for all ops); y: AM)"; - break; - case 0x51: - return "51xy: Set sustain level (x: operator from 1 to 2 (0 for all ops); y: sustain)"; - break; - case 0x52: - return "52xy: Set release (x: operator from 1 to 2 (0 for all ops); y: release)"; - break; - case 0x53: - return "53xy: Set vibrato (x: operator from 1 to 2 (0 for all ops); y: enabled)"; - break; - case 0x54: - return "54xy: Set key scale level (x: operator from 1 to 2 (0 for all ops); y: level from 0 to 3)"; - break; - case 0x55: - return "55xy: Set envelope sustain (x: operator from 1 to 2 (0 for all ops); y: enabled)"; - break; - case 0x56: - return "56xx: Set decay of all operators (0 to F)"; - break; - case 0x57: - return "57xx: Set decay of operator 1 (0 to F)"; - break; - case 0x58: - return "58xx: Set decay of operator 2 (0 to F)"; - break; - case 0x5b: - return "5Bxy: Set whether key will scale envelope (x: operator from 1 to 2 (0 for all ops); y: enabled)"; - break; - } - return NULL; -} - const unsigned char cycleMapOPLL[18]={ 8, 7, 6, 7, 8, 7, 8, 6, 0, 1, 2, 7, 8, 9, 3, 4, 5, 9 }; @@ -97,6 +35,10 @@ const unsigned char drumSlot[11]={ 0, 0, 0, 0, 0, 0, 6, 7, 8, 8, 7 }; +const unsigned char visMapOPLL[9]={ + 6, 7, 8, 3, 4, 5, 0, 1, 2 +}; + void DivPlatformOPLL::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) { static int o[2]; static int os; @@ -124,10 +66,18 @@ void DivPlatformOPLL::acquire_nuked(short* bufL, short* bufR, size_t start, size OPLL_Clock(&fm,o); unsigned char nextOut=cycleMapOPLL[fm.cycles]; if ((nextOut>=6 && properDrums) || !isMuted[nextOut]) { - oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=(o[0]+o[1])<<6; os+=(o[0]+o[1]); + if (vrc7 || (fm.rm_enable&0x20)) oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=(o[0]+o[1])<<6; } else { - oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=0; + if (vrc7 || (fm.rm_enable&0x20)) oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=0; + } + } + if (!(vrc7 || (fm.rm_enable&0x20))) for (int i=0; i<9; i++) { + unsigned char ch=visMapOPLL[i]; + if ((i>=6 && properDrums) || !isMuted[ch]) { + oscBuf[ch]->data[oscBuf[ch]->needle++]=(fm.output_ch[i])<<6; + } else { + oscBuf[ch]->data[oscBuf[ch]->needle++]=0; } } os*=50; @@ -157,18 +107,9 @@ void DivPlatformOPLL::tick(bool sysTick) { if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.wave.had && chan[i].state.opllPreset!=16) { @@ -819,6 +760,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: if (c.chan>=9 && !properDrums) return 0; + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_PRE_NOTE: diff --git a/src/engine/platform/opll.h b/src/engine/platform/opll.h index dad660de..21a77b4e 100644 --- a/src/engine/platform/opll.h +++ b/src/engine/platform/opll.h @@ -122,7 +122,6 @@ class DivPlatformOPLL: public DivDispatch { int getPortaFloor(int ch); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformOPLL(); diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index b8c580e5..476b1ecc 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -53,27 +53,6 @@ const char** DivPlatformPCE::getRegisterSheet() { return regCheatSheetPCE; } -const char* DivPlatformPCE::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Change waveform"; - break; - case 0x11: - return "11xx: Toggle noise mode"; - break; - case 0x12: - return "12xx: Setup LFO (0: disabled; 1: 1x depth; 2: 16x depth; 3: 256x depth)"; - break; - case 0x13: - return "13xx: Set LFO speed"; - break; - case 0x17: - return "17xx: Toggle PCM mode"; - break; - } - return NULL; -} - void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t h=start; hdata8[chan[i].dacPos]+0x80)>>3)); chan[i].dacPos++; - if (chan[i].dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - chan[i].dacPos=s->loopStart; - } else { - chan[i].dacSample=-1; - } + if (s->isLoopable() && chan[i].dacPos>=s->getEndPosition()) { + chan[i].dacPos=s->loopStart; + } else if (chan[i].dacPos>=s->samples) { + chan[i].dacSample=-1; } chan[i].dacPeriod-=rate; } @@ -117,7 +94,7 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len) pce->ResetTS(0); for (int i=0; i<6; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1])<<1; + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP((pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1])<<1,-32768,32767); } tempL[0]=(tempL[0]>>1)+(tempL[0]>>2); @@ -135,14 +112,22 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len) } void DivPlatformPCE::updateWave(int ch) { + if (chan[ch].pcm) { + chan[ch].deferredWaveUpdate=true; + return; + } chWrite(ch,0x04,0x5f); chWrite(ch,0x04,0x1f); for (int i=0; i<32; i++) { - chWrite(ch,0x06,chan[ch].ws.output[i]); + chWrite(ch,0x06,chan[ch].ws.output[(i+chan[ch].antiClickWavePos)&31]); } + chan[ch].antiClickWavePos&=31; if (chan[ch].active) { chWrite(ch,0x04,0x80|chan[ch].outVol); } + if (chan[ch].deferredWaveUpdate) { + chan[ch].deferredWaveUpdate=false; + } } // TODO: in octave 6 the noise table changes to a tonal one @@ -152,6 +137,13 @@ static unsigned char noiseFreq[12]={ void DivPlatformPCE::tick(bool sysTick) { for (int i=0; i<6; i++) { + // anti-click + if (antiClickEnabled && sysTick && chan[i].freq>0) { + chan[i].antiClickPeriodCount+=(chipClock/MAX(parent->getCurHz(),1.0f)); + chan[i].antiClickWavePos+=chan[i].antiClickPeriodCount/chan[i].freq; + chan[i].antiClickPeriodCount%=chan[i].freq; + } + chan[i].std.next(); if (chan[i].std.vol.had) { chan[i].outVol=VOL_SCALE_LOG(chan[i].vol&31,MIN(31,chan[i].std.vol.val),31); @@ -170,28 +162,12 @@ void DivPlatformPCE::tick(bool sysTick) { } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); - // noise - int noiseSeek=chan[i].std.arp.val; - if (noiseSeek<0) noiseSeek=0; - chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0); - } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); - int noiseSeek=chan[i].note+chan[i].std.arp.val; - if (noiseSeek<0) noiseSeek=0; - chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0); - } - } - chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); - int noiseSeek=chan[i].note; + int noiseSeek=parent->calcArp(chan[i].note,chan[i].std.arp.val); + chan[i].baseFreq=NOTE_PERIODIC(noiseSeek); if (noiseSeek<0) noiseSeek=0; chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0); - chan[i].freqChanged=true; } + chan[i].freqChanged=true; } if (chan[i].std.wave.had && !chan[i].pcm) { if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { @@ -220,8 +196,12 @@ void DivPlatformPCE::tick(bool sysTick) { } chan[i].freqChanged=true; } + if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) { + chan[i].antiClickWavePos=0; + chan[i].antiClickPeriodCount=0; + } if (chan[i].active) { - if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1)) { + if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) || chan[i].deferredWaveUpdate) { updateWave(i); } } @@ -445,6 +425,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: @@ -556,10 +537,18 @@ void DivPlatformPCE::setFlags(unsigned int flags) { } else { chipClock=COLOR_NTSC; } + // flags&4 will be chip revision + antiClickEnabled=!(flags&8); rate=chipClock/12; for (int i=0; i<6; i++) { oscBuf[i]->rate=rate; } + + if (pce!=NULL) { + delete pce; + pce=NULL; + } + pce=new PCE_PSG(tempL,tempR,(flags&4)?PCE_PSG::REVISION_HUC6280A:PCE_PSG::REVISION_HUC6280); } void DivPlatformPCE::poke(unsigned int addr, unsigned short val) { @@ -578,8 +567,8 @@ int DivPlatformPCE::init(DivEngine* p, int channels, int sugRate, unsigned int f isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } + pce=NULL; setFlags(flags); - pce=new PCE_PSG(tempL,tempR,PCE_PSG::REVISION_HUC6280A); reset(); return 6; } @@ -588,7 +577,10 @@ void DivPlatformPCE::quit() { for (int i=0; i<6; i++) { delete oscBuf[i]; } - delete pce; + if (pce!=NULL) { + delete pce; + pce=NULL; + } } DivPlatformPCE::~DivPlatformPCE() { diff --git a/src/engine/platform/pce.h b/src/engine/platform/pce.h index 870b5218..164b70a9 100644 --- a/src/engine/platform/pce.h +++ b/src/engine/platform/pce.h @@ -28,12 +28,12 @@ class DivPlatformPCE: public DivDispatch { struct Channel { - int freq, baseFreq, pitch, pitch2, note; + int freq, baseFreq, pitch, pitch2, note, antiClickPeriodCount, antiClickWavePos; int dacPeriod, dacRate; unsigned int dacPos; int dacSample, ins; unsigned char pan; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac, deferredWaveUpdate; signed char vol, outVol, wave; DivMacroInt std; DivWaveSynth ws; @@ -47,6 +47,8 @@ class DivPlatformPCE: public DivDispatch { pitch(0), pitch2(0), note(0), + antiClickPeriodCount(0), + antiClickWavePos(0), dacPeriod(0), dacRate(0), dacPos(0), @@ -62,6 +64,7 @@ class DivPlatformPCE: public DivDispatch { noise(false), pcm(false), furnaceDac(false), + deferredWaveUpdate(false), vol(31), outVol(31), wave(-1) {} @@ -69,6 +72,7 @@ class DivPlatformPCE: public DivDispatch { Channel chan[6]; DivDispatchOscBuffer* oscBuf[6]; bool isMuted[6]; + bool antiClickEnabled; struct QueuedWrite { unsigned char addr; unsigned char val; @@ -105,7 +109,6 @@ class DivPlatformPCE: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformPCE(); diff --git a/src/engine/platform/pcmdac.cpp b/src/engine/platform/pcmdac.cpp new file mode 100644 index 00000000..026e989c --- /dev/null +++ b/src/engine/platform/pcmdac.cpp @@ -0,0 +1,359 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#define _USE_MATH_DEFINES +#include "pcmdac.h" +#include "../engine.h" +#include + +// to ease the driver, freqency register is a 8.16 counter relative to output sample rate +#define CHIP_FREQBASE 65536 + +void DivPlatformPCMDAC::acquire(short* bufL, short* bufR, size_t start, size_t len) { + const int depthScale=(15-outDepth); + int output=0; + for (size_t h=start; hdata[oscBuf->needle++]=0; + continue; + } + if (chan.useWave || (chan.sample>=0 && chan.samplesong.sampleLen)) { + chan.audPos+=chan.freq>>16; + chan.audSub+=(chan.freq&0xffff); + if (chan.audSub>=0x10000) { + chan.audSub-=0x10000; + chan.audPos+=1; + } + if (chan.useWave) { + if (chan.audPos>=(unsigned int)(chan.audLen<<1)) { + chan.audPos=0; + } + output=(chan.ws.output[chan.audPos]^0x80)<<8; + } else { + DivSample* s=parent->getSample(chan.sample); + if (s->samples>0) { + if (s->isLoopable() && chan.audPos>=s->getEndPosition()) { + chan.audPos=s->loopStart; + } else if (chan.audPos>=s->samples) { + chan.sample=-1; + } + if (chan.audPossamples) { + output=s->data16[chan.audPos]; + } + } else { + chan.sample=-1; + } + } + } + output=output*chan.vol*chan.envVol/16384; + oscBuf->data[oscBuf->needle++]=output; + if (outStereo) { + bufL[h]=((output*chan.panL)>>(depthScale+8))<>(depthScale+8))<>depthScale)<calcArp(chan.note,chan.std.arp.val)); + } + chan.freqChanged=true; + } + if (chan.useWave && chan.std.wave.had) { + if (chan.wave!=chan.std.wave.val || chan.ws.activeChanged()) { + chan.wave=chan.std.wave.val; + chan.ws.changeWave1(chan.wave); + if (!chan.keyOff) chan.keyOn=true; + } + } + if (chan.useWave && chan.active) { + chan.ws.tick(); + } + if (chan.std.pitch.had) { + if (chan.std.pitch.mode) { + chan.pitch2+=chan.std.pitch.val; + CLAMP_VAR(chan.pitch2,-32768,32767); + } else { + chan.pitch2=chan.std.pitch.val; + } + chan.freqChanged=true; + } + if (chan.std.panL.had) { + int val=chan.std.panL.val&0x7f; + chan.panL=val*2; + } + if (chan.std.panR.had) { + int val=chan.std.panR.val&0x7f; + chan.panR=val*2; + } + if (chan.std.phaseReset.had) { + if (chan.std.phaseReset.val==1) { + chan.audPos=0; + } + } + if (chan.freqChanged || chan.keyOn || chan.keyOff) { + //DivInstrument* ins=parent->getIns(chan.ins,DIV_INS_AMIGA); + double off=1.0; + if (!chan.useWave && chan.sample>=0 && chan.samplesong.sampleLen) { + DivSample* s=parent->getSample(chan.sample); + off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0; + } + chan.freq=off*parent->calcFreq(chan.baseFreq,chan.pitch,false,2,chan.pitch2,chipClock,CHIP_FREQBASE); + if (chan.freq>16777215) chan.freq=16777215; + if (chan.keyOn) { + if (!chan.std.vol.had) { + chan.envVol=64; + } + chan.keyOn=false; + } + if (chan.keyOff) { + chan.keyOff=false; + } + chan.freqChanged=false; + } +} + +int DivPlatformPCMDAC::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan.ins,DIV_INS_AMIGA); + if (ins->amiga.useWave) { + chan.useWave=true; + chan.audLen=(ins->amiga.waveLen+1)>>1; + if (chan.insChanged) { + if (chan.wave<0) { + chan.wave=0; + chan.ws.setWidth(chan.audLen<<1); + chan.ws.changeWave1(chan.wave); + } + } + } else { + chan.sample=ins->amiga.getSample(c.value); + chan.useWave=false; + } + if (c.value!=DIV_NOTE_NULL) { + chan.baseFreq=round(NOTE_FREQUENCY(c.value)); + } + if (chan.useWave || chan.sample<0 || chan.sample>=parent->song.sampleLen) { + chan.sample=-1; + } + if (chan.setPos) { + chan.setPos=false; + } else { + chan.audPos=0; + } + chan.audSub=0; + if (c.value!=DIV_NOTE_NULL) { + chan.freqChanged=true; + chan.note=c.value; + } + chan.active=true; + chan.keyOn=true; + chan.macroInit(ins); + if (!parent->song.brokenOutVol && !chan.std.vol.will) { + chan.envVol=64; + } + if (chan.useWave) { + chan.ws.init(ins,chan.audLen<<1,255,chan.insChanged); + } + chan.insChanged=false; + break; + } + case DIV_CMD_NOTE_OFF: + chan.sample=-1; + chan.active=false; + chan.keyOff=true; + chan.macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan.std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan.ins!=c.value || c.value2==1) { + chan.ins=c.value; + chan.insChanged=true; + } + break; + case DIV_CMD_VOLUME: + if (chan.vol!=c.value) { + chan.vol=c.value; + if (!chan.std.vol.has) { + chan.envVol=64; + } + } + break; + case DIV_CMD_GET_VOLUME: + return chan.vol; + break; + case DIV_CMD_PANNING: + chan.panL=c.value; + chan.panR=c.value2; + break; + case DIV_CMD_PITCH: + chan.pitch=c.value; + chan.freqChanged=true; + break; + case DIV_CMD_WAVE: + if (!chan.useWave) break; + chan.wave=c.value; + chan.keyOn=true; + chan.ws.changeWave1(chan.wave); + break; + case DIV_CMD_NOTE_PORTA: { + DivInstrument* ins=parent->getIns(chan.ins,DIV_INS_AMIGA); + chan.sample=ins->amiga.getSample(c.value2); + int destFreq=round(NOTE_FREQUENCY(c.value2)); + bool return2=false; + if (destFreq>chan.baseFreq) { + chan.baseFreq+=c.value; + if (chan.baseFreq>=destFreq) { + chan.baseFreq=destFreq; + return2=true; + } + } else { + chan.baseFreq-=c.value; + if (chan.baseFreq<=destFreq) { + chan.baseFreq=destFreq; + return2=true; + } + } + chan.freqChanged=true; + if (return2) { + chan.inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: { + chan.baseFreq=round(NOTE_FREQUENCY(c.value+((chan.std.arp.will && !chan.std.arp.mode)?(chan.std.arp.val):(0)))); + chan.freqChanged=true; + chan.note=c.value; + break; + } + case DIV_CMD_PRE_PORTA: + if (chan.active && c.value2) { + if (parent->song.resetMacroOnPorta) chan.macroInit(parent->getIns(chan.ins,DIV_INS_AMIGA)); + } + chan.inPorta=c.value; + break; + case DIV_CMD_SAMPLE_POS: + if (chan.useWave) break; + chan.audPos=c.value; + chan.setPos=true; + break; + case DIV_CMD_GET_VOLMAX: + return 255; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformPCMDAC::muteChannel(int ch, bool mute) { + isMuted=mute; +} + +void DivPlatformPCMDAC::forceIns() { + chan.insChanged=true; + chan.freqChanged=true; + chan.audPos=0; + chan.sample=-1; +} + +void* DivPlatformPCMDAC::getChanState(int ch) { + return &chan; +} + +DivDispatchOscBuffer* DivPlatformPCMDAC::getOscBuffer(int ch) { + return oscBuf; +} + +void DivPlatformPCMDAC::reset() { + chan=DivPlatformPCMDAC::Channel(); + chan.std.setEngine(parent); + chan.ws.setEngine(parent); + chan.ws.init(NULL,32,255); +} + +bool DivPlatformPCMDAC::isStereo() { + return true; +} + +DivMacroInt* DivPlatformPCMDAC::getChanMacroInt(int ch) { + return &chan.std; +} + +void DivPlatformPCMDAC::notifyInsChange(int ins) { + if (chan.ins==ins) { + chan.insChanged=true; + } +} + +void DivPlatformPCMDAC::notifyWaveChange(int wave) { + if (chan.useWave && chan.wave==wave) { + chan.ws.changeWave1(wave); + } +} + +void DivPlatformPCMDAC::notifyInsDeletion(void* ins) { + chan.std.notifyInsDeletion((DivInstrument*)ins); +} + +void DivPlatformPCMDAC::setFlags(unsigned int flags) { + // default to 44100Hz 16-bit stereo + if (!flags) flags=0x1f0000|44099; + rate=(flags&0xffff)+1; + // rate can't be too low or the resampler will break + if (rate<1000) rate=1000; + chipClock=rate; + outDepth=(flags>>16)&0xf; + outStereo=(flags>>20)&1; +} + +int DivPlatformPCMDAC::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + oscBuf=new DivDispatchOscBuffer; + isMuted=false; + setFlags(flags); + reset(); + return 1; +} + +void DivPlatformPCMDAC::quit() { + delete oscBuf; +} diff --git a/src/engine/platform/pcmdac.h b/src/engine/platform/pcmdac.h new file mode 100644 index 00000000..60d2a6fc --- /dev/null +++ b/src/engine/platform/pcmdac.h @@ -0,0 +1,99 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _PCM_DAC_H +#define _PCM_DAC_H + +#include "../dispatch.h" +#include +#include "../macroInt.h" +#include "../waveSynth.h" + +class DivPlatformPCMDAC: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, pitch2; + unsigned int audLoc; + unsigned short audLen; + unsigned int audPos; + int audSub; + int sample, wave, ins; + int note; + int panL, panR; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos; + int vol, envVol; + DivMacroInt std; + DivWaveSynth ws; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): + freq(0), + baseFreq(0), + pitch(0), + pitch2(0), + audLoc(0), + audLen(0), + audPos(0), + audSub(0), + sample(-1), + wave(-1), + ins(-1), + note(0), + panL(255), + panR(255), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + useWave(false), + setPos(false), + vol(255), + envVol(64) {} + }; + Channel chan; + DivDispatchOscBuffer* oscBuf; + bool isMuted; + int outDepth; + bool outStereo; + + friend void putDispatchChan(void*,int,int); + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + bool isStereo(); + DivMacroInt* getChanMacroInt(int ch); + void setFlags(unsigned int flags); + void notifyInsChange(int ins); + void notifyWaveChange(int wave); + void notifyInsDeletion(void* ins); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); +}; + +#endif diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index d977690f..ab3b8a7c 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -27,11 +27,17 @@ #include #include #include +#ifdef HAVE_LINUX_INPUT #include +#endif +#ifdef HAVE_LINUX_KD #include +#endif #include +#ifdef HAVE_SYS_IO #include #endif +#endif #define PCSPKR_DIVIDER 4 #define CHIP_DIVIDER 1 @@ -80,6 +86,7 @@ void DivPlatformPCSpeaker::pcSpeakerThread() { } if (beepFD>=0) { switch (realOutMethod) { +#ifdef HAVE_LINUX_INPUT case 0: { // evdev static struct input_event ie; ie.time.tv_sec=r.tv_sec; @@ -98,11 +105,14 @@ void DivPlatformPCSpeaker::pcSpeakerThread() { } break; } +#endif +#ifdef HAVE_LINUX_KD case 1: // KIOCSOUND (on tty) if (ioctl(beepFD,KIOCSOUND,r.val)<0) { logW("ioctl error! %s",strerror(errno)); } break; +#endif case 2: { // /dev/port unsigned char bOut; bOut=0; @@ -144,11 +154,14 @@ void DivPlatformPCSpeaker::pcSpeakerThread() { } break; } +#ifdef HAVE_LINUX_KD case 3: // KIOCSOUND (on stdout) if (ioctl(beepFD,KIOCSOUND,r.val)<0) { logW("ioctl error! %s",strerror(errno)); } break; +#endif +#ifdef HAVE_SYS_IO case 4: // outb() if (r.val==0) { outb(inb(0x61)&(~3),0x61); @@ -163,6 +176,7 @@ void DivPlatformPCSpeaker::pcSpeakerThread() { } } break; +#endif } } else { //logV("not writing because fd is less than 0"); @@ -176,10 +190,6 @@ const char** DivPlatformPCSpeaker::getRegisterSheet() { return regCheatSheetPCSpeaker; } -const char* DivPlatformPCSpeaker::getEffectName(unsigned char effect) { - return NULL; -} - const float cut=0.05; const float reso=0.06; @@ -337,18 +347,9 @@ void DivPlatformPCSpeaker::tick(bool sysTick) { } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.pitch.had) { if (chan[i].std.pitch.mode) { @@ -457,6 +458,7 @@ int DivPlatformPCSpeaker::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: @@ -544,6 +546,7 @@ void DivPlatformPCSpeaker::reset() { break; case 4: // outb() beepFD=-1; +#ifdef HAVE_SYS_IO if (ioperm(0x61,8,1)<0) { logW("ioperm 0x61: %s",strerror(errno)); break; @@ -557,6 +560,9 @@ void DivPlatformPCSpeaker::reset() { break; } beepFD=STDOUT_FILENO; +#else + errno=ENOSYS; +#endif break; } if (beepFD<0) { diff --git a/src/engine/platform/pcspkr.h b/src/engine/platform/pcspkr.h index cb6f070f..ba6275c3 100644 --- a/src/engine/platform/pcspkr.h +++ b/src/engine/platform/pcspkr.h @@ -113,7 +113,6 @@ class DivPlatformPCSpeaker: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformPCSpeaker(); diff --git a/src/engine/platform/pet.cpp b/src/engine/platform/pet.cpp index 0decd909..c314edf6 100644 --- a/src/engine/platform/pet.cpp +++ b/src/engine/platform/pet.cpp @@ -21,15 +21,15 @@ #include "../engine.h" #include -#define rWrite(a,v) {regPool[(a)]=(v)&0xff; if((a)==10) {chan.sreg=(v); chan.cnt=2;}} - #define CHIP_DIVIDER 16 #define SAMP_DIVIDER 4 const char* regCheatSheet6522[]={ "T2L", "08", + "T2H", "09", "SR", "0A", "ACR", "0B", + "PCR", "0C", NULL }; @@ -37,35 +37,45 @@ const char** DivPlatformPET::getRegisterSheet() { return regCheatSheet6522; } -const char* DivPlatformPET::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Change waveform"; +// high-level emulation of 6522 shift register and driver software for now +void DivPlatformPET::rWrite(unsigned int addr, unsigned char val) { + bool hwSROutput=((regPool[11]>>2)&7)==4; + switch (addr) { + case 9: + // simulate phase reset from switching between hw/sw shift registers + if ((regPool[9]==0)^(val==0)) { + chan.sreg=chan.wave; + } + break; + case 10: + chan.sreg=val; + if (hwSROutput) chan.cnt=2; break; } - return NULL; + regPool[addr]=val; } void DivPlatformPET::acquire(short* bufL, short* bufR, size_t start, size_t len) { - // high-level emulation of 6522 shift register for now - int t2=regPool[8]*2+4; - if (((regPool[11]>>2)&7)==4) { + bool hwSROutput=((regPool[11]>>2)&7)==4; + if (chan.enable) { + int reload=regPool[8]*2+4; + if (!hwSROutput) { + reload+=regPool[9]*512; + } for (size_t h=start; h0) { - int adv=MIN(cycs,chan.cnt); - chan.cnt-=adv; - cycs-=adv; - if (chan.cnt==0) { - chan.out=(chan.sreg&1)*32767; - chan.sreg=(chan.sreg>>1)|((chan.sreg&1)<<7); - chan.cnt=t2; - } + if (SAMP_DIVIDER>chan.cnt) { + chan.out=(chan.sreg&1)*32767; + chan.sreg=(chan.sreg>>1)|((chan.sreg&1)<<7); + chan.cnt+=reload-SAMP_DIVIDER; + } else { + chan.cnt-=SAMP_DIVIDER; } bufL[h]=chan.out; bufR[h]=chan.out; oscBuf->data[oscBuf->needle++]=chan.out; } + // emulate driver writes to PCR + if (!hwSROutput) regPool[12]=chan.out?0xe0:0xc0; } else { chan.out=0; for (size_t h=start; h0) { - if (regPool[11]!=16) { - rWrite(11,16); - rWrite(10,chan.wave); - } + chan.enable=true; + rWrite(11,regPool[9]==0?16:0); } else { + chan.enable=false; rWrite(11,0); } } @@ -95,18 +104,9 @@ void DivPlatformPET::tick(bool sysTick) { } if (chan.std.arp.had) { if (!chan.inPorta) { - if (chan.std.arp.mode) { - chan.baseFreq=NOTE_PERIODIC(chan.std.arp.val); - } else { - chan.baseFreq=NOTE_PERIODIC(chan.note+chan.std.arp.val); - } + chan.baseFreq=NOTE_PERIODIC(parent->calcArp(chan.note,chan.std.arp.val)); } chan.freqChanged=true; - } else { - if (chan.std.arp.mode && chan.std.arp.finished) { - chan.baseFreq=NOTE_PERIODIC(chan.note); - chan.freqChanged=true; - } } if (chan.std.wave.had) { if (chan.wave!=chan.std.wave.val) { @@ -115,24 +115,31 @@ void DivPlatformPET::tick(bool sysTick) { } } if (chan.std.pitch.had) { - chan.freqChanged=true; + if (chan.std.pitch.mode) { + chan.pitch2+=chan.std.pitch.val; + CLAMP_VAR(chan.pitch2,-32768,32767); + } else { + chan.pitch2=chan.std.pitch.val; } + chan.freqChanged=true; + } if (chan.freqChanged || chan.keyOn || chan.keyOff) { - chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2,chipClock,CHIP_DIVIDER); - if (chan.freq>257) chan.freq=257; - if (chan.freq<2) chan.freq=2; - rWrite(8,chan.freq-2); + chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2,chipClock,CHIP_DIVIDER)-2; + if (chan.freq>65535) chan.freq=65535; + if (chan.freq<0) chan.freq=0; + rWrite(8,chan.freq&0xff); + rWrite(9,chan.freq>>8); if (chan.keyOn) { if (!chan.std.vol.will) { chan.outVol=chan.vol; - writeOutVol(); } chan.keyOn=false; } if (chan.keyOff) { - rWrite(11,0); chan.keyOff=false; } + // update mode setting and channel enable + writeOutVol(); chan.freqChanged=false; } } @@ -220,6 +227,7 @@ int DivPlatformPET::dispatch(DivCommand c) { if (chan.active && c.value2) { if (parent->song.resetMacroOnPorta) chan.macroInit(parent->getIns(chan.ins,DIV_INS_PET)); } + if (!chan.inPorta && c.value && !parent->song.brokenPortaArp && chan.std.arp.will) chan.baseFreq=NOTE_PERIODIC(chan.note); chan.inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/pet.h b/src/engine/platform/pet.h index a5037045..8de21779 100644 --- a/src/engine/platform/pet.h +++ b/src/engine/platform/pet.h @@ -26,7 +26,7 @@ class DivPlatformPET: public DivDispatch { struct Channel { int freq, baseFreq, pitch, pitch2, note, ins; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, enable; int vol, outVol, wave; unsigned char sreg; int cnt; @@ -49,6 +49,7 @@ class DivPlatformPET: public DivDispatch { keyOn(false), keyOff(false), inPorta(false), + enable(false), vol(1), outVol(1), wave(0b00001111), @@ -79,12 +80,12 @@ class DivPlatformPET: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformPET(); private: void writeOutVol(); + void rWrite(unsigned int addr, unsigned char val); }; #endif diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index 8af565df..9120464e 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -249,24 +249,6 @@ const char** DivPlatformQSound::getRegisterSheet() { return regCheatSheetQSound; } -const char* DivPlatformQSound::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Set echo feedback level (00 to FF)"; - break; - case 0x11: - return "11xx: Set channel echo level (00 to FF)"; - break; - case 0x12: - return "12xx: Toggle QSound algorithm (0: disabled; 1: enabled)"; - break; - default: - if ((effect & 0xf0) == 0x30) { - return "3xxx: Set echo delay buffer length (000 to AA5)"; - } - } - return NULL; -} void DivPlatformQSound::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t h=start; hoffQSound >> 16); qsound_addr = s->offQSound & 0xffff; - int length = s->samples; + int length = s->getEndPosition(); if (length > 65536 - 16) { length = 65536 - 16; } @@ -315,18 +297,9 @@ void DivPlatformQSound::tick(bool sysTick) { } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].std.arp.val); - } else { - chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); - } + chan[i].baseFreq=QS_NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.pitch.had) { if (chan[i].std.pitch.mode) { @@ -358,7 +331,7 @@ void DivPlatformQSound::tick(bool sysTick) { } } chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,440.0,4096.0); - if (chan[i].freq>0xffff) chan[i].freq=0xffff; + if (chan[i].freq>0xefff) chan[i].freq=0xefff; if (chan[i].keyOn) { rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank); rWrite(q1_reg_map[Q1V_END][i], qsound_end); @@ -496,6 +469,7 @@ int DivPlatformQSound::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/qsound.h b/src/engine/platform/qsound.h index 28576013..37217072 100644 --- a/src/engine/platform/qsound.h +++ b/src/engine/platform/qsound.h @@ -96,7 +96,6 @@ class DivPlatformQSound: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); const void* getSampleMem(int index = 0); size_t getSampleMemCapacity(int index = 0); size_t getSampleMemUsage(int index = 0); diff --git a/src/engine/platform/rf5c68.cpp b/src/engine/platform/rf5c68.cpp index 2106a5a7..c032284f 100644 --- a/src/engine/platform/rf5c68.cpp +++ b/src/engine/platform/rf5c68.cpp @@ -43,10 +43,6 @@ const char** DivPlatformRF5C68::getRegisterSheet() { return regCheatSheetRF5C68; } -const char* DivPlatformRF5C68::getEffectName(unsigned char effect) { - return NULL; -} - void DivPlatformRF5C68::chWrite(unsigned char ch, unsigned int addr, unsigned char val) { if (!skipRegisterWrites) { if (curChan!=ch) { @@ -88,18 +84,9 @@ void DivPlatformRF5C68::tick(bool sysTick) { } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.pitch.had) { if (chan[i].std.pitch.mode) { @@ -120,7 +107,7 @@ void DivPlatformRF5C68::tick(bool sysTick) { chan[i].panning|=(chan[i].std.panR.val&15)<<4; } if (chan[i].std.panL.had || chan[i].std.panR.had) { - chWrite(i,0x05,isMuted[i]?0:chan[i].panning); + chWrite(i,1,isMuted[i]?0:chan[i].panning); } if (chan[i].setPos) { // force keyon @@ -142,7 +129,7 @@ void DivPlatformRF5C68::tick(bool sysTick) { if (chan[i].audPos>0) { start=start+MIN(chan[i].audPos,s->length8); } - if (s->loopStart>=0) { + if (s->isLoopable()) { loop=start+s->loopStart; } start=MIN(start,getSampleMemCapacity()-31); @@ -265,6 +252,7 @@ int DivPlatformRF5C68::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_SAMPLE_POS: @@ -392,7 +380,7 @@ void DivPlatformRF5C68::renderSamples() { size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; - int length=s->length8; + int length=s->getEndPosition(DIV_SAMPLE_DEPTH_8BIT); int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-31,length); if (actualLength>0) { s->offRF5C68=memPos; diff --git a/src/engine/platform/rf5c68.h b/src/engine/platform/rf5c68.h index 6946b490..d82c926e 100644 --- a/src/engine/platform/rf5c68.h +++ b/src/engine/platform/rf5c68.h @@ -92,7 +92,6 @@ class DivPlatformRF5C68: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); const void* getSampleMem(int index = 0); size_t getSampleMemCapacity(int index = 0); size_t getSampleMemUsage(int index = 0); diff --git a/src/engine/platform/saa.cpp b/src/engine/platform/saa.cpp index c562838a..b7a77d96 100644 --- a/src/engine/platform/saa.cpp +++ b/src/engine/platform/saa.cpp @@ -56,21 +56,6 @@ const char** DivPlatformSAA1099::getRegisterSheet() { return regCheatSheetSAA; } -const char* DivPlatformSAA1099::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xy: Set channel mode (x: noise; y: tone)"; - break; - case 0x11: - return "11xx: Set noise frequency"; - break; - case 0x12: - return "12xx: Setup envelope (refer to docs for more information)"; - break; - } - return NULL; -} - void DivPlatformSAA1099::acquire_saaSound(short* bufL, short* bufR, size_t start, size_t len) { if (saaBufLencalcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.duty.had) { saaNoise[i/3]=chan[i].std.duty.val&3; @@ -335,6 +311,7 @@ int DivPlatformSAA1099::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SAA1099)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_PRE_NOTE: diff --git a/src/engine/platform/saa.h b/src/engine/platform/saa.h index 0efd498d..92855be7 100644 --- a/src/engine/platform/saa.h +++ b/src/engine/platform/saa.h @@ -96,7 +96,6 @@ class DivPlatformSAA1099: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); }; diff --git a/src/engine/platform/scc.cpp b/src/engine/platform/scc.cpp index 0cfe5a46..d8859cff 100644 --- a/src/engine/platform/scc.cpp +++ b/src/engine/platform/scc.cpp @@ -80,15 +80,6 @@ const char** DivPlatformSCC::getRegisterSheet() { return isPlus ? regCheatSheetSCCPlus : regCheatSheetSCC; } -const char* DivPlatformSCC::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Change waveform"; - break; - } - return NULL; -} - void DivPlatformSCC::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t h=start; hcalcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.wave.had) { if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { @@ -267,6 +249,7 @@ int DivPlatformSCC::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SCC)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: @@ -377,6 +360,27 @@ void DivPlatformSCC::setChipModel(bool isplus) { isPlus=isplus; } +void DivPlatformSCC::setFlags(unsigned int flags) { + switch (flags&0x7f) { + case 0x00: + chipClock=COLOR_NTSC/2.0; + break; + case 0x01: + chipClock=COLOR_PAL*2.0/5.0; + break; + case 0x02: + chipClock=3000000.0/2.0; + break; + case 0x03: + chipClock=4000000.0/2.0; + break; + } + rate=chipClock/8; + for (int i=0; i<5; i++) { + oscBuf[i]->rate=rate; + } +} + int DivPlatformSCC::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; @@ -386,11 +390,7 @@ int DivPlatformSCC::init(DivEngine* p, int channels, int sugRate, unsigned int f isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - chipClock=COLOR_NTSC/2.0; - rate=chipClock/8; - for (int i=0; i<5; i++) { - oscBuf[i]->rate=rate; - } + setFlags(flags); if (isPlus) { scc=new k052539_scc_core; regBase=0xa0; diff --git a/src/engine/platform/scc.h b/src/engine/platform/scc.h index 31f6abc5..f9fa3172 100644 --- a/src/engine/platform/scc.h +++ b/src/engine/platform/scc.h @@ -84,7 +84,7 @@ class DivPlatformSCC: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); + void setFlags(unsigned int flags); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void setChipModel(bool isPlus); void quit(); diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 19b8eb82..329e3b82 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -26,15 +26,6 @@ //#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} //#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } -const char* DivPlatformSegaPCM::getEffectName(unsigned char effect) { - switch (effect) { - case 0x20: - return "20xx: Set PCM frequency"; - break; - } - return NULL; -} - void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; @@ -56,12 +47,10 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t pcmR+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolR); } chan[i].pcm.pos+=chan[i].pcm.freq; - if (chan[i].pcm.pos>=(s->samples<<8)) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - chan[i].pcm.pos=s->loopStart<<8; - } else { - chan[i].pcm.sample=-1; - } + if (s->isLoopable() && chan[i].pcm.pos>=(s->getEndPosition()<<8)) { + chan[i].pcm.pos=s->loopStart<<8; + } else if (chan[i].pcm.pos>=(s->samples<<8)) { + chan[i].pcm.sample=-1; } } else { oscBuf[i]->data[oscBuf[i]->needle++]=0; @@ -99,18 +88,9 @@ void DivPlatformSegaPCM::tick(bool sysTick) { if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=(chan[i].std.arp.val<<6); - } else { - chan[i].baseFreq=((chan[i].note+(signed char)chan[i].std.arp.val)<<6); - } + chan[i].baseFreq=(parent->calcArp(chan[i].note,chan[i].std.arp.val)<<6); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=(chan[i].note<<6); - chan[i].freqChanged=true; - } } if (chan[i].std.panL.had) { @@ -202,7 +182,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { chan[c.chan].macroInit(ins); if (dumpWrites) { // Sega PCM writes DivSample* s=parent->getSample(chan[c.chan].pcm.sample); - int actualLength=(int)s->length8; + int actualLength=(int)(s->getEndPosition(DIV_SAMPLE_DEPTH_8BIT)); if (actualLength>0xfeff) actualLength=0xfeff; addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3)); addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff); @@ -235,7 +215,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { chan[c.chan].furnacePCM=false; if (dumpWrites) { // Sega PCM writes DivSample* s=parent->getSample(chan[c.chan].pcm.sample); - int actualLength=(int)s->length8; + int actualLength=(int)(s->getEndPosition(DIV_SAMPLE_DEPTH_8BIT)); if (actualLength>65536) actualLength=65536; addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3)); addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff); @@ -365,6 +345,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { return 127; break; case DIV_CMD_PRE_PORTA: + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=(chan[c.chan].note<<6); chan[c.chan].inPorta=c.value; break; case DIV_CMD_PRE_NOTE: diff --git a/src/engine/platform/segapcm.h b/src/engine/platform/segapcm.h index 6edc8530..0dd4b837 100644 --- a/src/engine/platform/segapcm.h +++ b/src/engine/platform/segapcm.h @@ -114,7 +114,6 @@ class DivPlatformSegaPCM: public DivDispatch { bool isStereo(); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformSegaPCM(); diff --git a/src/engine/platform/sms.cpp b/src/engine/platform/sms.cpp index a32305a3..d4d77f17 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -21,32 +21,34 @@ #include "../engine.h" #include -#define rWrite(v) {if (!skipRegisterWrites) {writes.push(v); if (dumpWrites) {addWrite(0x200,v);}}} +#define rWrite(a,v) {if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(0x200+a,v);}}} const char* regCheatSheetSN[]={ "DATA", "0", NULL }; -const char** DivPlatformSMS::getRegisterSheet() { - return regCheatSheetSN; -} +const char* regCheatSheetGG[]={ + "DATA", "0", + "Stereo", "1", + NULL +}; -const char* DivPlatformSMS::getEffectName(unsigned char effect) { - switch (effect) { - case 0x20: - return "20xy: Set noise mode (x: preset freq/ch3 freq; y: thin pulse/noise)"; - break; - } - return NULL; +const char** DivPlatformSMS::getRegisterSheet() { + return stereo?regCheatSheetGG:regCheatSheetSN; } void DivPlatformSMS::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) { - int o=0; + int oL=0; + int oR=0; for (size_t h=start; h32767) o=32767; - bufL[h]=o; + YMPSG_GetOutput(&sn_nuked,&oL,&oR); + if (oL<-32768) oL=-32768; + if (oL>32767) oL=32767; + if (oR<-32768) oR=-32768; + if (oR>32767) oR=32767; + bufL[h]=oL; + bufR[h]=oR; for (int i=0; i<4; i++) { if (isMuted[i]) { oscBuf[i]->data[oscBuf[i]->needle++]=0; @@ -81,12 +86,20 @@ void DivPlatformSMS::acquire_nuked(short* bufL, short* bufR, size_t start, size_ void DivPlatformSMS::acquire_mame(short* bufL, short* bufR, size_t start, size_t len) { while (!writes.empty()) { - unsigned char w=writes.front(); - sn->write(w); + QueuedWrite w=writes.front(); + if (stereo && (w.addr==1)) + sn->stereo_w(w.val); + else if (w.addr==0) { + sn->write(w.val); + } writes.pop(); } for (size_t h=start; hsound_stream_update(bufL+h,1); + short* outs[2]={ + &bufL[h], + &bufR[h] + }; + sn->sound_stream_update(outs,1); for (int i=0; i<4; i++) { if (isMuted[i]) { oscBuf[i]->data[oscBuf[i]->needle++]=0; @@ -105,42 +118,26 @@ void DivPlatformSMS::acquire(short* bufL, short* bufR, size_t start, size_t len) } } -int DivPlatformSMS::acquireOne() { - short v; - sn->sound_stream_update(&v,1); - return v; -} - void DivPlatformSMS::tick(bool sysTick) { for (int i=0; i<4; i++) { - int CHIP_DIVIDER=64; - if (i==3 && isRealSN) CHIP_DIVIDER=60; + double CHIP_DIVIDER=toneDivider; + if (i==3) CHIP_DIVIDER=noiseDivider; chan[i].std.next(); if (chan[i].std.vol.had) { - chan[i].outVol=MIN(15,chan[i].std.vol.val)-(15-(chan[i].vol&15)); + chan[i].outVol=VOL_SCALE_LOG(chan[i].std.vol.val,chan[i].vol,15); if (chan[i].outVol<0) chan[i].outVol=0; // old formula // ((chan[i].vol&15)*MIN(15,chan[i].std.vol.val))>>4; - rWrite(0x90|(i<<5)|(isMuted[i]?15:(15-(chan[i].outVol&15)))); + rWrite(0,0x90|(i<<5)|(isMuted[i]?15:(15-(chan[i].outVol&15)))); } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); - chan[i].actualNote=chan[i].std.arp.val; - } else { - // TODO: check whether this weird octave boundary thing applies to other systems as well - int areYouSerious=chan[i].note+chan[i].std.arp.val; - while (areYouSerious>0x60) areYouSerious-=12; - chan[i].baseFreq=NOTE_PERIODIC(areYouSerious); - chan[i].actualNote=areYouSerious; - } - chan[i].freqChanged=true; - } - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); - chan[i].actualNote=chan[i].note; + // TODO: check whether this weird octave boundary thing applies to other systems as well + // TODO: add compatibility flag. this is horrible. + int areYouSerious=parent->calcArp(chan[i].note,chan[i].std.arp.val); + while (areYouSerious>0x60) areYouSerious-=12; + chan[i].baseFreq=NOTE_PERIODIC(areYouSerious); + chan[i].actualNote=areYouSerious; chan[i].freqChanged=true; } } @@ -160,6 +157,13 @@ void DivPlatformSMS::tick(bool sysTick) { } } } + if (stereo) { + if (chan[i].std.panL.had) { + lastPan&=~(0x11<>1)&1)<<(i+4)); + rWrite(1,lastPan); + } + } if (chan[i].std.pitch.had) { if (chan[i].std.pitch.mode) { chan[i].pitch2+=chan[i].std.pitch.val; @@ -172,12 +176,16 @@ void DivPlatformSMS::tick(bool sysTick) { } for (int i=0; i<3; i++) { if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,64); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,toneDivider); if (chan[i].freq>1023) chan[i].freq=1023; - if (chan[i].freq<8) chan[i].freq=1; + if (parent->song.snNoLowPeriods) { + if (chan[i].freq<8) chan[i].freq=1; + } else { + if (chan[i].freq<0) chan[i].freq=0; + } //if (chan[i].actualNote>0x5d) chan[i].freq=0x01; - rWrite(0x80|i<<5|(chan[i].freq&15)); - rWrite(chan[i].freq>>4); + rWrite(0,0x80|i<<5|(chan[i].freq&15)); + rWrite(0,chan[i].freq>>4); // what? /*if (i==2 && snNoiseMode&2) { chan[3].baseFreq=chan[2].baseFreq; @@ -187,41 +195,39 @@ void DivPlatformSMS::tick(bool sysTick) { } } if (chan[3].freqChanged || updateSNMode) { - chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2,chipClock,isRealSN?60:64); + chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2,chipClock,noiseDivider); if (chan[3].freq>1023) chan[3].freq=1023; - if (chan[3].actualNote>0x5d) chan[3].freq=0x01; + if (parent->song.snNoLowPeriods) { + if (chan[3].actualNote>0x5d) chan[3].freq=0x01; + } if (snNoiseMode&2) { // take period from channel 3 if (updateSNMode || resetPhase) { if (snNoiseMode&1) { - rWrite(0xe7); + rWrite(0,0xe7); } else { - rWrite(0xe3); + rWrite(0,0xe3); } if (updateSNMode) { - rWrite(0xdf); + rWrite(0,0xdf); } } if (chan[3].freqChanged) { - rWrite(0xc0|(chan[3].freq&15)); - rWrite(chan[3].freq>>4); + rWrite(0,0xc0|(chan[3].freq&15)); + rWrite(0,chan[3].freq>>4); } } else { // 3 fixed values unsigned char value; if (chan[3].std.arp.had) { - if (chan[3].std.arp.mode) { - value=chan[3].std.arp.val%12; - } else { - value=(chan[3].note+chan[3].std.arp.val)%12; - } - } else { + value=parent->calcArp(chan[3].note,chan[3].std.arp.val)%12; + } else { // pardon? value=chan[3].note%12; } if (value<3) { value=2-value; if (value!=oldValue || updateSNMode || resetPhase) { oldValue=value; - rWrite(0xe0|value|((snNoiseMode&1)<<2)); + rWrite(0,0xe0|value|((snNoiseMode&1)<<2)); } } } @@ -231,8 +237,8 @@ void DivPlatformSMS::tick(bool sysTick) { } int DivPlatformSMS::dispatch(DivCommand c) { - int CHIP_DIVIDER=64; - if (c.chan==3 && isRealSN) CHIP_DIVIDER=60; + double CHIP_DIVIDER=toneDivider; + if (c.chan==3) CHIP_DIVIDER=noiseDivider; switch (c.cmd) { case DIV_CMD_NOTE_ON: if (c.value!=DIV_NOTE_NULL) { @@ -242,7 +248,7 @@ int DivPlatformSMS::dispatch(DivCommand c) { chan[c.chan].actualNote=c.value; } chan[c.chan].active=true; - rWrite(0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15)))); + rWrite(0,0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15)))); chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD)); if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; @@ -250,7 +256,7 @@ int DivPlatformSMS::dispatch(DivCommand c) { break; case DIV_CMD_NOTE_OFF: chan[c.chan].active=false; - rWrite(0x9f|c.chan<<5); + rWrite(0,0x9f|c.chan<<5); chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: @@ -267,7 +273,7 @@ int DivPlatformSMS::dispatch(DivCommand c) { if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } - if (chan[c.chan].active) rWrite(0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15)))); + if (chan[c.chan].active) rWrite(0,0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15)))); } break; case DIV_CMD_GET_VOLUME: @@ -307,6 +313,19 @@ int DivPlatformSMS::dispatch(DivCommand c) { snNoiseMode=(c.value&1)|((c.value&16)>>3); updateSNMode=true; break; + case DIV_CMD_PANNING: { + if (stereo) { + if (c.chan>3) c.chan=3; + lastPan&=~(0x11<0) pan|=0x10; + if (c.value2>0) pan|=0x01; + if (pan==0) pan=0x11; + lastPan|=pan<song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; - // TODO: pre porta cancel arp compat flag - //if (chan[c.chan].inPorta) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); break; case DIV_CMD_GET_VOLMAX: return 15; @@ -335,7 +353,7 @@ int DivPlatformSMS::dispatch(DivCommand c) { void DivPlatformSMS::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - if (chan[ch].active) rWrite(0x90|ch<<5|(isMuted[ch]?15:(15-(chan[ch].outVol&15)))); + if (chan[ch].active) rWrite(0,0x90|ch<<5|(isMuted[ch]?15:(15-(chan[ch].outVol&15)))); } void DivPlatformSMS::forceIns() { @@ -370,11 +388,19 @@ void DivPlatformSMS::reset() { addWrite(0xffffffff,0); } sn->device_start(); - YMPSG_Init(&sn_nuked,isRealSN); + YMPSG_Init(&sn_nuked,isRealSN,12,isRealSN?13:15,isRealSN?16383:32767); snNoiseMode=3; - rWrite(0xe7); + rWrite(0,0xe7); updateSNMode=false; oldValue=0xff; + lastPan=0xff; + if (stereo) { + rWrite(1,0xff); + } +} + +bool DivPlatformSMS::isStereo() { + return stereo; } bool DivPlatformSMS::keyOffAffectsArp(int ch) { @@ -396,45 +422,109 @@ void DivPlatformSMS::notifyInsDeletion(void* ins) { } void DivPlatformSMS::poke(unsigned int addr, unsigned short val) { - rWrite(val); + rWrite(addr,val); } void DivPlatformSMS::poke(std::vector& wlist) { - for (DivRegWrite& i: wlist) rWrite(i.val); + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); } void DivPlatformSMS::setFlags(unsigned int flags) { - if ((flags&3)==3) { - chipClock=COLOR_NTSC/2.0; - } else if ((flags&3)==2) { - chipClock=4000000; - } else if ((flags&3)==1) { - chipClock=COLOR_PAL*4.0/5.0; - } else { - chipClock=COLOR_NTSC; + switch (flags&0xff03) { + default: + case 0x0000: + chipClock=COLOR_NTSC; + break; + case 0x0001: + chipClock=COLOR_PAL*4.0/5.0; + break; + case 0x0002: + chipClock=4000000; + break; + case 0x0003: + chipClock=COLOR_NTSC/2.0; + break; + case 0x0100: + chipClock=3000000; + break; + case 0x0101: + chipClock=2000000; + break; + case 0x0102: + chipClock=COLOR_NTSC/8.0; + break; } resetPhase=!(flags&16); - + divider=16; + toneDivider=64.0; + noiseDivider=64.0; if (sn!=NULL) delete sn; - switch ((flags>>2)&3) { - case 1: // TI - sn=new sn76496_base_device(0x4000, 0x4000, 0x01, 0x02, true, 1, false, true); - isRealSN=true; - break; - case 2: // TI+Atari - sn=new sn76496_base_device(0x4000, 0x0f35, 0x01, 0x02, true, 1, false, true); - isRealSN=true; - break; - case 3: // Game Gear (not fully emulated yet!) - sn=new sn76496_base_device(0x8000, 0x8000, 0x01, 0x08, false, 1, false, false); - isRealSN=false; - break; + switch (flags&0xcc) { default: // Sega - sn=new sn76496_base_device(0x8000, 0x8000, 0x01, 0x08, false, 1, false, false); + case 0x00: + sn=new segapsg_device(); isRealSN=false; + stereo=false; + break; + case 0x04: // TI SN76489 + sn=new sn76489_device(); + isRealSN=true; + stereo=false; + noiseDivider=60.0; // 64 for match to tone frequency on non-Sega PSG but compatibility + break; + case 0x08: // TI+Atari + sn=new sn76496_base_device(0x4000, 0x0f35, 0x01, 0x02, true, false, 1/*8*/, false, true); + isRealSN=true; + stereo=false; + noiseDivider=60.0; + break; + case 0x0c: // Game Gear (not fully emulated yet!) + sn=new gamegear_device(); + isRealSN=false; + stereo=true; + break; + case 0x40: // TI SN76489A + sn=new sn76489a_device(); + isRealSN=false; // TODO + stereo=false; + noiseDivider=60.0; + break; + case 0x44: // TI SN76496 + sn=new sn76496_device(); + isRealSN=false; // TODO + stereo=false; + noiseDivider=60.0; + break; + case 0x48: // NCR 8496 + sn=new ncr8496_device(); + isRealSN=false; + stereo=false; + noiseDivider=60.0; + break; + case 0x4c: // Tandy PSSJ 3-voice sound + sn=new pssj3_device(); + isRealSN=false; + stereo=false; + noiseDivider=60.0; + break; + case 0x80: // TI SN94624 + sn=new sn94624_device(); + isRealSN=true; + stereo=false; + divider=2; + toneDivider=8.0; + noiseDivider=7.5; + break; + case 0x84: // TI SN76494 + sn=new sn76494_device(); + isRealSN=false; // TODO + stereo=false; + divider=2; + toneDivider=8.0; + noiseDivider=7.5; break; } - rate=chipClock/16; + rate=chipClock/divider; for (int i=0; i<4; i++) { oscBuf[i]->rate=rate; } @@ -450,6 +540,7 @@ int DivPlatformSMS::init(DivEngine* p, int channels, int sugRate, unsigned int f skipRegisterWrites=false; resetPhase=false; oldValue=0xff; + lastPan=0xff; for (int i=0; i<4; i++) { isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; diff --git a/src/engine/platform/sms.h b/src/engine/platform/sms.h index 9054eb7a..eef54da1 100644 --- a/src/engine/platform/sms.h +++ b/src/engine/platform/sms.h @@ -58,21 +58,31 @@ class DivPlatformSMS: public DivDispatch { Channel chan[4]; DivDispatchOscBuffer* oscBuf[4]; bool isMuted[4]; + unsigned char lastPan; unsigned char oldValue; unsigned char snNoiseMode; + int divider=16; + double toneDivider=64.0; + double noiseDivider=64.0; bool updateSNMode; bool resetPhase; bool isRealSN; + bool stereo; bool nuked; sn76496_base_device* sn; ympsg_t sn_nuked; - std::queue writes; + struct QueuedWrite { + unsigned short addr; + unsigned char val; + bool addrOrVal; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + }; + std::queue writes; friend void putDispatchChan(void*,int,int); void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len); void acquire_mame(short* bufL, short* bufR, size_t start, size_t len); public: - int acquireOne(); void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); @@ -82,6 +92,7 @@ class DivPlatformSMS: public DivDispatch { void forceIns(); void tick(bool sysTick=true); void muteChannel(int ch, bool mute); + bool isStereo(); bool keyOffAffectsArp(int ch); bool keyOffAffectsPorta(int ch); int getPortaFloor(int ch); @@ -90,7 +101,6 @@ class DivPlatformSMS: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); void setNuked(bool value); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); diff --git a/src/engine/platform/sound/ay8910.cpp b/src/engine/platform/sound/ay8910.cpp index 2c11b6e3..c7be503e 100644 --- a/src/engine/platform/sound/ay8910.cpp +++ b/src/engine/platform/sound/ay8910.cpp @@ -924,6 +924,7 @@ float ay8910_device::mix_3D() indx |= tone_mask | (m_vol_enabled[chan] ? tone_volume(tone) << (chan*5) : 0); } } + lastIndx=indx; return m_vol3d_table[indx]; } @@ -1359,6 +1360,7 @@ unsigned char ay8910_device::ay8910_read_ym() void ay8910_device::device_reset() { + lastIndx=0; ay8910_reset_ym(); } diff --git a/src/engine/platform/sound/ay8910.h b/src/engine/platform/sound/ay8910.h index 314383f5..6f4c6f31 100644 --- a/src/engine/platform/sound/ay8910.h +++ b/src/engine/platform/sound/ay8910.h @@ -146,6 +146,8 @@ public: double m_Kn[32]; }; + int lastIndx; + // internal interface for PSG component of YM device // FIXME: these should be private, but vector06 accesses them directly diff --git a/src/engine/platform/sound/c64/sid.cc b/src/engine/platform/sound/c64/sid.cc index d6ebbb44..aaed90a5 100644 --- a/src/engine/platform/sound/c64/sid.cc +++ b/src/engine/platform/sound/c64/sid.cc @@ -60,6 +60,13 @@ SID::~SID() delete[] fir; } +// ---------------------------------------------------------------------------- +// Get DC offset of channel. +// ---------------------------------------------------------------------------- +sound_sample SID::get_dc(int ch) { + return voice[ch].getDC(); +} + // ---------------------------------------------------------------------------- // Mute/unmute channel. // ---------------------------------------------------------------------------- diff --git a/src/engine/platform/sound/c64/sid.h b/src/engine/platform/sound/c64/sid.h index f6b39271..e8b0d5c6 100644 --- a/src/engine/platform/sound/c64/sid.h +++ b/src/engine/platform/sound/c64/sid.h @@ -34,6 +34,7 @@ public: sound_sample last_chan_out[3]; + sound_sample get_dc(int ch); void set_is_muted(int ch, bool val); void set_chip_model(chip_model model); void enable_filter(bool enable); diff --git a/src/engine/platform/sound/c64/voice.h b/src/engine/platform/sound/c64/voice.h index d248f32a..85b4fede 100644 --- a/src/engine/platform/sound/c64/voice.h +++ b/src/engine/platform/sound/c64/voice.h @@ -38,6 +38,7 @@ public: // Amplitude modulated waveform output. // Range [-2048*255, 2047*255]. RESID_INLINE sound_sample output(); + RESID_INLINE sound_sample getDC(); protected: WaveformGenerator wave; @@ -72,6 +73,12 @@ sound_sample Voice::output() return (wave.output() - wave_zero)*envelope.output() + voice_DC; } +RESID_INLINE +sound_sample Voice::getDC() +{ + return voice_DC; +} + #endif // RESID_INLINING || defined(__VOICE_CC__) #endif // not __VOICE_H__ diff --git a/src/engine/platform/sound/c64_fp/AUTHORS b/src/engine/platform/sound/c64_fp/AUTHORS new file mode 100644 index 00000000..b04ee0f0 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/AUTHORS @@ -0,0 +1,6 @@ +Authors of reSIDfp. + +Dag Lem: Designed and programmed complete emulation engine. +Antti S. Lankila: Distortion simulation and calculation of combined waveforms +Ken Händel: source code conversion to Java +Leandro Nini: port to c++, merge with reSID 1.0 diff --git a/src/engine/platform/sound/c64_fp/COPYING b/src/engine/platform/sound/c64_fp/COPYING new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/src/engine/platform/sound/c64_fp/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/src/engine/platform/sound/c64_fp/Dac.cpp b/src/engine/platform/sound/c64_fp/Dac.cpp new file mode 100644 index 00000000..0665da81 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Dac.cpp @@ -0,0 +1,123 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2016 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * 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 "Dac.h" + +namespace reSIDfp +{ + +Dac::Dac(unsigned int bits) : + dac(new double[bits]), + dacLength(bits) +{} + +Dac::~Dac() +{ + delete [] dac; +} + +double Dac::getOutput(unsigned int input) const +{ + double dacValue = 0.; + + for (unsigned int i = 0; i < dacLength; i++) + { + if ((input & (1 << i)) != 0) + { + dacValue += dac[i]; + } + } + + return dacValue; +} + +void Dac::kinkedDac(ChipModel chipModel) +{ + const double R_INFINITY = 1e6; + + // Non-linearity parameter, 8580 DACs are perfectly linear + const double _2R_div_R = chipModel == MOS6581 ? 2.20 : 2.00; + + // 6581 DACs are not terminated by a 2R resistor + const bool term = chipModel == MOS8580; + + // Calculate voltage contribution by each individual bit in the R-2R ladder. + for (unsigned int set_bit = 0; set_bit < dacLength; set_bit++) + { + double Vn = 1.; // Normalized bit voltage. + double R = 1.; // Normalized R + const double _2R = _2R_div_R * R; // 2R + double Rn = term ? // Rn = 2R for correct termination, + _2R : R_INFINITY; // INFINITY for missing termination. + + unsigned int bit; + + // Calculate DAC "tail" resistance by repeated parallel substitution. + for (bit = 0; bit < set_bit; bit++) + { + Rn = (Rn == R_INFINITY) ? + R + _2R : + R + (_2R * Rn) / (_2R + Rn); // R + 2R || Rn + } + + // Source transformation for bit voltage. + if (Rn == R_INFINITY) + { + Rn = _2R; + } + else + { + Rn = (_2R * Rn) / (_2R + Rn); // 2R || Rn + Vn = Vn * Rn / _2R; + } + + // Calculate DAC output voltage by repeated source transformation from + // the "tail". + + for (++bit; bit < dacLength; bit++) + { + Rn += R; + const double I = Vn / Rn; + Rn = (_2R * Rn) / (_2R + Rn); // 2R || Rn + Vn = Rn * I; + } + + dac[set_bit] = Vn; + } + + // Normalize to integerish behavior + double Vsum = 0.; + + for (unsigned int i = 0; i < dacLength; i++) + { + Vsum += dac[i]; + } + + Vsum /= 1 << dacLength; + + for (unsigned int i = 0; i < dacLength; i++) + { + dac[i] /= Vsum; + } +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/Dac.h b/src/engine/platform/sound/c64_fp/Dac.h new file mode 100644 index 00000000..35bc0b2c --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Dac.h @@ -0,0 +1,111 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2016 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef DAC_H +#define DAC_H + +#include "siddefs-fp.h" + +namespace reSIDfp +{ + +/** + * Estimate DAC nonlinearity. + * The SID DACs are built up as R-2R ladder as follows: + * + * n n-1 2 1 0 VGND + * | | | | | | Termination + * 2R 2R 2R 2R 2R 2R only for + * | | | | | | MOS 8580 + * Vo -o-R-o-R-...-o-R-o-R-- --+ + * + * + * All MOS 6581 DACs are missing a termination resistor at bit 0. This causes + * pronounced errors for the lower 4 - 5 bits (e.g. the output for bit 0 is + * actually equal to the output for bit 1), resulting in DAC discontinuities + * for the lower bits. + * In addition to this, the 6581 DACs exhibit further severe discontinuities + * for higher bits, which may be explained by a less than perfect match between + * the R and 2R resistors, or by output impedance in the NMOS transistors + * providing the bit voltages. A good approximation of the actual DAC output is + * achieved for 2R/R ~ 2.20. + * + * The MOS 8580 DACs, on the other hand, do not exhibit any discontinuities. + * These DACs include the correct termination resistor, and also seem to have + * very accurately matched R and 2R resistors (2R/R = 2.00). + * + * On the 6581 the output of the waveform and envelope DACs go through + * a voltage follower built with two NMOS: + * + * Vdd + * + * | + * |-+ + * Vin -------| T1 (enhancement-mode) + * |-+ + * | + * o-------- Vout + * | + * |-+ + * +---| T2 (depletion-mode) + * | |-+ + * | | + * + * GND GND + */ +class Dac +{ +private: + /// analog values + double * const dac; + + /// the dac array length + const unsigned int dacLength; + +public: + /** + * Initialize DAC model. + * + * @param bits the number of input bits + */ + Dac(unsigned int bits); + ~Dac(); + + /** + * Build DAC model for specific chip. + * + * @param chipModel 6581 or 8580 + */ + void kinkedDac(ChipModel chipModel); + + /** + * Get the Vo output for a given combination of input bits. + * + * @param input the digital input + * @return the analog output value + */ + double getOutput(unsigned int input) const; +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp b/src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp new file mode 100644 index 00000000..af636ac7 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp @@ -0,0 +1,155 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2020 Leandro Nini + * Copyright 2018 VICE Project + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * 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. + */ + +#define ENVELOPEGENERATOR_CPP + +#include "EnvelopeGenerator.h" + +namespace reSIDfp +{ + +/** + * Lookup table to convert from attack, decay, or release value to rate + * counter period. + * + * The rate counter is a 15 bit register which is left shifted each cycle. + * When the counter reaches a specific comparison value, + * the envelope counter is incremented (attack) or decremented + * (decay/release) and the rate counter is resetted. + * + * see [kevtris.org](http://blog.kevtris.org/?p=13) + */ +const unsigned int EnvelopeGenerator::adsrtable[16] = +{ + 0x007f, + 0x3000, + 0x1e00, + 0x0660, + 0x0182, + 0x5573, + 0x000e, + 0x3805, + 0x2424, + 0x2220, + 0x090c, + 0x0ecd, + 0x010e, + 0x23f7, + 0x5237, + 0x64a8 +}; + +void EnvelopeGenerator::reset() +{ + // counter is not changed on reset + envelope_pipeline = 0; + + state_pipeline = 0; + + attack = 0; + decay = 0; + sustain = 0; + release = 0; + + gate = false; + + resetLfsr = true; + + exponential_counter = 0; + exponential_counter_period = 1; + new_exponential_counter_period = 0; + + state = RELEASE; + counter_enabled = true; + rate = adsrtable[release]; +} + +void EnvelopeGenerator::writeCONTROL_REG(unsigned char control) +{ + const bool gate_next = (control & 0x01) != 0; + + if (gate_next != gate) + { + gate = gate_next; + + // The rate counter is never reset, thus there will be a delay before the + // envelope counter starts counting up (attack) or down (release). + + if (gate_next) + { + // Gate bit on: Start attack, decay, sustain. + next_state = ATTACK; + state_pipeline = 2; + + if (resetLfsr || (exponential_pipeline == 2)) + { + envelope_pipeline = (exponential_counter_period == 1) || (exponential_pipeline == 2) ? 2 : 4; + } + else if (exponential_pipeline == 1) + { + state_pipeline = 3; + } + } + else + { + // Gate bit off: Start release. + next_state = RELEASE; + state_pipeline = envelope_pipeline > 0 ? 3 : 2; + } + } +} + +void EnvelopeGenerator::writeATTACK_DECAY(unsigned char attack_decay) +{ + attack = (attack_decay >> 4) & 0x0f; + decay = attack_decay & 0x0f; + + if (state == ATTACK) + { + rate = adsrtable[attack]; + } + else if (state == DECAY_SUSTAIN) + { + rate = adsrtable[decay]; + } +} + +void EnvelopeGenerator::writeSUSTAIN_RELEASE(unsigned char sustain_release) +{ + // From the sustain levels it follows that both the low and high 4 bits + // of the envelope counter are compared to the 4-bit sustain value. + // This has been verified by sampling ENV3. + // + // For a detailed description see: + // http://ploguechipsounds.blogspot.it/2010/11/new-research-on-sid-adsr.html + sustain = (sustain_release & 0xf0) | ((sustain_release >> 4) & 0x0f); + + release = sustain_release & 0x0f; + + if (state == RELEASE) + { + rate = adsrtable[release]; + } +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/EnvelopeGenerator.h b/src/engine/platform/sound/c64_fp/EnvelopeGenerator.h new file mode 100644 index 00000000..f2aab387 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/EnvelopeGenerator.h @@ -0,0 +1,419 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2018 VICE Project + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef ENVELOPEGENERATOR_H +#define ENVELOPEGENERATOR_H + +#include "siddefs-fp.h" + +namespace reSIDfp +{ + +/** + * A 15 bit [LFSR] is used to implement the envelope rates, in effect dividing + * the clock to the envelope counter by the currently selected rate period. + * + * In addition, another 5 bit counter is used to implement the exponential envelope decay, + * in effect further dividing the clock to the envelope counter. + * The period of this counter is set to 1, 2, 4, 8, 16, 30 at the envelope counter + * values 255, 93, 54, 26, 14, 6, respectively. + * + * [LFSR]: https://en.wikipedia.org/wiki/Linear_feedback_shift_register + */ +class EnvelopeGenerator +{ +private: + /** + * The envelope state machine's distinct states. In addition to this, + * envelope has a hold mode, which freezes envelope counter to zero. + */ + enum State + { + ATTACK, DECAY_SUSTAIN, RELEASE + }; + +private: + /// XOR shift register for ADSR prescaling. + unsigned int lfsr; + + /// Comparison value (period) of the rate counter before next event. + unsigned int rate; + + /** + * During release mode, the SID approximates envelope decay via piecewise + * linear decay rate. + */ + unsigned int exponential_counter; + + /** + * Comparison value (period) of the exponential decay counter before next + * decrement. + */ + unsigned int exponential_counter_period; + unsigned int new_exponential_counter_period; + + unsigned int state_pipeline; + + /// + unsigned int envelope_pipeline; + + unsigned int exponential_pipeline; + + /// Current envelope state + State state; + State next_state; + + /// Whether counter is enabled. Only switching to ATTACK can release envelope. + bool counter_enabled; + + /// Gate bit + bool gate; + + /// + bool resetLfsr; + + /// The current digital value of envelope output. + unsigned char envelope_counter; + + /// Attack register + unsigned char attack; + + /// Decay register + unsigned char decay; + + /// Sustain register + unsigned char sustain; + + /// Release register + unsigned char release; + + /// The ENV3 value, sampled at the first phase of the clock + unsigned char env3; + +private: + static const unsigned int adsrtable[16]; + +private: + void set_exponential_counter(); + + void state_change(); + +public: + /** + * SID clocking. + */ + void clock(); + + /** + * Get the Envelope Generator digital output. + */ + unsigned int output() const { return envelope_counter; } + + /** + * Constructor. + */ + EnvelopeGenerator() : + lfsr(0x7fff), + rate(0), + exponential_counter(0), + exponential_counter_period(1), + new_exponential_counter_period(0), + state_pipeline(0), + envelope_pipeline(0), + exponential_pipeline(0), + state(RELEASE), + next_state(RELEASE), + counter_enabled(true), + gate(false), + resetLfsr(false), + envelope_counter(0xaa), + attack(0), + decay(0), + sustain(0), + release(0), + env3(0) + {} + + /** + * SID reset. + */ + void reset(); + + /** + * Write control register. + * + * @param control + * control register value + */ + void writeCONTROL_REG(unsigned char control); + + /** + * Write Attack/Decay register. + * + * @param attack_decay + * attack/decay value + */ + void writeATTACK_DECAY(unsigned char attack_decay); + + /** + * Write Sustain/Release register. + * + * @param sustain_release + * sustain/release value + */ + void writeSUSTAIN_RELEASE(unsigned char sustain_release); + + /** + * Return the envelope current value. + * + * @return envelope counter value + */ + unsigned char readENV() const { return env3; } +}; + +} // namespace reSIDfp + +#if RESID_INLINING || defined(ENVELOPEGENERATOR_CPP) + +namespace reSIDfp +{ + +RESID_INLINE +void EnvelopeGenerator::clock() +{ + env3 = envelope_counter; + + if (unlikely(new_exponential_counter_period > 0)) + { + exponential_counter_period = new_exponential_counter_period; + new_exponential_counter_period = 0; + } + + if (unlikely(state_pipeline)) + { + state_change(); + } + + if (unlikely(envelope_pipeline != 0) && (--envelope_pipeline == 0)) + { + if (likely(counter_enabled)) + { + if (state == ATTACK) + { + if (++envelope_counter==0xff) + { + next_state = DECAY_SUSTAIN; + state_pipeline = 3; + } + } + else if ((state == DECAY_SUSTAIN) || (state == RELEASE)) + { + if (--envelope_counter==0x00) + { + counter_enabled = false; + } + } + + set_exponential_counter(); + } + } + else if (unlikely(exponential_pipeline != 0) && (--exponential_pipeline == 0)) + { + exponential_counter = 0; + + if (((state == DECAY_SUSTAIN) && (envelope_counter != sustain)) + || (state == RELEASE)) + { + // The envelope counter can flip from 0x00 to 0xff by changing state to + // attack, then to release. The envelope counter will then continue + // counting down in the release state. + // This has been verified by sampling ENV3. + + envelope_pipeline = 1; + } + } + else if (unlikely(resetLfsr)) + { + lfsr = 0x7fff; + resetLfsr = false; + + if (state == ATTACK) + { + // The first envelope step in the attack state also resets the exponential + // counter. This has been verified by sampling ENV3. + exponential_counter = 0; // NOTE this is actually delayed one cycle, not modeled + + // The envelope counter can flip from 0xff to 0x00 by changing state to + // release, then to attack. The envelope counter is then frozen at + // zero; to unlock this situation the state must be changed to release, + // then to attack. This has been verified by sampling ENV3. + + envelope_pipeline = 2; + } + else + { + if (counter_enabled && (++exponential_counter == exponential_counter_period)) + exponential_pipeline = exponential_counter_period != 1 ? 2 : 1; + } + } + + // ADSR delay bug. + // If the rate counter comparison value is set below the current value of the + // rate counter, the counter will continue counting up until it wraps around + // to zero at 2^15 = 0x8000, and then count rate_period - 1 before the + // envelope can constly be stepped. + // This has been verified by sampling ENV3. + + // check to see if LFSR matches table value + if (likely(lfsr != rate)) + { + // it wasn't a match, clock the LFSR once + // by performing XOR on last 2 bits + const unsigned int feedback = ((lfsr << 14) ^ (lfsr << 13)) & 0x4000; + lfsr = (lfsr >> 1) | feedback; + } + else + { + resetLfsr = true; + } +} + +/** + * This is what happens on chip during state switching, + * based on die reverse engineering and transistor level + * emulation. + * + * Attack + * + * 0 - Gate on + * 1 - Counting direction changes + * During this cycle the decay rate is "accidentally" activated + * 2 - Counter is being inverted + * Now the attack rate is correctly activated + * Counter is enabled + * 3 - Counter will be counting upward from now on + * + * Decay + * + * 0 - Counter == $ff + * 1 - Counting direction changes + * The attack state is still active + * 2 - Counter is being inverted + * During this cycle the decay state is activated + * 3 - Counter will be counting downward from now on + * + * Release + * + * 0 - Gate off + * 1 - During this cycle the release state is activated if coming from sustain/decay + * *2 - Counter is being inverted, the release state is activated + * *3 - Counter will be counting downward from now on + * + * (* only if coming directly from Attack state) + * + * Freeze + * + * 0 - Counter == $00 + * 1 - Nothing + * 2 - Counter is disabled + */ +RESID_INLINE +void EnvelopeGenerator::state_change() +{ + state_pipeline--; + + switch (next_state) + { + case ATTACK: + if (state_pipeline == 1) + { + // The decay rate is "accidentally" enabled during first cycle of attack phase + rate = adsrtable[decay]; + } + else if (state_pipeline == 0) + { + state = ATTACK; + // The attack rate is correctly enabled during second cycle of attack phase + rate = adsrtable[attack]; + counter_enabled = true; + } + break; + case DECAY_SUSTAIN: + if (state_pipeline == 0) + { + state = DECAY_SUSTAIN; + rate = adsrtable[decay]; + } + break; + case RELEASE: + if (((state == ATTACK) && (state_pipeline == 0)) + || ((state == DECAY_SUSTAIN) && (state_pipeline == 1))) + { + state = RELEASE; + rate = adsrtable[release]; + } + break; + } +} + +RESID_INLINE +void EnvelopeGenerator::set_exponential_counter() +{ + // Check for change of exponential counter period. + // + // For a detailed description see: + // http://ploguechipsounds.blogspot.it/2010/03/sid-6581r3-adsr-tables-up-close.html + switch (envelope_counter) + { + case 0xff: + case 0x00: + new_exponential_counter_period = 1; + break; + + case 0x5d: + new_exponential_counter_period = 2; + break; + + case 0x36: + new_exponential_counter_period = 4; + break; + + case 0x1a: + new_exponential_counter_period = 8; + break; + + case 0x0e: + new_exponential_counter_period = 16; + break; + + case 0x06: + new_exponential_counter_period = 30; + break; + } +} + +} // namespace reSIDfp + +#endif + +#endif diff --git a/src/engine/platform/sound/c64_fp/ExternalFilter.cpp b/src/engine/platform/sound/c64_fp/ExternalFilter.cpp new file mode 100644 index 00000000..eac790b3 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/ExternalFilter.cpp @@ -0,0 +1,68 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2020 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * 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. + */ + +#define EXTERNALFILTER_CPP + +#include "ExternalFilter.h" + +namespace reSIDfp +{ + +/** + * Get the 3 dB attenuation point. + * + * @param res the resistance value in Ohms + * @param cap the capacitance value in Farads + */ +inline double getRC(double res, double cap) +{ + return res * cap; +} + +ExternalFilter::ExternalFilter() : + w0lp_1_s7(0), + w0hp_1_s17(0) +{ + reset(); +} + +void ExternalFilter::setClockFrequency(double frequency) +{ + const double dt = 1. / frequency; + + // Low-pass: R = 10kOhm, C = 1000pF; w0l = dt/(dt+RC) = 1e-6/(1e-6+1e4*1e-9) = 0.091 + // Cutoff 1/2*PI*RC = 1/2*PI*1e4*1e-9 = 15915.5 Hz + w0lp_1_s7 = static_cast((dt / (dt + getRC(10e3, 1000e-12))) * (1 << 7) + 0.5); + + // High-pass: R = 10kOhm, C = 10uF; w0h = dt/(dt+RC) = 1e-6/(1e-6+1e4*1e-5) = 0.00000999 + // Cutoff 1/2*PI*RC = 1/2*PI*1e4*1e-5 = 1.59155 Hz + w0hp_1_s17 = static_cast((dt / (dt + getRC(10e3, 10e-6))) * (1 << 17) + 0.5); +} + +void ExternalFilter::reset() +{ + // State of filter. + Vlp = 0; //1 << (15 + 11); + Vhp = 0; +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/ExternalFilter.h b/src/engine/platform/sound/c64_fp/ExternalFilter.h new file mode 100644 index 00000000..760ee5c2 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/ExternalFilter.h @@ -0,0 +1,125 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2020 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef EXTERNALFILTER_H +#define EXTERNALFILTER_H + +#include "siddefs-fp.h" + +namespace reSIDfp +{ + +/** + * The audio output stage in a Commodore 64 consists of two STC networks, a + * low-pass RC filter with 3 dB frequency 16kHz followed by a DC-blocker which + * acts as a high-pass filter with a cutoff dependent on the attached audio + * equipment impedance. Here we suppose an impedance of 10kOhm resulting + * in a 3 dB attenuation at 1.6Hz. + * To operate properly the 6581 audio output needs a pull-down resistor + *(1KOhm recommended, not needed on 8580) + * + * ~~~ + * 9/12V + * -----+ + * audio| 10k | + * +---o----R---o--------o-----(K) +----- + * out | | | | | |audio + * -----+ R 1k C 1000 | | 10 uF | + * | | pF +-C----o-----C-----+ 10k + * 470 | | + * GND GND pF R 1K | amp + * * * | +----- + * + * GND + * ~~~ + * + * The STC networks are connected with a [BJT] based [common collector] + * used as a voltage follower (featuring a 2SC1815 NPN transistor). + * * The C64c board additionally includes a [bootstrap] condenser to increase + * the input impedance of the common collector. + * + * [BJT]: https://en.wikipedia.org/wiki/Bipolar_junction_transistor + * [common collector]: https://en.wikipedia.org/wiki/Common_collector + * [bootstrap]: https://en.wikipedia.org/wiki/Bootstrapping_(electronics) + */ +class ExternalFilter +{ +private: + /// Lowpass filter voltage + int Vlp; + + /// Highpass filter voltage + int Vhp; + + int w0lp_1_s7; + + int w0hp_1_s17; + +public: + /** + * SID clocking. + * + * @param input + */ + int clock(unsigned short input); + + /** + * Constructor. + */ + ExternalFilter(); + + /** + * Setup of the external filter sampling parameters. + * + * @param frequency the main system clock frequency + */ + void setClockFrequency(double frequency); + + /** + * SID reset. + */ + void reset(); +}; + +} // namespace reSIDfp + +#if RESID_INLINING || defined(EXTERNALFILTER_CPP) + +namespace reSIDfp +{ + +RESID_INLINE +int ExternalFilter::clock(unsigned short input) +{ + const int Vi = (static_cast(input)<<11) - (1 << (11+15)); + const int dVlp = (w0lp_1_s7 * (Vi - Vlp) >> 7); + const int dVhp = (w0hp_1_s17 * (Vlp - Vhp) >> 17); + Vlp += dVlp; + Vhp += dVhp; + return (Vlp - Vhp) >> 11; +} + +} // namespace reSIDfp + +#endif + +#endif diff --git a/src/engine/platform/sound/c64_fp/Filter.cpp b/src/engine/platform/sound/c64_fp/Filter.cpp new file mode 100644 index 00000000..2a2dd24f --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Filter.cpp @@ -0,0 +1,90 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2013 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * 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 "Filter.h" + +namespace reSIDfp +{ + +void Filter::enable(bool enable) +{ + enabled = enable; + + if (enabled) + { + writeRES_FILT(filt); + } + else + { + filt1 = filt2 = filt3 = filtE = false; + } +} + +void Filter::reset() +{ + writeFC_LO(0); + writeFC_HI(0); + writeMODE_VOL(0); + writeRES_FILT(0); +} + +void Filter::writeFC_LO(unsigned char fc_lo) +{ + fc = (fc & 0x7f8) | (fc_lo & 0x007); + updatedCenterFrequency(); +} + +void Filter::writeFC_HI(unsigned char fc_hi) +{ + fc = (fc_hi << 3 & 0x7f8) | (fc & 0x007); + updatedCenterFrequency(); +} + +void Filter::writeRES_FILT(unsigned char res_filt) +{ + filt = res_filt; + + updateResonance((res_filt >> 4) & 0x0f); + + if (enabled) + { + filt1 = (filt & 0x01) != 0; + filt2 = (filt & 0x02) != 0; + filt3 = (filt & 0x04) != 0; + filtE = (filt & 0x08) != 0; + } + + updatedMixing(); +} + +void Filter::writeMODE_VOL(unsigned char mode_vol) +{ + vol = mode_vol & 0x0f; + lp = (mode_vol & 0x10) != 0; + bp = (mode_vol & 0x20) != 0; + hp = (mode_vol & 0x40) != 0; + voice3off = (mode_vol & 0x80) != 0; + + updatedMixing(); +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/Filter.h b/src/engine/platform/sound/c64_fp/Filter.h new file mode 100644 index 00000000..4b347336 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Filter.h @@ -0,0 +1,177 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2017 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef FILTER_H +#define FILTER_H + +namespace reSIDfp +{ + +/** + * SID filter base class + */ +class Filter +{ +protected: + /// Current volume amplifier setting. + unsigned short* currentGain; + + /// Current filter/voice mixer setting. + unsigned short* currentMixer; + + /// Filter input summer setting. + unsigned short* currentSummer; + + /// Filter resonance value. + unsigned short* currentResonance; + + /// Filter highpass state. + int Vhp; + + /// Filter bandpass state. + int Vbp; + + /// Filter lowpass state. + int Vlp; + + /// Filter external input. + int ve; + + /// Filter cutoff frequency. + unsigned int fc; + + /// Routing to filter or outside filter + bool filt1, filt2, filt3, filtE; + + /// Switch voice 3 off. + bool voice3off; + + /// Highpass, bandpass, and lowpass filter modes. + bool hp, bp, lp; + + /// Current volume. + unsigned char vol; + +private: + /// Filter enabled. + bool enabled; + + /// Selects which inputs to route through filter. + unsigned char filt; + +protected: + /** + * Set filter cutoff frequency. + */ + virtual void updatedCenterFrequency() = 0; + + /** + * Set filter resonance. + */ + virtual void updateResonance(unsigned char res) = 0; + + /** + * Mixing configuration modified (offsets change) + */ + virtual void updatedMixing() = 0; + +public: + Filter() : + currentGain(nullptr), + currentMixer(nullptr), + currentSummer(nullptr), + currentResonance(nullptr), + Vhp(0), + Vbp(0), + Vlp(0), + ve(0), + fc(0), + filt1(false), + filt2(false), + filt3(false), + filtE(false), + voice3off(false), + hp(false), + bp(false), + lp(false), + vol(0), + enabled(true), + filt(0) {} + + virtual ~Filter() {} + + /** + * SID clocking - 1 cycle + * + * @param v1 voice 1 in + * @param v2 voice 2 in + * @param v3 voice 3 in + * @return filtered output + */ + virtual unsigned short clock(int v1, int v2, int v3) = 0; + + /** + * Enable filter. + * + * @param enable + */ + void enable(bool enable); + + /** + * SID reset. + */ + void reset(); + + /** + * Write Frequency Cutoff Low register. + * + * @param fc_lo Frequency Cutoff Low-Byte + */ + void writeFC_LO(unsigned char fc_lo); + + /** + * Write Frequency Cutoff High register. + * + * @param fc_hi Frequency Cutoff High-Byte + */ + void writeFC_HI(unsigned char fc_hi); + + /** + * Write Resonance/Filter register. + * + * @param res_filt Resonance/Filter + */ + void writeRES_FILT(unsigned char res_filt); + + /** + * Write filter Mode/Volume register. + * + * @param mode_vol Filter Mode/Volume + */ + void writeMODE_VOL(unsigned char mode_vol); + + virtual void input(int input) = 0; +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/Filter6581.cpp b/src/engine/platform/sound/c64_fp/Filter6581.cpp new file mode 100644 index 00000000..c064a880 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Filter6581.cpp @@ -0,0 +1,75 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2015 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * 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. + */ + +#define FILTER6581_CPP + +#include "Filter6581.h" + +#include "Integrator6581.h" + +namespace reSIDfp +{ + +Filter6581::~Filter6581() +{ + delete [] f0_dac; +} + +void Filter6581::updatedCenterFrequency() +{ + const unsigned short Vw = f0_dac[fc]; + hpIntegrator->setVw(Vw); + bpIntegrator->setVw(Vw); +} + +void Filter6581::updatedMixing() +{ + currentGain = gain_vol[vol]; + + unsigned int ni = 0; + unsigned int no = 0; + + (filt1 ? ni : no)++; + (filt2 ? ni : no)++; + + if (filt3) ni++; + else if (!voice3off) no++; + + (filtE ? ni : no)++; + + currentSummer = summer[ni]; + + if (lp) no++; + if (bp) no++; + if (hp) no++; + + currentMixer = mixer[no]; +} + +void Filter6581::setFilterCurve(double curvePosition) +{ + delete [] f0_dac; + f0_dac = FilterModelConfig6581::getInstance()->getDAC(curvePosition); + updatedCenterFrequency(); +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/Filter6581.h b/src/engine/platform/sound/c64_fp/Filter6581.h new file mode 100644 index 00000000..7fca331a --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Filter6581.h @@ -0,0 +1,425 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef FILTER6581_H +#define FILTER6581_H + +#include "siddefs-fp.h" + +#include + +#include "Filter.h" +#include "FilterModelConfig6581.h" + +#include "sidcxx11.h" + +namespace reSIDfp +{ + +class Integrator6581; + +/** + * The SID filter is modeled with a two-integrator-loop biquadratic filter, + * which has been confirmed by Bob Yannes to be the actual circuit used in + * the SID chip. + * + * Measurements show that excellent emulation of the SID filter is achieved, + * except when high resonance is combined with high sustain levels. + * In this case the SID op-amps are performing less than ideally and are + * causing some peculiar behavior of the SID filter. This however seems to + * have more effect on the overall amplitude than on the color of the sound. + * + * The theory for the filter circuit can be found in "Microelectric Circuits" + * by Adel S. Sedra and Kenneth C. Smith. + * The circuit is modeled based on the explanation found there except that + * an additional inverter is used in the feedback from the bandpass output, + * allowing the summer op-amp to operate in single-ended mode. This yields + * filter outputs with levels independent of Q, which corresponds with the + * results obtained from a real SID. + * + * We have been able to model the summer and the two integrators of the circuit + * to form components of an IIR filter. + * Vhp is the output of the summer, Vbp is the output of the first integrator, + * and Vlp is the output of the second integrator in the filter circuit. + * + * According to Bob Yannes, the active stages of the SID filter are not really + * op-amps. Rather, simple NMOS inverters are used. By biasing an inverter + * into its region of quasi-linear operation using a feedback resistor from + * input to output, a MOS inverter can be made to act like an op-amp for + * small signals centered around the switching threshold. + * + * In 2008, Michael Huth facilitated closer investigation of the SID 6581 + * filter circuit by publishing high quality microscope photographs of the die. + * Tommi Lempinen has done an impressive work on re-vectorizing and annotating + * the die photographs, substantially simplifying further analysis of the + * filter circuit. + * + * The filter schematics below are reverse engineered from these re-vectorized + * and annotated die photographs. While the filter first depicted in reSID 0.9 + * is a correct model of the basic filter, the schematics are now completed + * with the audio mixer and output stage, including details on intended + * relative resistor values. Also included are schematics for the NMOS FET + * voltage controlled resistors (VCRs) used to control cutoff frequency, the + * DAC which controls the VCRs, the NMOS op-amps, and the output buffer. + * + * + * SID filter / mixer / output + * --------------------------- + * ~~~ + * +---------------------------------------------------+ + * | | + * | +--1R1-- \--+ D7 | + * | +---R1--+ | | | + * | | | o--2R1-- \--o D6 | + * | +---------o----o--Rw--o--[A>--o--Rw--o--[A>--o + * ve (EXT IN) | | | | + * D3 \ ---------------R8--o | | (CAP2A) | (CAP1A) + * | v3 | | vhp | vbp | vlp + * D2 | \ -----------R8--o +-----+ | | + * | | v2 | | | | + * D1 | | \ -------R8--o | +----------------+ | + * | | | v1 | | | | + * D0 | | | \ ---R8--+ | | +---------------------------+ + * | | | | | | | + * R6 R6 R6 R6 R6 R6 R6 + * | | | | $18 | | | $18 + * | \ | | D7: 1=open \ \ \ D6 - D4: 0=open + * | | | | | | | + * +---o---o---o-------------o---o---+ 12V + * | + * | D3 +--/ --1R2--+ | + * | +---R8--+ | | +---R2--+ | + * | | | D2 o--/ --2R2--o | | ||--+ + * +---o--[A>--o------o o--o--[A>--o--|| + * D1 o--/ --4R2--o (4.25R2) ||--+ + * $18 | | | + * 0=open D0 +--/ --8R2--+ (8.75R2) | + * + * vo (AUDIO + * OUT) + * + * + * v1 - voice 1 + * v2 - voice 2 + * v3 - voice 3 + * ve - ext in + * vhp - highpass output + * vbp - bandpass output + * vlp - lowpass output + * vo - audio out + * [A> - single ended inverting op-amp (self-biased NMOS inverter) + * Rn - "resistors", implemented with custom NMOS FETs + * Rw - cutoff frequency resistor (VCR) + * C - capacitor + * ~~~ + * Notes: + * + * R2 ~ 2.0*R1 + * R6 ~ 6.0*R1 + * R8 ~ 8.0*R1 + * R24 ~ 24.0*R1 + * + * The Rn "resistors" in the circuit are implemented with custom NMOS FETs, + * probably because of space constraints on the SID die. The silicon substrate + * is laid out in a narrow strip or "snake", with a strip length proportional + * to the intended resistance. The polysilicon gate electrode covers the entire + * silicon substrate and is fixed at 12V in order for the NMOS FET to operate + * in triode mode (a.k.a. linear mode or ohmic mode). + * + * Even in "linear mode", an NMOS FET is only an approximation of a resistor, + * as the apparant resistance increases with increasing drain-to-source + * voltage. If the drain-to-source voltage should approach the gate voltage + * of 12V, the NMOS FET will enter saturation mode (a.k.a. active mode), and + * the NMOS FET will not operate anywhere like a resistor. + * + * + * + * NMOS FET voltage controlled resistor (VCR) + * ------------------------------------------ + * ~~~ + * Vw + * + * | + * | + * R1 + * | + * +--R1--o + * | __|__ + * | ----- + * | | | + * vi -----o----+ +--o----- vo + * | | + * +----R24----+ + * + * + * vi - input + * vo - output + * Rn - "resistors", implemented with custom NMOS FETs + * Vw - voltage from 11-bit DAC (frequency cutoff control) + * ~~~ + * Notes: + * + * An approximate value for R24 can be found by using the formula for the + * filter cutoff frequency: + * + * FCmin = 1/(2*pi*Rmax*C) + * + * Assuming that a the setting for minimum cutoff frequency in combination with + * a low level input signal ensures that only negligible current will flow + * through the transistor in the schematics above, values for FCmin and C can + * be substituted in this formula to find Rmax. + * Using C = 470pF and FCmin = 220Hz (measured value), we get: + * + * FCmin = 1/(2*pi*Rmax*C) + * Rmax = 1/(2*pi*FCmin*C) = 1/(2*pi*220*470e-12) ~ 1.5MOhm + * + * From this it follows that: + * R24 = Rmax ~ 1.5MOhm + * R1 ~ R24/24 ~ 64kOhm + * R2 ~ 2.0*R1 ~ 128kOhm + * R6 ~ 6.0*R1 ~ 384kOhm + * R8 ~ 8.0*R1 ~ 512kOhm + * + * Note that these are only approximate values for one particular SID chip, + * due to process variations the values can be substantially different in + * other chips. + * + * + * + * Filter frequency cutoff DAC + * --------------------------- + * + * ~~~ + * 12V 10 9 8 7 6 5 4 3 2 1 0 VGND + * | | | | | | | | | | | | | Missing + * 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R termination + * | | | | | | | | | | | | | + * Vw --o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o- -+ + * + * + * Bit on: 12V + * Bit off: 5V (VGND) + * ~~~ + * As is the case with all MOS 6581 DACs, the termination to (virtual) ground + * at bit 0 is missing. + * + * Furthermore, the control of the two VCRs imposes a load on the DAC output + * which varies with the input signals to the VCRs. This can be seen from the + * VCR figure above. + * + * + * + * "Op-amp" (self-biased NMOS inverter) + * ------------------------------------ + * ~~~ + * + * 12V + * + * | + * +-----------o + * | | + * | +------o + * | | | + * | | ||--+ + * | +--|| + * | ||--+ + * ||--+ | + * vi -----|| o---o----- vo + * ||--+ | | + * | ||--+ | + * |-------|| | + * | ||--+ | + * ||--+ | | + * +--|| | | + * | ||--+ | | + * | | | | + * | +-----------o | + * | | | + * | | + * | GND | + * | | + * +----------------------+ + * + * + * vi - input + * vo - output + * ~~~ + * Notes: + * + * The schematics above are laid out to show that the "op-amp" logically + * consists of two building blocks; a saturated load NMOS inverter (on the + * right hand side of the schematics) with a buffer / bias input stage + * consisting of a variable saturated load NMOS inverter (on the left hand + * side of the schematics). + * + * Provided a reasonably high input impedance and a reasonably low output + * impedance, the "op-amp" can be modeled as a voltage transfer function + * mapping input voltage to output voltage. + * + * + * + * Output buffer (NMOS voltage follower) + * ------------------------------------- + * ~~~ + * + * 12V + * + * | + * | + * ||--+ + * vi -----|| + * ||--+ + * | + * o------ vo + * | (AUDIO + * Rext OUT) + * | + * | + * + * GND + * + * vi - input + * vo - output + * Rext - external resistor, 1kOhm + * ~~~ + * Notes: + * + * The external resistor Rext is needed to complete the NMOS voltage follower, + * this resistor has a recommended value of 1kOhm. + * + * Die photographs show that actually, two NMOS transistors are used in the + * voltage follower. However the two transistors are coupled in parallel (all + * terminals are pairwise common), which implies that we can model the two + * transistors as one. + */ +class Filter6581 final : public Filter +{ +private: + const unsigned short* f0_dac; + + unsigned short** mixer; + unsigned short** summer; + unsigned short** gain_res; + unsigned short** gain_vol; + + const int voiceScaleS11; + const int voiceDC; + + /// VCR + associated capacitor connected to highpass output. + std::unique_ptr const hpIntegrator; + + /// VCR + associated capacitor connected to bandpass output. + std::unique_ptr const bpIntegrator; + +protected: + /** + * Set filter cutoff frequency. + */ + void updatedCenterFrequency() override; + + /** + * Set filter resonance. + * + * In the MOS 6581, 1/Q is controlled linearly by res. + */ + void updateResonance(unsigned char res) override { currentResonance = gain_res[res]; } + + void updatedMixing() override; + +public: + Filter6581() : + f0_dac(FilterModelConfig6581::getInstance()->getDAC(0.5)), + mixer(FilterModelConfig6581::getInstance()->getMixer()), + summer(FilterModelConfig6581::getInstance()->getSummer()), + gain_res(FilterModelConfig6581::getInstance()->getGainRes()), + gain_vol(FilterModelConfig6581::getInstance()->getGainVol()), + voiceScaleS11(FilterModelConfig6581::getInstance()->getVoiceScaleS11()), + voiceDC(FilterModelConfig6581::getInstance()->getNormalizedVoiceDC()), + hpIntegrator(FilterModelConfig6581::getInstance()->buildIntegrator()), + bpIntegrator(FilterModelConfig6581::getInstance()->buildIntegrator()) + { + input(0); + } + + ~Filter6581(); + + unsigned short clock(int voice1, int voice2, int voice3) override; + + void input(int sample) override { ve = (sample * voiceScaleS11 * 3 >> 11) + mixer[0][0]; } + + /** + * Set filter curve type based on single parameter. + * + * @param curvePosition 0 .. 1, where 0 sets center frequency high ("light") and 1 sets it low ("dark"), default is 0.5 + */ + void setFilterCurve(double curvePosition); +}; + +} // namespace reSIDfp + +#if RESID_INLINING || defined(FILTER6581_CPP) + +#include "Integrator6581.h" + +namespace reSIDfp +{ + +RESID_INLINE +unsigned short Filter6581::clock(int voice1, int voice2, int voice3) +{ + voice1 = (voice1 * voiceScaleS11 >> 15) + voiceDC; + voice2 = (voice2 * voiceScaleS11 >> 15) + voiceDC; + // Voice 3 is silenced by voice3off if it is not routed through the filter. + voice3 = (filt3 || !voice3off) ? (voice3 * voiceScaleS11 >> 15) + voiceDC : 0; + + int Vi = 0; + int Vo = 0; + + (filt1 ? Vi : Vo) += voice1; + (filt2 ? Vi : Vo) += voice2; + (filt3 ? Vi : Vo) += voice3; + (filtE ? Vi : Vo) += ve; + + Vhp = currentSummer[currentResonance[Vbp] + Vlp + Vi]; + Vbp = hpIntegrator->solve(Vhp); + Vlp = bpIntegrator->solve(Vbp); + + if (lp) Vo += Vlp; + if (bp) Vo += Vbp; + if (hp) Vo += Vhp; + + return currentGain[currentMixer[Vo]]; +} + +} // namespace reSIDfp + +#endif + +#endif diff --git a/src/engine/platform/sound/c64_fp/Filter8580.cpp b/src/engine/platform/sound/c64_fp/Filter8580.cpp new file mode 100644 index 00000000..a70285a8 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Filter8580.cpp @@ -0,0 +1,101 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2019 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * 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. + */ + +#define FILTER8580_CPP + +#include "Filter8580.h" + +#include "Integrator8580.h" + +namespace reSIDfp +{ + +/** + * W/L ratio of frequency DAC bit 0, + * other bit are proportional. + * When no bit are selected a resistance with half + * W/L ratio is selected. + */ +const double DAC_WL0 = 0.00615; + +Filter8580::~Filter8580() {} + +void Filter8580::updatedCenterFrequency() +{ + double wl; + double dacWL = DAC_WL0; + if (fc) + { + wl = 0.; + for (unsigned int i = 0; i < 11; i++) + { + if (fc & (1 << i)) + { + wl += dacWL; + } + dacWL *= 2.; + } + } + else + { + wl = dacWL/2.; + } + + hpIntegrator->setFc(wl); + bpIntegrator->setFc(wl); +} + +void Filter8580::updatedMixing() +{ + currentGain = gain_vol[vol]; + + unsigned int ni = 0; + unsigned int no = 0; + + (filt1 ? ni : no)++; + (filt2 ? ni : no)++; + + if (filt3) ni++; + else if (!voice3off) no++; + + (filtE ? ni : no)++; + + currentSummer = summer[ni]; + + if (lp) no++; + if (bp) no++; + if (hp) no++; + + currentMixer = mixer[no]; +} + +void Filter8580::setFilterCurve(double curvePosition) +{ + // Adjust cp + // 1.2 <= cp <= 1.8 + cp = 1.8 - curvePosition * 3./5.; + + hpIntegrator->setV(cp); + bpIntegrator->setV(cp); +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/Filter8580.h b/src/engine/platform/sound/c64_fp/Filter8580.h new file mode 100644 index 00000000..2166ec0d --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Filter8580.h @@ -0,0 +1,383 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef FILTER8580_H +#define FILTER8580_H + +#include "siddefs-fp.h" + +#include + +#include "Filter.h" +#include "FilterModelConfig8580.h" +#include "Integrator8580.h" + +#include "sidcxx11.h" + +namespace reSIDfp +{ + +class Integrator8580; + +/** + * Filter for 8580 chip + * -------------------- + * The 8580 filter stage had been redesigned to be more linear and robust + * against temperature change. It also features real op-amps and a + * revisited resonance model. + * The filter schematics below are reverse engineered from re-vectorized + * and annotated die photographs. Credits to Michael Huth for the microscope + * photographs of the die, Tommi Lempinen for re-vectorizating and annotating + * the images and ttlworks from forum.6502.org for the circuit analysis. + * + * ~~~ + * + * +---------------------------------------------------+ + * | $17 +----Rf-+ | + * | | | | + * | D4&!D5 o- \-R3-o | + * | | | $17 | + * | !D4&!D5 o- \-R2-o | + * | | | +---R8-- \--+ !D6&D7 | + * | D4&!D5 o- \-R1-o | | | + * | | | o---RC-- \--o D6&D7 | + * | +---------o----o--Rfc-o--[A>--o--Rfc-o--[A>--o + * ve (EXT IN) | | | | + * D3 \ --------------R12--o | | (CAP2A) | (CAP1A) + * | v3 | | vhp | vbp | vlp + * D2 | \ -----------R7--o +-----+ | | + * | | v2 | | | | + * D1 | | \ -------R7--o | +----------------+ | + * | | | v1 | | | | + * D0 | | | \ ---R7--+ | | +---------------------------+ + * | | | | | | | + * R9 R5 R5 R5 R5 R5 R5 + * | | | | $18 | | | $18 + * | \ | | D7: 1=open \ \ \ D6 - D4: 0=open + * | | | | | | | + * +---o---o---o-------------o---o---+ + * | + * | D3 +--/ --1R4--+ + * | +---R8--+ | | +---R2--+ + * | | | D2 o--/ --2R4--o | | + * +---o--[A>--o------o o--o--[A>--o-- vo (AUDIO OUT) + * D1 o--/ --4R4--o + * $18 | | + * 0=open D0 +--/ --8R4--+ + * + * + * + * Resonance + * --------- + * For resonance, we have two tiny DACs that controls both the input + * and feedback resistances. + * + * The "resistors" are switched in as follows by bits in register $17: + * + * feedback: + * R1: bit4&!bit5 + * R2: !bit4&bit5 + * R3: bit4&bit5 + * Rf: always on + * + * input: + * R4: bit6&!bit7 + * R8: !bit6&bit7 + * RC: bit6&bit7 + * Ri: !(R4|R8|RC) = !(bit6|bit7) = !bit6&!bit7 + * + * + * The relative "resistor" values are approximately (using channel length): + * + * R1 = 15.3*Ri + * R2 = 7.3*Ri + * R3 = 4.7*Ri + * Rf = 1.4*Ri + * R4 = 1.4*Ri + * R8 = 2.0*Ri + * RC = 2.8*Ri + * + * + * Approximate values for 1/Q can now be found as follows (assuming an + * ideal op-amp): + * + * res feedback input -gain (1/Q) + * --- -------- ----- ---------- + * 0 Rf Ri Rf/Ri = 1/(Ri*(1/Rf)) = 1/0.71 + * 1 Rf|R1 Ri (Rf|R1)/Ri = 1/(Ri*(1/Rf+1/R1)) = 1/0.78 + * 2 Rf|R2 Ri (Rf|R2)/Ri = 1/(Ri*(1/Rf+1/R2)) = 1/0.85 + * 3 Rf|R3 Ri (Rf|R3)/Ri = 1/(Ri*(1/Rf+1/R3)) = 1/0.92 + * 4 Rf R4 Rf/R4 = 1/(R4*(1/Rf)) = 1/1.00 + * 5 Rf|R1 R4 (Rf|R1)/R4 = 1/(R4*(1/Rf+1/R1)) = 1/1.10 + * 6 Rf|R2 R4 (Rf|R2)/R4 = 1/(R4*(1/Rf+1/R2)) = 1/1.20 + * 7 Rf|R3 R4 (Rf|R3)/R4 = 1/(R4*(1/Rf+1/R3)) = 1/1.30 + * 8 Rf R8 Rf/R8 = 1/(R8*(1/Rf)) = 1/1.43 + * 9 Rf|R1 R8 (Rf|R1)/R8 = 1/(R8*(1/Rf+1/R1)) = 1/1.56 + * A Rf|R2 R8 (Rf|R2)/R8 = 1/(R8*(1/Rf+1/R2)) = 1/1.70 + * B Rf|R3 R8 (Rf|R3)/R8 = 1/(R8*(1/Rf+1/R3)) = 1/1.86 + * C Rf RC Rf/RC = 1/(RC*(1/Rf)) = 1/2.00 + * D Rf|R1 RC (Rf|R1)/RC = 1/(RC*(1/Rf+1/R1)) = 1/2.18 + * E Rf|R2 RC (Rf|R2)/RC = 1/(RC*(1/Rf+1/R2)) = 1/2.38 + * F Rf|R3 RC (Rf|R3)/RC = 1/(RC*(1/Rf+1/R3)) = 1/2.60 + * + * + * These data indicate that the following function for 1/Q has been + * modeled in the MOS 8580: + * + * 1/Q = 2^(1/2)*2^(-x/8) = 2^(1/2 - x/8) = 2^((4 - x)/8) + * + * + * + * Op-amps + * ------- + * Unlike the 6581, the 8580 has real OpAmps. + * + * Temperature compensated differential amplifier: + * + * 9V + * + * | + * +-------o-o-o-------+ + * | | | | + * | R R | + * +--|| | | ||--+ + * ||---o o---|| + * +--|| | | ||--+ + * | | | | + * o-----+ | | o--- Va + * | | | | | + * +--|| | | | ||--+ + * ||-o-+---+---|| + * +--|| | | ||--+ + * | | | | + * | | + * GND | | GND + * ||--+ +--|| + * in- -----|| ||------ in+ + * ||----o----|| + * | + * 8 Current sink + * | + * + * GND + * + * Inverter + non-inverting output amplifier: + * + * Va ---o---||-------------------o--------------------+ + * | | 9V | + * | +----------+----------+ | | + * | 9V | | 9V | ||--+ | + * | | | 9V | | +-|| | + * | R | | | ||--+ ||--+ | + * | | | ||--+ +--|| o---o--- Vout + * | o---o---|| ||--+ ||--+ + * | | ||--+ o-----|| + * | ||--+ | ||--+ ||--+ + * +-----|| o-----|| | + * ||--+ | ||--+ + * | R | GND + * | + * GND GND + * GND + * + * + * + * Virtual ground + * -------------- + * A PolySi resitive voltage divider provides the voltage + * for the positive input of the filter op-amps. + * + * 5V + * +----------+ + * | | |\ | + * R1 +---|-\ | + * 5V | |A >---o--- Vref + * o-------|+/ + * | | |/ + * R10 R4 + * | | + * o---+ + * | + * R10 + * | + * + * GND + * + * Rn = n*R1 + * + * + * + * Rfc - freq control DAC resistance ladder + * ---------------------------------------- + * The 8580 has 11 bits for frequency control, but 12 bit DACs. + * If those 11 bits would be '0', the impedance of the DACs would be "infinitely high". + * To get around this, there is an 11 input NOR gate below the DACs sensing those 11 bits. + * If all are 0, the NOR gate gives the gate control voltage to the 12 bit DAC LSB. + * + * ----o---o--...--o---o---o--- + * | | | | | + * Rb10 Rb9 ... Rb1 Rb0 R0 + * | | | | | + * ----o---o--...--o---o---o--- + * + * + * + * Crystal stabilized precision switched capacitor voltage divider + * --------------------------------------------------------------- + * There is a FET working as a temperature sensor close to the DACs which changes the gate voltage + * of the frequency control DACs according to the temperature of the DACs, + * to reduce the effects of temperature on the filter curve. + * An asynchronous 3 bit binary counter, running at the speed of PHI2, drives two big capacitors + * whose AC resistance is then used as a voltage divider. + * This implicates that frequency difference between PAL and NTSC might shift the filter curve by 4% or such. + * + * |\ OpAmp has a smaller capacitor than the other OPs + * Vref ---|+\ + * |A >---o--- Vdac + * +-------|-/ | + * | |/ | + * | | + * C1 | C2 | + * +---||---o---+ +---o-----||-------o + * | | | | | | + * o----+ | ----- | | + * | | | ----- +----+ +-----o + * | ----- | | | | + * | ----- | ----- | + * | | | ----- | + * | +-----------+ | | + * | /Q Q | +-------+ + * GND +-----------+ FET close to DAC + * | clk/8 | working as temperature sensor + * +-----------+ + */ +class Filter8580 final : public Filter +{ +private: + unsigned short** mixer; + unsigned short** summer; + unsigned short** gain_res; + unsigned short** gain_vol; + + const int voiceScaleS11; + const int voiceDC; + + double cp; + + /// VCR + associated capacitor connected to highpass output. + std::unique_ptr const hpIntegrator; + + /// VCR + associated capacitor connected to bandpass output. + std::unique_ptr const bpIntegrator; + +protected: + /** + * Set filter cutoff frequency. + */ + void updatedCenterFrequency() override; + + /** + * Set filter resonance. + * + * @param res the new resonance value + */ + void updateResonance(unsigned char res) override { currentResonance = gain_res[res]; } + + void updatedMixing() override; + +public: + Filter8580() : + mixer(FilterModelConfig8580::getInstance()->getMixer()), + summer(FilterModelConfig8580::getInstance()->getSummer()), + gain_res(FilterModelConfig8580::getInstance()->getGainRes()), + gain_vol(FilterModelConfig8580::getInstance()->getGainVol()), + voiceScaleS11(FilterModelConfig8580::getInstance()->getVoiceScaleS11()), + voiceDC(FilterModelConfig8580::getInstance()->getNormalizedVoiceDC()), + cp(0.5), + hpIntegrator(FilterModelConfig8580::getInstance()->buildIntegrator()), + bpIntegrator(FilterModelConfig8580::getInstance()->buildIntegrator()) + { + setFilterCurve(cp); + input(0); + } + + ~Filter8580(); + + unsigned short clock(int voice1, int voice2, int voice3) override; + + void input(int sample) override { ve = (sample * voiceScaleS11 * 3 >> 11) + mixer[0][0]; } + + /** + * Set filter curve type based on single parameter. + * + * @param curvePosition 0 .. 1, where 0 sets center frequency high ("light") and 1 sets it low ("dark"), default is 0.5 + */ + void setFilterCurve(double curvePosition); +}; + +} // namespace reSIDfp + +#if RESID_INLINING || defined(FILTER8580_CPP) + +namespace reSIDfp +{ + +RESID_INLINE +unsigned short Filter8580::clock(int voice1, int voice2, int voice3) +{ + voice1 = (voice1 * voiceScaleS11 >> 15) + voiceDC; + voice2 = (voice2 * voiceScaleS11 >> 15) + voiceDC; + // Voice 3 is silenced by voice3off if it is not routed through the filter. + voice3 = (filt3 || !voice3off) ? (voice3 * voiceScaleS11 >> 15) + voiceDC : 0; + + int Vi = 0; + int Vo = 0; + + (filt1 ? Vi : Vo) += voice1; + (filt2 ? Vi : Vo) += voice2; + (filt3 ? Vi : Vo) += voice3; + (filtE ? Vi : Vo) += ve; + + Vhp = currentSummer[currentResonance[Vbp] + Vlp + Vi]; + Vbp = hpIntegrator->solve(Vhp); + Vlp = bpIntegrator->solve(Vbp); + + if (lp) Vo += Vlp; + if (bp) Vo += Vbp; + if (hp) Vo += Vhp; + + return currentGain[currentMixer[Vo]]; +} + +} // namespace reSIDfp + +#endif + +#endif diff --git a/src/engine/platform/sound/c64_fp/FilterModelConfig.cpp b/src/engine/platform/sound/c64_fp/FilterModelConfig.cpp new file mode 100644 index 00000000..8fb76238 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/FilterModelConfig.cpp @@ -0,0 +1,79 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * 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 "FilterModelConfig.h" + +#include + +namespace reSIDfp +{ + +FilterModelConfig::FilterModelConfig( + double vvr, + double vdv, + double c, + double vdd, + double vth, + double ucox, + const Spline::Point *opamp_voltage, + int opamp_size +) : + voice_voltage_range(vvr), + voice_DC_voltage(vdv), + C(c), + Vdd(vdd), + Vth(vth), + Ut(26.0e-3), + uCox(ucox), + Vddt(Vdd - Vth), + vmin(opamp_voltage[0].x), + vmax(std::max(Vddt, opamp_voltage[0].y)), + denorm(vmax - vmin), + norm(1.0 / denorm), + N16(norm * ((1 << 16) - 1)), + currFactorCoeff(denorm * (uCox / 2. * 1.0e-6 / C)) +{ + // Convert op-amp voltage transfer to 16 bit values. + + std::vector scaled_voltage(opamp_size); + + for (int i = 0; i < opamp_size; i++) + { + scaled_voltage[i].x = N16 * (opamp_voltage[i].x - opamp_voltage[i].y + denorm) / 2.; + scaled_voltage[i].y = N16 * (opamp_voltage[i].x - vmin); + } + + // Create lookup table mapping capacitor voltage to op-amp input voltage: + + Spline s(scaled_voltage); + + for (int x = 0; x < (1 << 16); x++) + { + const Spline::Point out = s.evaluate(x); + // If Vmax > max opamp_voltage the first elements may be negative + double tmp = out.x > 0. ? out.x : 0.; + assert(tmp < 65535.5); + opamp_rev[x] = static_cast(tmp + 0.5); + } +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/FilterModelConfig.h b/src/engine/platform/sound/c64_fp/FilterModelConfig.h new file mode 100644 index 00000000..d8ae77ab --- /dev/null +++ b/src/engine/platform/sound/c64_fp/FilterModelConfig.h @@ -0,0 +1,166 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef FILTERMODELCONFIG_H +#define FILTERMODELCONFIG_H + +#include +#include + +#include "Spline.h" + +#include "sidcxx11.h" + +namespace reSIDfp +{ + +class FilterModelConfig +{ +protected: + const double voice_voltage_range; + const double voice_DC_voltage; + + /// Capacitor value. + const double C; + + /// Transistor parameters. + //@{ + const double Vdd; + const double Vth; ///< Threshold voltage + const double Ut; ///< Thermal voltage: Ut = kT/q = 8.61734315e-5*T ~ 26mV + const double uCox; ///< Transconductance coefficient: u*Cox + const double Vddt; ///< Vdd - Vth + //@} + + // Derived stuff + const double vmin, vmax; + const double denorm, norm; + + /// Fixed point scaling for 16 bit op-amp output. + const double N16; + + /// Current factor coefficient for op-amp integrators. + const double currFactorCoeff; + + /// Lookup tables for gain and summer op-amps in output stage / filter. + //@{ + unsigned short* mixer[8]; //-V730_NOINIT this is initialized in the derived class constructor + unsigned short* summer[5]; //-V730_NOINIT this is initialized in the derived class constructor + unsigned short* gain_vol[16]; //-V730_NOINIT this is initialized in the derived class constructor + unsigned short* gain_res[16]; //-V730_NOINIT this is initialized in the derived class constructor + //@} + + /// Reverse op-amp transfer function. + unsigned short opamp_rev[1 << 16]; //-V730_NOINIT this is initialized in the derived class constructor + +private: + FilterModelConfig (const FilterModelConfig&) DELETE; + FilterModelConfig& operator= (const FilterModelConfig&) DELETE; + +protected: + /** + * @param vvr voice voltage range + * @param vdv voice DC voltage + * @param c capacitor value + * @param vdd Vdd + * @param vth threshold voltage + * @param ucox u*Cox + * @param ominv opamp min voltage + * @param omaxv opamp max voltage + */ + FilterModelConfig( + double vvr, + double vdv, + double c, + double vdd, + double vth, + double ucox, + const Spline::Point *opamp_voltage, + int opamp_size + ); + + ~FilterModelConfig() + { + for (int i = 0; i < 8; i++) + { + delete [] mixer[i]; + } + + for (int i = 0; i < 5; i++) + { + delete [] summer[i]; + } + + for (int i = 0; i < 16; i++) + { + delete [] gain_vol[i]; + delete [] gain_res[i]; + } + } + +public: + unsigned short** getGainVol() { return gain_vol; } + unsigned short** getGainRes() { return gain_res; } + unsigned short** getSummer() { return summer; } + unsigned short** getMixer() { return mixer; } + + /** + * The digital range of one voice is 20 bits; create a scaling term + * for multiplication which fits in 11 bits. + */ + int getVoiceScaleS11() const { return static_cast((norm * ((1 << 11) - 1)) * voice_voltage_range); } + + /** + * The "zero" output level of the voices. + */ + int getNormalizedVoiceDC() const { return static_cast(N16 * (voice_DC_voltage - vmin)); } + + inline unsigned short getOpampRev(int i) const { return opamp_rev[i]; } + inline double getVddt() const { return Vddt; } + inline double getVth() const { return Vth; } + inline double getVoiceDCVoltage() const { return voice_DC_voltage; } + + // helper functions + inline unsigned short getNormalizedValue(double value) const + { + const double tmp = N16 * (value - vmin); + assert(tmp > -0.5 && tmp < 65535.5); + return static_cast(tmp + 0.5); + } + + inline unsigned short getNormalizedCurrentFactor(double wl) const + { + const double tmp = (1 << 13) * currFactorCoeff * wl; + assert(tmp > -0.5 && tmp < 65535.5); + return static_cast(tmp + 0.5); + } + + inline unsigned short getNVmin() const { + const double tmp = N16 * vmin; + assert(tmp > -0.5 && tmp < 65535.5); + return static_cast(tmp + 0.5); + } +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/FilterModelConfig6581.cpp b/src/engine/platform/sound/c64_fp/FilterModelConfig6581.cpp new file mode 100644 index 00000000..3d86bdcf --- /dev/null +++ b/src/engine/platform/sound/c64_fp/FilterModelConfig6581.cpp @@ -0,0 +1,263 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2010 Dag Lem + * + * 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 "FilterModelConfig6581.h" + +#include + +#include "Integrator6581.h" +#include "OpAmp.h" + +namespace reSIDfp +{ + +#ifndef HAVE_CXX11 +/** + * Compute log(1+x) without losing precision for small values of x + * + * @note when compiling with -ffastm-math the compiler will + * optimize the expression away leaving a plain log(1. + x) + */ +inline double log1p(double x) +{ + return log(1. + x) - (((1. + x) - 1.) - x) / (1. + x); +} +#endif + +const unsigned int OPAMP_SIZE = 33; + +/** + * This is the SID 6581 op-amp voltage transfer function, measured on + * CAP1B/CAP1A on a chip marked MOS 6581R4AR 0687 14. + * All measured chips have op-amps with output voltages (and thus input + * voltages) within the range of 0.81V - 10.31V. + */ +const Spline::Point opamp_voltage[OPAMP_SIZE] = +{ + { 0.81, 10.31 }, // Approximate start of actual range + { 2.40, 10.31 }, + { 2.60, 10.30 }, + { 2.70, 10.29 }, + { 2.80, 10.26 }, + { 2.90, 10.17 }, + { 3.00, 10.04 }, + { 3.10, 9.83 }, + { 3.20, 9.58 }, + { 3.30, 9.32 }, + { 3.50, 8.69 }, + { 3.70, 8.00 }, + { 4.00, 6.89 }, + { 4.40, 5.21 }, + { 4.54, 4.54 }, // Working point (vi = vo) + { 4.60, 4.19 }, + { 4.80, 3.00 }, + { 4.90, 2.30 }, // Change of curvature + { 4.95, 2.03 }, + { 5.00, 1.88 }, + { 5.05, 1.77 }, + { 5.10, 1.69 }, + { 5.20, 1.58 }, + { 5.40, 1.44 }, + { 5.60, 1.33 }, + { 5.80, 1.26 }, + { 6.00, 1.21 }, + { 6.40, 1.12 }, + { 7.00, 1.02 }, + { 7.50, 0.97 }, + { 8.50, 0.89 }, + { 10.00, 0.81 }, + { 10.31, 0.81 }, // Approximate end of actual range +}; + +std::unique_ptr FilterModelConfig6581::instance(nullptr); + +FilterModelConfig6581* FilterModelConfig6581::getInstance() +{ + if (!instance.get()) + { + instance.reset(new FilterModelConfig6581()); + } + + return instance.get(); +} + +FilterModelConfig6581::FilterModelConfig6581() : + FilterModelConfig( + 1.5, // voice voltage range + 5.075, // voice DC voltage + 470e-12, // capacitor value + 12.18, // Vdd + 1.31, // Vth + 20e-6, // uCox + opamp_voltage, + OPAMP_SIZE + ), + WL_vcr(9.0 / 1.0), + WL_snake(1.0 / 115.0), + dac_zero(6.65), + dac_scale(2.63), + dac(DAC_BITS) +{ + dac.kinkedDac(MOS6581); + + // Create lookup tables for gains / summers. + + OpAmp opampModel(std::vector(std::begin(opamp_voltage), std::end(opamp_voltage)), Vddt); + + // The filter summer operates at n ~ 1, and has 5 fundamentally different + // input configurations (2 - 6 input "resistors"). + // + // Note that all "on" transistors are modeled as one. This is not + // entirely accurate, since the input for each transistor is different, + // and transistors are not linear components. However modeling all + // transistors separately would be extremely costly. + for (int i = 0; i < 5; i++) + { + const int idiv = 2 + i; // 2 - 6 input "resistors". + const int size = idiv << 16; + const double n = idiv; + opampModel.reset(); + summer[i] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */ + summer[i][vi] = getNormalizedValue(opampModel.solve(n, vin)); + } + } + + // The audio mixer operates at n ~ 8/6, and has 8 fundamentally different + // input configurations (0 - 7 input "resistors"). + // + // All "on", transistors are modeled as one - see comments above for + // the filter summer. + for (int i = 0; i < 8; i++) + { + const int idiv = (i == 0) ? 1 : i; + const int size = (i == 0) ? 1 : i << 16; + const double n = i * 8.0 / 6.0; + opampModel.reset(); + mixer[i] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */ + mixer[i][vi] = getNormalizedValue(opampModel.solve(n, vin)); + } + } + + // 4 bit "resistor" ladders in the audio + // output gain necessitate 16 gain tables. + // From die photographs of the bandpass and volume "resistor" ladders + // it follows that gain ~ vol/12 (assuming ideal + // op-amps and ideal "resistors"). + for (int n8 = 0; n8 < 16; n8++) + { + const int size = 1 << 16; + const double n = n8 / 12.0; + opampModel.reset(); + gain_vol[n8] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi / N16; /* vmin .. vmax */ + gain_vol[n8][vi] = getNormalizedValue(opampModel.solve(n, vin)); + } + } + + // 4 bit "resistor" ladders in the bandpass resonance gain + // necessitate 16 gain tables. + // From die photographs of the bandpass and volume "resistor" ladders + // it follows that 1/Q ~ ~res/8 (assuming ideal + // op-amps and ideal "resistors"). + for (int n8 = 0; n8 < 16; n8++) + { + const int size = 1 << 16; + const double n = (~n8 & 0xf) / 8.0; + opampModel.reset(); + gain_res[n8] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi / N16; /* vmin .. vmax */ + gain_res[n8][vi] = getNormalizedValue(opampModel.solve(n, vin)); + } + } + + const double nVddt = N16 * (Vddt - vmin); + + for (unsigned int i = 0; i < (1 << 16); i++) + { + // The table index is right-shifted 16 times in order to fit in + // 16 bits; the argument to sqrt is thus multiplied by (1 << 16). + const double tmp = nVddt - sqrt(static_cast(i << 16)); + assert(tmp > -0.5 && tmp < 65535.5); + vcr_nVg[i] = static_cast(tmp + 0.5); + } + + // EKV model: + // + // Ids = Is * (if - ir) + // Is = (2 * u*Cox * Ut^2)/k * W/L + // if = ln^2(1 + e^((k*(Vg - Vt) - Vs)/(2*Ut)) + // ir = ln^2(1 + e^((k*(Vg - Vt) - Vd)/(2*Ut)) + + // moderate inversion characteristic current + const double Is = (2. * uCox * Ut * Ut) * WL_vcr; + + // Normalized current factor for 1 cycle at 1MHz. + const double N15 = norm * ((1 << 15) - 1); + const double n_Is = N15 * 1.0e-6 / C * Is; + + // kVgt_Vx = k*(Vg - Vt) - Vx + // I.e. if k != 1.0, Vg must be scaled accordingly. + for (int kVgt_Vx = 0; kVgt_Vx < (1 << 16); kVgt_Vx++) + { + const double log_term = log1p(exp((kVgt_Vx / N16) / (2. * Ut))); + // Scaled by m*2^15 + const double tmp = n_Is * log_term * log_term; + assert(tmp > -0.5 && tmp < 65535.5); + vcr_n_Ids_term[kVgt_Vx] = static_cast(tmp + 0.5); + } +} + +unsigned short* FilterModelConfig6581::getDAC(double adjustment) const +{ + const double dac_zero = getDacZero(adjustment); + + unsigned short* f0_dac = new unsigned short[1 << DAC_BITS]; + + for (unsigned int i = 0; i < (1 << DAC_BITS); i++) + { + const double fcd = dac.getOutput(i); + f0_dac[i] = getNormalizedValue(dac_zero + fcd * dac_scale / (1 << DAC_BITS)); + } + + return f0_dac; +} + +std::unique_ptr FilterModelConfig6581::buildIntegrator() +{ + return MAKE_UNIQUE(Integrator6581, this, WL_snake); +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/FilterModelConfig6581.h b/src/engine/platform/sound/c64_fp/FilterModelConfig6581.h new file mode 100644 index 00000000..85cbd43f --- /dev/null +++ b/src/engine/platform/sound/c64_fp/FilterModelConfig6581.h @@ -0,0 +1,112 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2020 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef FILTERMODELCONFIG6581_H +#define FILTERMODELCONFIG6581_H + +#include "FilterModelConfig.h" + +#include + +#include "Dac.h" + +#include "sidcxx14.h" + +namespace reSIDfp +{ + +class Integrator6581; + +/** + * Calculate parameters for 6581 filter emulation. + */ +class FilterModelConfig6581 final : public FilterModelConfig +{ +private: + static const unsigned int DAC_BITS = 11; + +private: + static std::unique_ptr instance; + // This allows access to the private constructor +#ifdef HAVE_CXX11 + friend std::unique_ptr::deleter_type; +#else + friend class std::auto_ptr; +#endif + + /// Transistor parameters. + //@{ + const double WL_vcr; ///< W/L for VCR + const double WL_snake; ///< W/L for "snake" + //@} + + /// DAC parameters. + //@{ + const double dac_zero; + const double dac_scale; + //@} + + /// DAC lookup table + Dac dac; + + /// VCR - 6581 only. + //@{ + unsigned short vcr_nVg[1 << 16]; + unsigned short vcr_n_Ids_term[1 << 16]; + //@} + +private: + double getDacZero(double adjustment) const { return dac_zero + (1. - adjustment); } + + FilterModelConfig6581(); + ~FilterModelConfig6581() DEFAULT; + +public: + static FilterModelConfig6581* getInstance(); + + /** + * Construct an 11 bit cutoff frequency DAC output voltage table. + * Ownership is transferred to the requester which becomes responsible + * of freeing the object when done. + * + * @param adjustment + * @return the DAC table + */ + unsigned short* getDAC(double adjustment) const; + + /** + * Construct an integrator solver. + * + * @return the integrator + */ + std::unique_ptr buildIntegrator(); + + inline unsigned short getVcr_nVg(int i) const { return vcr_nVg[i]; } + inline unsigned short getVcr_n_Ids_term(int i) const { return vcr_n_Ids_term[i]; } + // only used if SLOPE_FACTOR is defined + inline double getUt() const { return Ut; } + inline double getN16() const { return N16; } +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/FilterModelConfig8580.cpp b/src/engine/platform/sound/c64_fp/FilterModelConfig8580.cpp new file mode 100644 index 00000000..fd2a16fa --- /dev/null +++ b/src/engine/platform/sound/c64_fp/FilterModelConfig8580.cpp @@ -0,0 +1,222 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2020 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2010 Dag Lem + * + * 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 "FilterModelConfig8580.h" + +#include "Integrator8580.h" +#include "OpAmp.h" + +namespace reSIDfp +{ + +/* + * R1 = 15.3*Ri + * R2 = 7.3*Ri + * R3 = 4.7*Ri + * Rf = 1.4*Ri + * R4 = 1.4*Ri + * R8 = 2.0*Ri + * RC = 2.8*Ri + * + * res feedback input + * --- -------- ----- + * 0 Rf Ri + * 1 Rf|R1 Ri + * 2 Rf|R2 Ri + * 3 Rf|R3 Ri + * 4 Rf R4 + * 5 Rf|R1 R4 + * 6 Rf|R2 R4 + * 7 Rf|R3 R4 + * 8 Rf R8 + * 9 Rf|R1 R8 + * A Rf|R2 R8 + * B Rf|R3 R8 + * C Rf RC + * D Rf|R1 RC + * E Rf|R2 RC + * F Rf|R3 RC + */ +const double resGain[16] = +{ + 1.4/1.0, // Rf/Ri 1.4 + ((1.4*15.3)/(1.4+15.3))/1.0, // (Rf|R1)/Ri 1.28263 + ((1.4*7.3)/(1.4+7.3))/1.0, // (Rf|R2)/Ri 1.17471 + ((1.4*4.7)/(1.4+4.7))/1.0, // (Rf|R3)/Ri 1.07869 + 1.4/1.4, // Rf/R4 1 + ((1.4*15.3)/(1.4+15.3))/1.4, // (Rf|R1)/R4 0.916168 + ((1.4*7.3)/(1.4+7.3))/1.4, // (Rf|R2)/R4 0.83908 + ((1.4*4.7)/(1.4+4.7))/1.4, // (Rf|R3)/R4 0.770492 + 1.4/2.0, // Rf/R8 0.7 + ((1.4*15.3)/(1.4+15.3))/2.0, // (Rf|R1)/R8 0.641317 + ((1.4*7.3)/(1.4+7.3))/2.0, // (Rf|R2)/R8 0.587356 + ((1.4*4.7)/(1.4+4.7))/2.0, // (Rf|R3)/R8 0.539344 + 1.4/2.8, // Rf/RC 0.5 + ((1.4*15.3)/(1.4+15.3))/2.8, // (Rf|R1)/RC 0.458084 + ((1.4*7.3)/(1.4+7.3))/2.8, // (Rf|R2)/RC 0.41954 + ((1.4*4.7)/(1.4+4.7))/2.8, // (Rf|R3)/RC 0.385246 +}; + +const unsigned int OPAMP_SIZE = 21; + +/** + * This is the SID 8580 op-amp voltage transfer function, measured on + * CAP1B/CAP1A on a chip marked CSG 8580R5 1690 25. + */ +const Spline::Point opamp_voltage[OPAMP_SIZE] = +{ + { 1.30, 8.91 }, // Approximate start of actual range + { 4.76, 8.91 }, + { 4.77, 8.90 }, + { 4.78, 8.88 }, + { 4.785, 8.86 }, + { 4.79, 8.80 }, + { 4.795, 8.60 }, + { 4.80, 8.25 }, + { 4.805, 7.50 }, + { 4.81, 6.10 }, + { 4.815, 4.05 }, // Change of curvature + { 4.82, 2.27 }, + { 4.825, 1.65 }, + { 4.83, 1.55 }, + { 4.84, 1.47 }, + { 4.85, 1.43 }, + { 4.87, 1.37 }, + { 4.90, 1.34 }, + { 5.00, 1.30 }, + { 5.10, 1.30 }, + { 8.91, 1.30 }, // Approximate end of actual range +}; + +std::unique_ptr FilterModelConfig8580::instance(nullptr); + +FilterModelConfig8580* FilterModelConfig8580::getInstance() +{ + if (!instance.get()) + { + instance.reset(new FilterModelConfig8580()); + } + + return instance.get(); +} + +FilterModelConfig8580::FilterModelConfig8580() : + FilterModelConfig( + 0.25, // voice voltage range FIXME measure + 4.80, // voice DC voltage FIXME was 4.76 + 22e-9, // capacitor value + 9.09, // Vdd + 0.80, // Vth + 100e-6, // uCox + opamp_voltage, + OPAMP_SIZE + ) +{ + // Create lookup tables for gains / summers. + + OpAmp opampModel(std::vector(std::begin(opamp_voltage), std::end(opamp_voltage)), Vddt); + + // The filter summer operates at n ~ 1, and has 5 fundamentally different + // input configurations (2 - 6 input "resistors"). + // + // Note that all "on" transistors are modeled as one. This is not + // entirely accurate, since the input for each transistor is different, + // and transistors are not linear components. However modeling all + // transistors separately would be extremely costly. + for (int i = 0; i < 5; i++) + { + const int idiv = 2 + i; // 2 - 6 input "resistors". + const int size = idiv << 16; + const double n = idiv; + opampModel.reset(); + summer[i] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */ + summer[i][vi] = getNormalizedValue(opampModel.solve(n, vin)); + } + } + + // The audio mixer operates at n ~ 8/5, and has 8 fundamentally different + // input configurations (0 - 7 input "resistors"). + // + // All "on", transistors are modeled as one - see comments above for + // the filter summer. + for (int i = 0; i < 8; i++) + { + const int idiv = (i == 0) ? 1 : i; + const int size = (i == 0) ? 1 : i << 16; + const double n = i * 8.0 / 5.0; + opampModel.reset(); + mixer[i] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */ + mixer[i][vi] = getNormalizedValue(opampModel.solve(n, vin)); + } + } + + // 4 bit "resistor" ladders in the audio output gain + // necessitate 16 gain tables. + // From die photographs of the volume "resistor" ladders + // it follows that gain ~ vol/16 (assuming ideal op-amps + for (int n8 = 0; n8 < 16; n8++) + { + const int size = 1 << 16; + const double n = n8 / 16.0; + opampModel.reset(); + gain_vol[n8] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi / N16; /* vmin .. vmax */ + gain_vol[n8][vi] = getNormalizedValue(opampModel.solve(n, vin)); + } + } + + // 4 bit "resistor" ladders in the bandpass resonance gain + // necessitate 16 gain tables. + // From die photographs of the bandpass and volume "resistor" ladders + // it follows that 1/Q ~ 2^((4 - res)/8) (assuming ideal + // op-amps and ideal "resistors"). + for (int n8 = 0; n8 < 16; n8++) + { + const int size = 1 << 16; + opampModel.reset(); + gain_res[n8] = new unsigned short[size]; + + for (int vi = 0; vi < size; vi++) + { + const double vin = vmin + vi / N16; /* vmin .. vmax */ + gain_res[n8][vi] = getNormalizedValue(opampModel.solve(resGain[n8], vin)); + } + } +} + +std::unique_ptr FilterModelConfig8580::buildIntegrator() +{ + return MAKE_UNIQUE(Integrator8580, this); +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/FilterModelConfig8580.h b/src/engine/platform/sound/c64_fp/FilterModelConfig8580.h new file mode 100644 index 00000000..ee2b4008 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/FilterModelConfig8580.h @@ -0,0 +1,68 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2020 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef FILTERMODELCONFIG8580_H +#define FILTERMODELCONFIG8580_H + +#include "FilterModelConfig.h" + +#include + +#include "sidcxx14.h" + +namespace reSIDfp +{ + +class Integrator8580; + +/** + * Calculate parameters for 8580 filter emulation. + */ +class FilterModelConfig8580 final : public FilterModelConfig +{ +private: + static std::unique_ptr instance; + // This allows access to the private constructor +#ifdef HAVE_CXX11 + friend std::unique_ptr::deleter_type; +#else + friend class std::auto_ptr; +#endif + +private: + FilterModelConfig8580(); + ~FilterModelConfig8580() DEFAULT; + +public: + static FilterModelConfig8580* getInstance(); + + /** + * Construct an integrator solver. + * + * @return the integrator + */ + std::unique_ptr buildIntegrator(); +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/Integrator6581.cpp b/src/engine/platform/sound/c64_fp/Integrator6581.cpp new file mode 100644 index 00000000..490be9b5 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Integrator6581.cpp @@ -0,0 +1,25 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2014 Leandro Nini + * + * 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. + */ + +#define INTEGRATOR_CPP + +#include "Integrator6581.h" + +// This is needed when compiling with --disable-inline diff --git a/src/engine/platform/sound/c64_fp/Integrator6581.h b/src/engine/platform/sound/c64_fp/Integrator6581.h new file mode 100644 index 00000000..99ac3bea --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Integrator6581.h @@ -0,0 +1,285 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004, 2010 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef INTEGRATOR6581_H +#define INTEGRATOR6581_H + +#include "FilterModelConfig6581.h" + +#include +#include + +// uncomment to enable use of the slope factor +// in the EKV model +// actually produces worse results, needs investigation +//#define SLOPE_FACTOR + +#ifdef SLOPE_FACTOR +# include +#endif + +#include "siddefs-fp.h" + +namespace reSIDfp +{ + +/** + * Find output voltage in inverting integrator SID op-amp circuits, using a + * single fixpoint iteration step. + * + * A circuit diagram of a MOS 6581 integrator is shown below. + * + * +---C---+ + * | | + * vi --o--Rw--o-o--[A>--o-- vo + * | | vx + * +--Rs--+ + * + * From Kirchoff's current law it follows that + * + * IRw + IRs + ICr = 0 + * + * Using the formula for current through a capacitor, i = C*dv/dt, we get + * + * IRw + IRs + C*(vc - vc0)/dt = 0 + * dt/C*(IRw + IRs) + vc - vc0 = 0 + * vc = vc0 - n*(IRw(vi,vx) + IRs(vi,vx)) + * + * which may be rewritten as the following iterative fixpoint function: + * + * vc = vc0 - n*(IRw(vi,g(vc)) + IRs(vi,g(vc))) + * + * To accurately calculate the currents through Rs and Rw, we need to use + * transistor models. Rs has a gate voltage of Vdd = 12V, and can be + * assumed to always be in triode mode. For Rw, the situation is rather + * more complex, as it turns out that this transistor will operate in + * both subthreshold, triode, and saturation modes. + * + * The Shichman-Hodges transistor model routinely used in textbooks may + * be written as follows: + * + * Ids = 0 , Vgst < 0 (subthreshold mode) + * Ids = K*W/L*(2*Vgst - Vds)*Vds , Vgst >= 0, Vds < Vgst (triode mode) + * Ids = K*W/L*Vgst^2 , Vgst >= 0, Vds >= Vgst (saturation mode) + * + * where + * K = u*Cox/2 (transconductance coefficient) + * W/L = ratio between substrate width and length + * Vgst = Vg - Vs - Vt (overdrive voltage) + * + * This transistor model is also called the quadratic model. + * + * Note that the equation for the triode mode can be reformulated as + * independent terms depending on Vgs and Vgd, respectively, by the + * following substitution: + * + * Vds = Vgst - (Vgst - Vds) = Vgst - Vgdt + * + * Ids = K*W/L*(2*Vgst - Vds)*Vds + * = K*W/L*(2*Vgst - (Vgst - Vgdt)*(Vgst - Vgdt) + * = K*W/L*(Vgst + Vgdt)*(Vgst - Vgdt) + * = K*W/L*(Vgst^2 - Vgdt^2) + * + * This turns out to be a general equation which covers both the triode + * and saturation modes (where the second term is 0 in saturation mode). + * The equation is also symmetrical, i.e. it can calculate negative + * currents without any change of parameters (since the terms for drain + * and source are identical except for the sign). + * + * FIXME: Subthreshold as function of Vgs, Vgd. + * + * Ids = I0*W/L*e^(Vgst/(Ut/k)) , Vgst < 0 (subthreshold mode) + * + * where + * I0 = (2 * uCox * Ut^2) / k + * + * The remaining problem with the textbook model is that the transition + * from subthreshold the triode/saturation is not continuous. + * + * Realizing that the subthreshold and triode/saturation modes may both + * be defined by independent (and equal) terms of Vgs and Vds, + * respectively, the corresponding terms can be blended into (equal) + * continuous functions suitable for table lookup. + * + * The EKV model (Enz, Krummenacher and Vittoz) essentially performs this + * blending using an elegant mathematical formulation: + * + * Ids = Is * (if - ir) + * Is = ((2 * u*Cox * Ut^2)/k) * W/L + * if = ln^2(1 + e^((k*(Vg - Vt) - Vs)/(2*Ut)) + * ir = ln^2(1 + e^((k*(Vg - Vt) - Vd)/(2*Ut)) + * + * For our purposes, the EKV model preserves two important properties + * discussed above: + * + * - It consists of two independent terms, which can be represented by + * the same lookup table. + * - It is symmetrical, i.e. it calculates current in both directions, + * facilitating a branch-free implementation. + * + * Rw in the circuit diagram above is a VCR (voltage controlled resistor), + * as shown in the circuit diagram below. + * + * + * Vdd + * | + * Vdd _|_ + * | +---+ +---- Vw + * _|_ | + * +--+ +---o Vg + * | __|__ + * | ----- Rw + * | | | + * vi -----o------+ +-------- vo + * + * + * In order to calculalate the current through the VCR, its gate voltage + * must be determined. + * + * Assuming triode mode and applying Kirchoff's current law, we get the + * following equation for Vg: + * + * u*Cox/2*W/L*((nVddt - Vg)^2 - (nVddt - vi)^2 + (nVddt - Vg)^2 - (nVddt - Vw)^2) = 0 + * 2*(nVddt - Vg)^2 - (nVddt - vi)^2 - (nVddt - Vw)^2 = 0 + * (nVddt - Vg) = sqrt(((nVddt - vi)^2 + (nVddt - Vw)^2)/2) + * + * Vg = nVddt - sqrt(((nVddt - vi)^2 + (nVddt - Vw)^2)/2) + */ +class Integrator6581 +{ +private: + unsigned int nVddt_Vw_2; + mutable int vx; + mutable int vc; + +#ifdef SLOPE_FACTOR + // Slope factor n = 1/k + // where k is the gate coupling coefficient + // k = Cox/(Cox+Cdep) ~ 0.7 (depends on gate voltage) + mutable double n; +#endif + const unsigned short nVddt; + const unsigned short nVt; + const unsigned short nVmin; + const unsigned short nSnake; + + const FilterModelConfig6581* fmc; + +public: + Integrator6581(const FilterModelConfig6581* fmc, + double WL_snake) : + nVddt_Vw_2(0), + vx(0), + vc(0), +#ifdef SLOPE_FACTOR + n(1.4), +#endif + nVddt(fmc->getNormalizedValue(fmc->getVddt())), + nVt(fmc->getNormalizedValue(fmc->getVth())), + nVmin(fmc->getNVmin()), + nSnake(fmc->getNormalizedCurrentFactor(WL_snake)), + fmc(fmc) {} + + void setVw(unsigned short Vw) { nVddt_Vw_2 = ((nVddt - Vw) * (nVddt - Vw)) >> 1; } + + int solve(int vi) const; +}; + +} // namespace reSIDfp + +#if RESID_INLINING || defined(INTEGRATOR_CPP) + +namespace reSIDfp +{ + +RESID_INLINE +int Integrator6581::solve(int vi) const +{ + // Make sure Vgst>0 so we're not in subthreshold mode + assert(vx < nVddt); + + // Check that transistor is actually in triode mode + // Vds < Vgs - Vth + assert(vi < nVddt); + + // "Snake" voltages for triode mode calculation. + const unsigned int Vgst = nVddt - vx; + const unsigned int Vgdt = nVddt - vi; + + const unsigned int Vgst_2 = Vgst * Vgst; + const unsigned int Vgdt_2 = Vgdt * Vgdt; + + // "Snake" current, scaled by (1/m)*2^13*m*2^16*m*2^16*2^-15 = m*2^30 + const int n_I_snake = nSnake * (static_cast(Vgst_2 - Vgdt_2) >> 15); + + // VCR gate voltage. // Scaled by m*2^16 + // Vg = Vddt - sqrt(((Vddt - Vw)^2 + Vgdt^2)/2) + const int nVg = static_cast(fmc->getVcr_nVg((nVddt_Vw_2 + (Vgdt_2 >> 1)) >> 16)); +#ifdef SLOPE_FACTOR + const double nVp = static_cast(nVg - nVt) / n; // Pinch-off voltage + const int kVg = static_cast(nVp + 0.5) - nVmin; +#else + const int kVg = (nVg - nVt) - nVmin; +#endif + + // VCR voltages for EKV model table lookup. + const int kVgt_Vs = (vx < kVg) ? kVg - vx : 0; + assert(kVgt_Vs < (1 << 16)); + const int kVgt_Vd = (vi < kVg) ? kVg - vi : 0; + assert(kVgt_Vd < (1 << 16)); + + // VCR current, scaled by m*2^15*2^15 = m*2^30 + const unsigned int If = static_cast(fmc->getVcr_n_Ids_term(kVgt_Vs)) << 15; + const unsigned int Ir = static_cast(fmc->getVcr_n_Ids_term(kVgt_Vd)) << 15; +#ifdef SLOPE_FACTOR + const double iVcr = static_cast(If - Ir); + const int n_I_vcr = static_cast((iVcr * n) + 0.5); +#else + const int n_I_vcr = If - Ir; +#endif + +#ifdef SLOPE_FACTOR + // estimate new slope factor based on gate voltage + const double gamma = 1.0; // body effect factor + const double phi = 0.8; // bulk Fermi potential + const double Vp = nVp / fmc->getN16(); + n = 1. + (gamma / (2. * sqrt(Vp + phi + 4. * fmc->getUt()))); + assert((n > 1.2) && (n < 1.8)); +#endif + + // Change in capacitor charge. + vc += n_I_snake + n_I_vcr; + + // vx = g(vc) + const int tmp = (vc >> 15) + (1 << 15); + assert(tmp < (1 << 16)); + vx = fmc->getOpampRev(tmp); + + // Return vo. + return vx - (vc >> 14); +} + +} // namespace reSIDfp + +#endif + +#endif diff --git a/src/engine/platform/sound/c64_fp/Integrator8580.cpp b/src/engine/platform/sound/c64_fp/Integrator8580.cpp new file mode 100644 index 00000000..6fba9521 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Integrator8580.cpp @@ -0,0 +1,25 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2014-2016 Leandro Nini + * + * 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. + */ + +#define INTEGRATOR8580_CPP + +#include "Integrator8580.h" + +// This is needed when compiling with --disable-inline diff --git a/src/engine/platform/sound/c64_fp/Integrator8580.h b/src/engine/platform/sound/c64_fp/Integrator8580.h new file mode 100644 index 00000000..7137e940 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Integrator8580.h @@ -0,0 +1,142 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004, 2010 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef INTEGRATOR8580_H +#define INTEGRATOR8580_H + +#include "FilterModelConfig8580.h" + +#include +#include + +#include "siddefs-fp.h" + +namespace reSIDfp +{ + +/** + * 8580 integrator + * + * +---C---+ + * | | + * vi -----Rfc---o--[A>--o-- vo + * vx + * + * IRfc + ICr = 0 + * IRfc + C*(vc - vc0)/dt = 0 + * dt/C*(IRfc) + vc - vc0 = 0 + * vc = vc0 - n*(IRfc(vi,vx)) + * vc = vc0 - n*(IRfc(vi,g(vc))) + * + * IRfc = K*W/L*(Vgst^2 - Vgdt^2) = n*((Vddt - vx)^2 - (Vddt - vi)^2) + * + * Rfc gate voltage is generated by an OP Amp and depends on chip temperature. + */ +class Integrator8580 +{ +private: + mutable int vx; + mutable int vc; + + unsigned short nVgt; + unsigned short n_dac; + + const FilterModelConfig8580* fmc; + +public: + Integrator8580(const FilterModelConfig8580* fmc) : + vx(0), + vc(0), + fmc(fmc) + { + setV(1.5); + } + + /** + * Set Filter Cutoff resistor ratio. + */ + void setFc(double wl) + { + // Normalized current factor, 1 cycle at 1MHz. + // Fit in 5 bits. + n_dac = fmc->getNormalizedCurrentFactor(wl); + } + + /** + * Set FC gate voltage multiplier. + */ + void setV(double v) + { + // Gate voltage is controlled by the switched capacitor voltage divider + // Ua = Ue * v = 4.76v 1 1.0 && v < 2.0); + const double Vg = fmc->getVoiceDCVoltage() * v; + const double Vgt = Vg - fmc->getVth(); + + // Vg - Vth, normalized so that translated values can be subtracted: + // Vgt - x = (Vgt - t) - (x - t) + nVgt = fmc->getNormalizedValue(Vgt); + } + + int solve(int vi) const; +}; + +} // namespace reSIDfp + +#if RESID_INLINING || defined(INTEGRATOR8580_CPP) + +namespace reSIDfp +{ + +RESID_INLINE +int Integrator8580::solve(int vi) const +{ + // Make sure we're not in subthreshold mode + assert(vx < nVgt); + + // DAC voltages + const unsigned int Vgst = nVgt - vx; + const unsigned int Vgdt = (vi < nVgt) ? nVgt - vi : 0; // triode/saturation mode + + const unsigned int Vgst_2 = Vgst * Vgst; + const unsigned int Vgdt_2 = Vgdt * Vgdt; + + // DAC current, scaled by (1/m)*2^13*m*2^16*m*2^16*2^-15 = m*2^30 + const int n_I_dac = n_dac * (static_cast(Vgst_2 - Vgdt_2) >> 15); + + // Change in capacitor charge. + vc += n_I_dac; + + // vx = g(vc) + const int tmp = (vc >> 15) + (1 << 15); + assert(tmp < (1 << 16)); + vx = fmc->getOpampRev(tmp); + + // Return vo. + return vx - (vc >> 14); +} + +} // namespace reSIDfp + +#endif + +#endif diff --git a/src/engine/platform/sound/c64_fp/OpAmp.cpp b/src/engine/platform/sound/c64_fp/OpAmp.cpp new file mode 100644 index 00000000..b26b2efc --- /dev/null +++ b/src/engine/platform/sound/c64_fp/OpAmp.cpp @@ -0,0 +1,84 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2015 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * + * 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 "OpAmp.h" + +#include + +#include "siddefs-fp.h" + +namespace reSIDfp +{ + +const double EPSILON = 1e-8; + +double OpAmp::solve(double n, double vi) const +{ + // Start off with an estimate of x and a root bracket [ak, bk]. + // f is decreasing, so that f(ak) > 0 and f(bk) < 0. + double ak = vmin; + double bk = vmax; + + const double a = n + 1.; + const double b = Vddt; + const double b_vi = (b > vi) ? (b - vi) : 0.; + const double c = n * (b_vi * b_vi); + + for (;;) + { + const double xk = x; + + // Calculate f and df. + + Spline::Point out = opamp->evaluate(x); + const double vo = out.x; + const double dvo = out.y; + + const double b_vx = (b > x) ? b - x : 0.; + const double b_vo = (b > vo) ? b - vo : 0.; + + // f = a*(b - vx)^2 - c - (b - vo)^2 + const double f = a * (b_vx * b_vx) - c - (b_vo * b_vo); + + // df = 2*((b - vo)*dvo - a*(b - vx)) + const double df = 2. * (b_vo * dvo - a * b_vx); + + // Newton-Raphson step: xk1 = xk - f(xk)/f'(xk) + x -= f / df; + + if (unlikely(fabs(x - xk) < EPSILON)) + { + out = opamp->evaluate(x); + return out.x; + } + + // Narrow down root bracket. + (f < 0. ? bk : ak) = xk; + + if (unlikely(x <= ak) || unlikely(x >= bk)) + { + // Bisection step (ala Dekker's method). + x = (ak + bk) * 0.5; + } + } +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/OpAmp.h b/src/engine/platform/sound/c64_fp/OpAmp.h new file mode 100644 index 00000000..9d2c8f16 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/OpAmp.h @@ -0,0 +1,113 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2015 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef OPAMP_H +#define OPAMP_H + +#include +#include + +#include "Spline.h" + +#include "sidcxx11.h" + +namespace reSIDfp +{ + +/** + * Find output voltage in inverting gain and inverting summer SID op-amp + * circuits, using a combination of Newton-Raphson and bisection. + * + * +---R2--+ + * | | + * vi ---R1--o--[A>--o-- vo + * vx + * + * From Kirchoff's current law it follows that + * + * IR1f + IR2r = 0 + * + * Substituting the triode mode transistor model K*W/L*(Vgst^2 - Vgdt^2) + * for the currents, we get: + * + * n*((Vddt - vx)^2 - (Vddt - vi)^2) + (Vddt - vx)^2 - (Vddt - vo)^2 = 0 + * + * Our root function f can thus be written as: + * + * f = (n + 1)*(Vddt - vx)^2 - n*(Vddt - vi)^2 - (Vddt - vo)^2 = 0 + * + * Using substitution constants + * + * a = n + 1 + * b = Vddt + * c = n*(Vddt - vi)^2 + * + * the equations for the root function and its derivative can be written as: + * + * f = a*(b - vx)^2 - c - (b - vo)^2 + * df = 2*((b - vo)*dvo - a*(b - vx)) + */ +class OpAmp +{ +private: + /// Current root position (cached as guess to speed up next iteration) + mutable double x; + + const double Vddt; + const double vmin; + const double vmax; + + std::unique_ptr const opamp; + +public: + /** + * Opamp input -> output voltage conversion + * + * @param opamp opamp mapping table as pairs of points (in -> out) + * @param opamplength length of the opamp array + * @param kVddt transistor dt parameter (in volts) + */ + OpAmp(const std::vector &opamp, double Vddt) : + x(0.), + Vddt(Vddt), + vmin(opamp.front().x), + vmax(opamp.back().x), + opamp(new Spline(opamp)) {} + + void reset() const + { + x = vmin; + } + + /** + * Solve the opamp equation for input vi in loading context n + * + * @param n the ratio of input/output loading + * @param vi input + * @return vo + */ + double solve(double n, double vi) const; +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/Potentiometer.h b/src/engine/platform/sound/c64_fp/Potentiometer.h new file mode 100644 index 00000000..8b63df13 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Potentiometer.h @@ -0,0 +1,50 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2013 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright (C) 2004 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef POTENTIOMETER_H +#define POTENTIOMETER_H + +namespace reSIDfp +{ + +/** + * Potentiometer representation. + * + * This class will probably never be implemented in any real way. + * + * @author Ken Händel + * @author Dag Lem + */ +class Potentiometer +{ +public: + /** + * Read paddle value. Not modeled. + * + * @return paddle value (always 0xff) + */ + unsigned char readPOT() const { return 0xff; } +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/README b/src/engine/platform/sound/c64_fp/README new file mode 100644 index 00000000..45d4bfb9 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/README @@ -0,0 +1,20 @@ +reSIDfp is a fork of Dag Lem's reSID 0.16, a reverse engineered software emulation +of the MOS6581/8580 SID (Sound Interface Device). + +The project was started by Antti S. Lankila in order to improve SID emulation +with special focus on the 6581 filter. +The codebase has been later on ported to java by Ken Händel within the jsidplay2 project +and has seen further work by Antti Lankila. +It was then ported back to c++ and integrated with improvements from reSID 1.0 by Leandro Nini. + + +Main differences from reSID: + +* combined waveforms are emulated by a parametrized model based on samplings from Kevtris; +* envelope generator is implemented like in the real machine with a shift register; +* high quality resampling is done in two steps to allow computational savings using lower order filters; +* part of the calculations are done with floats instead of fixed point; +* interpolation is accomplished with Fritsch-Carlson method to preserve monotonicity. + + +reSIDfp is free software. See the file COPYING for copying permission. diff --git a/src/engine/platform/sound/c64_fp/SID.cpp b/src/engine/platform/sound/c64_fp/SID.cpp new file mode 100644 index 00000000..a996d223 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/SID.cpp @@ -0,0 +1,504 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2016 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * 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. + */ + +#define SID_CPP + +#include "SID.h" + +#include + +#include "array.h" +#include "Dac.h" +#include "Filter6581.h" +#include "Filter8580.h" +#include "Potentiometer.h" +#include "WaveformCalculator.h" +#include "resample/TwoPassSincResampler.h" +#include "resample/ZeroOrderResampler.h" + +namespace reSIDfp +{ + +const unsigned int ENV_DAC_BITS = 8; +const unsigned int OSC_DAC_BITS = 12; + +/** + * The waveform D/A converter introduces a DC offset in the signal + * to the envelope multiplying D/A converter. The "zero" level of + * the waveform D/A converter can be found as follows: + * + * Measure the "zero" voltage of voice 3 on the SID audio output + * pin, routing only voice 3 to the mixer ($d417 = $0b, $d418 = + * $0f, all other registers zeroed). + * + * Then set the sustain level for voice 3 to maximum and search for + * the waveform output value yielding the same voltage as found + * above. This is done by trying out different waveform output + * values until the correct value is found, e.g. with the following + * program: + * + * lda #$08 + * sta $d412 + * lda #$0b + * sta $d417 + * lda #$0f + * sta $d418 + * lda #$f0 + * sta $d414 + * lda #$21 + * sta $d412 + * lda #$01 + * sta $d40e + * + * ldx #$00 + * lda #$38 ; Tweak this to find the "zero" level + *l cmp $d41b + * bne l + * stx $d40e ; Stop frequency counter - freeze waveform output + * brk + * + * The waveform output range is 0x000 to 0xfff, so the "zero" + * level should ideally have been 0x800. In the measured chip, the + * waveform output "zero" level was found to be 0x380 (i.e. $d41b + * = 0x38) at an audio output voltage of 5.94V. + * + * With knowledge of the mixer op-amp characteristics, further estimates + * of waveform voltages can be obtained by sampling the EXT IN pin. + * From EXT IN samples, the corresponding waveform output can be found by + * using the model for the mixer. + * + * Such measurements have been done on a chip marked MOS 6581R4AR + * 0687 14, and the following results have been obtained: + * * The full range of one voice is approximately 1.5V. + * * The "zero" level rides at approximately 5.0V. + * + * + * zero-x did the measuring on the 8580 (https://sourceforge.net/p/vice-emu/bugs/1036/#c5b3): + * When it sits on basic from powerup it's at 4.72 + * Run 1.prg and check the output pin level. + * Then run 2.prg andadjust it until the output level is the same... + * 0x94-0xA8 gives me the same 4.72 1.prg shows. + * On another 8580 it's 0x90-0x9C + * Third chip 0x94-0xA8 + * Fourth chip 0x90-0xA4 + * On the 8580 that plays digis the output is 4.66 and 0x93 is the only value to reach that. + * To me that seems as regular 8580s have somewhat wide 0-level range, + * whereas that digi-compatible 8580 has it very narrow. + * On my 6581R4AR has 0x3A as the only value giving the same output level as 1.prg + */ +//@{ +unsigned int constexpr OFFSET_6581 = 0x380; +unsigned int constexpr OFFSET_8580 = 0x9c0; +//@} + +/** + * Bus value stays alive for some time after each operation. + * Values differs between chip models, the timings used here + * are taken from VICE [1]. + * See also the discussion "How do I reliably detect 6581/8580 sid?" on CSDb [2]. + * + * Results from real C64 (testprogs/SID/bitfade/delayfrq0.prg): + * + * (new SID) (250469/8580R5) (250469/8580R5) + * delayfrq0 ~7a000 ~108000 + * + * (old SID) (250407/6581) + * delayfrq0 ~01d00 + * + * [1]: http://sourceforge.net/p/vice-emu/patches/99/ + * [2]: http://noname.c64.org/csdb/forums/?roomid=11&topicid=29025&showallposts=1 + */ +//@{ +int constexpr BUS_TTL_6581 = 0x01d00; +int constexpr BUS_TTL_8580 = 0xa2000; +//@} + +SID::SID() : + filter6581(new Filter6581()), + filter8580(new Filter8580()), + externalFilter(new ExternalFilter()), + resampler(nullptr), + potX(new Potentiometer()), + potY(new Potentiometer()) +{ + voice[0].reset(new Voice()); + voice[1].reset(new Voice()); + voice[2].reset(new Voice()); + + muted[0] = muted[1] = muted[2] = false; + + reset(); + setChipModel(MOS8580); +} + +SID::~SID() +{ + // Needed to delete auto_ptr with complete type +} + +void SID::setFilter6581Curve(double filterCurve) +{ + filter6581->setFilterCurve(filterCurve); +} + +void SID::setFilter8580Curve(double filterCurve) +{ + filter8580->setFilterCurve(filterCurve); +} + +void SID::enableFilter(bool enable) +{ + filter6581->enable(enable); + filter8580->enable(enable); +} + +void SID::voiceSync(bool sync) +{ + if (sync) + { + // Synchronize the 3 waveform generators. + for (int i = 0; i < 3; i++) + { + voice[i]->wave()->synchronize(voice[(i + 1) % 3]->wave(), voice[(i + 2) % 3]->wave()); + } + } + + // Calculate the time to next voice sync + nextVoiceSync = std::numeric_limits::max(); + + for (int i = 0; i < 3; i++) + { + WaveformGenerator* const wave = voice[i]->wave(); + const unsigned int freq = wave->readFreq(); + + if (wave->readTest() || freq == 0 || !voice[(i + 1) % 3]->wave()->readSync()) + { + continue; + } + + const unsigned int accumulator = wave->readAccumulator(); + const unsigned int thisVoiceSync = ((0x7fffff - accumulator) & 0xffffff) / freq + 1; + + if (thisVoiceSync < nextVoiceSync) + { + nextVoiceSync = thisVoiceSync; + } + } +} + +void SID::setChipModel(ChipModel model) +{ + switch (model) + { + case MOS6581: + filter = filter6581.get(); + modelTTL = BUS_TTL_6581; + break; + + case MOS8580: + filter = filter8580.get(); + modelTTL = BUS_TTL_8580; + break; + + default: + throw SIDError("Unknown chip type"); + } + + this->model = model; + + // calculate waveform-related tables + matrix_t* tables = WaveformCalculator::getInstance()->buildTable(model); + + // calculate envelope DAC table + { + Dac dacBuilder(ENV_DAC_BITS); + dacBuilder.kinkedDac(model); + + for (unsigned int i = 0; i < (1 << ENV_DAC_BITS); i++) + { + envDAC[i] = static_cast(dacBuilder.getOutput(i)); + } + } + + // calculate oscillator DAC table + const bool is6581 = model == MOS6581; + + { + Dac dacBuilder(OSC_DAC_BITS); + dacBuilder.kinkedDac(model); + + const double offset = dacBuilder.getOutput(is6581 ? OFFSET_6581 : OFFSET_8580); + + for (unsigned int i = 0; i < (1 << OSC_DAC_BITS); i++) + { + const double dacValue = dacBuilder.getOutput(i); + oscDAC[i] = static_cast(dacValue - offset); + } + } + + // set voice tables + for (int i = 0; i < 3; i++) + { + voice[i]->setEnvDAC(envDAC); + voice[i]->setWavDAC(oscDAC); + voice[i]->wave()->setModel(is6581); + voice[i]->wave()->setWaveformModels(tables); + } +} + +void SID::reset() +{ + for (int i = 0; i < 3; i++) + { + voice[i]->reset(); + } + + filter6581->reset(); + filter8580->reset(); + externalFilter->reset(); + + if (resampler.get()) + { + resampler->reset(); + } + + busValue = 0; + busValueTtl = 0; + voiceSync(false); +} + +void SID::input(int value) +{ + filter6581->input(value); + filter8580->input(value); +} + +unsigned char SID::read(int offset) +{ + switch (offset) + { + case 0x19: // X value of paddle + busValue = potX->readPOT(); + busValueTtl = modelTTL; + break; + + case 0x1a: // Y value of paddle + busValue = potY->readPOT(); + busValueTtl = modelTTL; + break; + + case 0x1b: // Voice #3 waveform output + busValue = voice[2]->wave()->readOSC(); + busValueTtl = modelTTL; + break; + + case 0x1c: // Voice #3 ADSR output + busValue = voice[2]->envelope()->readENV(); + busValueTtl = modelTTL; + break; + + default: + // Reading from a write-only or non-existing register + // makes the bus discharge faster. + // Emulate this by halving the residual TTL. + busValueTtl /= 2; + break; + } + + return busValue; +} + +void SID::write(int offset, unsigned char value) +{ + busValue = value; + busValueTtl = modelTTL; + + switch (offset) + { + case 0x00: // Voice #1 frequency (Low-byte) + voice[0]->wave()->writeFREQ_LO(value); + break; + + case 0x01: // Voice #1 frequency (High-byte) + voice[0]->wave()->writeFREQ_HI(value); + break; + + case 0x02: // Voice #1 pulse width (Low-byte) + voice[0]->wave()->writePW_LO(value); + break; + + case 0x03: // Voice #1 pulse width (bits #8-#15) + voice[0]->wave()->writePW_HI(value); + break; + + case 0x04: // Voice #1 control register + voice[0]->writeCONTROL_REG(muted[0] ? 0 : value); + break; + + case 0x05: // Voice #1 Attack and Decay length + voice[0]->envelope()->writeATTACK_DECAY(value); + break; + + case 0x06: // Voice #1 Sustain volume and Release length + voice[0]->envelope()->writeSUSTAIN_RELEASE(value); + break; + + case 0x07: // Voice #2 frequency (Low-byte) + voice[1]->wave()->writeFREQ_LO(value); + break; + + case 0x08: // Voice #2 frequency (High-byte) + voice[1]->wave()->writeFREQ_HI(value); + break; + + case 0x09: // Voice #2 pulse width (Low-byte) + voice[1]->wave()->writePW_LO(value); + break; + + case 0x0a: // Voice #2 pulse width (bits #8-#15) + voice[1]->wave()->writePW_HI(value); + break; + + case 0x0b: // Voice #2 control register + voice[1]->writeCONTROL_REG(muted[1] ? 0 : value); + break; + + case 0x0c: // Voice #2 Attack and Decay length + voice[1]->envelope()->writeATTACK_DECAY(value); + break; + + case 0x0d: // Voice #2 Sustain volume and Release length + voice[1]->envelope()->writeSUSTAIN_RELEASE(value); + break; + + case 0x0e: // Voice #3 frequency (Low-byte) + voice[2]->wave()->writeFREQ_LO(value); + break; + + case 0x0f: // Voice #3 frequency (High-byte) + voice[2]->wave()->writeFREQ_HI(value); + break; + + case 0x10: // Voice #3 pulse width (Low-byte) + voice[2]->wave()->writePW_LO(value); + break; + + case 0x11: // Voice #3 pulse width (bits #8-#15) + voice[2]->wave()->writePW_HI(value); + break; + + case 0x12: // Voice #3 control register + voice[2]->writeCONTROL_REG(muted[2] ? 0 : value); + break; + + case 0x13: // Voice #3 Attack and Decay length + voice[2]->envelope()->writeATTACK_DECAY(value); + break; + + case 0x14: // Voice #3 Sustain volume and Release length + voice[2]->envelope()->writeSUSTAIN_RELEASE(value); + break; + + case 0x15: // Filter cut off frequency (bits #0-#2) + filter6581->writeFC_LO(value); + filter8580->writeFC_LO(value); + break; + + case 0x16: // Filter cut off frequency (bits #3-#10) + filter6581->writeFC_HI(value); + filter8580->writeFC_HI(value); + break; + + case 0x17: // Filter control + filter6581->writeRES_FILT(value); + filter8580->writeRES_FILT(value); + break; + + case 0x18: // Volume and filter modes + filter6581->writeMODE_VOL(value); + filter8580->writeMODE_VOL(value); + break; + + default: + break; + } + + // Update voicesync just in case. + voiceSync(false); +} + +void SID::setSamplingParameters(double clockFrequency, SamplingMethod method, double samplingFrequency, double highestAccurateFrequency) +{ + externalFilter->setClockFrequency(clockFrequency); + + switch (method) + { + case DECIMATE: + resampler.reset(new ZeroOrderResampler(clockFrequency, samplingFrequency)); + break; + + case RESAMPLE: + resampler.reset(TwoPassSincResampler::create(clockFrequency, samplingFrequency, highestAccurateFrequency)); + break; + + default: + throw SIDError("Unknown sampling method"); + } +} + +void SID::clockSilent(unsigned int cycles) +{ + ageBusValue(cycles); + + while (cycles != 0) + { + int delta_t = std::min(nextVoiceSync, cycles); + + if (delta_t > 0) + { + for (int i = 0; i < delta_t; i++) + { + // clock waveform generators (can affect OSC3) + voice[0]->wave()->clock(); + voice[1]->wave()->clock(); + voice[2]->wave()->clock(); + + voice[0]->wave()->output(voice[2]->wave()); + voice[1]->wave()->output(voice[0]->wave()); + voice[2]->wave()->output(voice[1]->wave()); + + // clock ENV3 only + voice[2]->envelope()->clock(); + } + + cycles -= delta_t; + nextVoiceSync -= delta_t; + } + + if (nextVoiceSync == 0) + { + voiceSync(true); + } + } +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/SID.h b/src/engine/platform/sound/c64_fp/SID.h new file mode 100644 index 00000000..85b6a4e4 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/SID.h @@ -0,0 +1,378 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2016 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SIDFP_H +#define SIDFP_H + +#include + +#include "siddefs-fp.h" + +#include "sidcxx11.h" + +namespace reSIDfp +{ + +class Filter; +class Filter6581; +class Filter8580; +class ExternalFilter; +class Potentiometer; +class Voice; +class Resampler; + +/** + * SID error exception. + */ +class SIDError +{ +private: + const char* message; + +public: + SIDError(const char* msg) : + message(msg) {} + const char* getMessage() const { return message; } +}; + +/** + * MOS6581/MOS8580 emulation. + */ +class SID +{ +private: + /// Currently active filter + Filter* filter; + + /// Filter used, if model is set to 6581 + std::unique_ptr const filter6581; + + /// Filter used, if model is set to 8580 + std::unique_ptr const filter8580; + + /** + * External filter that provides high-pass and low-pass filtering + * to adjust sound tone slightly. + */ + std::unique_ptr const externalFilter; + + /// Resampler used by audio generation code. + std::unique_ptr resampler; + + /// Paddle X register support + std::unique_ptr const potX; + + /// Paddle Y register support + std::unique_ptr const potY; + + /// SID voices + std::unique_ptr voice[3]; + + /// Time to live for the last written value + int busValueTtl; + + /// Current chip model's bus value TTL + int modelTTL; + + /// Time until #voiceSync must be run. + unsigned int nextVoiceSync; + + /// Currently active chip model. + ChipModel model; + + /// Last written value + unsigned char busValue; + + /// Flags for muted channels + bool muted[3]; + + /** + * Emulated nonlinearity of the envelope DAC. + * + * @See Dac + */ + float envDAC[256]; + + /** + * Emulated nonlinearity of the oscillator DAC. + * + * @See Dac + */ + float oscDAC[4096]; + +private: + /** + * Age the bus value and zero it if it's TTL has expired. + * + * @param n the number of cycles + */ + void ageBusValue(unsigned int n); + + /** + * Get output sample. + * + * @return the output sample + */ + int output(); + + /** + * Calculate the numebr of cycles according to current parameters + * that it takes to reach sync. + * + * @param sync whether to do the actual voice synchronization + */ + void voiceSync(bool sync); + +public: + SID(); + ~SID(); + + int lastChanOut[3]; + + /** + * Set chip model. + * + * @param model chip model to use + * @throw SIDError + */ + void setChipModel(ChipModel model); + + /** + * Get currently emulated chip model. + */ + ChipModel getChipModel() const { return model; } + + /** + * SID reset. + */ + void reset(); + + /** + * 16-bit input (EXT IN). Write 16-bit sample to audio input. NB! The caller + * is responsible for keeping the value within 16 bits. Note that to mix in + * an external audio signal, the signal should be resampled to 1MHz first to + * avoid sampling noise. + * + * @param value input level to set + */ + void input(int value); + + /** + * Read registers. + * + * Reading a write only register returns the last char written to any SID register. + * The individual bits in this value start to fade down towards zero after a few cycles. + * All bits reach zero within approximately $2000 - $4000 cycles. + * It has been claimed that this fading happens in an orderly fashion, + * however sampling of write only registers reveals that this is not the case. + * NOTE: This is not correctly modeled. + * The actual use of write only registers has largely been made + * in the belief that all SID registers are readable. + * To support this belief the read would have to be done immediately + * after a write to the same register (remember that an intermediate write + * to another register would yield that value instead). + * With this in mind we return the last value written to any SID register + * for $2000 cycles without modeling the bit fading. + * + * @param offset SID register to read + * @return value read from chip + */ + unsigned char read(int offset); + + /** + * Write registers. + * + * @param offset chip register to write + * @param value value to write + */ + void write(int offset, unsigned char value); + + /** + * SID voice muting. + * + * @param channel channel to modify + * @param enable is muted? + */ + void mute(int channel, bool enable) { muted[channel] = enable; } + + /** + * Setting of SID sampling parameters. + * + * Use a clock freqency of 985248Hz for PAL C64, 1022730Hz for NTSC C64. + * The default end of passband frequency is pass_freq = 0.9*sample_freq/2 + * for sample frequencies up to ~ 44.1kHz, and 20kHz for higher sample frequencies. + * + * For resampling, the ratio between the clock frequency and the sample frequency + * is limited as follows: 125*clock_freq/sample_freq < 16384 + * E.g. provided a clock frequency of ~ 1MHz, the sample frequency can not be set + * lower than ~ 8kHz. A lower sample frequency would make the resampling code + * overfill its 16k sample ring buffer. + * + * The end of passband frequency is also limited: pass_freq <= 0.9*sample_freq/2 + * + * E.g. for a 44.1kHz sampling rate the end of passband frequency + * is limited to slightly below 20kHz. + * This constraint ensures that the FIR table is not overfilled. + * + * @param clockFrequency System clock frequency at Hz + * @param method sampling method to use + * @param samplingFrequency Desired output sampling rate + * @param highestAccurateFrequency + * @throw SIDError + */ + void setSamplingParameters(double clockFrequency, SamplingMethod method, double samplingFrequency, double highestAccurateFrequency); + + /** + * Clock SID forward using chosen output sampling algorithm. + * + * @param cycles c64 clocks to clock + * @param buf audio output buffer + * @return number of samples produced + */ + int clock(unsigned int cycles, short* buf); + + /** + * Clock SID forward with no audio production. + * + * _Warning_: + * You can't mix this method of clocking with the audio-producing + * clock() because components that don't affect OSC3/ENV3 are not + * emulated. + * + * @param cycles c64 clocks to clock. + */ + void clockSilent(unsigned int cycles); + + /** + * Set filter curve parameter for 6581 model. + * + * @see Filter6581::setFilterCurve(double) + */ + void setFilter6581Curve(double filterCurve); + + /** + * Set filter curve parameter for 8580 model. + * + * @see Filter8580::setFilterCurve(double) + */ + void setFilter8580Curve(double filterCurve); + + /** + * Enable filter emulation. + * + * @param enable false to turn off filter emulation + */ + void enableFilter(bool enable); +}; + +} // namespace reSIDfp + +#if RESID_INLINING || defined(SID_CPP) + +#include + +#include "Filter.h" +#include "ExternalFilter.h" +#include "Voice.h" +#include "resample/Resampler.h" + +namespace reSIDfp +{ + +RESID_INLINE +void SID::ageBusValue(unsigned int n) +{ + if (likely(busValueTtl != 0)) + { + busValueTtl -= n; + + if (unlikely(busValueTtl <= 0)) + { + busValue = 0; + busValueTtl = 0; + } + } +} + +RESID_INLINE +int SID::output() +{ + const int v1 = voice[0]->output(voice[2]->wave()); + const int v2 = voice[1]->output(voice[0]->wave()); + const int v3 = voice[2]->output(voice[1]->wave()); + + lastChanOut[0]=v1; + lastChanOut[1]=v2; + lastChanOut[2]=v3; + + return externalFilter->clock(filter->clock(v1, v2, v3)); +} + + +RESID_INLINE +int SID::clock(unsigned int cycles, short* buf) +{ + ageBusValue(cycles); + int s = 0; + + while (cycles != 0) + { + unsigned int delta_t = std::min(nextVoiceSync, cycles); + + if (likely(delta_t > 0)) + { + for (unsigned int i = 0; i < delta_t; i++) + { + // clock waveform generators + voice[0]->wave()->clock(); + voice[1]->wave()->clock(); + voice[2]->wave()->clock(); + + // clock envelope generators + voice[0]->envelope()->clock(); + voice[1]->envelope()->clock(); + voice[2]->envelope()->clock(); + + if (unlikely(resampler->input(output()))) + { + buf[s++] = resampler->getOutput(); + } + } + + cycles -= delta_t; + nextVoiceSync -= delta_t; + } + + if (unlikely(nextVoiceSync == 0)) + { + voiceSync(true); + } + } + + return s; +} + +} // namespace reSIDfp + +#endif + +#endif diff --git a/src/engine/platform/sound/c64_fp/Spline.cpp b/src/engine/platform/sound/c64_fp/Spline.cpp new file mode 100644 index 00000000..50d55fef --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Spline.cpp @@ -0,0 +1,119 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2015 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * + * 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 "Spline.h" + +#include +#include + +namespace reSIDfp +{ + +Spline::Spline(const std::vector &input) : + params(input.size()), + c(¶ms[0]) +{ + assert(input.size() > 2); + + const size_t coeffLength = input.size() - 1; + + std::vector dxs(coeffLength); + std::vector ms(coeffLength); + + // Get consecutive differences and slopes + for (size_t i = 0; i < coeffLength; i++) + { + assert(input[i].x < input[i + 1].x); + + const double dx = input[i + 1].x - input[i].x; + const double dy = input[i + 1].y - input[i].y; + dxs[i] = dx; + ms[i] = dy/dx; + } + + // Get degree-1 coefficients + params[0].c = ms[0]; + for (size_t i = 1; i < coeffLength; i++) + { + const double m = ms[i - 1]; + const double mNext = ms[i]; + if (m * mNext <= 0) + { + params[i].c = 0.0; + } + else + { + const double dx = dxs[i - 1]; + const double dxNext = dxs[i]; + const double common = dx + dxNext; + params[i].c = 3.0 * common / ((common + dxNext) / m + (common + dx) / mNext); + } + } + params[coeffLength].c = ms[coeffLength - 1]; + + // Get degree-2 and degree-3 coefficients + for (size_t i = 0; i < coeffLength; i++) + { + params[i].x1 = input[i].x; + params[i].x2 = input[i + 1].x; + params[i].d = input[i].y; + + const double c1 = params[i].c; + const double m = ms[i]; + const double invDx = 1.0 / dxs[i]; + const double common = c1 + params[i + 1].c - m - m; + params[i].b = (m - c1 - common) * invDx; + params[i].a = common * invDx * invDx; + } + + // Fix the upper range, because we interpolate outside original bounds if necessary. + params[coeffLength - 1].x2 = std::numeric_limits::max(); +} + +Spline::Point Spline::evaluate(double x) const +{ + if ((x < c->x1) || (x > c->x2)) + { + for (size_t i = 0; i < params.size(); i++) + { + if (x <= params[i].x2) + { + c = ¶ms[i]; + break; + } + } + } + + // Interpolate + const double diff = x - c->x1; + + Point out; + + // y = a*x^3 + b*x^2 + c*x + d + out.x = ((c->a * diff + c->b) * diff + c->c) * diff + c->d; + + // dy = 3*a*x^2 + 2*b*x + c + out.y = (3.0 * c->a * diff + 2.0 * c->b) * diff + c->c; + + return out; +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/Spline.h b/src/engine/platform/sound/c64_fp/Spline.h new file mode 100644 index 00000000..6cc2b1ed --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Spline.h @@ -0,0 +1,78 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2015 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SPLINE_H +#define SPLINE_H + +#include +#include + +namespace reSIDfp +{ + +/** + * Fritsch-Carlson monotone cubic spline interpolation. + * + * Based on the implementation from the [Monotone cubic interpolation] wikipedia page. + * + * [Monotone cubic interpolation]: https://en.wikipedia.org/wiki/Monotone_cubic_interpolation + */ +class Spline +{ +public: + typedef struct + { + double x; + double y; + } Point; + +private: + typedef struct + { + double x1; + double x2; + double a; + double b; + double c; + double d; + } Param; + + typedef std::vector ParamVector; + +private: + /// Interpolation parameters + ParamVector params; + + /// Last used parameters, cached for speed up + mutable ParamVector::const_pointer c; + +public: + Spline(const std::vector &input); + + /** + * Evaluate y and its derivative at given point x. + */ + Point evaluate(double x) const; +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/Voice.h b/src/engine/platform/sound/c64_fp/Voice.h new file mode 100644 index 00000000..fc7ed41b --- /dev/null +++ b/src/engine/platform/sound/c64_fp/Voice.h @@ -0,0 +1,130 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef VOICE_H +#define VOICE_H + +#include + +#include "siddefs-fp.h" +#include "WaveformGenerator.h" +#include "EnvelopeGenerator.h" + +#include "sidcxx11.h" + +namespace reSIDfp +{ + +/** + * Representation of SID voice block. + */ +class Voice +{ +private: + std::unique_ptr const waveformGenerator; + + std::unique_ptr const envelopeGenerator; + + /// The DAC LUT for analog waveform output + float* wavDAC; //-V730_NOINIT this is initialized in the SID constructor + + /// The DAC LUT for analog envelope output + float* envDAC; //-V730_NOINIT this is initialized in the SID constructor + +public: + /** + * Amplitude modulated waveform output. + * + * The waveform DAC generates a voltage between virtual ground and Vdd + * (5-12 V for the 6581 and 4.75-9 V for the 8580) + * corresponding to oscillator state 0 .. 4095. + * + * The envelope DAC generates a voltage between waveform gen output and + * the virtual ground level, corresponding to envelope state 0 .. 255. + * + * Ideal range [-2048*255, 2047*255]. + * + * @param ringModulator Ring-modulator for waveform + * @return the voice analog output + */ + RESID_INLINE + int output(const WaveformGenerator* ringModulator) const + { + unsigned int const wav = waveformGenerator->output(ringModulator); + unsigned int const env = envelopeGenerator->output(); + + // DAC imperfections are emulated by using the digital output + // as an index into a DAC lookup table. + return static_cast(wavDAC[wav] * envDAC[env]); + } + + /** + * Constructor. + */ + Voice() : + waveformGenerator(new WaveformGenerator()), + envelopeGenerator(new EnvelopeGenerator()) {} + + /** + * Set the analog DAC emulation for waveform generator. + * Must be called before any operation. + * + * @param dac + */ + void setWavDAC(float* dac) { wavDAC = dac; } + + /** + * Set the analog DAC emulation for envelope. + * Must be called before any operation. + * + * @param dac + */ + void setEnvDAC(float* dac) { envDAC = dac; } + + WaveformGenerator* wave() const { return waveformGenerator.get(); } + + EnvelopeGenerator* envelope() const { return envelopeGenerator.get(); } + + /** + * Write control register. + * + * @param control Control register value. + */ + void writeCONTROL_REG(unsigned char control) + { + waveformGenerator->writeCONTROL_REG(control); + envelopeGenerator->writeCONTROL_REG(control); + } + + /** + * SID reset. + */ + void reset() + { + waveformGenerator->reset(); + envelopeGenerator->reset(); + } +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/WaveformCalculator.cpp b/src/engine/platform/sound/c64_fp/WaveformCalculator.cpp new file mode 100644 index 00000000..fe5030fa --- /dev/null +++ b/src/engine/platform/sound/c64_fp/WaveformCalculator.cpp @@ -0,0 +1,204 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2016 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * + * 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 "WaveformCalculator.h" + +#include + +namespace reSIDfp +{ + +WaveformCalculator* WaveformCalculator::getInstance() +{ + static WaveformCalculator instance; + return &instance; +} + +/** + * Parameters derived with the Monte Carlo method based on + * samplings by kevtris. Code and data available in the project repository [1]. + * + * The score here reported is the acoustic error + * calculated XORing the estimated and the sampled values. + * In parentheses the number of mispredicted bits + * on a total of 32768. + * + * [1] https://github.com/libsidplayfp/combined-waveforms + */ +const CombinedWaveformConfig config[2][4] = +{ + { /* kevtris chip G (6581 R2) */ + {0.90251f, 0.f, 0.f, 1.9147f, 1.6747f, 0.62376f }, // error 1689 (280) + {0.93088f, 2.4843f, 0.f, 1.0353f, 1.1484f, 0.f }, // error 6128 (130) + {0.90988f, 2.26303f, 1.13126f, 1.0035f, 1.13801f, 0.f }, // error 14243 (632) + {0.91f, 1.192f, 0.f, 1.0169f, 1.2f, 0.637f }, // error 64 (2) + }, + { /* kevtris chip V (8580 R5) */ + {0.9632f, 0.f, 0.975f, 1.7467f, 2.36132f, 0.975395f}, // error 1380 (169) + {0.92886f, 1.67696f, 0.f, 1.1014f, 1.4352f, 0.f }, // error 8007 (218) + {0.94043f, 1.7937f, 0.981f, 1.1213f, 1.4259f, 0.f }, // error 11957 (362) + {0.96211f, 0.98695f, 1.00387f, 1.46499f, 1.98375f, 0.77777f }, // error 2369 (89) + }, +}; + +/** + * Generate bitstate based on emulation of combined waves. + * + * @param config model parameters matrix + * @param waveform the waveform to emulate, 1 .. 7 + * @param accumulator the high bits of the accumulator value + */ +short calculateCombinedWaveform(const CombinedWaveformConfig& config, int waveform, int accumulator) +{ + float o[12]; + + // Saw + for (unsigned int i = 0; i < 12; i++) + { + o[i] = (accumulator & (1 << i)) != 0 ? 1.f : 0.f; + } + + // convert to Triangle + if ((waveform & 3) == 1) + { + const bool top = (accumulator & 0x800) != 0; + + for (int i = 11; i > 0; i--) + { + o[i] = top ? 1.0f - o[i - 1] : o[i - 1]; + } + + o[0] = 0.f; + } + + // or to Saw+Triangle + else if ((waveform & 3) == 3) + { + // bottom bit is grounded via T waveform selector + o[0] *= config.stmix; + + for (int i = 1; i < 12; i++) + { + /* + * Enabling the S waveform pulls the XOR circuit selector transistor down + * (which would normally make the descending ramp of the triangle waveform), + * so ST does not actually have a sawtooth and triangle waveform combined, + * but merely combines two sawtooths, one rising double the speed the other. + * + * http://www.lemon64.com/forum/viewtopic.php?t=25442&postdays=0&postorder=asc&start=165 + */ + o[i] = o[i - 1] * (1.f - config.stmix) + o[i] * config.stmix; + } + } + + // topbit for Saw + if ((waveform & 2) == 2) + { + o[11] *= config.topbit; + } + + // ST, P* waveforms + if (waveform == 3 || waveform > 4) + { + float distancetable[12 * 2 + 1]; + distancetable[12] = 1.f; + for (int i = 12; i > 0; i--) + { + distancetable[12-i] = 1.0f / pow(config.distance1, i); + distancetable[12+i] = 1.0f / pow(config.distance2, i); + } + + float tmp[12]; + + for (int i = 0; i < 12; i++) + { + float avg = 0.f; + float n = 0.f; + + for (int j = 0; j < 12; j++) + { + const float weight = distancetable[i - j + 12]; + avg += o[j] * weight; + n += weight; + } + + // pulse control bit + if (waveform > 4) + { + const float weight = distancetable[i - 12 + 12]; + avg += config.pulsestrength * weight; + n += weight; + } + + tmp[i] = (o[i] + avg / n) * 0.5f; + } + + for (int i = 0; i < 12; i++) + { + o[i] = tmp[i]; + } + } + + short value = 0; + + for (unsigned int i = 0; i < 12; i++) + { + if (o[i] > config.bias) + { + value |= 1 << i; + } + } + + return value; +} + +matrix_t* WaveformCalculator::buildTable(ChipModel model) +{ + const CombinedWaveformConfig* cfgArray = config[model == MOS6581 ? 0 : 1]; + + cw_cache_t::iterator lb = CACHE.lower_bound(cfgArray); + + if (lb != CACHE.end() && !(CACHE.key_comp()(cfgArray, lb->first))) + { + return &(lb->second); + } + + matrix_t wftable(8, 4096); + + for (unsigned int idx = 0; idx < 1 << 12; idx++) + { + wftable[0][idx] = 0xfff; + wftable[1][idx] = static_cast((idx & 0x800) == 0 ? idx << 1 : (idx ^ 0xfff) << 1); + wftable[2][idx] = static_cast(idx); + wftable[3][idx] = calculateCombinedWaveform(cfgArray[0], 3, idx); + wftable[4][idx] = 0xfff; + wftable[5][idx] = calculateCombinedWaveform(cfgArray[1], 5, idx); + wftable[6][idx] = calculateCombinedWaveform(cfgArray[2], 6, idx); + wftable[7][idx] = calculateCombinedWaveform(cfgArray[3], 7, idx); + } +#ifdef HAVE_CXX11 + return &(CACHE.emplace_hint(lb, cw_cache_t::value_type(cfgArray, wftable))->second); +#else + return &(CACHE.insert(lb, cw_cache_t::value_type(cfgArray, wftable))->second); +#endif +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/WaveformCalculator.h b/src/engine/platform/sound/c64_fp/WaveformCalculator.h new file mode 100644 index 00000000..f9183c5d --- /dev/null +++ b/src/engine/platform/sound/c64_fp/WaveformCalculator.h @@ -0,0 +1,128 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2016 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef WAVEFORMCALCULATOR_h +#define WAVEFORMCALCULATOR_h + +#include + +#include "array.h" +#include "sidcxx11.h" +#include "siddefs-fp.h" + + +namespace reSIDfp +{ + +/** + * Combined waveform model parameters. + */ +typedef struct +{ + float bias; + float pulsestrength; + float topbit; + float distance1; + float distance2; + float stmix; +} CombinedWaveformConfig; + +/** + * Combined waveform calculator for WaveformGenerator. + * By combining waveforms, the bits of each waveform are effectively short + * circuited. A zero bit in one waveform will result in a zero output bit + * (thus the infamous claim that the waveforms are AND'ed). + * However, a zero bit in one waveform may also affect the neighboring bits + * in the output. + * + * Example: + * + * 1 1 + * Bit # 1 0 9 8 7 6 5 4 3 2 1 0 + * ----------------------- + * Sawtooth 0 0 0 1 1 1 1 1 1 0 0 0 + * + * Triangle 0 0 1 1 1 1 1 1 0 0 0 0 + * + * AND 0 0 0 1 1 1 1 1 0 0 0 0 + * + * Output 0 0 0 0 1 1 1 0 0 0 0 0 + * + * + * Re-vectorized die photographs reveal the mechanism behind this behavior. + * Each waveform selector bit acts as a switch, which directly connects + * internal outputs into the waveform DAC inputs as follows: + * + * - Noise outputs the shift register bits to DAC inputs as described above. + * Each output is also used as input to the next bit when the shift register + * is shifted. Lower four bits are grounded. + * - Pulse connects a single line to all DAC inputs. The line is connected to + * either 5V (pulse on) or 0V (pulse off) at bit 11, and ends at bit 0. + * - Triangle connects the upper 11 bits of the (MSB EOR'ed) accumulator to the + * DAC inputs, so that DAC bit 0 = 0, DAC bit n = accumulator bit n - 1. + * - Sawtooth connects the upper 12 bits of the accumulator to the DAC inputs, + * so that DAC bit n = accumulator bit n. Sawtooth blocks out the MSB from + * the EOR used to generate the triangle waveform. + * + * We can thus draw the following conclusions: + * + * - The shift register may be written to by combined waveforms. + * - The pulse waveform interconnects all bits in combined waveforms via the + * pulse line. + * - The combination of triangle and sawtooth interconnects neighboring bits + * of the sawtooth waveform. + * + * Also in the 6581 the MSB of the oscillator, used as input for the + * triangle xor logic and the pulse adder's last bit, is connected directly + * to the waveform selector, while in the 8580 it is latched at sid_clk2 + * before being forwarded to the selector. Thus in the 6581 if the sawtooth MSB + * is pulled down it might affect the oscillator's adder + * driving the top bit low. + * + */ +class WaveformCalculator +{ +private: + typedef std::map cw_cache_t; + +private: + cw_cache_t CACHE; + + WaveformCalculator() DEFAULT; + +public: + /** + * Get the singleton instance. + */ + static WaveformCalculator* getInstance(); + + /** + * Build waveform tables for use by WaveformGenerator. + * + * @param model Chip model to use + * @return Waveform table + */ + matrix_t* buildTable(ChipModel model); +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/WaveformGenerator.cpp b/src/engine/platform/sound/c64_fp/WaveformGenerator.cpp new file mode 100644 index 00000000..e5e544e7 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/WaveformGenerator.cpp @@ -0,0 +1,357 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2021 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * 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. + */ + +#define WAVEFORMGENERATOR_CPP + +#include "WaveformGenerator.h" + +/* + * This fixes tests + * SID/wb_testsuite/noise_writeback_check_8_to_C_old + * SID/wb_testsuite/noise_writeback_check_9_to_C_old + * SID/wb_testsuite/noise_writeback_check_A_to_C_old + * SID/wb_testsuite/noise_writeback_check_C_to_C_old + * + * but breaks SID/wf12nsr/wf12nsr + * + * needs more digging... + */ +//#define NO_WB_NOI_PUL + +namespace reSIDfp +{ + +/** + * Number of cycles after which the waveform output fades to 0 when setting + * the waveform register to 0. + * Values measured on warm chips (6581R3/R4 and 8580R5) + * checking OSC3. + * Times vary wildly with temperature and may differ + * from chip to chip so the numbers here represent + * only the big difference between the old and new models. + * + * See [VICE Bug #290](http://sourceforge.net/p/vice-emu/bugs/290/) + * and [VICE Bug #1128](http://sourceforge.net/p/vice-emu/bugs/1128/) + */ +// ~95ms +const unsigned int FLOATING_OUTPUT_TTL_6581R3 = 54000; +const unsigned int FLOATING_OUTPUT_FADE_6581R3 = 1400; +// ~1s +//const unsigned int FLOATING_OUTPUT_TTL_6581R4 = 1000000; +// ~1s +const unsigned int FLOATING_OUTPUT_TTL_8580R5 = 800000; +const unsigned int FLOATING_OUTPUT_FADE_8580R5 = 50000; + +/** + * Number of cycles after which the shift register is reset + * when the test bit is set. + * Values measured on warm chips (6581R3/R4 and 8580R5) + * checking OSC3. + * Times vary wildly with temperature and may differ + * from chip to chip so the numbers here represent + * only the big difference between the old and new models. + */ +// ~210ms +const unsigned int SHIFT_REGISTER_RESET_6581R3 = 50000; +const unsigned int SHIFT_REGISTER_FADE_6581R3 = 15000; +// ~2.15s +//const unsigned int SHIFT_REGISTER_RESET_6581R4 = 2150000; +// ~2.8s +const unsigned int SHIFT_REGISTER_RESET_8580R5 = 986000; +const unsigned int SHIFT_REGISTER_FADE_8580R5 = 314300; + +/* + * This is what happens when the lfsr is clocked: + * + * cycle 0: bit 19 of the accumulator goes from low to high, the noise register acts normally, + * the output may overwrite a bit; + * + * cycle 1: first phase of the shift, the bits are interconnected and the output of each bit + * is latched into the following. The output may overwrite the latched value. + * + * cycle 2: second phase of the shift, the latched value becomes active in the first + * half of the clock and from the second half the register returns to normal operation. + * + * When the test or reset lines are active the first phase is executed at every cyle + * until the signal is released triggering the second phase. + */ +void WaveformGenerator::clock_shift_register(unsigned int bit0) +{ + shift_register = (shift_register >> 1) | bit0; + + // New noise waveform output. + set_noise_output(); +} + +unsigned int WaveformGenerator::get_noise_writeback() +{ + return + ~( + (1 << 2) | // Bit 20 + (1 << 4) | // Bit 18 + (1 << 8) | // Bit 14 + (1 << 11) | // Bit 11 + (1 << 13) | // Bit 9 + (1 << 17) | // Bit 5 + (1 << 20) | // Bit 2 + (1 << 22) // Bit 0 + ) | + ((waveform_output & (1 << 11)) >> 9) | // Bit 11 -> bit 20 + ((waveform_output & (1 << 10)) >> 6) | // Bit 10 -> bit 18 + ((waveform_output & (1 << 9)) >> 1) | // Bit 9 -> bit 14 + ((waveform_output & (1 << 8)) << 3) | // Bit 8 -> bit 11 + ((waveform_output & (1 << 7)) << 6) | // Bit 7 -> bit 9 + ((waveform_output & (1 << 6)) << 11) | // Bit 6 -> bit 5 + ((waveform_output & (1 << 5)) << 15) | // Bit 5 -> bit 2 + ((waveform_output & (1 << 4)) << 18); // Bit 4 -> bit 0 +} + +void WaveformGenerator::write_shift_register() +{ + if (unlikely(waveform > 0x8) && likely(!test) && likely(shift_pipeline != 1)) + { + // Write changes to the shift register output caused by combined waveforms + // back into the shift register. This happens only when the register is clocked + // (see $D1+$81_wave_test [1]) or when the test bit is falling. + // A bit once set to zero cannot be changed, hence the and'ing. + // + // [1] ftp://ftp.untergrund.net/users/nata/sid_test/$D1+$81_wave_test.7z + // + // FIXME: Write test program to check the effect of 1 bits and whether + // neighboring bits are affected. + +#ifdef NO_WB_NOI_PUL + if (waveform == 0xc) + return; +#endif + shift_register &= get_noise_writeback(); + + noise_output &= waveform_output; + set_no_noise_or_noise_output(); + } +} + +void WaveformGenerator::set_noise_output() +{ + noise_output = + ((shift_register & (1 << 2)) << 9) | // Bit 20 -> bit 11 + ((shift_register & (1 << 4)) << 6) | // Bit 18 -> bit 10 + ((shift_register & (1 << 8)) << 1) | // Bit 14 -> bit 9 + ((shift_register & (1 << 11)) >> 3) | // Bit 11 -> bit 8 + ((shift_register & (1 << 13)) >> 6) | // Bit 9 -> bit 7 + ((shift_register & (1 << 17)) >> 11) | // Bit 5 -> bit 6 + ((shift_register & (1 << 20)) >> 15) | // Bit 2 -> bit 5 + ((shift_register & (1 << 22)) >> 18); // Bit 0 -> bit 4 + + set_no_noise_or_noise_output(); +} + +void WaveformGenerator::setWaveformModels(matrix_t* models) +{ + model_wave = models; +} + +void WaveformGenerator::synchronize(WaveformGenerator* syncDest, const WaveformGenerator* syncSource) const +{ + // A special case occurs when a sync source is synced itself on the same + // cycle as when its MSB is set high. In this case the destination will + // not be synced. This has been verified by sampling OSC3. + if (unlikely(msb_rising) && syncDest->sync && !(sync && syncSource->msb_rising)) + { + syncDest->accumulator = 0; + } +} + +bool do_pre_writeback(unsigned int waveform_prev, unsigned int waveform, bool is6581) +{ + // no writeback without combined waveforms + if (likely(waveform_prev <= 0x8)) + return false; + // no writeback when changing to noise + if (waveform == 8) + return false; + // What's happening here? + if (is6581 && + ((((waveform_prev & 0x3) == 0x1) && ((waveform & 0x3) == 0x2)) + || (((waveform_prev & 0x3) == 0x2) && ((waveform & 0x3) == 0x1)))) + return false; + if (waveform_prev == 0xc) + { + if (is6581) + return false; + else if ((waveform != 0x9) && (waveform != 0xe)) + return false; + } +#ifdef NO_WB_NOI_PUL + if (waveform == 0xc) + return false; +#endif + // ok do the writeback + return true; +} + +/* + * When noise and pulse are combined all the bits are + * connected and the four lower ones are grounded. + * This causes the adjacent bits to be pulled down, + * with different strength depending on model. + * + * This is just a rough attempt at modelling the effect. + */ + +static unsigned int noise_pulse6581(unsigned int noise) +{ + return (noise < 0xf00) ? 0x000 : noise & (noise << 1) & (noise << 2); +} + +static unsigned int noise_pulse8580(unsigned int noise) +{ + return (noise < 0xfc0) ? noise & (noise << 1) : 0xfc0; +} + +void WaveformGenerator::set_no_noise_or_noise_output() +{ + no_noise_or_noise_output = no_noise | noise_output; + + // pulse+noise + if (unlikely((waveform & 0xc) == 0xc)) + no_noise_or_noise_output = is6581 + ? noise_pulse6581(no_noise_or_noise_output) + : noise_pulse8580(no_noise_or_noise_output); + +} + +void WaveformGenerator::writeCONTROL_REG(unsigned char control) +{ + const unsigned int waveform_prev = waveform; + const bool test_prev = test; + + waveform = (control >> 4) & 0x0f; + test = (control & 0x08) != 0; + sync = (control & 0x02) != 0; + + // Substitution of accumulator MSB when sawtooth = 0, ring_mod = 1. + ring_msb_mask = ((~control >> 5) & (control >> 2) & 0x1) << 23; + + if (waveform != waveform_prev) + { + // Set up waveform table. + wave = (*model_wave)[waveform & 0x7]; + + // no_noise and no_pulse are used in set_waveform_output() as bitmasks to + // only let the noise or pulse influence the output when the noise or pulse + // waveforms are selected. + no_noise = (waveform & 0x8) != 0 ? 0x000 : 0xfff; + set_no_noise_or_noise_output(); + no_pulse = (waveform & 0x4) != 0 ? 0x000 : 0xfff; + + if (waveform == 0) + { + // Change to floating DAC input. + // Reset fading time for floating DAC input. + floating_output_ttl = is6581 ? FLOATING_OUTPUT_TTL_6581R3 : FLOATING_OUTPUT_TTL_8580R5; + } + } + + if (test != test_prev) + { + if (test) + { + // Reset accumulator. + accumulator = 0; + + // Flush shift pipeline. + shift_pipeline = 0; + + // Set reset time for shift register. + shift_register_reset = is6581 ? SHIFT_REGISTER_RESET_6581R3 : SHIFT_REGISTER_RESET_8580R5; + } + else + { + // When the test bit is falling, the second phase of the shift is + // completed by enabling SRAM write. + + // During first phase of the shift the bits are interconnected + // and the output of each bit is latched into the following. + // The output may overwrite the latched value. + if (do_pre_writeback(waveform_prev, waveform, is6581)) + { + shift_register &= get_noise_writeback(); + } + + // bit0 = (bit22 | test) ^ bit17 = 1 ^ bit17 = ~bit17 + clock_shift_register((~shift_register << 17) & (1 << 22)); + } + } +} + +void WaveformGenerator::waveBitfade() +{ + waveform_output &= waveform_output >> 1; + osc3 = waveform_output; + if (waveform_output != 0) + floating_output_ttl = is6581 ? FLOATING_OUTPUT_FADE_6581R3 : FLOATING_OUTPUT_FADE_8580R5; +} + +void WaveformGenerator::shiftregBitfade() +{ + shift_register |= shift_register >> 1; + shift_register |= 0x400000; + if (shift_register != 0x7fffff) + shift_register_reset = is6581 ? SHIFT_REGISTER_FADE_6581R3 : SHIFT_REGISTER_FADE_8580R5; +} + +void WaveformGenerator::reset() +{ + // accumulator is not changed on reset + freq = 0; + pw = 0; + + msb_rising = false; + + waveform = 0; + osc3 = 0; + + test = false; + sync = false; + + wave = model_wave ? (*model_wave)[0] : nullptr; + + ring_msb_mask = 0; + no_noise = 0xfff; + no_pulse = 0xfff; + pulse_output = 0xfff; + + shift_register_reset = 0; + shift_register = 0x7fffff; + // when reset is released the shift register is clocked once + // so the lower bit is zeroed out + // bit0 = (bit22 | test) ^ bit17 = 1 ^ 1 = 0 + clock_shift_register(0); + + shift_pipeline = 0; + + waveform_output = 0; + floating_output_ttl = 0; +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/WaveformGenerator.h b/src/engine/platform/sound/c64_fp/WaveformGenerator.h new file mode 100644 index 00000000..9fd617f6 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/WaveformGenerator.h @@ -0,0 +1,396 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2022 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004,2010 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef WAVEFORMGENERATOR_H +#define WAVEFORMGENERATOR_H + +#include "siddefs-fp.h" +#include "array.h" + +#include "sidcxx11.h" + +namespace reSIDfp +{ + +/** + * A 24 bit accumulator is the basis for waveform generation. + * FREQ is added to the lower 16 bits of the accumulator each cycle. + * The accumulator is set to zero when TEST is set, and starts counting + * when TEST is cleared. + * + * Waveforms are generated as follows: + * + * - No waveform: + * When no waveform is selected, the DAC input is floating. + * + * + * - Triangle: + * The upper 12 bits of the accumulator are used. + * The MSB is used to create the falling edge of the triangle by inverting + * the lower 11 bits. The MSB is thrown away and the lower 11 bits are + * left-shifted (half the resolution, full amplitude). + * Ring modulation substitutes the MSB with MSB EOR NOT sync_source MSB. + * + * + * - Sawtooth: + * The output is identical to the upper 12 bits of the accumulator. + * + * + * - Pulse: + * The upper 12 bits of the accumulator are used. + * These bits are compared to the pulse width register by a 12 bit digital + * comparator; output is either all one or all zero bits. + * The pulse setting is delayed one cycle after the compare. + * The test bit, when set to one, holds the pulse waveform output at 0xfff + * regardless of the pulse width setting. + * + * + * - Noise: + * The noise output is taken from intermediate bits of a 23-bit shift register + * which is clocked by bit 19 of the accumulator. + * The shift is delayed 2 cycles after bit 19 is set high. + * + * Operation: Calculate EOR result, shift register, set bit 0 = result. + * + * reset +--------------------------------------------+ + * | | | + * test--OR-->EOR<--+ | + * | | | + * 2 2 2 1 1 1 1 1 1 1 1 1 1 | + * Register bits: 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 <---+ + * | | | | | | | | + * Waveform bits: 1 1 9 8 7 6 5 4 + * 1 0 + * + * The low 4 waveform bits are zero (grounded). + */ +class WaveformGenerator +{ +private: + matrix_t* model_wave; + + short* wave; + + // PWout = (PWn/40.95)% + unsigned int pw; + + unsigned int shift_register; + + /// Emulation of pipeline causing bit 19 to clock the shift register. + int shift_pipeline; + + unsigned int ring_msb_mask; + unsigned int no_noise; + unsigned int noise_output; + unsigned int no_noise_or_noise_output; + unsigned int no_pulse; + unsigned int pulse_output; + + /// The control register right-shifted 4 bits; used for output function table lookup. + unsigned int waveform; + + unsigned int waveform_output; + + /// Current accumulator value. + unsigned int accumulator; + + // Fout = (Fn*Fclk/16777216)Hz + unsigned int freq; + + /// 8580 tri/saw pipeline + unsigned int tri_saw_pipeline; + + /// The OSC3 value + unsigned int osc3; + + /// Remaining time to fully reset shift register. + unsigned int shift_register_reset; + + // The wave signal TTL when no waveform is selected + unsigned int floating_output_ttl; + + /// The control register bits. Gate is handled by EnvelopeGenerator. + //@{ + bool test; + bool sync; + //@} + + /// Tell whether the accumulator MSB was set high on this cycle. + bool msb_rising; + + bool is6581; //-V730_NOINIT this is initialized in the SID constructor + +private: + void clock_shift_register(unsigned int bit0); + + unsigned int get_noise_writeback(); + + void write_shift_register(); + + void set_noise_output(); + + void set_no_noise_or_noise_output(); + + void waveBitfade(); + + void shiftregBitfade(); + +public: + void setWaveformModels(matrix_t* models); + + /** + * Set the chip model. + * Must be called before any operation. + * + * @param is6581 true if MOS6581, false if CSG8580 + */ + void setModel(bool is6581) { this->is6581 = is6581; } + + /** + * SID clocking. + */ + void clock(); + + /** + * Synchronize oscillators. + * This must be done after all the oscillators have been clock()'ed, + * so that they are in the same state. + * + * @param syncDest The oscillator that will be synced + * @param syncSource The sync source oscillator + */ + void synchronize(WaveformGenerator* syncDest, const WaveformGenerator* syncSource) const; + + /** + * Constructor. + */ + WaveformGenerator() : + model_wave(nullptr), + wave(nullptr), + pw(0), + shift_register(0), + shift_pipeline(0), + ring_msb_mask(0), + no_noise(0), + noise_output(0), + no_noise_or_noise_output(0), + no_pulse(0), + pulse_output(0), + waveform(0), + waveform_output(0), + accumulator(0x555555), // Accumulator's even bits are high on powerup + freq(0), + tri_saw_pipeline(0x555), + osc3(0), + shift_register_reset(0), + floating_output_ttl(0), + test(false), + sync(false), + msb_rising(false) {} + + /** + * Write FREQ LO register. + * + * @param freq_lo low 8 bits of frequency + */ + void writeFREQ_LO(unsigned char freq_lo) { freq = (freq & 0xff00) | (freq_lo & 0xff); } + + /** + * Write FREQ HI register. + * + * @param freq_hi high 8 bits of frequency + */ + void writeFREQ_HI(unsigned char freq_hi) { freq = (freq_hi << 8 & 0xff00) | (freq & 0xff); } + + /** + * Write PW LO register. + * + * @param pw_lo low 8 bits of pulse width + */ + void writePW_LO(unsigned char pw_lo) { pw = (pw & 0xf00) | (pw_lo & 0x0ff); } + + /** + * Write PW HI register. + * + * @param pw_hi high 8 bits of pulse width + */ + void writePW_HI(unsigned char pw_hi) { pw = (pw_hi << 8 & 0xf00) | (pw & 0x0ff); } + + /** + * Write CONTROL REGISTER register. + * + * @param control control register value + */ + void writeCONTROL_REG(unsigned char control); + + /** + * SID reset. + */ + void reset(); + + /** + * 12-bit waveform output. + * + * @param ringModulator The oscillator ring-modulating current one. + * @return the waveform generator digital output + */ + unsigned int output(const WaveformGenerator* ringModulator); + + /** + * Read OSC3 value. + */ + unsigned char readOSC() const { return static_cast(osc3 >> 4); } + + /** + * Read accumulator value. + */ + unsigned int readAccumulator() const { return accumulator; } + + /** + * Read freq value. + */ + unsigned int readFreq() const { return freq; } + + /** + * Read test value. + */ + bool readTest() const { return test; } + + /** + * Read sync value. + */ + bool readSync() const { return sync; } +}; + +} // namespace reSIDfp + +#if RESID_INLINING || defined(WAVEFORMGENERATOR_CPP) + +namespace reSIDfp +{ + +RESID_INLINE +void WaveformGenerator::clock() +{ + if (unlikely(test)) + { + if (unlikely(shift_register_reset != 0) && unlikely(--shift_register_reset == 0)) + { + shiftregBitfade(); + + // New noise waveform output. + set_noise_output(); + } + + // The test bit sets pulse high. + pulse_output = 0xfff; + } + else + { + // Calculate new accumulator value; + const unsigned int accumulator_old = accumulator; + accumulator = (accumulator + freq) & 0xffffff; + + // Check which bit have changed + const unsigned int accumulator_bits_set = ~accumulator_old & accumulator; + + // Check whether the MSB is set high. This is used for synchronization. + msb_rising = (accumulator_bits_set & 0x800000) != 0; + + // Shift noise register once for each time accumulator bit 19 is set high. + // The shift is delayed 2 cycles. + if (unlikely((accumulator_bits_set & 0x080000) != 0)) + { + // Pipeline: Detect rising bit, shift phase 1, shift phase 2. + shift_pipeline = 2; + } + else if (unlikely(shift_pipeline != 0) && --shift_pipeline == 0) + { + // bit0 = (bit22 | test) ^ bit17 + clock_shift_register(((shift_register << 22) ^ (shift_register << 17)) & (1 << 22)); + } + } +} + +RESID_INLINE +unsigned int WaveformGenerator::output(const WaveformGenerator* ringModulator) +{ + // Set output value. + if (likely(waveform != 0)) + { + const unsigned int ix = (accumulator ^ (~ringModulator->accumulator & ring_msb_mask)) >> 12; + + // The bit masks no_pulse and no_noise are used to achieve branch-free + // calculation of the output value. + waveform_output = wave[ix] & (no_pulse | pulse_output) & no_noise_or_noise_output; + + // Triangle/Sawtooth output is delayed half cycle on 8580. + // This will appear as a one cycle delay on OSC3 as it is latched first phase of the clock. + if ((waveform & 3) && !is6581) + { + osc3 = tri_saw_pipeline & (no_pulse | pulse_output) & no_noise_or_noise_output; + tri_saw_pipeline = wave[ix]; + } + else + { + osc3 = waveform_output; + } + + // In the 6581 the top bit of the accumulator may be driven low by combined waveforms + // when the sawtooth is selected + // FIXME doesn't seem to always happen + if ((waveform & 2) && unlikely(waveform & 0xd) && is6581) + accumulator &= (waveform_output << 12) | 0x7fffff; + + write_shift_register(); + } + else + { + // Age floating DAC input. + if (likely(floating_output_ttl != 0) && unlikely(--floating_output_ttl == 0)) + { + waveBitfade(); + } + } + + // The pulse level is defined as (accumulator >> 12) >= pw ? 0xfff : 0x000. + // The expression -((accumulator >> 12) >= pw) & 0xfff yields the same + // results without any branching (and thus without any pipeline stalls). + // NB! This expression relies on that the result of a boolean expression + // is either 0 or 1, and furthermore requires two's complement integer. + // A few more cycles may be saved by storing the pulse width left shifted + // 12 bits, and dropping the and with 0xfff (this is valid since pulse is + // used as a bit mask on 12 bit values), yielding the expression + // -(accumulator >= pw24). However this only results in negligible savings. + + // The result of the pulse width compare is delayed one cycle. + // Push next pulse level into pulse level pipeline. + pulse_output = ((accumulator >> 12) >= pw) ? 0xfff : 0x000; + + return waveform_output; +} + +} // namespace reSIDfp + +#endif + +#endif diff --git a/src/engine/platform/sound/c64_fp/array.h b/src/engine/platform/sound/c64_fp/array.h new file mode 100644 index 00000000..5291938c --- /dev/null +++ b/src/engine/platform/sound/c64_fp/array.h @@ -0,0 +1,73 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright (C) 2011-2014 Leandro Nini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ARRAY_H +#define ARRAY_H + +/** + * Counter. + */ +class counter +{ +private: + unsigned int c; + +public: + counter() : c(1) {} + void increase() { ++c; } + unsigned int decrease() { return --c; } +}; + +/** + * Reference counted pointer to matrix wrapper, for use with standard containers. + */ +template +class matrix +{ +private: + T* data; + counter* count; + const unsigned int x, y; + +public: + matrix(unsigned int x, unsigned int y) : + data(new T[x * y]), + count(new counter()), + x(x), + y(y) {} + + matrix(const matrix& p) : + data(p.data), + count(p.count), + x(p.x), + y(p.y) { count->increase(); } + + ~matrix() { if (count->decrease() == 0) { delete count; delete [] data; } } + + unsigned int length() const { return x * y; } + + T* operator[](unsigned int a) { return &data[a * y]; } + + T const* operator[](unsigned int a) const { return &data[a * y]; } +}; + +typedef matrix matrix_t; + +#endif diff --git a/src/engine/platform/sound/c64_fp/resample/Resampler.h b/src/engine/platform/sound/c64_fp/resample/Resampler.h new file mode 100644 index 00000000..904f6545 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/resample/Resampler.h @@ -0,0 +1,86 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2020 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef RESAMPLER_H +#define RESAMPLER_H + +#include + +#include "../sidcxx11.h" + +#include "../siddefs-fp.h" + +namespace reSIDfp +{ + +/** + * Abstraction of a resampling process. Given enough input, produces output. + * Constructors take additional arguments that configure these objects. + */ +class Resampler +{ +protected: + inline short softClip(int x) const + { + constexpr int threshold = 28000; + if (likely(x < threshold)) + return x; + + constexpr double t = threshold / 32768.; + constexpr double a = 1. - t; + constexpr double b = 1. / a; + + double value = static_cast(x - threshold) / 32768.; + value = t + a * tanh(b * value); + return static_cast(value * 32768.); + } + + virtual int output() const = 0; + + Resampler() {} + +public: + virtual ~Resampler() {} + + /** + * Input a sample into resampler. Output "true" when resampler is ready with new sample. + * + * @param sample input sample + * @return true when a sample is ready + */ + virtual bool input(int sample) = 0; + + /** + * Output a sample from resampler. + * + * @return resampled sample + */ + short getOutput() const + { + return softClip(output()); + } + + virtual void reset() = 0; +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/resample/SincResampler.cpp b/src/engine/platform/sound/c64_fp/resample/SincResampler.cpp new file mode 100755 index 00000000..adb17f9e --- /dev/null +++ b/src/engine/platform/sound/c64_fp/resample/SincResampler.cpp @@ -0,0 +1,393 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2020 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * 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 "SincResampler.h" + +#include +#include +#include +#include +#include + +#include "../siddefs-fp.h" + +#ifdef HAVE_EMMINTRIN_H +# include +#elif defined HAVE_MMINTRIN_H +# include +#elif defined(HAVE_ARM_NEON_H) +# include +#endif + +namespace reSIDfp +{ + +typedef std::map fir_cache_t; + +/// Cache for the expensive FIR table computation results. +fir_cache_t FIR_CACHE; + +/// Maximum error acceptable in I0 is 1e-6, or ~96 dB. +const double I0E = 1e-6; + +const int BITS = 16; + +/** + * Compute the 0th order modified Bessel function of the first kind. + * This function is originally from resample-1.5/filterkit.c by J. O. Smith. + * It is used to build the Kaiser window for resampling. + * + * @param x evaluate I0 at x + * @return value of I0 at x. + */ +double I0(double x) +{ + double sum = 1.; + double u = 1.; + double n = 1.; + const double halfx = x / 2.; + + do + { + const double temp = halfx / n; + u *= temp * temp; + sum += u; + n += 1.; + } + while (u >= I0E * sum); + + return sum; +} + +/** + * Calculate convolution with sample and sinc. + * + * @param a sample buffer input + * @param b sinc buffer + * @param bLength length of the sinc buffer + * @return convolved result + */ +int convolve(const short* a, const short* b, int bLength) +{ +#ifdef HAVE_EMMINTRIN_H + int out = 0; + + const uintptr_t offset = (uintptr_t)(a) & 0x0f; + + // check for aligned accesses + if (offset == ((uintptr_t)(b) & 0x0f)) + { + if (offset) + { + const int l = (0x10 - offset)/2; + + for (int i = 0; i < l; i++) + { + out += *a++ * *b++; + } + + bLength -= offset; + } + + __m128i acc = _mm_setzero_si128(); + + const int n = bLength / 8; + + for (int i = 0; i < n; i++) + { + const __m128i tmp = _mm_madd_epi16(*(__m128i*)a, *(__m128i*)b); + acc = _mm_add_epi16(acc, tmp); + a += 8; + b += 8; + } + + __m128i vsum = _mm_add_epi32(acc, _mm_srli_si128(acc, 8)); + vsum = _mm_add_epi32(vsum, _mm_srli_si128(vsum, 4)); + out += _mm_cvtsi128_si32(vsum); + + bLength &= 7; + } +#elif defined HAVE_MMINTRIN_H + __m64 acc = _mm_setzero_si64(); + + const int n = bLength / 4; + + for (int i = 0; i < n; i++) + { + const __m64 tmp = _mm_madd_pi16(*(__m64*)a, *(__m64*)b); + acc = _mm_add_pi16(acc, tmp); + a += 4; + b += 4; + } + + int out = _mm_cvtsi64_si32(acc) + _mm_cvtsi64_si32(_mm_srli_si64(acc, 32)); + _mm_empty(); + + bLength &= 3; +#elif defined(HAVE_ARM_NEON_H) +#if (defined(__arm64__) && defined(__APPLE__)) || defined(__aarch64__) + int32x4_t acc1Low = vdupq_n_s32(0); + int32x4_t acc1High = vdupq_n_s32(0); + int32x4_t acc2Low = vdupq_n_s32(0); + int32x4_t acc2High = vdupq_n_s32(0); + + const int n = bLength / 16; + + for (int i = 0; i < n; i++) + { + int16x8_t v11 = vld1q_s16(a); + int16x8_t v12 = vld1q_s16(a + 8); + int16x8_t v21 = vld1q_s16(b); + int16x8_t v22 = vld1q_s16(b + 8); + + acc1Low = vmlal_s16(acc1Low, vget_low_s16(v11), vget_low_s16(v21)); + acc1High = vmlal_high_s16(acc1High, v11, v21); + acc2Low = vmlal_s16(acc2Low, vget_low_s16(v12), vget_low_s16(v22)); + acc2High = vmlal_high_s16(acc2High, v12, v22); + + a += 16; + b += 16; + } + + bLength &= 15; + + if (bLength >= 8) + { + int16x8_t v1 = vld1q_s16(a); + int16x8_t v2 = vld1q_s16(b); + + acc1Low = vmlal_s16(acc1Low, vget_low_s16(v1), vget_low_s16(v2)); + acc1High = vmlal_high_s16(acc1High, v1, v2); + + a += 8; + b += 8; + } + + bLength &= 7; + + if (bLength >= 4) + { + int16x4_t v1 = vld1_s16(a); + int16x4_t v2 = vld1_s16(b); + + acc1Low = vmlal_s16(acc1Low, v1, v2); + + a += 4; + b += 4; + } + + int32x4_t accSumsNeon = vaddq_s32(acc1Low, acc1High); + accSumsNeon = vaddq_s32(accSumsNeon, acc2Low); + accSumsNeon = vaddq_s32(accSumsNeon, acc2High); + + int out = vaddvq_s32(accSumsNeon); + + bLength &= 3; +#else + int32x4_t acc = vdupq_n_s32(0); + + const int n = bLength / 4; + + for (int i = 0; i < n; i++) + { + const int16x4_t h_vec = vld1_s16(a); + const int16x4_t x_vec = vld1_s16(b); + acc = vmlal_s16(acc, h_vec, x_vec); + a += 4; + b += 4; + } + + int out = vgetq_lane_s32(acc, 0) + + vgetq_lane_s32(acc, 1) + + vgetq_lane_s32(acc, 2) + + vgetq_lane_s32(acc, 3); + + bLength &= 3; +#endif +#else + int out = 0; +#endif + + for (int i = 0; i < bLength; i++) + { + out += *a++ * *b++; + } + + return (out + (1 << 14)) >> 15; +} + +int SincResampler::fir(int subcycle) +{ + // Find the first of the nearest fir tables close to the phase + int firTableFirst = (subcycle * firRES >> 10); + const int firTableOffset = (subcycle * firRES) & 0x3ff; + + // Find firN most recent samples, plus one extra in case the FIR wraps. + int sampleStart = sampleIndex - firN + RINGSIZE - 1; + + const int v1 = convolve(sample + sampleStart, (*firTable)[firTableFirst], firN); + + // Use next FIR table, wrap around to first FIR table using + // previous sample. + if (unlikely(++firTableFirst == firRES)) + { + firTableFirst = 0; + ++sampleStart; + } + + const int v2 = convolve(sample + sampleStart, (*firTable)[firTableFirst], firN); + + // Linear interpolation between the sinc tables yields good + // approximation for the exact value. + return v1 + (firTableOffset * (v2 - v1) >> 10); +} + +SincResampler::SincResampler(double clockFrequency, double samplingFrequency, double highestAccurateFrequency) : + sampleIndex(0), + cyclesPerSample(static_cast(clockFrequency / samplingFrequency * 1024.)), + sampleOffset(0), + outputValue(0) +{ + // 16 bits -> -96dB stopband attenuation. + const double A = -20. * log10(1.0 / (1 << BITS)); + // A fraction of the bandwidth is allocated to the transition band, which we double + // because we design the filter to transition halfway at nyquist. + const double dw = (1. - 2.*highestAccurateFrequency / samplingFrequency) * M_PI * 2.; + + // For calculation of beta and N see the reference for the kaiserord + // function in the MATLAB Signal Processing Toolbox: + // http://www.mathworks.com/help/signal/ref/kaiserord.html + const double beta = 0.1102 * (A - 8.7); + const double I0beta = I0(beta); + const double cyclesPerSampleD = clockFrequency / samplingFrequency; + + { + // The filter order will maximally be 124 with the current constraints. + // N >= (96.33 - 7.95)/(2 * pi * 2.285 * (maxfreq - passbandfreq) >= 123 + // The filter order is equal to the number of zero crossings, i.e. + // it should be an even number (sinc is symmetric with respect to x = 0). + int N = static_cast((A - 7.95) / (2.285 * dw) + 0.5); + N += N & 1; + + // The filter length is equal to the filter order + 1. + // The filter length must be an odd number (sinc is symmetric with respect to + // x = 0). + firN = static_cast(N * cyclesPerSampleD) + 1; + firN |= 1; + + // Check whether the sample ring buffer would overflow. + assert(firN < RINGSIZE); + + // Error is bounded by err < 1.234 / L^2, so L = sqrt(1.234 / (2^-16)) = sqrt(1.234 * 2^16). + firRES = static_cast(ceil(sqrt(1.234 * (1 << BITS)) / cyclesPerSampleD)); + + // firN*firRES represent the total resolution of the sinc sampling. JOS + // recommends a length of 2^BITS, but we don't quite use that good a filter. + // The filter test program indicates that the filter performs well, though. + } + + // Create the map key + std::ostringstream o; + o << firN << "," << firRES << "," << cyclesPerSampleD; + const std::string firKey = o.str(); + fir_cache_t::iterator lb = FIR_CACHE.lower_bound(firKey); + + // The FIR computation is expensive and we set sampling parameters often, but + // from a very small set of choices. Thus, caching is used to speed initialization. + if (lb != FIR_CACHE.end() && !(FIR_CACHE.key_comp()(firKey, lb->first))) + { + firTable = &(lb->second); + } + else + { + // Allocate memory for FIR tables. + matrix_t tempTable(firRES, firN); +#ifdef HAVE_CXX11 + firTable = &(FIR_CACHE.emplace_hint(lb, fir_cache_t::value_type(firKey, tempTable))->second); +#else + firTable = &(FIR_CACHE.insert(lb, fir_cache_t::value_type(firKey, tempTable))->second); +#endif + + // The cutoff frequency is midway through the transition band, in effect the same as nyquist. + const double wc = M_PI; + + // Calculate the sinc tables. + const double scale = 32768.0 * wc / cyclesPerSampleD / M_PI; + + // we're not interested in the fractional part + // so use int division before converting to double + const int tmp = firN / 2; + const double firN_2 = static_cast(tmp); + + for (int i = 0; i < firRES; i++) + { + const double jPhase = (double) i / firRES + firN_2; + + for (int j = 0; j < firN; j++) + { + const double x = j - jPhase; + + const double xt = x / firN_2; + const double kaiserXt = fabs(xt) < 1. ? I0(beta * sqrt(1. - xt * xt)) / I0beta : 0.; + + const double wt = wc * x / cyclesPerSampleD; + const double sincWt = fabs(wt) >= 1e-8 ? sin(wt) / wt : 1.; + + (*firTable)[i][j] = static_cast(scale * sincWt * kaiserXt); + } + } + } +} + +bool SincResampler::input(int input) +{ + bool ready = false; + + /* + * Clip the input as it may overflow the 16 bit range. + * + * Approximate measured input ranges: + * 6581: [-24262,+25080] (Kawasaki_Synthesizer_Demo) + * 8580: [-21514,+35232] (64_Forever, Drum_Fool) + */ + sample[sampleIndex] = sample[sampleIndex + RINGSIZE] = softClip(input); + sampleIndex = (sampleIndex + 1) & (RINGSIZE - 1); + + if (sampleOffset < 1024) + { + outputValue = fir(sampleOffset); + ready = true; + sampleOffset += cyclesPerSample; + } + + sampleOffset -= 1024; + + return ready; +} + +void SincResampler::reset() +{ + memset(sample, 0, sizeof(sample)); + sampleOffset = 0; +} + +} // namespace reSIDfp diff --git a/src/engine/platform/sound/c64_fp/resample/SincResampler.h b/src/engine/platform/sound/c64_fp/resample/SincResampler.h new file mode 100644 index 00000000..7502d96f --- /dev/null +++ b/src/engine/platform/sound/c64_fp/resample/SincResampler.h @@ -0,0 +1,114 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2013 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * Copyright 2004 Dag Lem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SINCRESAMPLER_H +#define SINCRESAMPLER_H + +#include "Resampler.h" + +#include +#include + +#include "../array.h" + +#include "../sidcxx11.h" + +namespace reSIDfp +{ + +/** + * This is the theoretically correct (and computationally intensive) audio sample generation. + * The samples are generated by resampling to the specified sampling frequency. + * The work rate is inversely proportional to the percentage of the bandwidth + * allocated to the filter transition band. + * + * This implementation is based on the paper "A Flexible Sampling-Rate Conversion Method", + * by J. O. Smith and P. Gosset, or rather on the expanded tutorial on the + * [Digital Audio Resampling Home Page](http://www-ccrma.stanford.edu/~jos/resample/). + * + * By building shifted FIR tables with samples according to the sampling frequency, + * this implementation dramatically reduces the computational effort in the + * filter convolutions, without any loss of accuracy. + * The filter convolutions are also vectorizable on current hardware. + */ +class SincResampler final : public Resampler +{ +private: + /// Size of the ring buffer, must be a power of 2 + static const int RINGSIZE = 2048; + +private: + /// Table of the fir filter coefficients + matrix_t* firTable; + + int sampleIndex; + + /// Filter resolution + int firRES; + + /// Filter length + int firN; + + const int cyclesPerSample; + + int sampleOffset; + + int outputValue; + + short sample[RINGSIZE * 2]; + +private: + int fir(int subcycle); + +public: + /** + * Use a clock freqency of 985248Hz for PAL C64, 1022730Hz for NTSC C64. + * The default end of passband frequency is pass_freq = 0.9*sample_freq/2 + * for sample frequencies up to ~ 44.1kHz, and 20kHz for higher sample frequencies. + * + * For resampling, the ratio between the clock frequency and the sample frequency + * is limited as follows: 125*clock_freq/sample_freq < 16384 + * E.g. provided a clock frequency of ~ 1MHz, the sample frequency + * can not be set lower than ~ 8kHz. + * A lower sample frequency would make the resampling code overfill its 16k sample ring buffer. + * + * The end of passband frequency is also limited: pass_freq <= 0.9*sample_freq/2 + * + * E.g. for a 44.1kHz sampling rate the end of passband frequency is limited + * to slightly below 20kHz. This constraint ensures that the FIR table is not overfilled. + * + * @param clockFrequency System clock frequency at Hz + * @param samplingFrequency Desired output sampling rate + * @param highestAccurateFrequency + */ + SincResampler(double clockFrequency, double samplingFrequency, double highestAccurateFrequency); + + bool input(int input) override; + + int output() const override { return outputValue; } + + void reset() override; +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/resample/TwoPassSincResampler.h b/src/engine/platform/sound/c64_fp/resample/TwoPassSincResampler.h new file mode 100644 index 00000000..81659193 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/resample/TwoPassSincResampler.h @@ -0,0 +1,83 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2015 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TWOPASSSINCRESAMPLER_H +#define TWOPASSSINCRESAMPLER_H + +#include + +#include + +#include "Resampler.h" +#include "SincResampler.h" + +#include "../sidcxx11.h" + +namespace reSIDfp +{ + +/** + * Compose a more efficient SINC from chaining two other SINCs. + */ +class TwoPassSincResampler final : public Resampler +{ +private: + std::unique_ptr const s1; + std::unique_ptr const s2; + +private: + TwoPassSincResampler(double clockFrequency, double samplingFrequency, double highestAccurateFrequency, double intermediateFrequency) : + s1(new SincResampler(clockFrequency, intermediateFrequency, highestAccurateFrequency)), + s2(new SincResampler(intermediateFrequency, samplingFrequency, highestAccurateFrequency)) + {} + +public: + // Named constructor + static TwoPassSincResampler* create(double clockFrequency, double samplingFrequency, double highestAccurateFrequency) + { + // Calculation according to Laurent Ganier. It evaluates to about 120 kHz at typical settings. + // Some testing around the chosen value seems to confirm that this does work. + double const intermediateFrequency = 2. * highestAccurateFrequency + + sqrt(2. * highestAccurateFrequency * clockFrequency + * (samplingFrequency - 2. * highestAccurateFrequency) / samplingFrequency); + return new TwoPassSincResampler(clockFrequency, samplingFrequency, highestAccurateFrequency, intermediateFrequency); + } + + bool input(int sample) override + { + return s1->input(sample) && s2->input(s1->output()); + } + + int output() const override + { + return s2->output(); + } + + void reset() override + { + s1->reset(); + s2->reset(); + } +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/resample/ZeroOrderResampler.h b/src/engine/platform/sound/c64_fp/resample/ZeroOrderResampler.h new file mode 100644 index 00000000..2bc80cde --- /dev/null +++ b/src/engine/platform/sound/c64_fp/resample/ZeroOrderResampler.h @@ -0,0 +1,88 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2011-2013 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef ZEROORDER_RESAMPLER_H +#define ZEROORDER_RESAMPLER_H + +#include "Resampler.h" + +#include "../sidcxx11.h" + +namespace reSIDfp +{ + +/** + * Return sample with linear interpolation. + * + * @author Antti Lankila + */ +class ZeroOrderResampler final : public Resampler +{ + +private: + /// Last sample + int cachedSample; + + /// Number of cycles per sample + const int cyclesPerSample; + + int sampleOffset; + + /// Calculated sample + int outputValue; + +public: + ZeroOrderResampler(double clockFrequency, double samplingFrequency) : + cachedSample(0), + cyclesPerSample(static_cast(clockFrequency / samplingFrequency * 1024.)), + sampleOffset(0), + outputValue(0) {} + + bool input(int sample) override + { + bool ready = false; + + if (sampleOffset < 1024) + { + outputValue = cachedSample + (sampleOffset * (sample - cachedSample) >> 10); + ready = true; + sampleOffset += cyclesPerSample; + } + + sampleOffset -= 1024; + + cachedSample = sample; + + return ready; + } + + int output() const override { return outputValue; } + + void reset() override + { + sampleOffset = 0; + cachedSample = 0; + } +}; + +} // namespace reSIDfp + +#endif diff --git a/src/engine/platform/sound/c64_fp/resample/test.cpp b/src/engine/platform/sound/c64_fp/resample/test.cpp new file mode 100644 index 00000000..5e5026ff --- /dev/null +++ b/src/engine/platform/sound/c64_fp/resample/test.cpp @@ -0,0 +1,87 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2012-2013 Leandro Nini + * Copyright 2007-2010 Antti Lankila + * + * 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 +#include +#include +#include +#include +#include + +#include "siddefs-fp.h" + +#include "Resampler.h" +#include "TwoPassSincResampler.h" + +/** + * Simple sin waveform in, power output measurement function. + * It would be far better to use FFT. + */ +int main(int argc, const char* argv[]) +{ + const double RATE = 985248.4; + const int RINGSIZE = 2048; + + std::auto_ptr r(reSIDfp::TwoPassSincResampler::create(RATE, 48000.0, 20000.0)); + + std::map results; + clock_t start = clock(); + + for (double freq = 1000.; freq < RATE / 2.; freq *= 1.01) + { + /* prefill resampler buffer */ + int k = 0; + double omega = 2 * M_PI * freq / RATE; + + for (int j = 0; j < RINGSIZE; j ++) + { + int signal = static_cast(32768.0 * sin(k++ * omega) * sqrt(2)); + r->input(signal); + } + + int n = 0; + float pwr = 0; + + /* Now, during measurement stage, put 100 cycles of waveform through filter. */ + for (int j = 0; j < 100000; j ++) + { + int signal = static_cast(32768.0 * sin(k++ * omega) * sqrt(2)); + + if (r->input(signal)) + { + float out = r->output(); + pwr += out * out; + n += 1; + } + } + + results.insert(std::make_pair(freq, 10 * log10(pwr / n))); + } + + clock_t end = clock(); + + for (std::map::iterator it = results.begin(); it != results.end(); ++it) + { + std::cout << std::fixed << std::setprecision(0) << std::setw(6) << (*it).first << " Hz " << (*it).second << " dB" << std::endl; + } + + std::cout << "Filtering time " << (end - start) * 1000. / CLOCKS_PER_SEC << " ms" << std::endl; +} diff --git a/src/engine/platform/sound/c64_fp/sidcxx11.h b/src/engine/platform/sound/c64_fp/sidcxx11.h new file mode 100644 index 00000000..18eadf4a --- /dev/null +++ b/src/engine/platform/sound/c64_fp/sidcxx11.h @@ -0,0 +1,29 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2014-2015 Leandro Nini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SIDCXX11_H +#define SIDCXX11_H + +#define DEFAULT = default +#define DELETE = delete + +#define HAVE_CXX11 + +#endif diff --git a/src/engine/platform/sound/c64_fp/sidcxx14.h b/src/engine/platform/sound/c64_fp/sidcxx14.h new file mode 100644 index 00000000..5078a0b1 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/sidcxx14.h @@ -0,0 +1,29 @@ +/* + * This file is part of libsidplayfp, a SID player engine. + * + * Copyright 2014-2015 Leandro Nini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SIDCXX14_H +#define SIDCXX14_H + +#include "sidcxx11.h" + +#define MAKE_UNIQUE(type, ...) std::make_unique(__VA_ARGS__) +#define HAVE_CXX14 + +#endif diff --git a/src/engine/platform/sound/c64_fp/siddefs-fp.h b/src/engine/platform/sound/c64_fp/siddefs-fp.h new file mode 100644 index 00000000..43900862 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/siddefs-fp.h @@ -0,0 +1,62 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 1999 Dag Lem +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// --------------------------------------------------------------------------- + +#ifndef SIDDEFS_FP_H +#define SIDDEFS_FP_H + +// Compilation configuration. +#define RESID_BRANCH_HINTS 0 + +// Compiler specifics. +#define HAVE_BUILTIN_EXPECT 0 + +#ifndef M_PI +# define M_PI 3.14159265358979323846 +#endif + +// Branch prediction macros, lifted off the Linux kernel. +#if RESID_BRANCH_HINTS && HAVE_BUILTIN_EXPECT +# define likely(x) __builtin_expect(!!(x), 1) +# define unlikely(x) __builtin_expect(!!(x), 0) +#else +# define likely(x) (x) +# define unlikely(x) (x) +#endif + +namespace reSIDfp { + +typedef enum { MOS6581=1, MOS8580 } ChipModel; + +typedef enum { DECIMATE=1, RESAMPLE } SamplingMethod; +} + +extern "C" +{ +#ifndef __VERSION_CC__ +extern const char* residfp_version_string; +#else +const char* residfp_version_string = "furnace"; +#endif +} + +// Inlining on/off. +#define RESID_INLINING 1 +#define RESID_INLINE inline + +#endif // SIDDEFS_FP_H diff --git a/src/engine/platform/sound/c64_fp/version.cc b/src/engine/platform/sound/c64_fp/version.cc new file mode 100644 index 00000000..3ed8b449 --- /dev/null +++ b/src/engine/platform/sound/c64_fp/version.cc @@ -0,0 +1,21 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2004 Dag Lem +// +// 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. +// --------------------------------------------------------------------------- + +#define __VERSION_CC__ +#include "siddefs-fp.h" diff --git a/src/engine/platform/sound/gb/apu.c b/src/engine/platform/sound/gb/apu.c index 4c473997..8836ddd2 100644 --- a/src/engine/platform/sound/gb/apu.c +++ b/src/engine/platform/sound/gb/apu.c @@ -1180,11 +1180,11 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if ((value & 0x80)) { /* DMG bug: wave RAM gets corrupted if the channel is retriggerred 1 cycle before the APU reads from it. */ - /*if (!CGB && + if (!CGB && gb->apu.is_active[GB_WAVE] && gb->apu.wave_channel.sample_countdown == 0 && gb->apu.wave_channel.enable) { - unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF;*/ + unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF; /* This glitch varies between models and even specific instances: DMG-B: Most of them behave as emulated. A few behave differently. @@ -1193,7 +1193,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) Additionally, I believe DMGs, including those we behave differently than emulated, are all deterministic. */ - /*if (offset < 4) { + if (offset < 4) { gb->io_registers[GB_IO_WAV_START] = gb->io_registers[GB_IO_WAV_START + offset]; gb->apu.wave_channel.wave_form[0] = gb->apu.wave_channel.wave_form[offset / 2]; gb->apu.wave_channel.wave_form[1] = gb->apu.wave_channel.wave_form[offset / 2 + 1]; @@ -1206,7 +1206,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.wave_channel.wave_form + (offset & ~3) * 2, 8); } - }*/ + } if (!gb->apu.is_active[GB_WAVE]) { gb->apu.is_active[GB_WAVE] = true; update_sample(gb, GB_WAVE, diff --git a/src/engine/platform/sound/gb/gb.h b/src/engine/platform/sound/gb/gb.h index ac817395..ca365085 100644 --- a/src/engine/platform/sound/gb/gb.h +++ b/src/engine/platform/sound/gb/gb.h @@ -16,7 +16,7 @@ extern "C" { #define GB_STRUCT_VERSION 13 -#define CGB 0 +#define CGB (gb->model&GB_MODEL_CGB_FAMILY) #define GB_MODEL_FAMILY_MASK 0xF00 #define GB_MODEL_DMG_FAMILY 0x000 diff --git a/src/engine/platform/sound/k005289/k005289.cpp b/src/engine/platform/sound/k005289/k005289.cpp index 8b21245a..c9bdf83a 100644 --- a/src/engine/platform/sound/k005289/k005289.cpp +++ b/src/engine/platform/sound/k005289/k005289.cpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: cam900 diff --git a/src/engine/platform/sound/k005289/k005289.hpp b/src/engine/platform/sound/k005289/k005289.hpp index d042e1d1..575a98b8 100644 --- a/src/engine/platform/sound/k005289/k005289.hpp +++ b/src/engine/platform/sound/k005289/k005289.hpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: cam900 diff --git a/src/engine/platform/sound/lynx/Mikey.cpp b/src/engine/platform/sound/lynx/Mikey.cpp index 270e21bd..791336c1 100644 --- a/src/engine/platform/sound/lynx/Mikey.cpp +++ b/src/engine/platform/sound/lynx/Mikey.cpp @@ -26,37 +26,11 @@ #include #include -namespace Lynx -{ +#if defined( _MSC_VER ) -namespace -{ +#include -static constexpr int64_t CNT_MAX = std::numeric_limits::max() & ~15; - -#if defined ( __cpp_lib_bitops ) - -#define popcnt(X) std::popcount(X) - -#elif defined( _MSC_VER ) - -# include - -uint32_t popcnt( uint32_t x ) -{ - return __popcnt( x ); -} - -#elif defined( __GNUC__ ) - -uint32_t popcnt( uint32_t x ) -{ - return __builtin_popcount( x ); -} - -#else - -uint32_t popcnt( uint32_t x ) +static uint32_t popcnt_generic( uint32_t x ) { int v = 0; while ( x != 0 ) @@ -67,8 +41,61 @@ uint32_t popcnt( uint32_t x ) return v; } +#if defined( _M_IX86 ) || defined( _M_X64 ) + +static uint32_t popcnt_intrinsic( uint32_t x ) +{ + return __popcnt( x ); +} + +static uint32_t( *popcnt )( uint32_t ); + +//detecting popcnt availability on msvc intel +static void selectPOPCNT() +{ + int info[4]; + __cpuid( info, 1 ); + if ( ( info[2] & ( (int)1 << 23 ) ) != 0 ) + { + popcnt = &popcnt_intrinsic; + } + else + { + popcnt = &popcnt_generic; + } +} + +#else //defined( _M_IX86 ) || defined( _M_X64 ) + +//MSVC non INTEL should use generic implementation +inline void selectPOPCNT() +{ +} + +#define popcnt popcnt_generic + #endif +#else //defined( _MSC_VER ) + +//non MVSC should use builtin implementation + +inline void selectPOPCNT() +{ +} + +#define popcnt __builtin_popcount + +#endif + +namespace Lynx +{ + +namespace +{ + +static constexpr int64_t CNT_MAX = std::numeric_limits::max() & ~15; + int32_t clamp( int32_t v, int32_t lo, int32_t hi ) { return v < lo ? lo : ( v > hi ? hi : v ); @@ -513,6 +540,7 @@ private: Mikey::Mikey( uint32_t sampleRate ) : mMikey{ std::make_unique() }, mQueue{ std::make_unique() }, mTick{}, mNextTick{}, mSampleRate{ sampleRate }, mSamplesRemainder{}, mTicksPerSample{ 16000000 / mSampleRate, 16000000 % mSampleRate } { + selectPOPCNT(); enqueueSampling(); } diff --git a/src/engine/platform/sound/n163/n163.cpp b/src/engine/platform/sound/n163/n163.cpp index 5d9deab1..3fd30bc4 100644 --- a/src/engine/platform/sound/n163/n163.cpp +++ b/src/engine/platform/sound/n163/n163.cpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: cam900, tildearrow diff --git a/src/engine/platform/sound/n163/n163.hpp b/src/engine/platform/sound/n163/n163.hpp index 800d1ea1..317a33b4 100644 --- a/src/engine/platform/sound/n163/n163.hpp +++ b/src/engine/platform/sound/n163/n163.hpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: cam900, tildearrow diff --git a/src/engine/platform/sound/namco.cpp b/src/engine/platform/sound/namco.cpp index 82141301..811c34ab 100644 --- a/src/engine/platform/sound/namco.cpp +++ b/src/engine/platform/sound/namco.cpp @@ -172,10 +172,11 @@ void namco_audio_device::build_decoded_waveform(uint8_t *rgnbase) /* generate sound by oversampling */ -uint32_t namco_audio_device::namco_update_one(short* buffer, int size, const int16_t *wave, uint32_t counter, uint32_t freq) +uint32_t namco_audio_device::namco_update_one(short* buffer, int size, const int16_t *wave, uint32_t counter, uint32_t freq, int16_t& last_out) { for (int sampindex = 0; sampindex < size; sampindex++) { + last_out=wave[WAVEFORM_POSITION(counter)]; buffer[sampindex]+=wave[WAVEFORM_POSITION(counter)]; counter += freq; } @@ -700,7 +701,7 @@ void namco_audio_device::sound_stream_update(short** outputs, int len) const int16_t *lw = &m_waveform[lv][voice->waveform_select * 32]; /* generate sound into the buffer */ - c = namco_update_one(lmix, len, lw, voice->counter, voice->frequency); + c = namco_update_one(lmix, len, lw, voice->counter, voice->frequency, voice->last_out); } /* only update if we have non-zero right volume */ @@ -709,7 +710,7 @@ void namco_audio_device::sound_stream_update(short** outputs, int len) const int16_t *rw = &m_waveform[rv][voice->waveform_select * 32]; /* generate sound into the buffer */ - c = namco_update_one(rmix, len, rw, voice->counter, voice->frequency); + c = namco_update_one(rmix, len, rw, voice->counter, voice->frequency, voice->last_out); } /* update the counter for this voice */ @@ -789,7 +790,7 @@ void namco_audio_device::sound_stream_update(short** outputs, int len) const int16_t *w = &m_waveform[v][voice->waveform_select * 32]; /* generate sound into buffer and update the counter for this voice */ - voice->counter = namco_update_one(buffer, len, w, voice->counter, voice->frequency); + voice->counter = namco_update_one(buffer, len, w, voice->counter, voice->frequency, voice->last_out); } } } diff --git a/src/engine/platform/sound/namco.h b/src/engine/platform/sound/namco.h index 507eaed2..d3844abc 100644 --- a/src/engine/platform/sound/namco.h +++ b/src/engine/platform/sound/namco.h @@ -31,6 +31,7 @@ public: uint32_t noise_counter; int32_t noise_hold; int32_t waveform_select; + int16_t last_out; }; namco_audio_device(uint32_t clock); @@ -43,7 +44,7 @@ public: void build_decoded_waveform( uint8_t *rgnbase ); void update_namco_waveform(int offset, uint8_t data); - uint32_t namco_update_one(short* buffer, int size, const int16_t *wave, uint32_t counter, uint32_t freq); + uint32_t namco_update_one(short* buffer, int size, const int16_t *wave, uint32_t counter, uint32_t freq, int16_t& last_out); /* waveform region */ uint8_t* m_wave_ptr; diff --git a/src/engine/platform/sound/oki/msm6295.cpp b/src/engine/platform/sound/oki/msm6295.cpp index 1b7fe568..e7f39d27 100644 --- a/src/engine/platform/sound/oki/msm6295.cpp +++ b/src/engine/platform/sound/oki/msm6295.cpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: tildearrow diff --git a/src/engine/platform/sound/oki/msm6295.hpp b/src/engine/platform/sound/oki/msm6295.hpp index 5203f434..ca8b81d4 100644 --- a/src/engine/platform/sound/oki/msm6295.hpp +++ b/src/engine/platform/sound/oki/msm6295.hpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: tildearrow diff --git a/src/engine/platform/sound/oki/util.hpp b/src/engine/platform/sound/oki/util.hpp index 731772ac..b9c50d7b 100644 --- a/src/engine/platform/sound/oki/util.hpp +++ b/src/engine/platform/sound/oki/util.hpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: tildearrow diff --git a/src/engine/platform/sound/oki/vox.hpp b/src/engine/platform/sound/oki/vox.hpp index c085c0b7..23fbfd78 100644 --- a/src/engine/platform/sound/oki/vox.hpp +++ b/src/engine/platform/sound/oki/vox.hpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: tildearrow diff --git a/src/engine/platform/sound/scc/scc.cpp b/src/engine/platform/sound/scc/scc.cpp index 3e230447..e2ebcf20 100644 --- a/src/engine/platform/sound/scc/scc.cpp +++ b/src/engine/platform/sound/scc/scc.cpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst diff --git a/src/engine/platform/sound/scc/scc.hpp b/src/engine/platform/sound/scc/scc.hpp index db99ec87..24a365cc 100644 --- a/src/engine/platform/sound/scc/scc.hpp +++ b/src/engine/platform/sound/scc/scc.hpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst diff --git a/src/engine/platform/sound/sn76496.cpp b/src/engine/platform/sound/sn76496.cpp index 351c94d8..e76b403f 100644 --- a/src/engine/platform/sound/sn76496.cpp +++ b/src/engine/platform/sound/sn76496.cpp @@ -128,7 +128,7 @@ 10/12/2019: Michael Zapf * READY line handling by own emu_timer, not depending on sound_stream_update - additional modifications by tildearrow for furnace + additional modifications by tildearrow, cam900 for furnace TODO: * Implement the TMS9919 - any difference to sn94624? * Implement the T6W28; has registers in a weird order, needs writes @@ -150,18 +150,20 @@ sn76496_base_device::sn76496_base_device( int feedbackmask, - int noise_start, + int noise_start, int noisetap1, int noisetap2, bool negate, + bool stereo, int clockdivider, bool ncr, bool sega) : m_feedback_mask(feedbackmask) - , m_noise_start(noise_start) + , m_noise_start(noise_start) , m_whitenoise_tap1(noisetap1) , m_whitenoise_tap2(noisetap2) - , m_negate(negate) + , m_negate(negate) + , m_stereo(stereo) , m_clock_divider(clockdivider) , m_ncr_style_psg(ncr) , m_sega_style_psg(sega) @@ -169,10 +171,54 @@ sn76496_base_device::sn76496_base_device( } sn76496_device::sn76496_device() - : sn76496_base_device(0x8000, 0x8000, 0x01, 0x08, false, 1, false, false) + : sn76496_base_device(0x10000, 0x04, 0x08, false, false, 1/*8*/, false, true) { } +y2404_device::y2404_device() + : sn76496_base_device(0x10000, 0x04, 0x08, false, false, 1/*8*/, false, true) +{ +} + +sn76489_device::sn76489_device() + : sn76496_base_device(0x4000, 0x01, 0x02, true, false, 1/*8*/, false, true) +{ +} + +sn76489a_device::sn76489a_device() + : sn76496_base_device(0x10000, 0x04, 0x08, false, false, 1/*8*/, false, true) +{ +} + +sn76494_device::sn76494_device() + : sn76496_base_device(0x10000, 0x04, 0x08, false, false, 1, false, true) +{ +} + +sn94624_device::sn94624_device() + : sn76496_base_device(0x4000, 0x01, 0x02, true, false, 1, false, true) +{ +} + +ncr8496_device::ncr8496_device() + : sn76496_base_device(0x8000, 0x02, 0x20, true, false, 1/*8*/, true, true) +{ +} + +pssj3_device::pssj3_device() + : sn76496_base_device(0x8000, 0x02, 0x20, false, false, 1/*8*/, true, true) +{ +} + +gamegear_device::gamegear_device() + : sn76496_base_device(0x8000, 0x01, 0x08, true, true, 1/*8*/, false, false) +{ +} + +segapsg_device::segapsg_device() + : sn76496_base_device(0x8000, 0x01, 0x08, true, false, 1/*8*/, false, false) +{ +} void sn76496_base_device::device_start() { @@ -199,6 +245,7 @@ void sn76496_base_device::device_start() m_RNG = m_feedback_mask; m_output[3] = m_RNG & 1; + m_stereo_mask = 0xFF; // all channels enabled m_current_clock = m_clock_divider-1; // set gain @@ -225,6 +272,11 @@ void sn76496_base_device::device_start() m_ready_state = true; } +void sn76496_base_device::stereo_w(u8 data) +{ + if (m_stereo) m_stereo_mask = data; +} + void sn76496_base_device::write(u8 data) { int n, r, c; @@ -285,7 +337,7 @@ inline bool sn76496_base_device::in_noise_mode() return ((m_register[6] & 4)!=0); } -void sn76496_base_device::sound_stream_update(short* outputs, int outLen) +void sn76496_base_device::sound_stream_update(short** outputs, int outLen) { int i; @@ -336,13 +388,30 @@ void sn76496_base_device::sound_stream_update(short* outputs, int outLen) } } + if (m_stereo) + { + out = ((((m_stereo_mask & 0x10)!=0) && (m_output[0]!=0))? m_volume[0] : 0) + + ((((m_stereo_mask & 0x20)!=0) && (m_output[1]!=0))? m_volume[1] : 0) + + ((((m_stereo_mask & 0x40)!=0) && (m_output[2]!=0))? m_volume[2] : 0) + + ((((m_stereo_mask & 0x80)!=0) && (m_output[3]!=0))? m_volume[3] : 0); + + out2= ((((m_stereo_mask & 0x1)!=0) && (m_output[0]!=0))? m_volume[0] : 0) + + ((((m_stereo_mask & 0x2)!=0) && (m_output[1]!=0))? m_volume[1] : 0) + + ((((m_stereo_mask & 0x4)!=0) && (m_output[2]!=0))? m_volume[2] : 0) + + ((((m_stereo_mask & 0x8)!=0) && (m_output[3]!=0))? m_volume[3] : 0); + } + else + { out= ((m_output[0]!=0)? m_volume[0]:0) +((m_output[1]!=0)? m_volume[1]:0) +((m_output[2]!=0)? m_volume[2]:0) +((m_output[3]!=0)? m_volume[3]:0); + } if (m_negate) { out = -out; out2 = -out2; } - outputs[sampindex]=out; + outputs[0][sampindex]=out; + if (m_stereo && (outputs[1] != nullptr)) + outputs[1][sampindex]=out2; } } diff --git a/src/engine/platform/sound/sn76496.h b/src/engine/platform/sound/sn76496.h index 4c24e938..7a8a5c62 100644 --- a/src/engine/platform/sound/sn76496.h +++ b/src/engine/platform/sound/sn76496.h @@ -1,7 +1,7 @@ // license:BSD-3-Clause // copyright-holders:Nicola Salmoria -// additional modifications by tildearrow for furnace +// additional modifications by tildearrow, cam900 for furnace #ifndef MAME_SOUND_SN76496_H #define MAME_SOUND_SN76496_H @@ -14,34 +14,57 @@ class sn76496_base_device { public: void stereo_w(u8 data); void write(u8 data); - void device_start(); - void sound_stream_update(short* outputs, int outLen); - inline int32_t get_channel_output(int ch) { - return ((m_output[ch]!=0)?m_volume[ch]:0); - } + void device_start(); + void sound_stream_update(short** outputs, int outLen); + inline int32_t get_channel_output(int ch) { + return ((m_output[ch]!=0)?m_volume[ch]:0); + } //DECLARE_READ_LINE_MEMBER( ready_r ) { return m_ready_state ? 1 : 0; } sn76496_base_device( int feedbackmask, - int noise_start, + int noise_start, int noisetap1, int noisetap2, bool negate, + bool stereo, int clockdivider, bool ncr, bool sega); + sn76496_base_device( + int feedbackmask, + int noisetap1, + int noisetap2, + bool negate, + bool stereo, + int clockdivider, + bool ncr, + bool sega) + : sn76496_base_device( + feedbackmask, + feedbackmask, + noisetap1, + noisetap2, + negate, + stereo, + clockdivider, + ncr, + sega) + {} + private: inline bool in_noise_mode(); bool m_ready_state; - const int32_t m_feedback_mask; // mask for feedback - const int32_t m_noise_start; // noise start value - const int32_t m_whitenoise_tap1; // mask for white noise tap 1 (higher one, usually bit 14) - const int32_t m_whitenoise_tap2; // mask for white noise tap 2 (lower one, usually bit 13) - bool m_negate; // output negate flag - const int32_t m_clock_divider; // clock divider + const int32_t m_feedback_mask; // mask for feedback + const int32_t m_noise_start; // noise start value + const int32_t m_whitenoise_tap1; // mask for white noise tap 1 (higher one, usually bit 14) + const int32_t m_whitenoise_tap2; // mask for white noise tap 2 (lower one, usually bit 13) + bool m_negate; // output negate flag + const bool m_stereo; // whether we're dealing with stereo or not + const int32_t m_clock_divider; // clock divider const bool m_ncr_style_psg; // flag to ignore writes to regs 1,3,5,6,7 with bit 7 low const bool m_sega_style_psg; // flag to make frequency zero acts as if it is one more than max (0x3ff+1) or if it acts like 0; the initial register is pointing to 0x3 instead of 0x0; the volume reg is preloaded with 0xF instead of 0x0 @@ -51,6 +74,7 @@ private: int32_t m_volume[4]; // db volume of voice 0-2 and noise uint32_t m_RNG; // noise generator LFSR int32_t m_current_clock; + int32_t m_stereo_mask; // the stereo output mask int32_t m_period[4]; // Length of 1/2 of waveform int32_t m_count[4]; // Position within the waveform int32_t m_output[4]; // 1-bit output of each channel, pre-volume @@ -63,4 +87,67 @@ public: sn76496_device(); }; +// Y2404 not verified yet. todo: verify; (don't be fooled by the Y, it's a TI chip, not Yamaha) +class y2404_device : public sn76496_base_device +{ +public: + y2404_device(); +}; + +// SN76489 not verified yet. todo: verify; +class sn76489_device : public sn76496_base_device +{ +public: + sn76489_device(); +}; + +// SN76489A: whitenoise verified, phase verified, periodic verified (by plgdavid) +class sn76489a_device : public sn76496_base_device +{ +public: + sn76489a_device(); +}; + +// SN76494 not verified, (according to datasheet: same as sn76489a but without the /8 divider) +class sn76494_device : public sn76496_base_device +{ +public: + sn76494_device(); +}; + +// SN94624 whitenoise verified, phase verified, period verified; verified by PlgDavid +class sn94624_device : public sn76496_base_device +{ +public: + sn94624_device(); +}; + +// NCR8496 whitenoise verified, phase verified; verified by ValleyBell & NewRisingSun +class ncr8496_device : public sn76496_base_device +{ +public: + ncr8496_device(); +}; + +// PSSJ-3 whitenoise verified, phase verified; verified by ValleyBell & NewRisingSun +class pssj3_device : public sn76496_base_device +{ +public: + pssj3_device(); +}; + +// Verified by Justin Kerk +class gamegear_device : public sn76496_base_device +{ +public: + gamegear_device(); +}; + +// todo: verify; from smspower wiki, assumed to have same invert as gamegear +class segapsg_device : public sn76496_base_device +{ +public: + segapsg_device(); +}; + #endif // MAME_SOUND_SN76496_H diff --git a/src/engine/platform/sound/su.cpp b/src/engine/platform/sound/su.cpp index b7fa731a..a8720545 100644 --- a/src/engine/platform/sound/su.cpp +++ b/src/engine/platform/sound/su.cpp @@ -5,9 +5,22 @@ #define minval(a,b) (((a)<(b))?(a):(b)) #define maxval(a,b) (((a)>(b))?(a):(b)) +#define FILVOL chan[4].special1C +#define ILCTRL chan[4].special1D +#define ILSIZE chan[5].special1C +#define FIL1 chan[5].special1D +#define IL1 chan[6].special1C +#define IL2 chan[6].special1D +#define IL0 chan[7].special1C +#define MVOL chan[7].special1D + void SoundUnit::NextSample(short* l, short* r) { + // run channels for (int i=0; i<8; i++) { - if (chan[i].vol==0 && !chan[i].flags.swvol) {fns[i]=0; continue;} + if (chan[i].vol==0 && !chan[i].flags.swvol) { + fns[i]=0; + continue; + } if (chan[i].flags.pcm) { ns[i]=pcm[chan[i].pcmpos]; } else switch (chan[i].flags.shape) { @@ -48,13 +61,12 @@ void SoundUnit::NextSample(short* l, short* r) { pcmdec[i]-=32768; if (chan[i].pcmpos>2; tnsR=(nsR[0]+nsR[1]+nsR[2]+nsR[3]+nsR[4]+nsR[5]+nsR[6]+nsR[7])>>2; - - *l=minval(32767,maxval(-32767,tnsL)); - *r=minval(32767,maxval(-32767,tnsR)); + + IL1=minval(32767,maxval(-32767,tnsL))>>8; + IL2=minval(32767,maxval(-32767,tnsR))>>8; + + // write input lines to sample memory + if (ILSIZE&64) { + if (++ilBufPeriod>=((1+(FIL1>>4))<<2)) { + ilBufPeriod=0; + unsigned short ilLowerBound=pcmSize-((1+(ILSIZE&63))<<7); + short next; + if (ilBufPos>4); + if (next<-128) next=-128; + if (next>127) next=127; + pcm[ilBufPos]=next; + if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound; + break; + case 1: + ilFeedback0=ilFeedback1=pcm[ilBufPos]; + next=((signed char)IL1)+((pcm[ilBufPos]*(FIL1&15))>>4); + if (next<-128) next=-128; + if (next>127) next=127; + pcm[ilBufPos]=next; + if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound; + break; + case 2: + ilFeedback0=ilFeedback1=pcm[ilBufPos]; + next=((signed char)IL2)+((pcm[ilBufPos]*(FIL1&15))>>4); + if (next<-128) next=-128; + if (next>127) next=127; + pcm[ilBufPos]=next; + if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound; + break; + case 3: + ilFeedback0=pcm[ilBufPos]; + next=((signed char)IL1)+((pcm[ilBufPos]*(FIL1&15))>>4); + if (next<-128) next=-128; + if (next>127) next=127; + pcm[ilBufPos]=next; + if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound; + ilFeedback1=pcm[ilBufPos]; + next=((signed char)IL2)+((pcm[ilBufPos]*(FIL1&15))>>4); + if (next<-128) next=-128; + if (next>127) next=127; + pcm[ilBufPos]=next; + if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound; + break; + } + } + if (ILCTRL&4) { + if (ILSIZE&128) { + tnsL+=ilFeedback1*(signed char)FILVOL; + tnsR+=ilFeedback0*(signed char)FILVOL; + } else { + tnsL+=ilFeedback0*(signed char)FILVOL; + tnsR+=ilFeedback1*(signed char)FILVOL; + } + } + } + + if (dsOut) { + tnsL=minval(32767,maxval(-32767,tnsL<<1)); + tnsR=minval(32767,maxval(-32767,tnsR<<1)); + + short accumL=0; + short accumR=0; + + for (int i=0; i<4; i++) { + if ((tnsL>>8)==0 && dsCounterL>0) dsCounterL=0; + dsCounterL+=tnsL>>8; + if (dsCounterL>=0) { + accumL+=4095; + dsCounterL-=127; + } else { + accumL+=-4095; + dsCounterL+=127; + } + + if ((tnsR>>8)==0 && dsCounterR>0) dsCounterR=0; + dsCounterR+=tnsR>>8; + if (dsCounterR>=0) { + accumR+=4095; + dsCounterR-=127; + } else { + accumR+=-4095; + dsCounterR+=127; + } + } + + *l=accumL; + *r=accumR; + } else { + *l=minval(32767,maxval(-32767,tnsL)); + *r=minval(32767,maxval(-32767,tnsR)); + } } -void SoundUnit::Init() { +void SoundUnit::Init(int sampleMemSize, bool dsOutMode) { + pcmSize=sampleMemSize; + dsOut=dsOutMode; Reset(); - memset(pcm,0,SOUNDCHIP_PCM_SIZE); + memset(pcm,0,pcmSize); for (int i=0; i<256; i++) { SCsine[i]=sin((i/128.0f)*M_PI)*127; SCtriangle[i]=(i>127)?(255-i):(i); @@ -242,9 +353,6 @@ void SoundUnit::Init() { SCpantabR[128+i]=i-1; } SCpantabR[128]=0; - for (int i=0; i<8; i++) { - muted[i]=false; - } } void SoundUnit::Reset() { @@ -272,8 +380,14 @@ void SoundUnit::Reset() { oldflags[i]=0; pcmdec[i]=0; } + dsCounterL=0; + dsCounterR=0; tnsL=0; tnsR=0; + ilBufPos=0; + ilBufPeriod=0; + ilFeedback0=0; + ilFeedback1=0; memset(chan,0,sizeof(SUChannel)*8); } @@ -282,6 +396,8 @@ void SoundUnit::Write(unsigned char addr, unsigned char data) { } SoundUnit::SoundUnit() { - Init(); - memset(pcm,0,SOUNDCHIP_PCM_SIZE); + Init(65536); // default + for (int i=0; i<8; i++) { + muted[i]=false; + } } diff --git a/src/engine/platform/sound/su.h b/src/engine/platform/sound/su.h index 3152e856..546acfc9 100644 --- a/src/engine/platform/sound/su.h +++ b/src/engine/platform/sound/su.h @@ -3,8 +3,6 @@ #include #include -#define SOUNDCHIP_PCM_SIZE 8192 - class SoundUnit { signed char SCsine[256]; signed char SCtriangle[256]; @@ -22,8 +20,15 @@ class SoundUnit { int nshigh[8]; int nsband[8]; int tnsL, tnsR; + unsigned char ilBufPeriod; + unsigned short ilBufPos; + signed char ilFeedback0; + signed char ilFeedback1; unsigned short oldfreq[8]; unsigned short oldflags[8]; + unsigned int pcmSize; + bool dsOut; + short dsCounterL, dsCounterR; public: unsigned short resetfreq[8]; unsigned short voldcycles[8]; @@ -81,11 +86,13 @@ class SoundUnit { unsigned char dir: 1; unsigned char bound; } swcut; - unsigned short wc; + unsigned char special1C; + unsigned char special1D; unsigned short restimer; } chan[8]; - signed char pcm[SOUNDCHIP_PCM_SIZE]; + signed char pcm[65536]; bool muted[8]; + void SetIL0(unsigned char addr); void Write(unsigned char addr, unsigned char data); void NextSample(short* l, short* r); inline int GetSample(int ch) { @@ -94,7 +101,7 @@ class SoundUnit { if (ret>32767) ret=32767; return ret; } - void Init(); + void Init(int sampleMemSize=8192, bool dsOutMode=false); void Reset(); SoundUnit(); }; diff --git a/src/engine/platform/sound/tia/Audio.cpp b/src/engine/platform/sound/tia/Audio.cpp new file mode 100644 index 00000000..662bd7fd --- /dev/null +++ b/src/engine/platform/sound/tia/Audio.cpp @@ -0,0 +1,143 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#define _USE_MATH_DEFINES +#include "Audio.h" + +#include + +namespace { + constexpr double R_MAX = 30.; + constexpr double R = 1.; + + short mixingTableEntry(unsigned char v, unsigned char vMax) + { + return static_cast( + floor(0x7fff * double(v) / double(vMax) * (R_MAX + R * double(vMax)) / (R_MAX + R * double(v))) + ); + } +} + +namespace TIA { + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Audio::Audio() +{ + for (unsigned char i = 0; i <= 0x1e; ++i) myMixingTableSum[i] = mixingTableEntry(i, 0x1e); + for (unsigned char i = 0; i <= 0x0f; ++i) myMixingTableIndividual[i] = mixingTableEntry(i, 0x0f); + + reset(false); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::reset(bool st) +{ + myCounter = 0; + mySampleIndex = 0; + stereo = st; + + myCurrentSample[0]=0; + myCurrentSample[1]=0; + + myChannelOut[0]=0; + myChannelOut[1]=0; + + myChannel0.reset(); + myChannel1.reset(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::tick() +{ + switch (myCounter) { + case 9: + case 81: + myChannel0.phase0(); + myChannel1.phase0(); + + break; + + case 37: + case 149: + phase1(); + break; + } + + if (++myCounter == 228) myCounter = 0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::write(unsigned char addr, unsigned char val) { + switch (addr&0x3f) { + case 0x15: + myChannel0.audc(val); + break; + case 0x16: + myChannel1.audc(val); + break; + case 0x17: + myChannel0.audf(val); + break; + case 0x18: + myChannel1.audf(val); + break; + case 0x19: + myChannel0.audv(val); + break; + case 0x1a: + myChannel1.audv(val); + break; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::phase1() +{ + unsigned char sample0 = myChannel0.phase1(); + unsigned char sample1 = myChannel1.phase1(); + + addSample(sample0, sample1); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::addSample(unsigned char sample0, unsigned char sample1) +{ + if(stereo) { + myCurrentSample[0] = myMixingTableIndividual[sample0]; + myCurrentSample[1] = myMixingTableIndividual[sample1]; + } + else { + myCurrentSample[0] = myMixingTableSum[sample0 + sample1]; + } + + myChannelOut[0] = myMixingTableIndividual[sample0]; + myChannelOut[1] = myMixingTableIndividual[sample1]; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioChannel& Audio::channel0() +{ + return myChannel0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioChannel& Audio::channel1() +{ + return myChannel1; +} + +} \ No newline at end of file diff --git a/src/engine/platform/sound/tia/Audio.h b/src/engine/platform/sound/tia/Audio.h new file mode 100644 index 00000000..4a782bb3 --- /dev/null +++ b/src/engine/platform/sound/tia/Audio.h @@ -0,0 +1,72 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#ifndef TIA_AUDIO_HXX +#define TIA_AUDIO_HXX + +#include "AudioChannel.h" +#include + +namespace TIA { + class Audio + { + public: + Audio(); + + void reset(bool stereo); + + void tick(); + + void write(unsigned char addr, unsigned char val); + + AudioChannel& channel0(); + + AudioChannel& channel1(); + + short myCurrentSample[2]; + short myChannelOut[2]; + + private: + void phase1(); + void addSample(unsigned char sample0, unsigned char sample1); + + private: + unsigned char myCounter{0}; + + AudioChannel myChannel0; + AudioChannel myChannel1; + + bool stereo; + + std::array myMixingTableSum; + std::array myMixingTableIndividual; + + unsigned int mySampleIndex{0}; + #ifdef GUI_SUPPORT + bool myRewindMode{false}; + mutable ByteArray mySamples; + #endif + + private: + Audio(const Audio&) = delete; + Audio(Audio&&) = delete; + Audio& operator=(const Audio&) = delete; + Audio& operator=(Audio&&) = delete; + }; +} + +#endif // TIA_AUDIO_HXX diff --git a/src/engine/platform/sound/tia/AudioChannel.cpp b/src/engine/platform/sound/tia/AudioChannel.cpp new file mode 100644 index 00000000..c780c172 --- /dev/null +++ b/src/engine/platform/sound/tia/AudioChannel.cpp @@ -0,0 +1,140 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#include "AudioChannel.h" + +namespace TIA { + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::reset() +{ + myAudc = myAudv = myAudf = 0; + myClockEnable = myNoiseFeedback = myNoiseCounterBit4 = myPulseCounterHold = false; + myDivCounter = myPulseCounter = myNoiseCounter = 0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::phase0() +{ + if (myClockEnable) { + myNoiseCounterBit4 = myNoiseCounter & 0x01; + + switch (myAudc & 0x03) { + case 0x00: + case 0x01: + myPulseCounterHold = false; + break; + + case 0x02: + myPulseCounterHold = (myNoiseCounter & 0x1e) != 0x02; + break; + + case 0x03: + myPulseCounterHold = !myNoiseCounterBit4; + break; + } + + switch (myAudc & 0x03) { + case 0x00: + myNoiseFeedback = + ((myPulseCounter ^ myNoiseCounter) & 0x01) || + !(myNoiseCounter || (myPulseCounter != 0x0a)) || + !(myAudc & 0x0c); + + break; + + default: + myNoiseFeedback = + (((myNoiseCounter & 0x04) ? 1 : 0) ^ (myNoiseCounter & 0x01)) || + myNoiseCounter == 0; + + break; + } + } + + myClockEnable = myDivCounter == myAudf; + + if (myDivCounter == myAudf || myDivCounter == 0x1f) { + myDivCounter = 0; + } else { + ++myDivCounter; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +unsigned char AudioChannel::phase1() +{ + if (myClockEnable) { + bool pulseFeedback = false; + switch (myAudc >> 2) { + case 0x00: + pulseFeedback = + (((myPulseCounter & 0x02) ? 1 : 0) ^ (myPulseCounter & 0x01)) && + (myPulseCounter != 0x0a) && + (myAudc & 0x03); + + break; + + case 0x01: + pulseFeedback = !(myPulseCounter & 0x08); + break; + + case 0x02: + pulseFeedback = !myNoiseCounterBit4; + break; + + case 0x03: + pulseFeedback = !((myPulseCounter & 0x02) || !(myPulseCounter & 0x0e)); + break; + } + + myNoiseCounter >>= 1; + if (myNoiseFeedback) { + myNoiseCounter |= 0x10; + } + + if (!myPulseCounterHold) { + myPulseCounter = ~(myPulseCounter >> 1) & 0x07; + + if (pulseFeedback) { + myPulseCounter |= 0x08; + } + } + } + + return (myPulseCounter & 0x01) * myAudv; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::audc(unsigned char value) +{ + myAudc = value & 0x0f; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::audv(unsigned char value) +{ + myAudv = value & 0x0f; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::audf(unsigned char value) +{ + myAudf = value & 0x1f; +} + +} \ No newline at end of file diff --git a/src/engine/platform/sound/tia/AudioChannel.h b/src/engine/platform/sound/tia/AudioChannel.h new file mode 100644 index 00000000..942e101a --- /dev/null +++ b/src/engine/platform/sound/tia/AudioChannel.h @@ -0,0 +1,61 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#ifndef TIA_AUDIO_CHANNEL_HXX +#define TIA_AUDIO_CHANNEL_HXX + +namespace TIA { + class AudioChannel + { + public: + AudioChannel() = default; + + void reset(); + + void phase0(); + + unsigned char phase1(); + + void audc(unsigned char value); + + void audf(unsigned char value); + + void audv(unsigned char value); + + private: + unsigned char myAudc{0}; + unsigned char myAudv{0}; + unsigned char myAudf{0}; + + bool myClockEnable{false}; + bool myNoiseFeedback{false}; + bool myNoiseCounterBit4{false}; + bool myPulseCounterHold{false}; + + unsigned char myDivCounter{0}; + unsigned char myPulseCounter{0}; + unsigned char myNoiseCounter{0}; + + private: + AudioChannel(const AudioChannel&) = delete; + AudioChannel(AudioChannel&&) = delete; + AudioChannel& operator=(const AudioChannel&) = delete; + AudioChannel& operator=(AudioChannel&&) = delete; + }; +} + +#endif // TIA_AUDIO_CHANNEL_HXX diff --git a/src/engine/platform/sound/tia/TIASnd.cpp b/src/engine/platform/sound/tia/TIASnd.cpp deleted file mode 100644 index e2d0568c..00000000 --- a/src/engine/platform/sound/tia/TIASnd.cpp +++ /dev/null @@ -1,377 +0,0 @@ -//============================================================================ -// -// SSSS tt lll lll -// SS SS tt ll ll -// SS tttttt eeee ll ll aaaa -// SSSS tt ee ee ll ll aa -// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" -// SS SS tt ee ll ll aa aa -// SSSS ttt eeeee llll llll aaaaa -// -// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony -// and the Stella Team -// -// See the file "License.txt" for information on usage and redistribution of -// this file, and for a DISCLAIMER OF ALL WARRANTIES. -// -// $Id: TIASnd.cxx 3239 2015-12-29 19:22:46Z stephena $ -//============================================================================ - -#include "TIATables.h" -#include "TIASnd.h" - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -TIASound::TIASound(int outputFrequency) - : myChannelMode(Hardware2Stereo), - myOutputFrequency(outputFrequency), - myVolumePercentage(100) -{ - reset(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::reset() -{ - // Fill the polynomials - polyInit(Bit4, 4, 4, 3); - polyInit(Bit5, 5, 5, 3); - polyInit(Bit9, 9, 9, 5); - - // Initialize instance variables - for(int chan = 0; chan <= 1; ++chan) - { - myVolume[chan] = 0; - myDivNCnt[chan] = 0; - myDivNMax[chan] = 0; - myDiv3Cnt[chan] = 3; - myAUDC[chan] = 0; - myAUDF[chan] = 0; - myAUDV[chan] = 0; - myP4[chan] = 0; - myP5[chan] = 0; - myP9[chan] = 0; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::outputFrequency(int freq) -{ - myOutputFrequency = freq; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -std::string TIASound::channels(unsigned int hardware, bool stereo) -{ - if(hardware == 1) - myChannelMode = Hardware1; - else - myChannelMode = stereo ? Hardware2Stereo : Hardware2Mono; - - switch(myChannelMode) - { - case Hardware1: return "Hardware1"; - case Hardware2Mono: return "Hardware2Mono"; - case Hardware2Stereo: return "Hardware2Stereo"; - default: return ""; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::set(unsigned short address, unsigned char value) -{ - int chan = ~address & 0x1; - switch(address) - { - case TIARegister::AUDC0: - case TIARegister::AUDC1: - myAUDC[chan] = value & 0x0f; - break; - - case TIARegister::AUDF0: - case TIARegister::AUDF1: - myAUDF[chan] = value & 0x1f; - break; - - case TIARegister::AUDV0: - case TIARegister::AUDV1: - myAUDV[chan] = (value & 0x0f) << AUDV_SHIFT; - break; - - default: - return; - } - - unsigned short newVal = 0; - - // An AUDC value of 0 is a special case - if (myAUDC[chan] == SET_TO_1 || myAUDC[chan] == POLY5_POLY5) - { - // Indicate the clock is zero so no processing will occur, - // and set the output to the selected volume - newVal = 0; - myVolume[chan] = (myAUDV[chan] * myVolumePercentage) / 100; - } - else - { - // Otherwise calculate the 'divide by N' value - newVal = myAUDF[chan] + 1; - - // If bits 2 & 3 are set, then multiply the 'div by n' count by 3 - if((myAUDC[chan] & DIV3_MASK) == DIV3_MASK && myAUDC[chan] != POLY5_DIV3) - newVal *= 3; - } - - // Only reset those channels that have changed - if(newVal != myDivNMax[chan]) - { - // Reset the divide by n counters - myDivNMax[chan] = newVal; - - // If the channel is now volume only or was volume only, - // reset the counter (otherwise let it complete the previous) - if ((myDivNCnt[chan] == 0) || (newVal == 0)) - myDivNCnt[chan] = newVal; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -unsigned char TIASound::get(unsigned short address) const -{ - switch(address) - { - case TIARegister::AUDC0: return myAUDC[0]; - case TIARegister::AUDC1: return myAUDC[1]; - case TIARegister::AUDF0: return myAUDF[0]; - case TIARegister::AUDF1: return myAUDF[1]; - case TIARegister::AUDV0: return myAUDV[0] >> AUDV_SHIFT; - case TIARegister::AUDV1: return myAUDV[1] >> AUDV_SHIFT; - default: return 0; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::volume(unsigned int percent) -{ - if(percent <= 100) - myVolumePercentage = percent; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::process(short* buffer, unsigned int samples, DivDispatchOscBuffer** oscBuf) -{ - // Make temporary local copy - unsigned char audc0 = myAUDC[0], audc1 = myAUDC[1]; - unsigned char p5_0 = myP5[0], p5_1 = myP5[1]; - unsigned char div_n_cnt0 = myDivNCnt[0], div_n_cnt1 = myDivNCnt[1]; - short v0 = myVolume[0], v1 = myVolume[1]; - - // Take external volume into account - short audv0 = (myAUDV[0] * myVolumePercentage) / 100, - audv1 = (myAUDV[1] * myVolumePercentage) / 100; - - // Loop until the sample buffer is full - while(samples > 0) - { - // Process channel 0 - if (div_n_cnt0 > 1) - { - div_n_cnt0--; - } - else if (div_n_cnt0 == 1) - { - int prev_bit5 = Bit5[p5_0]; - div_n_cnt0 = myDivNMax[0]; - - // The P5 counter has multiple uses, so we increment it here - p5_0++; - if (p5_0 == POLY5_SIZE) - p5_0 = 0; - - // Check clock modifier for clock tick - if ((audc0 & 0x02) == 0 || - ((audc0 & 0x01) == 0 && Div31[p5_0]) || - ((audc0 & 0x01) == 1 && Bit5[p5_0]) || - ((audc0 & 0x0f) == POLY5_DIV3 && Bit5[p5_0] != prev_bit5)) - { - if (audc0 & 0x04) // Pure modified clock selected - { - if ((audc0 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode - { - if ( Bit5[p5_0] != prev_bit5 ) - { - myDiv3Cnt[0]--; - if ( !myDiv3Cnt[0] ) - { - myDiv3Cnt[0] = 3; - v0 = v0 ? 0 : audv0; - } - } - } - else - { - // If the output was set turn it off, else turn it on - v0 = v0 ? 0 : audv0; - } - } - else if (audc0 & 0x08) // Check for p5/p9 - { - if (audc0 == POLY9) // Check for poly9 - { - // Increase the poly9 counter - myP9[0]++; - if (myP9[0] == POLY9_SIZE) - myP9[0] = 0; - - v0 = Bit9[myP9[0]] ? audv0 : 0; - } - else if ( audc0 & 0x02 ) - { - v0 = (v0 || audc0 & 0x01) ? 0 : audv0; - } - else // Must be poly5 - { - v0 = Bit5[p5_0] ? audv0 : 0; - } - } - else // Poly4 is the only remaining option - { - // Increase the poly4 counter - myP4[0]++; - if (myP4[0] == POLY4_SIZE) - myP4[0] = 0; - - v0 = Bit4[myP4[0]] ? audv0 : 0; - } - } - } - - // Process channel 1 - if (div_n_cnt1 > 1) - { - div_n_cnt1--; - } - else if (div_n_cnt1 == 1) - { - int prev_bit5 = Bit5[p5_1]; - - div_n_cnt1 = myDivNMax[1]; - - // The P5 counter has multiple uses, so we increment it here - p5_1++; - if (p5_1 == POLY5_SIZE) - p5_1 = 0; - - // Check clock modifier for clock tick - if ((audc1 & 0x02) == 0 || - ((audc1 & 0x01) == 0 && Div31[p5_1]) || - ((audc1 & 0x01) == 1 && Bit5[p5_1]) || - ((audc1 & 0x0f) == POLY5_DIV3 && Bit5[p5_1] != prev_bit5)) - { - if (audc1 & 0x04) // Pure modified clock selected - { - if ((audc1 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode - { - if ( Bit5[p5_1] != prev_bit5 ) - { - myDiv3Cnt[1]--; - if ( ! myDiv3Cnt[1] ) - { - myDiv3Cnt[1] = 3; - v1 = v1 ? 0 : audv1; - } - } - } - else - { - // If the output was set turn it off, else turn it on - v1 = v1 ? 0 : audv1; - } - } - else if (audc1 & 0x08) // Check for p5/p9 - { - if (audc1 == POLY9) // Check for poly9 - { - // Increase the poly9 counter - myP9[1]++; - if (myP9[1] == POLY9_SIZE) - myP9[1] = 0; - - v1 = Bit9[myP9[1]] ? audv1 : 0; - } - else if ( audc1 & 0x02 ) - { - v1 = (v1 || audc1 & 0x01) ? 0 : audv1; - } - else // Must be poly5 - { - v1 = Bit5[p5_1] ? audv1 : 0; - } - } - else // Poly4 is the only remaining option - { - // Increase the poly4 counter - myP4[1]++; - if (myP4[1] == POLY4_SIZE) - myP4[1] = 0; - - v1 = Bit4[myP4[1]] ? audv1 : 0; - } - } - } - - short byte = v0 + v1; - switch(myChannelMode) - { - case Hardware2Mono: // mono sampling with 2 hardware channels - *(buffer++) = byte; - *(buffer++) = byte; - samples--; - break; - - case Hardware2Stereo: // stereo sampling with 2 hardware channels - *(buffer++) = v0; - *(buffer++) = v1; - samples--; - break; - - case Hardware1: // mono/stereo sampling with only 1 hardware channel - *(buffer++) = (v0 + v1) >> 1; - samples--; - break; - } - - if (oscBuf!=NULL) { - oscBuf[0]->data[oscBuf[0]->needle++]=v0; - oscBuf[1]->data[oscBuf[1]->needle++]=v1; - } - } - - // Save for next round - myP5[0] = p5_0; - myP5[1] = p5_1; - myVolume[0] = v0; - myVolume[1] = v1; - myDivNCnt[0] = div_n_cnt0; - myDivNCnt[1] = div_n_cnt1; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::polyInit(unsigned char* poly, int size, int f0, int f1) -{ - int mask = (1 << size) - 1, x = mask; - - for(int i = 0; i < mask; i++) - { - int bit0 = ( ( size - f0 ) ? ( x >> ( size - f0 ) ) : x ) & 0x01; - int bit1 = ( ( size - f1 ) ? ( x >> ( size - f1 ) ) : x ) & 0x01; - poly[i] = x & 1; - // calculate next bit - x = ( x >> 1 ) | ( ( bit0 ^ bit1 ) << ( size - 1) ); - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -const unsigned char TIASound::Div31[POLY5_SIZE] = { - 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; diff --git a/src/engine/platform/sound/tia/TIASnd.h b/src/engine/platform/sound/tia/TIASnd.h deleted file mode 100644 index 78459426..00000000 --- a/src/engine/platform/sound/tia/TIASnd.h +++ /dev/null @@ -1,186 +0,0 @@ -//============================================================================ -// -// SSSS tt lll lll -// SS SS tt ll ll -// SS tttttt eeee ll ll aaaa -// SSSS tt ee ee ll ll aa -// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" -// SS SS tt ee ll ll aa aa -// SSSS ttt eeeee llll llll aaaaa -// -// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony -// and the Stella Team -// -// See the file "License.txt" for information on usage and redistribution of -// this file, and for a DISCLAIMER OF ALL WARRANTIES. -// -// $Id: TIASnd.hxx 3239 2015-12-29 19:22:46Z stephena $ -//============================================================================ - -#ifndef TIASOUND_HXX -#define TIASOUND_HXX - -#include -#include "../../../dispatch.h" - -/** - This class implements a fairly accurate emulation of the TIA sound - hardware. This class uses code/ideas from z26 and MESS. - - Currently, the sound generation routines work at 31400Hz only. - Resampling can be done by passing in a different output frequency. - - @author Bradford W. Mott, Stephen Anthony, z26 and MESS teams - @version $Id: TIASnd.hxx 3239 2015-12-29 19:22:46Z stephena $ -*/ -class TIASound -{ - public: - /** - Create a new TIA Sound object using the specified output frequency - */ - TIASound(int outputFrequency = 31400); - - public: - /** - Reset the sound emulation to its power-on state - */ - void reset(); - - /** - Set the frequency output samples should be generated at - */ - void outputFrequency(int freq); - - /** - Selects the number of audio channels per sample. There are two factors - to consider: hardware capability and desired mixing. - - @param hardware The number of channels supported by the sound system - @param stereo Whether to output the internal sound signals into 1 - or 2 channels - - @return Status of the channel configuration used - */ - std::string channels(unsigned int hardware, bool stereo); - - public: - /** - Sets the specified sound register to the given value - - @param address Register address - @param value Value to store in the register - */ - void set(unsigned short address, unsigned char value); - - /** - Gets the specified sound register's value - - @param address Register address - */ - unsigned char get(unsigned short address) const; - - /** - Create sound samples based on the current sound register settings - in the specified buffer. NOTE: If channels is set to stereo then - the buffer will need to be twice as long as the number of samples. - - @param buffer The location to store generated samples - @param samples The number of samples to generate - */ - void process(short* buffer, unsigned int samples, DivDispatchOscBuffer** oscBuf=NULL); - - /** - Set the volume of the samples created (0-100) - */ - void volume(unsigned int percent); - - private: - void polyInit(unsigned char* poly, int size, int f0, int f1); - - private: - // Definitions for AUDCx (15, 16) - enum AUDCxRegister - { - SET_TO_1 = 0x00, // 0000 - POLY4 = 0x01, // 0001 - DIV31_POLY4 = 0x02, // 0010 - POLY5_POLY4 = 0x03, // 0011 - PURE1 = 0x04, // 0100 - PURE2 = 0x05, // 0101 - DIV31_PURE = 0x06, // 0110 - POLY5_2 = 0x07, // 0111 - POLY9 = 0x08, // 1000 - POLY5 = 0x09, // 1001 - DIV31_POLY5 = 0x0a, // 1010 - POLY5_POLY5 = 0x0b, // 1011 - DIV3_PURE = 0x0c, // 1100 - DIV3_PURE2 = 0x0d, // 1101 - DIV93_PURE = 0x0e, // 1110 - POLY5_DIV3 = 0x0f // 1111 - }; - - enum { - POLY4_SIZE = 0x000f, - POLY5_SIZE = 0x001f, - POLY9_SIZE = 0x01ff, - DIV3_MASK = 0x0c, - AUDV_SHIFT = 10 // shift 2 positions for AUDV, - // then another 8 for 16-bit sound - }; - - enum ChannelMode { - Hardware2Mono, // mono sampling with 2 hardware channels - Hardware2Stereo, // stereo sampling with 2 hardware channels - Hardware1 // mono/stereo sampling with only 1 hardware channel - }; - - private: - // Structures to hold the 6 tia sound control bytes - unsigned char myAUDC[2]; // AUDCx (15, 16) - unsigned char myAUDF[2]; // AUDFx (17, 18) - short myAUDV[2]; // AUDVx (19, 1A) - - short myVolume[2]; // Last output volume for each channel - - unsigned char myP4[2]; // Position pointer for the 4-bit POLY array - unsigned char myP5[2]; // Position pointer for the 5-bit POLY array - unsigned short myP9[2]; // Position pointer for the 9-bit POLY array - - unsigned char myDivNCnt[2]; // Divide by n counter. one for each channel - unsigned char myDivNMax[2]; // Divide by n maximum, one for each channel - unsigned char myDiv3Cnt[2]; // Div 3 counter, used for POLY5_DIV3 mode - - ChannelMode myChannelMode; - int myOutputFrequency; - unsigned int myVolumePercentage; - - /* - Initialize the bit patterns for the polynomials (at runtime). - - The 4bit and 5bit patterns are the identical ones used in the tia chip. - Though the patterns could be packed with 8 bits per byte, using only a - single bit per byte keeps the math simple, which is important for - efficient processing. - */ - unsigned char Bit4[POLY4_SIZE]; - unsigned char Bit5[POLY5_SIZE]; - unsigned char Bit9[POLY9_SIZE]; - - /* - The 'Div by 31' counter is treated as another polynomial because of - the way it operates. It does not have a 50% duty cycle, but instead - has a 13:18 ratio (of course, 13+18 = 31). This could also be - implemented by using counters. - */ - static const unsigned char Div31[POLY5_SIZE]; - - private: - // Following constructors and assignment operators not supported - TIASound(const TIASound&) = delete; - TIASound(TIASound&&) = delete; - TIASound& operator=(const TIASound&) = delete; - TIASound& operator=(TIASound&&) = delete; -}; - -#endif diff --git a/src/engine/platform/sound/tia/TIATables.h b/src/engine/platform/sound/tia/TIATables.h deleted file mode 100644 index 782a24de..00000000 --- a/src/engine/platform/sound/tia/TIATables.h +++ /dev/null @@ -1,219 +0,0 @@ -//============================================================================ -// -// SSSS tt lll lll -// SS SS tt ll ll -// SS tttttt eeee ll ll aaaa -// SSSS tt ee ee ll ll aa -// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" -// SS SS tt ee ll ll aa aa -// SSSS ttt eeeee llll llll aaaaa -// -// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony -// and the Stella Team -// -// See the file "License.txt" for information on usage and redistribution of -// this file, and for a DISCLAIMER OF ALL WARRANTIES. -// -// $Id: TIATables.hxx 3239 2015-12-29 19:22:46Z stephena $ -//============================================================================ - -#ifndef TIA_TABLES_HXX -#define TIA_TABLES_HXX - -enum TIABit { - P0Bit = 0x01, // Bit for Player 0 - M0Bit = 0x02, // Bit for Missle 0 - P1Bit = 0x04, // Bit for Player 1 - M1Bit = 0x08, // Bit for Missle 1 - BLBit = 0x10, // Bit for Ball - PFBit = 0x20, // Bit for Playfield - ScoreBit = 0x40, // Bit for Playfield score mode - PriorityBit = 0x80 // Bit for Playfield priority -}; - -enum TIAColor { - BKColor = 0, // Color index for Background - PFColor = 1, // Color index for Playfield - P0Color = 2, // Color index for Player 0 - P1Color = 3, // Color index for Player 1 - M0Color = 4, // Color index for Missle 0 - M1Color = 5, // Color index for Missle 1 - BLColor = 6, // Color index for Ball - HBLANKColor = 7 // Color index for HMove blank area -}; - -enum CollisionBit -{ - Cx_M0P1 = 1 << 0, // Missle0 - Player1 collision - Cx_M0P0 = 1 << 1, // Missle0 - Player0 collision - Cx_M1P0 = 1 << 2, // Missle1 - Player0 collision - Cx_M1P1 = 1 << 3, // Missle1 - Player1 collision - Cx_P0PF = 1 << 4, // Player0 - Playfield collision - Cx_P0BL = 1 << 5, // Player0 - Ball collision - Cx_P1PF = 1 << 6, // Player1 - Playfield collision - Cx_P1BL = 1 << 7, // Player1 - Ball collision - Cx_M0PF = 1 << 8, // Missle0 - Playfield collision - Cx_M0BL = 1 << 9, // Missle0 - Ball collision - Cx_M1PF = 1 << 10, // Missle1 - Playfield collision - Cx_M1BL = 1 << 11, // Missle1 - Ball collision - Cx_BLPF = 1 << 12, // Ball - Playfield collision - Cx_P0P1 = 1 << 13, // Player0 - Player1 collision - Cx_M0M1 = 1 << 14 // Missle0 - Missle1 collision -}; - -// TIA Write/Read register names -enum TIARegister { - VSYNC = 0x00, // Write: vertical sync set-clear (D1) - VBLANK = 0x01, // Write: vertical blank set-clear (D7-6,D1) - WSYNC = 0x02, // Write: wait for leading edge of hrz. blank (strobe) - RSYNC = 0x03, // Write: reset hrz. sync counter (strobe) - NUSIZ0 = 0x04, // Write: number-size player-missle 0 (D5-0) - NUSIZ1 = 0x05, // Write: number-size player-missle 1 (D5-0) - COLUP0 = 0x06, // Write: color-lum player 0 (D7-1) - COLUP1 = 0x07, // Write: color-lum player 1 (D7-1) - COLUPF = 0x08, // Write: color-lum playfield (D7-1) - COLUBK = 0x09, // Write: color-lum background (D7-1) - CTRLPF = 0x0a, // Write: cntrl playfield ballsize & coll. (D5-4,D2-0) - REFP0 = 0x0b, // Write: reflect player 0 (D3) - REFP1 = 0x0c, // Write: reflect player 1 (D3) - PF0 = 0x0d, // Write: playfield register byte 0 (D7-4) - PF1 = 0x0e, // Write: playfield register byte 1 (D7-0) - PF2 = 0x0f, // Write: playfield register byte 2 (D7-0) - RESP0 = 0x10, // Write: reset player 0 (strobe) - RESP1 = 0x11, // Write: reset player 1 (strobe) - RESM0 = 0x12, // Write: reset missle 0 (strobe) - RESM1 = 0x13, // Write: reset missle 1 (strobe) - RESBL = 0x14, // Write: reset ball (strobe) - AUDC0 = 0x15, // Write: audio control 0 (D3-0) - AUDC1 = 0x16, // Write: audio control 1 (D4-0) - AUDF0 = 0x17, // Write: audio frequency 0 (D4-0) - AUDF1 = 0x18, // Write: audio frequency 1 (D3-0) - AUDV0 = 0x19, // Write: audio volume 0 (D3-0) - AUDV1 = 0x1a, // Write: audio volume 1 (D3-0) - GRP0 = 0x1b, // Write: graphics player 0 (D7-0) - GRP1 = 0x1c, // Write: graphics player 1 (D7-0) - ENAM0 = 0x1d, // Write: graphics (enable) missle 0 (D1) - ENAM1 = 0x1e, // Write: graphics (enable) missle 1 (D1) - ENABL = 0x1f, // Write: graphics (enable) ball (D1) - HMP0 = 0x20, // Write: horizontal motion player 0 (D7-4) - HMP1 = 0x21, // Write: horizontal motion player 1 (D7-4) - HMM0 = 0x22, // Write: horizontal motion missle 0 (D7-4) - HMM1 = 0x23, // Write: horizontal motion missle 1 (D7-4) - HMBL = 0x24, // Write: horizontal motion ball (D7-4) - VDELP0 = 0x25, // Write: vertical delay player 0 (D0) - VDELP1 = 0x26, // Write: vertical delay player 1 (D0) - VDELBL = 0x27, // Write: vertical delay ball (D0) - RESMP0 = 0x28, // Write: reset missle 0 to player 0 (D1) - RESMP1 = 0x29, // Write: reset missle 1 to player 1 (D1) - HMOVE = 0x2a, // Write: apply horizontal motion (strobe) - HMCLR = 0x2b, // Write: clear horizontal motion registers (strobe) - CXCLR = 0x2c, // Write: clear collision latches (strobe) - - CXM0P = 0x00, // Read collision: D7=(M0,P1); D6=(M0,P0) - CXM1P = 0x01, // Read collision: D7=(M1,P0); D6=(M1,P1) - CXP0FB = 0x02, // Read collision: D7=(P0,PF); D6=(P0,BL) - CXP1FB = 0x03, // Read collision: D7=(P1,PF); D6=(P1,BL) - CXM0FB = 0x04, // Read collision: D7=(M0,PF); D6=(M0,BL) - CXM1FB = 0x05, // Read collision: D7=(M1,PF); D6=(M1,BL) - CXBLPF = 0x06, // Read collision: D7=(BL,PF); D6=(unused) - CXPPMM = 0x07, // Read collision: D7=(P0,P1); D6=(M0,M1) - INPT0 = 0x08, // Read pot port: D7 - INPT1 = 0x09, // Read pot port: D7 - INPT2 = 0x0a, // Read pot port: D7 - INPT3 = 0x0b, // Read pot port: D7 - INPT4 = 0x0c, // Read P1 joystick trigger: D7 - INPT5 = 0x0d // Read P2 joystick trigger: D7 -}; - -/** - The TIA class uses some static tables that aren't dependent on the actual - TIA state. For code organization, it's better to place that functionality - here. - - @author Stephen Anthony - @version $Id: TIATables.hxx 3239 2015-12-29 19:22:46Z stephena $ -*/ -class TIATables -{ - public: - /** - Compute all static tables used by the TIA - */ - static void computeAllTables(); - - // Player mask table - // [suppress mode][nusiz][pixel] - static unsigned char PxMask[2][8][320]; - - // Missle mask table (entries are true or false) - // [number][size][pixel] - // There are actually only 4 possible size combinations on a real system - // The fifth size is used for simulating the starfield effect in - // Cosmic Ark and Stay Frosty - static unsigned char MxMask[8][5][320]; - - // Ball mask table (entries are true or false) - // [size][pixel] - static unsigned char BLMask[4][320]; - - // Playfield mask table for reflected and non-reflected playfields - // [reflect, pixel] - static unsigned int PFMask[2][160]; - - // A mask table which can be used when an object is disabled - static unsigned char DisabledMask[640]; - - // Used to set the collision register to the correct value - static unsigned short CollisionMask[64]; - - // Indicates the update delay associated with poking at a TIA address - static const short PokeDelay[64]; - -#if 0 - // Used to convert value written in a motion register into - // its internal representation - static const int CompleteMotion[76][16]; -#endif - - // Indicates if HMOVE blanks should occur for the corresponding cycle - static const bool HMOVEBlankEnableCycles[76]; - - // Used to reflect a players graphics - static unsigned char GRPReflect[256]; - - // Indicates if player is being reset during delay, display or other times - // [nusiz][old pixel][new pixel] - static signed char PxPosResetWhen[8][160][160]; - - private: - // Compute the collision decode table - static void buildCollisionMaskTable(); - - // Compute the player mask table - static void buildPxMaskTable(); - - // Compute the missle mask table - static void buildMxMaskTable(); - - // Compute the ball mask table - static void buildBLMaskTable(); - - // Compute playfield mask table - static void buildPFMaskTable(); - - // Compute the player reflect table - static void buildGRPReflectTable(); - - // Compute the player position reset when table - static void buildPxPosResetWhenTable(); - - private: - // Following constructors and assignment operators not supported - TIATables() = delete; - TIATables(const TIATables&) = delete; - TIATables(TIATables&&) = delete; - TIATables& operator=(const TIATables&) = delete; - TIATables& operator=(TIATables&&) = delete; -}; - -#endif diff --git a/src/engine/platform/sound/vera_psg.c b/src/engine/platform/sound/vera_psg.c index 19f15d4e..fe1cf6b3 100644 --- a/src/engine/platform/sound/vera_psg.c +++ b/src/engine/platform/sound/vera_psg.c @@ -20,8 +20,8 @@ void psg_reset(struct VERA_PSG* psg) { memset(psg->channels, 0, sizeof(psg->channels)); - psg->noiseState=1; - psg->noiseOut=0; + psg->noiseOut = 0; + psg->noiseState = 1; } void @@ -54,10 +54,14 @@ render(struct VERA_PSG* psg, int16_t *left, int16_t *right) { int l = 0; int r = 0; - psg->noiseOut=((psg->noiseOut<<1)|(psg->noiseState&1))&63; - psg->noiseState=(psg->noiseState<<1)|(((psg->noiseState>>1)^(psg->noiseState>>2)^(psg->noiseState>>4)^(psg->noiseState>>15))&1); for (int i = 0; i < 16; i++) { + // In FPGA implementation, noise values are generated every system clock and + // the channel update is run sequentially. So, even if both two channels are + // fetching a noise value in the same sample, they should have different values + psg->noiseOut = ((psg->noiseOut << 1) | (psg->noiseState & 1)) & 63; + psg->noiseState = (psg->noiseState << 1) | (((psg->noiseState >> 1) ^ (psg->noiseState >> 2) ^ (psg->noiseState >> 4) ^ (psg->noiseState >> 15)) & 1); + struct VERAChannel *ch = &psg->channels[i]; unsigned new_phase = (ch->phase + ch->freq) & 0x1FFFF; @@ -87,11 +91,11 @@ render(struct VERA_PSG* psg, int16_t *left, int16_t *right) r += val; } - if (ch->left || ch->right) { - ch->lastOut=val; - } else { - ch->lastOut=0; - } + if (ch->left || ch->right) { + ch->lastOut = val; + } else { + ch->lastOut = 0; + } } *left = l; @@ -104,6 +108,6 @@ psg_render(struct VERA_PSG* psg, int16_t *bufL, int16_t *bufR, unsigned num_samp while (num_samples--) { render(psg, bufL, bufR); bufL++; - bufR++; + bufR++; } } diff --git a/src/engine/platform/sound/vrcvi/vrcvi.cpp b/src/engine/platform/sound/vrcvi/vrcvi.cpp index 87ff05d7..a811c2f4 100644 --- a/src/engine/platform/sound/vrcvi/vrcvi.cpp +++ b/src/engine/platform/sound/vrcvi/vrcvi.cpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: cam900, tildearrow diff --git a/src/engine/platform/sound/vrcvi/vrcvi.hpp b/src/engine/platform/sound/vrcvi/vrcvi.hpp index 790061c8..4a80f757 100644 --- a/src/engine/platform/sound/vrcvi/vrcvi.hpp +++ b/src/engine/platform/sound/vrcvi/vrcvi.hpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: cam900, tildearrow diff --git a/src/engine/platform/sound/x1_010/x1_010.cpp b/src/engine/platform/sound/x1_010/x1_010.cpp index c047b854..6b0041ba 100644 --- a/src/engine/platform/sound/x1_010/x1_010.cpp +++ b/src/engine/platform/sound/x1_010/x1_010.cpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: cam900, tildearrow diff --git a/src/engine/platform/sound/x1_010/x1_010.hpp b/src/engine/platform/sound/x1_010/x1_010.hpp index 3f5d9d4e..b533b66d 100644 --- a/src/engine/platform/sound/x1_010/x1_010.hpp +++ b/src/engine/platform/sound/x1_010/x1_010.hpp @@ -1,6 +1,6 @@ /* License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details + see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details Copyright holder(s): cam900 Modifiers and Contributors for Furnace: cam900, tildearrow diff --git a/src/engine/platform/sound/ymfm/ymfm_opn.h b/src/engine/platform/sound/ymfm/ymfm_opn.h index 34dc065d..00fd61ad 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opn.h +++ b/src/engine/platform/sound/ymfm/ymfm_opn.h @@ -784,7 +784,7 @@ public: protected: // simulate the DAC discontinuity - constexpr int32_t dac_discontinuity(int32_t value) const { return (value < 0) ? (value - 2) : (value + 3); } + int32_t dac_discontinuity(int32_t value) const { return (value < 0) ? (value - 2) : (value + 3); } // internal state uint16_t m_address; // address register diff --git a/src/engine/platform/sound/ymfm/ymfm_opz.cpp b/src/engine/platform/sound/ymfm/ymfm_opz.cpp index 94123bf6..37c6a5fc 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opz.cpp +++ b/src/engine/platform/sound/ymfm/ymfm_opz.cpp @@ -292,6 +292,10 @@ bool opz_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint3 // note from tildearrow: // - are you kidding? I have to write to this "load preset" register before keying on? + // another note from tildearrow: + // - see https://github.com/110-kenichi/ymfm/blob/main/src/ymfm_opz.cpp + // - is 0x08 the actual key on register just like OPM? + // - if so then what's bit 5? if ((index & 0xf8) == 0x20 /*&& bitfield(index, 0, 3) == bitfield(m_regdata[0x08], 0, 3)*/) { channel = bitfield(index, 0, 3); diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index 692fbbee..730f8d89 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -26,76 +26,18 @@ #define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } #define chWrite(c,a,v) rWrite(((c)<<5)|(a),v); +#define CHIP_DIVIDER 2 #define CHIP_FREQBASE 524288 const char** DivPlatformSoundUnit::getRegisterSheet() { return NULL; } -const char* DivPlatformSoundUnit::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Set waveform (0 to 7)"; - break; - case 0x12: - return "12xx: Set pulse width (0 to 7F)"; - break; - case 0x13: - return "13xx: Set resonance (0 to F)"; - break; - case 0x14: - return "14xx: Set filter mode (bit 0: ring mod; bit 1: low pass; bit 2: high pass; bit 3: band pass)"; - break; - case 0x15: - return "15xx: Set frequency sweep period low byte"; - break; - case 0x16: - return "16xx: Set frequency sweep period high byte"; - break; - case 0x17: - return "17xx: Set volume sweep period low byte"; - break; - case 0x18: - return "18xx: Set volume sweep period high byte"; - break; - case 0x19: - return "19xx: Set cutoff sweep period low byte"; - break; - case 0x1a: - return "1Axx: Set cutoff sweep period high byte"; - break; - case 0x1b: - return "1Bxx: Set frequency sweep boundary"; - break; - case 0x1c: - return "1Cxx: Set volume sweep boundary"; - break; - case 0x1d: - return "1Dxx: Set cutoff sweep boundary"; - break; - case 0x1e: - return "17xx: Set phase reset period low byte"; - break; - case 0x1f: - return "18xx: Set phase reset period high byte"; - break; - case 0x20: - return "20xx: Toggle frequency sweep (bit 0-6: speed; bit 7: direction is up)"; - break; - case 0x21: - return "21xx: Toggle volume sweep (bit 0-4: speed; bit 5: direciton is up; bit 6: loop; bit 7: alternate)"; - break; - case 0x22: - return "22xx: Toggle cutoff sweep (bit 0-6: speed; bit 7: direction is up)"; - break; - case 0x40: case 0x41: case 0x42: case 0x43: - case 0x44: case 0x45: case 0x46: case 0x47: - case 0x48: case 0x49: case 0x4a: case 0x4b: - case 0x4c: case 0x4d: case 0x4e: case 0x4f: - return "4xxx: Set cutoff (0 to FFF)"; - break; +double DivPlatformSoundUnit::NOTE_SU(int ch, int note) { + if (chan[ch].switchRoles) { + return NOTE_PERIODIC(note); } - return NULL; + return NOTE_FREQUENCY(note); } void DivPlatformSoundUnit::acquire(short* bufL, short* bufR, size_t start, size_t len) { @@ -136,18 +78,9 @@ void DivPlatformSoundUnit::tick(bool sysTick) { } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_SU(i,parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.duty.had) { chan[i].duty=chan[i].std.duty.val; @@ -187,9 +120,21 @@ void DivPlatformSoundUnit::tick(bool sysTick) { chan[i].control=chan[i].std.ex3.val&15; writeControl(i); } + if (chan[i].std.ex4.had) { + chan[i].syncTimer=chan[i].std.ex4.val&65535; + chan[i].timerSync=(chan[i].syncTimer>0); + if (chan[i].switchRoles) { + chWrite(i,0x00,chan[i].syncTimer&0xff); + chWrite(i,0x01,chan[i].syncTimer>>8); + } else { + chWrite(i,0x1e,chan[i].syncTimer&0xff); + chWrite(i,0x1f,chan[i].syncTimer>>8); + } + writeControlUpper(i); + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].switchRoles,2,chan[i].pitch2,chipClock,chan[i].switchRoles?CHIP_DIVIDER:CHIP_FREQBASE); if (chan[i].pcm) { DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); // TODO: sample map? @@ -206,14 +151,19 @@ void DivPlatformSoundUnit::tick(bool sysTick) { } if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>65535) chan[i].freq=65535; - chWrite(i,0x00,chan[i].freq&0xff); - chWrite(i,0x01,chan[i].freq>>8); + if (chan[i].switchRoles) { + chWrite(i,0x1e,chan[i].freq&0xff); + chWrite(i,0x1f,chan[i].freq>>8); + } else { + chWrite(i,0x00,chan[i].freq&0xff); + chWrite(i,0x01,chan[i].freq>>8); + } if (chan[i].keyOn) { if (chan[i].pcm) { DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note)); if (sample!=NULL) { - unsigned int sampleEnd=sample->offSU+sample->samples; + unsigned int sampleEnd=sample->offSU+(sample->getEndPosition()); unsigned int off=sample->offSU+chan[i].hasOffset; chan[i].hasOffset=0; if (sampleEnd>=getSampleMemCapacity(0)) sampleEnd=getSampleMemCapacity(0)-1; @@ -221,7 +171,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) { chWrite(i,0x0b,off>>8); chWrite(i,0x0c,sampleEnd&0xff); chWrite(i,0x0d,sampleEnd>>8); - if (sample->loopStart>=0 && sample->loopStart<(int)sample->samples) { + if (sample->isLoopable()) { unsigned int sampleLoop=sample->offSU+sample->loopStart; if (sampleLoop>=getSampleMemCapacity(0)) sampleLoop=getSampleMemCapacity(0)-1; chWrite(i,0x0e,sampleLoop&0xff); @@ -249,14 +199,15 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SU); - if (chan[c.chan].pcm && ins->type!=DIV_INS_AMIGA) { - chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA); + chan[c.chan].switchRoles=ins->su.switchRoles; + if (chan[c.chan].pcm && !(ins->type==DIV_INS_AMIGA || ins->su.useSample)) { + chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->su.useSample); writeControl(c.chan); writeControlUpper(c.chan); } - chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA); + chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->su.useSample); if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].baseFreq=NOTE_SU(c.chan,c.value); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; } @@ -406,7 +357,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) { writeControlUpper(c.chan); break; case DIV_CMD_C64_FINE_CUTOFF: - chan[c.chan].baseCutoff=c.value; + chan[c.chan].baseCutoff=c.value*4; if (!chan[c.chan].std.ex1.has) { chan[c.chan].cutoff=chan[c.chan].baseCutoff; chWrite(c.chan,0x06,chan[c.chan].cutoff&0xff); @@ -414,7 +365,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) { } break; case DIV_CMD_NOTE_PORTA: { - int destFreq=NOTE_FREQUENCY(c.value2); + int destFreq=NOTE_SU(c.chan,c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:(1+(chan[c.chan].baseFreq>>9))); @@ -446,7 +397,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) { chan[c.chan].keyOn=true; break; case DIV_CMD_LEGATO: - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); + chan[c.chan].baseFreq=NOTE_SU(c.chan,c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; @@ -454,6 +405,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SU)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_SU(c.chan,chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: @@ -477,6 +429,11 @@ void DivPlatformSoundUnit::forceIns() { for (int i=0; i<8; i++) { chan[i].insChanged=true; chan[i].freqChanged=true; + + // restore channel attributes + chWrite(i,0x03,chan[i].pan); + writeControl(i); + writeControlUpper(i); } } @@ -521,6 +478,16 @@ void DivPlatformSoundUnit::reset() { lfoMode=0; lfoSpeed=255; delay=500; + + // set initial IL status + ilCtrl=initIlCtrl; + ilSize=initIlSize; + fil1=initFil1; + echoVol=initEchoVol; + rWrite(0x9c,echoVol); + rWrite(0x9d,ilCtrl); + rWrite(0xbc,ilSize); + rWrite(0xbd,fil1); } bool DivPlatformSoundUnit::isStereo() { @@ -547,6 +514,15 @@ void DivPlatformSoundUnit::setFlags(unsigned int flags) { for (int i=0; i<8; i++) { oscBuf[i]->rate=rate; } + initIlCtrl=3|(flags&4); + initIlSize=((flags>>8)&63)|((flags&4)?0x40:0)|((flags&8)?0x80:0); + initFil1=flags>>16; + initEchoVol=flags>>24; + + sampleMemSize=flags&16; + + su->Init(sampleMemSize?65536:8192,flags&32); + renderSamples(); } void DivPlatformSoundUnit::poke(unsigned int addr, unsigned short val) { @@ -562,7 +538,7 @@ const void* DivPlatformSoundUnit::getSampleMem(int index) { } size_t DivPlatformSoundUnit::getSampleMemCapacity(int index) { - return (index==0)?8192:0; + return (index==0)?((sampleMemSize?65536:8192)-((initIlSize&64)?((1+(initIlSize&63))<<7):0)):0; } size_t DivPlatformSoundUnit::getSampleMemUsage(int index) { @@ -575,6 +551,7 @@ void DivPlatformSoundUnit::renderSamples() { size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; + if (s->data8==NULL) continue; int paddedLen=s->samples; if (memPos>=getSampleMemCapacity(0)) { logW("out of PCM memory for sample %d!",i); @@ -601,11 +578,10 @@ int DivPlatformSoundUnit::init(DivEngine* p, int channels, int sugRate, unsigned isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - setFlags(flags); su=new SoundUnit(); - su->Init(); + setFlags(flags); reset(); - return 6; + return 8; } void DivPlatformSoundUnit::quit() { diff --git a/src/engine/platform/su.h b/src/engine/platform/su.h index 1d39854f..d76d0722 100644 --- a/src/engine/platform/su.h +++ b/src/engine/platform/su.h @@ -31,7 +31,7 @@ class DivPlatformSoundUnit: public DivDispatch { int ins, cutoff, baseCutoff, res, control, hasOffset; signed char pan; unsigned char duty; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, phaseReset, filterPhaseReset; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, phaseReset, filterPhaseReset, switchRoles; bool pcmLoop, timerSync, freqSweep, volSweep, cutSweep; unsigned short freqSweepP, volSweepP, cutSweepP; unsigned char freqSweepB, volSweepB, cutSweepB; @@ -67,6 +67,7 @@ class DivPlatformSoundUnit: public DivDispatch { pcm(false), phaseReset(false), filterPhaseReset(false), + switchRoles(false), pcmLoop(false), timerSync(false), freqSweep(false), @@ -96,6 +97,10 @@ class DivPlatformSoundUnit: public DivDispatch { }; std::queue writes; unsigned char lastPan; + bool sampleMemSize; + unsigned char ilCtrl, ilSize, fil1; + unsigned char initIlCtrl, initIlSize, initFil1; + signed char echoVol, initEchoVol; int cycles, curChan, delay; short tempL; @@ -104,6 +109,7 @@ class DivPlatformSoundUnit: public DivDispatch { SoundUnit* su; size_t sampleMemLen; unsigned char regPool[128]; + double NOTE_SU(int ch, int note); void writeControl(int ch); void writeControlUpper(int ch); @@ -127,7 +133,6 @@ class DivPlatformSoundUnit: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); const void* getSampleMem(int index); size_t getSampleMemCapacity(int index); size_t getSampleMemUsage(int index); diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 73dc7928..7ac26629 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -50,27 +50,6 @@ const char** DivPlatformSwan::getRegisterSheet() { return regCheatSheetWS; } -const char* DivPlatformSwan::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Change waveform"; - break; - case 0x11: - return "11xx: Setup noise mode (0: disabled; 1-8: enabled/tap)"; - break; - case 0x12: - return "12xx: Setup sweep period (0: disabled; 1-20: enabled/period)"; - break; - case 0x13: - return "13xx: Set sweep amount"; - break; - case 0x17: - return "17xx: Toggle PCM mode"; - break; - } - return NULL; -} - void DivPlatformSwan::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t h=start; hgetSample(dacSample); if (s->samples<=0) { dacSample=-1; - continue; + dacPeriod=0; + break; } rWrite(0x09,(unsigned char)s->data8[dacPos++]+0x80); - if (dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - dacPos=s->loopStart; - } else { - dacSample=-1; - } + if (s->isLoopable() && dacPos>=s->getEndPosition()) { + dacPos=s->loopStart; + } else if (dacPos>=s->samples) { + dacSample=-1; } dacPeriod-=rate; } @@ -157,18 +135,9 @@ void DivPlatformSwan::tick(bool sysTick) { } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.wave.had && !(i==1 && pcm)) { if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { @@ -347,7 +316,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.vol.had) { + if (!chan[c.chan].std.vol.has) { calcAndWriteOutVol(c.chan,15); } } @@ -431,6 +400,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SWAN)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/swan.h b/src/engine/platform/swan.h index aafb17ac..cf792638 100644 --- a/src/engine/platform/swan.h +++ b/src/engine/platform/swan.h @@ -93,7 +93,6 @@ class DivPlatformSwan: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformSwan(); diff --git a/src/engine/platform/tia.cpp b/src/engine/platform/tia.cpp index da347244..796c9448 100644 --- a/src/engine/platform/tia.cpp +++ b/src/engine/platform/tia.cpp @@ -22,7 +22,7 @@ #include #include -#define rWrite(a,v) if (!skipRegisterWrites) {tia.set(a,v); regPool[((a)-0x15)&0x0f]=v; if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {tia.write(a,v); regPool[((a)-0x15)&0x0f]=v; if (dumpWrites) {addWrite(a,v);} } const char* regCheatSheetTIA[]={ "AUDC0", "15", @@ -34,21 +34,27 @@ const char* regCheatSheetTIA[]={ NULL }; -const char* DivPlatformTIA::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Select shape (0 to F)"; - break; - } - return NULL; -} - const char** DivPlatformTIA::getRegisterSheet() { return regCheatSheetTIA; } void DivPlatformTIA::acquire(short* bufL, short* bufR, size_t start, size_t len) { - tia.process(bufL+start,len,oscBuf); + for (size_t h=start; h>1; + } else { + bufL[h]=tia.myCurrentSample[0]; + } + if (++chanOscCounter>=114) { + chanOscCounter=0; + oscBuf[0]->data[oscBuf[0]->needle++]=tia.myChannelOut[0]; + oscBuf[1]->data[oscBuf[1]->needle++]=tia.myChannelOut[1]; + } + } } unsigned char DivPlatformTIA::dealWithFreq(unsigned char shape, int base, int pitch) { @@ -96,20 +102,18 @@ void DivPlatformTIA::tick(bool sysTick) { rWrite(0x19+i,chan[i].outVol&15); } } + // TODO: the way arps work on TIA is really weird if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=0x80000000|chan[i].std.arp.val; + if (chan[i].std.arp.val<0 && (!(chan[i].std.arp.val&0x40000000))) { + chan[i].baseFreq=0x80000000|(chan[i].std.arp.val|0x40000000); + } else if (chan[i].std.arp.val>=0 && chan[i].std.arp.val&0x40000000) { + chan[i].baseFreq=0x80000000|(chan[i].std.arp.val&(~0x40000000)); } else { chan[i].baseFreq=(chan[i].note+chan[i].std.arp.val)<<8; } } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=chan[i].note<<8; - chan[i].freqChanged=true; - } } if (chan[i].std.wave.had) { chan[i].shape=chan[i].std.wave.val&15; @@ -126,13 +130,6 @@ void DivPlatformTIA::tick(bool sysTick) { chan[i].freqChanged=true; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - if (chan[i].insChanged) { - if (!chan[i].std.wave.will) { - chan[i].shape=4; - rWrite(0x15+i,chan[i].shape); - } - chan[i].insChanged=false; - } chan[i].freq=dealWithFreq(chan[i].shape,chan[i].baseFreq,chan[i].pitch)+chan[i].pitch2; if ((chan[i].shape==4 || chan[i].shape==5) && !(chan[i].baseFreq&0x80000000 && ((chan[i].baseFreq&0x7fffffff)<32))) { if (chan[i].baseFreq<39*256) { @@ -173,6 +170,13 @@ int DivPlatformTIA::dispatch(DivCommand c) { if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } + if (chan[c.chan].insChanged) { + if (!chan[c.chan].std.wave.will) { + chan[c.chan].shape=4; + rWrite(0x15+c.chan,chan[c.chan].shape); + } + chan[c.chan].insChanged=false; + } if (isMuted[c.chan]) { rWrite(0x19+c.chan,0); } else { @@ -259,6 +263,7 @@ int DivPlatformTIA::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_TIA)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=(chan[c.chan].note<<8); chan[c.chan].inPorta=c.value; break; case DIV_CMD_PRE_NOTE: @@ -310,7 +315,7 @@ int DivPlatformTIA::getRegisterPoolSize() { } void DivPlatformTIA::reset() { - tia.reset(); + tia.reset(mixingType); memset(regPool,0,16); for (int i=0; i<2; i++) { chan[i]=DivPlatformTIA::Channel(); @@ -319,8 +324,12 @@ void DivPlatformTIA::reset() { } } +float DivPlatformTIA::getPostAmp() { + return 0.5f; +} + bool DivPlatformTIA::isStereo() { - return false; + return (mixingType==2); } bool DivPlatformTIA::keyOffAffectsArp(int ch) { @@ -343,25 +352,28 @@ void DivPlatformTIA::poke(std::vector& wlist) { void DivPlatformTIA::setFlags(unsigned int flags) { if (flags&1) { - rate=31250; + rate=COLOR_PAL*4.0/5.0; } else { - rate=31468; + rate=COLOR_NTSC; } chipClock=rate; + mixingType=(flags>>1)&3; for (int i=0; i<2; i++) { - oscBuf[i]->rate=rate; + oscBuf[i]->rate=rate/114; } + tia.reset(mixingType); } int DivPlatformTIA::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; skipRegisterWrites=false; + mixingType=0; + chanOscCounter=0; for (int i=0; i<2; i++) { isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - tia.channels(1,false); setFlags(flags); reset(); return 2; diff --git a/src/engine/platform/tia.h b/src/engine/platform/tia.h index cabe9153..b838e068 100644 --- a/src/engine/platform/tia.h +++ b/src/engine/platform/tia.h @@ -22,7 +22,7 @@ #include "../dispatch.h" #include "../macroInt.h" #include -#include "sound/tia/TIASnd.h" +#include "sound/tia/Audio.h" class DivPlatformTIA: public DivDispatch { protected: @@ -42,7 +42,9 @@ class DivPlatformTIA: public DivDispatch { Channel chan[2]; DivDispatchOscBuffer* oscBuf[2]; bool isMuted[2]; - TIASound tia; + unsigned char mixingType; + unsigned char chanOscCounter; + TIA::Audio tia; unsigned char regPool[16]; friend void putDispatchChan(void*,int,int); @@ -61,13 +63,13 @@ class DivPlatformTIA: public DivDispatch { void tick(bool sysTick=true); void muteChannel(int ch, bool mute); void setFlags(unsigned int flags); + float getPostAmp(); bool isStereo(); bool keyOffAffectsArp(int ch); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); }; diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index 30db376d..1c6470fc 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -22,43 +22,11 @@ #include #include -#include "fmshared_OPM.h" - // actually 0x40 but the upper bit of data selects address #define ADDR_WS_FINE 0x100 // actually 0xc0 but bit 5 of data selects address #define ADDR_EGS_REV 0x120 -static unsigned short chanOffs[8]={ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 -}; -static unsigned short opOffs[4]={ - 0x00, 0x08, 0x10, 0x18 -}; -static bool isOutput[8][4]={ - // 1 3 2 4 - {false,false,false,true}, - {false,false,false,true}, - {false,false,false,true}, - {false,false,false,true}, - {false,false,true ,true}, - {false,true ,true ,true}, - {false,true ,true ,true}, - {true ,true ,true ,true}, -}; -static unsigned char dtTable[8]={ - 7,6,5,0,1,2,3,4 -}; - -static int orderedOps[4]={ - 0,2,1,3 -}; - -#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} -#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } - -#define NOTE_LINEAR(x) (((x)<<6)+baseFreqOff+log2(parent->song.tuning/440.0)*12.0*64.0) - const char* regCheatSheetOPZ[]={ "Test", "00", "NoteCtl", "08", @@ -87,139 +55,6 @@ const char** DivPlatformTX81Z::getRegisterSheet() { return regCheatSheetOPZ; } -const char* DivPlatformTX81Z::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Set noise frequency (xx: value; 0 disables noise)"; - break; - case 0x11: - return "11xx: Set feedback (0 to 7)"; - break; - case 0x12: - return "12xx: Set level of operator 1 (0 highest, 7F lowest)"; - break; - case 0x13: - return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; - break; - case 0x14: - return "14xx: Set level of operator 3 (0 highest, 7F lowest)"; - break; - case 0x15: - return "15xx: Set level of operator 4 (0 highest, 7F lowest)"; - break; - case 0x16: - return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; - break; - case 0x17: - return "17xx: Set LFO speed"; - break; - case 0x18: - return "18xx: Set LFO waveform (0 saw, 1 square, 2 triangle, 3 noise)"; - break; - case 0x19: - return "19xx: Set attack of all operators (0 to 1F)"; - break; - case 0x1a: - return "1Axx: Set attack of operator 1 (0 to 1F)"; - break; - case 0x1b: - return "1Bxx: Set attack of operator 2 (0 to 1F)"; - break; - case 0x1c: - return "1Cxx: Set attack of operator 3 (0 to 1F)"; - break; - case 0x1d: - return "1Dxx: Set attack of operator 4 (0 to 1F)"; - break; - case 0x1e: - return "1Exx: Set AM depth (0 to 7F)"; - break; - case 0x1f: - return "1Fxx: Set PM depth (0 to 7F)"; - break; - case 0x28: - return "28xy: Set reverb (x: operator from 1 to 4 (0 for all ops); y: reverb from 0 to 7)"; - break; - case 0x2a: - return "2Axy: Set waveform (x: operator from 1 to 4 (0 for all ops); y: waveform from 0 to 7)"; - break; - case 0x2b: - return "2Bxy: Set envelope generator shift (x: operator from 1 to 4 (0 for all ops); y: shift from 0 to 3)"; - break; - case 0x2c: - return "2Cxy: Set fine multiplier (x: operator from 1 to 4 (0 for all ops); y: fine)"; - break; - case 0x2f: - return "2Fxx: Toggle hard envelope reset on new notes"; - break; - case 0x30: case 0x31: case 0x32: case 0x33: - case 0x34: case 0x35: case 0x36: case 0x37: - return "3xyy: Set fixed frequency of operator 1 (x: octave from 0 to 7; y: frequency)"; - break; - case 0x38: case 0x39: case 0x3a: case 0x3b: - case 0x3c: case 0x3d: case 0x3e: case 0x3f: - return "3xyy: Set fixed frequency of operator 2 (x: octave from 8 to F; y: frequency)"; - break; - case 0x40: case 0x41: case 0x42: case 0x43: - case 0x44: case 0x45: case 0x46: case 0x47: - return "4xyy: Set fixed frequency of operator 3 (x: octave from 0 to 7; y: frequency)"; - break; - case 0x48: case 0x49: case 0x4a: case 0x4b: - case 0x4c: case 0x4d: case 0x4e: case 0x4f: - return "4xyy: Set fixed frequency of operator 4 (x: octave from 8 to F; y: frequency)"; - break; - case 0x50: - return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; - break; - case 0x51: - return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; - break; - case 0x52: - return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; - break; - case 0x53: - return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)"; - break; - case 0x54: - return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)"; - break; - case 0x55: - return "55xy: Set detune 2 (x: operator from 1 to 4 (0 for all ops); y: detune from 0 to 3)"; - break; - case 0x56: - return "56xx: Set decay of all operators (0 to 1F)"; - break; - case 0x57: - return "57xx: Set decay of operator 1 (0 to 1F)"; - break; - case 0x58: - return "58xx: Set decay of operator 2 (0 to 1F)"; - break; - case 0x59: - return "59xx: Set decay of operator 3 (0 to 1F)"; - break; - case 0x5a: - return "5Axx: Set decay of operator 4 (0 to 1F)"; - break; - case 0x5b: - return "5Bxx: Set decay 2 of all operators (0 to 1F)"; - break; - case 0x5c: - return "5Cxx: Set decay 2 of operator 1 (0 to 1F)"; - break; - case 0x5d: - return "5Dxx: Set decay 2 of operator 2 (0 to 1F)"; - break; - case 0x5e: - return "5Exx: Set decay 2 of operator 3 (0 to 1F)"; - break; - case 0x5f: - return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; - break; - } - return NULL; -} - void DivPlatformTX81Z::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; @@ -233,7 +68,7 @@ void DivPlatformTX81Z::acquire(short* bufL, short* bufR, size_t start, size_t le fm_ymfm->write(0x0+((w.addr>>8)<<1),w.addr); fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val); regPool[w.addr&0xff]=w.val; - writes.pop(); + writes.pop_front(); delay=1; } } @@ -288,18 +123,9 @@ void DivPlatformTX81Z::tick(bool sysTick) { if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_LINEAR(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_LINEAR(chan[i].note+(signed char)chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_LINEAR(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_LINEAR(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.duty.had) { @@ -965,6 +791,7 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { return 127; break; case DIV_CMD_PRE_PORTA: + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_LINEAR(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_PRE_NOTE: @@ -1052,7 +879,7 @@ void DivPlatformTX81Z::poke(std::vector& wlist) { } void DivPlatformTX81Z::reset() { - while (!writes.empty()) writes.pop(); + while (!writes.empty()) writes.pop_front(); memset(regPool,0,330); fm_ymfm->reset(); if (dumpWrites) { @@ -1079,6 +906,7 @@ void DivPlatformTX81Z::reset() { pmDepth=0x7f; //rWrite(0x18,0x10); + immWrite(0x18,0x00); // LFO Freq Off immWrite(0x19,amDepth); immWrite(0x19,0x80|pmDepth); //rWrite(0x1b,0x00); diff --git a/src/engine/platform/tx81z.h b/src/engine/platform/tx81z.h index ece914d1..d1e42728 100644 --- a/src/engine/platform/tx81z.h +++ b/src/engine/platform/tx81z.h @@ -19,18 +19,22 @@ #ifndef _TX81Z_H #define _TX81Z_H -#include "../dispatch.h" +#include "fmshared_OPM.h" +#include "../macroInt.h" #include "../instrument.h" #include #include "sound/ymfm/ymfm_opz.h" -#include "../macroInt.h" class DivTXInterface: public ymfm::ymfm_interface { }; -class DivPlatformTX81Z: public DivDispatch { +class DivPlatformTX81Z: public DivPlatformOPM { protected: + const unsigned short chanOffs[8]={ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 + }; + struct Channel { DivInstrumentFM state; DivMacroInt std; @@ -69,31 +73,18 @@ class DivPlatformTX81Z: public DivDispatch { }; Channel chan[8]; DivDispatchOscBuffer* oscBuf[8]; - struct QueuedWrite { - unsigned short addr; - unsigned char val; - bool addrOrVal; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} - }; - std::queue writes; - int delay, baseFreqOff; + int baseFreqOff; int pcmL, pcmR, pcmCycles; - unsigned char lastBusy; unsigned char amDepth, pmDepth; ymfm::ym2414* fm_ymfm; ymfm::ym2414::output_data out_ymfm; DivTXInterface iface; - unsigned char regPool[330]; - bool extMode; bool isMuted[8]; - short oldWrites[330]; - short pendingWrites[330]; - int octave(int freq); int toFreq(int freq); @@ -117,7 +108,6 @@ class DivPlatformTX81Z: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformTX81Z(); diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 62e1ea68..bf47df5a 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -51,18 +51,6 @@ const char** DivPlatformVERA::getRegisterSheet() { return regCheatSheetVERA; } -const char* DivPlatformVERA::getEffectName(unsigned char effect) { - switch (effect) { - case 0x20: - return "20xx: Change waveform"; - break; - case 0x22: - return "22xx: Set duty cycle (0 to 3F)"; - break; - } - return NULL; -} - void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len) { // both PSG part and PCM part output a full 16-bit range, putting bufL/R // argument right into both could cause an overflow @@ -96,13 +84,11 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len rWritePCMData(tmp_r&0xff); } chan[16].pcm.pos++; - if (chan[16].pcm.pos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - chan[16].pcm.pos=s->loopStart; - } else { - chan[16].pcm.sample=-1; - break; - } + if (s->isLoopable() && chan[16].pcm.pos>=s->getEndPosition()) { + chan[16].pcm.pos=s->loopStart; + } else if (chan[16].pcm.pos>=s->samples) { + chan[16].pcm.sample=-1; + break; } } } else { @@ -173,18 +159,9 @@ void DivPlatformVERA::tick(bool sysTick) { } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=calcNoteFreq(0,chan[i].std.arp.val); - } else { - chan[i].baseFreq=calcNoteFreq(0,chan[i].note+chan[i].std.arp.val); - } + chan[i].baseFreq=calcNoteFreq(0,parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=calcNoteFreq(0,chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.duty.had) { rWriteLo(i,3,chan[i].std.duty.val); @@ -223,18 +200,9 @@ void DivPlatformVERA::tick(bool sysTick) { } if (chan[16].std.arp.had) { if (!chan[16].inPorta) { - if (chan[16].std.arp.mode) { - chan[16].baseFreq=calcNoteFreq(16,chan[16].std.arp.val); - } else { - chan[16].baseFreq=calcNoteFreq(16,chan[16].note+chan[16].std.arp.val); - } + chan[16].baseFreq=calcNoteFreq(16,parent->calcArp(chan[16].note,chan[16].std.arp.val)); } chan[16].freqChanged=true; - } else { - if (chan[16].std.arp.mode && chan[16].std.arp.finished) { - chan[16].baseFreq=calcNoteFreq(16,chan[16].note); - chan[16].freqChanged=true; - } } if (chan[16].freqChanged) { double off=65536.0; @@ -267,12 +235,12 @@ int DivPlatformVERA::dispatch(DivCommand c) { chan[16].pcm.pos=0; DivSample* s=parent->getSample(chan[16].pcm.sample); unsigned char ctrl=0x90|chan[16].vol; // always stereo - if (s->depth==16) { + if (s->depth==DIV_SAMPLE_DEPTH_16BIT) { chan[16].pcm.depth16=true; ctrl|=0x20; } else { chan[16].pcm.depth16=false; - if (s->depth!=8) chan[16].pcm.sample=-1; + if (s->depth!=DIV_SAMPLE_DEPTH_8BIT) chan[16].pcm.sample=-1; } rWritePCMCtrl(ctrl); } @@ -359,6 +327,7 @@ int DivPlatformVERA::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_VERA)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=calcNoteFreq(c.chan,chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_STD_NOISE_MODE: diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h index 612b4354..53e766dc 100644 --- a/src/engine/platform/vera.h +++ b/src/engine/platform/vera.h @@ -79,7 +79,6 @@ class DivPlatformVERA: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformVERA(); diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index a766ba44..93c11948 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -39,15 +39,6 @@ const char** DivPlatformVIC20::getRegisterSheet() { return regCheatSheetVIC; } -const char* DivPlatformVIC20::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Change waveform"; - break; - } - return NULL; -} - void DivPlatformVIC20::acquire(short* bufL, short* bufR, size_t start, size_t len) { const unsigned char loadFreq[3] = {0x7e, 0x7d, 0x7b}; const unsigned char wavePatterns[16] = { @@ -103,18 +94,9 @@ void DivPlatformVIC20::tick(bool sysTick) { } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.wave.had) { if (chan[i].wave!=chan[i].std.wave.val) { @@ -195,7 +177,7 @@ int DivPlatformVIC20::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.vol.had) { + if (!chan[c.chan].std.vol.has) { calcAndWriteOutVol(c.chan,15); } } @@ -243,6 +225,7 @@ int DivPlatformVIC20::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_VIC)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/vic20.h b/src/engine/platform/vic20.h index d23f27be..d4b56028 100644 --- a/src/engine/platform/vic20.h +++ b/src/engine/platform/vic20.h @@ -82,7 +82,6 @@ class DivPlatformVIC20: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformVIC20(); diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 08c6d053..6e5e0d73 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -46,18 +46,6 @@ const char** DivPlatformVRC6::getRegisterSheet() { return regCheatSheetVRC6; } -const char* DivPlatformVRC6::getEffectName(unsigned char effect) { - switch (effect) { - case 0x12: - return "12xx: Set duty cycle (pulse: 0 to 7)"; - break; - case 0x17: - return "17xx: Toggle PCM mode (pulse channel)"; - break; - } - return NULL; -} - void DivPlatformVRC6::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t i=start; i=s->samples) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples) { - chan[i].dacPos=s->loopStart; - } else { - chan[i].dacSample=-1; - chWrite(i,0,0); - } + if (s->isLoopable() && chan[i].dacPos>=s->getEndPosition()) { + chan[i].dacPos=s->loopStart; + } else if (chan[i].dacPos>=s->samples) { + chan[i].dacSample=-1; + chWrite(i,0,0); } chan[i].dacPeriod-=rate; } @@ -167,18 +153,9 @@ void DivPlatformVRC6::tick(bool sysTick) { } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.duty.had) { chan[i].duty=chan[i].std.duty.val; @@ -218,7 +195,7 @@ void DivPlatformVRC6::tick(bool sysTick) { if (chan[i].freq<0) chan[i].freq=0; if (chan[i].keyOff) { chWrite(i,2,0); - } else { + } else if (chan[i].active) { chWrite(i,1,chan[i].freq&0xff); chWrite(i,2,0x80|((chan[i].freq>>8)&0xf)); } @@ -399,6 +376,7 @@ int DivPlatformVRC6::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_VRC6)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: diff --git a/src/engine/platform/vrc6.h b/src/engine/platform/vrc6.h index 450e09b9..608baa10 100644 --- a/src/engine/platform/vrc6.h +++ b/src/engine/platform/vrc6.h @@ -99,7 +99,6 @@ class DivPlatformVRC6: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); DivPlatformVRC6() : vrc6(intf) {}; diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index dc5c7ed2..0f989ac9 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -205,39 +205,6 @@ const char** DivPlatformX1_010::getRegisterSheet() { return regCheatSheetX1_010; } -const char* DivPlatformX1_010::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Change waveform"; - break; - case 0x11: - return "11xx: Change envelope shape"; - break; - case 0x17: - return "17xx: Toggle PCM mode"; - break; - case 0x20: - return "20xx: Set PCM frequency (1 to FF)"; - break; - case 0x22: - return "22xx: Set envelope mode (bit 0: enable, bit 1: one-shot, bit 2: split shape to L/R, bit 3/5: H.invert right/left, bit 4/6: V.invert right/left)"; - break; - case 0x23: - return "23xx: Set envelope period"; - break; - case 0x25: - return "25xx: Envelope slide up"; - break; - case 0x26: - return "26xx: Envelope slide down"; - break; - case 0x29: - return "29xy: Set auto-envelope (x: numerator; y: denominator)"; - break; - } - return NULL; -} - void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t h=start; htick(); @@ -354,18 +321,9 @@ void DivPlatformX1_010::tick(bool sysTick) { if ((!chan[i].pcm) || chan[i].furnacePCM) { if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NoteX1_010(i,chan[i].std.arp.val); - } else { - chan[i].baseFreq=NoteX1_010(i,chan[i].note+chan[i].std.arp.val); - } + chan[i].baseFreq=NoteX1_010(i,parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NoteX1_010(i,chan[i].note); - chan[i].freqChanged=true; - } } } if (chan[i].std.wave.had && !chan[i].pcm) { @@ -732,6 +690,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_X1_010)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_SAMPLE_FREQ: diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index 7a85b633..178a8938 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -149,7 +149,6 @@ class DivPlatformX1_010: public DivDispatch { size_t getSampleMemUsage(int index = 0); void renderSamples(); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformX1_010(); diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 5507b11c..1c348800 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -23,16 +23,8 @@ #include #include -#include "sound/ymfm/ymfm_opn.h" -#include "ym2203shared.h" - -#include "fmshared_OPN.h" - -static unsigned char konOffs[3]={ - 0, 1, 2 -}; - -#define CHIP_DIVIDER 32 +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase const char* regCheatSheetYM2203[]={ // SSG @@ -164,123 +156,6 @@ const char** DivPlatformYM2203::getRegisterSheet() { return regCheatSheetYM2203; } -const char* DivPlatformYM2203::getEffectName(unsigned char effect) { - switch (effect) { - case 0x11: - return "11xx: Set feedback (0 to 7)"; - break; - case 0x12: - return "12xx: Set level of operator 1 (0 highest, 7F lowest)"; - break; - case 0x13: - return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; - break; - case 0x14: - return "14xx: Set level of operator 3 (0 highest, 7F lowest)"; - break; - case 0x15: - return "15xx: Set level of operator 4 (0 highest, 7F lowest)"; - break; - case 0x16: - return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; - break; - case 0x18: - return "18xx: Toggle extended channel 3 mode"; - break; - case 0x19: - return "19xx: Set attack of all operators (0 to 1F)"; - break; - case 0x1a: - return "1Axx: Set attack of operator 1 (0 to 1F)"; - break; - case 0x1b: - return "1Bxx: Set attack of operator 2 (0 to 1F)"; - break; - case 0x1c: - return "1Cxx: Set attack of operator 3 (0 to 1F)"; - break; - case 0x1d: - return "1Dxx: Set attack of operator 4 (0 to 1F)"; - break; - case 0x20: - return "20xx: Set SSG channel mode (bit 0: square; bit 1: noise; bit 2: envelope)"; - break; - case 0x21: - return "21xx: Set SSG noise frequency (0 to 1F)"; - break; - case 0x22: - return "22xy: Set SSG envelope mode (x: shape, y: enable for this channel)"; - break; - case 0x23: - return "23xx: Set SSG envelope period low byte"; - break; - case 0x24: - return "24xx: Set SSG envelope period high byte"; - break; - case 0x25: - return "25xx: SSG envelope slide up"; - break; - case 0x26: - return "26xx: SSG envelope slide down"; - break; - case 0x29: - return "29xy: Set SSG auto-envelope (x: numerator; y: denominator)"; - break; - case 0x30: - return "30xx: Toggle hard envelope reset on new notes"; - break; - case 0x50: - return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; - break; - case 0x51: - return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; - break; - case 0x52: - return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; - break; - case 0x53: - return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)"; - break; - case 0x54: - return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)"; - break; - case 0x55: - return "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)"; - break; - case 0x56: - return "56xx: Set decay of all operators (0 to 1F)"; - break; - case 0x57: - return "57xx: Set decay of operator 1 (0 to 1F)"; - break; - case 0x58: - return "58xx: Set decay of operator 2 (0 to 1F)"; - break; - case 0x59: - return "59xx: Set decay of operator 3 (0 to 1F)"; - break; - case 0x5a: - return "5Axx: Set decay of operator 4 (0 to 1F)"; - break; - case 0x5b: - return "5Bxx: Set decay 2 of all operators (0 to 1F)"; - break; - case 0x5c: - return "5Cxx: Set decay 2 of operator 1 (0 to 1F)"; - break; - case 0x5d: - return "5Dxx: Set decay 2 of operator 2 (0 to 1F)"; - break; - case 0x5e: - return "5Exx: Set decay 2 of operator 3 (0 to 1F)"; - break; - case 0x5f: - return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; - break; - } - return NULL; -} - void DivPlatformYM2203::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os; @@ -299,7 +174,7 @@ void DivPlatformYM2203::acquire(short* bufL, short* bufR, size_t start, size_t l fm->write(0x0,w.addr); fm->write(0x1,w.val); regPool[w.addr&0xff]=w.val; - writes.pop(); + writes.pop_front(); delay=6; } } @@ -356,18 +231,9 @@ void DivPlatformYM2203::tick(bool sysTick) { if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11); - } else { - chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11); - } + chan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(chan[i].note,chan[i].std.arp.val),11); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11); - chan[i].freqChanged=true; - } } if (chan[i].std.pitch.had) { @@ -407,6 +273,10 @@ void DivPlatformYM2203::tick(bool sysTick) { chan[i].state.fb=chan[i].std.fb.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); } + if (chan[i].std.ex4.had && chan[i].active) { + chan[i].opMask=chan[i].std.ex4.val&15; + chan[i].opMaskChanged=true; + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -519,8 +389,9 @@ void DivPlatformYM2203::tick(bool sysTick) { immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); chan[i].freqChanged=false; } - if (chan[i].keyOn) { - immWrite(0x28,0xf0|konOffs[i]); + if (chan[i].keyOn || chan[i].opMaskChanged) { + immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); + chan[i].opMaskChanged=false; chan[i].keyOn=false; } } @@ -543,6 +414,11 @@ int DivPlatformYM2203::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; + chan[c.chan].opMask= + (chan[c.chan].state.op[0].enable?1:0)| + (chan[c.chan].state.op[2].enable?2:0)| + (chan[c.chan].state.op[1].enable?4:0)| + (chan[c.chan].state.op[3].enable?8:0); } for (int i=0; i<4; i++) { @@ -667,6 +543,13 @@ int DivPlatformYM2203::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; } + case DIV_CMD_FM_EXTCH: { + if (extSys) { + extMode=c.value; + immWrite(0x27,extMode?0x40:0); + } + break; + } case DIV_CMD_FM_FB: { if (c.chan>2) break; chan[c.chan].state.fb=c.value&7; @@ -959,7 +842,7 @@ void DivPlatformYM2203::poke(std::vector& wlist) { } void DivPlatformYM2203::reset() { - while (!writes.empty()) writes.pop(); + while (!writes.empty()) writes.pop_front(); memset(regPool,0,256); if (dumpWrites) { addWrite(0xffffffff,0); @@ -989,6 +872,10 @@ void DivPlatformYM2203::reset() { extMode=false; + // set prescaler + immWrite(0x2d,0xff); + immWrite(prescale,0xff); + ay->reset(); ay->getRegisterWrites().clear(); ay->flushWrites(); @@ -1021,25 +908,58 @@ void DivPlatformYM2203::setSkipRegisterWrites(bool value) { } void DivPlatformYM2203::setFlags(unsigned int flags) { - unsigned char ayFlags=16; - if (flags==3) { - chipClock=3000000.0; - ayFlags=20; - } else if (flags==2) { - chipClock=4000000.0; - ayFlags=19; - } else if (flags==1) { - chipClock=COLOR_PAL*4.0/5.0; - ayFlags=17; - } else { - chipClock=COLOR_NTSC; - ayFlags=16; + // Clock flags + switch (flags&0x1f) { + default: + case 0x00: + chipClock=COLOR_NTSC; + break; + case 0x01: + chipClock=COLOR_PAL*4.0/5.0; + break; + case 0x02: + chipClock=4000000.0; + break; + case 0x03: + chipClock=3000000.0; + break; + case 0x04: + chipClock=38400*13*8; // 31948800/8 + break; + case 0x05: + chipClock=3000000.0/2.0; + break; + } + // Prescaler flags + switch ((flags>>5)&0x3) { + default: + case 0x00: // /6 + prescale=0x2d; + fmFreqBase=4720270.0, + fmDivBase=36, + ayDiv=16; + break; + case 0x01: // /3 + prescale=0x2e; + fmFreqBase=4720270.0/2.0, + fmDivBase=18, + ayDiv=8; + break; + case 0x02: // /2 + prescale=0x2f; + fmFreqBase=4720270.0/3.0, + fmDivBase=12, + ayDiv=4; + break; } - ay->setFlags(ayFlags); rate=fm->sample_rate(chipClock); for (int i=0; i<6; i++) { oscBuf[i]->rate=rate; } + immWrite(0x2d,0xff); + immWrite(prescale,0xff); + ay->setExtClockDiv(chipClock,ayDiv); + ay->setFlags(16); } int DivPlatformYM2203::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { @@ -1053,13 +973,13 @@ int DivPlatformYM2203::init(DivEngine* p, int channels, int sugRate, unsigned in fm=new ymfm::ym2203(iface); fm->set_fidelity(ymfm::OPN_FIDELITY_MIN); // YM2149, 2MHz - ay=new DivPlatformAY8910; - ay->init(p,3,sugRate,19); + ay=new DivPlatformAY8910(true,chipClock,ayDiv); + ay->init(p,3,sugRate,16); ay->toggleRegisterDump(true); setFlags(flags); reset(); - return 16; + return 6; } void DivPlatformYM2203::quit() { diff --git a/src/engine/platform/ym2203.h b/src/engine/platform/ym2203.h index f91b7e0f..75660365 100644 --- a/src/engine/platform/ym2203.h +++ b/src/engine/platform/ym2203.h @@ -19,9 +19,8 @@ #ifndef _YM2203_H #define _YM2203_H -#include "../dispatch.h" +#include "fmshared_OPN.h" #include "../macroInt.h" -#include #include "sound/ymfm/ymfm_opn.h" #include "ay.h" @@ -30,19 +29,23 @@ class DivYM2203Interface: public ymfm::ymfm_interface { }; -class DivPlatformYM2203: public DivDispatch { +class DivPlatformYM2203: public DivPlatformOPN { protected: const unsigned short chanOffs[3]={ 0x00, 0x01, 0x02 }; + const unsigned char konOffs[3]={ + 0, 1, 2 + }; + struct Channel { DivInstrumentFM state; unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; - unsigned char psgMode, autoEnvNum, autoEnvDen; + unsigned char psgMode, autoEnvNum, autoEnvDen, opMask; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged; int vol, outVol; int sample; DivMacroInt std; @@ -63,6 +66,7 @@ class DivPlatformYM2203: public DivDispatch { psgMode(1), autoEnvNum(0), autoEnvDen(0), + opMask(15), active(false), insChanged(true), freqChanged(false), @@ -72,6 +76,7 @@ class DivPlatformYM2203: public DivDispatch { inPorta(false), furnacePCM(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(15), sample(-1) {} @@ -79,29 +84,16 @@ class DivPlatformYM2203: public DivDispatch { Channel chan[6]; DivDispatchOscBuffer* oscBuf[6]; bool isMuted[6]; - struct QueuedWrite { - unsigned short addr; - unsigned char val; - bool addrOrVal; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} - }; - std::queue writes; ymfm::ym2203* fm; ymfm::ym2203::output_data fmout; DivYM2203Interface iface; - unsigned char regPool[512]; - unsigned char lastBusy; DivPlatformAY8910* ay; unsigned char sampleBank; - int delay; - bool extMode; + unsigned char prescale; - short oldWrites[256]; - short pendingWrites[256]; - friend void putDispatchChan(void*,int,int); public: @@ -124,10 +116,12 @@ class DivPlatformYM2203: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); void setFlags(unsigned int flags); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); + DivPlatformYM2203(): + DivPlatformOPN(4720270.0, 36, 16), + prescale(0x2d) {} ~DivPlatformYM2203(); }; #endif diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index 3a38bde2..c7080d43 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -21,8 +21,8 @@ #include "../engine.h" #include -#include "ym2203shared.h" -#include "fmshared_OPN.h" +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase int DivPlatformYM2203Ext::dispatch(DivCommand c) { if (c.chan<2) { @@ -34,6 +34,10 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) { } int ch=c.chan-2; int ordch=orderedOps[ch]; + if (!extMode) { + c.chan=2; + return DivPlatformYM2203::dispatch(c); + } switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); @@ -55,6 +59,7 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) { rWrite(baseAddr+0x70,op.d2r&31); rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); rWrite(baseAddr+0x90,op.ssgEnv&15); + opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); @@ -151,6 +156,11 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) { opChan[ch].freqChanged=true; break; } + case DIV_CMD_FM_EXTCH: { + extMode=c.value; + immWrite(0x27,extMode?0x40:0); + break; + } case DIV_CMD_FM_LFO: { rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); break; @@ -349,7 +359,7 @@ void DivPlatformYM2203Ext::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -386,10 +396,12 @@ void DivPlatformYM2203Ext::tick(bool sysTick) { immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); } - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn) { writeNoteOn=true; - writeMask|=1<<(4+i); + if (opChan[i].mask) { + writeMask|=1<<(4+i); + } opChan[i].keyOn=false; } } @@ -514,9 +526,10 @@ int DivPlatformYM2203Ext::init(DivEngine* parent, int channels, int sugRate, uns for (int i=0; i<4; i++) { isOpMuted[i]=false; } + extSys=true; reset(); - return 19; + return 9; } void DivPlatformYM2203Ext::quit() { diff --git a/src/engine/platform/ym2203ext.h b/src/engine/platform/ym2203ext.h index 1a398d1a..d25ca45d 100644 --- a/src/engine/platform/ym2203ext.h +++ b/src/engine/platform/ym2203ext.h @@ -27,12 +27,12 @@ class DivPlatformYM2203Ext: public DivPlatformYM2203 { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask; int vol; unsigned char pan; // UGLY OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), - inPorta(false), vol(0), pan(3) {} + inPorta(false), mask(true), vol(0), pan(3) {} }; OpChannel opChan[4]; bool isOpMuted[4]; diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 773f9ed5..ec90c1c6 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -24,16 +24,8 @@ #include #include -#include "sound/ymfm/ymfm_opn.h" -#include "ym2610shared.h" - -#include "fmshared_OPN.h" - -static unsigned char konOffs[6]={ - 0, 1, 2, 4, 5, 6 -}; - -#define CHIP_DIVIDER 32 +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase const char* regCheatSheetYM2608[]={ // SSG @@ -287,126 +279,6 @@ const char** DivPlatformYM2608::getRegisterSheet() { return regCheatSheetYM2608; } -const char* DivPlatformYM2608::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xy: Setup LFO (x: enable; y: speed)"; - break; - case 0x11: - return "11xx: Set feedback (0 to 7)"; - break; - case 0x12: - return "12xx: Set level of operator 1 (0 highest, 7F lowest)"; - break; - case 0x13: - return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; - break; - case 0x14: - return "14xx: Set level of operator 3 (0 highest, 7F lowest)"; - break; - case 0x15: - return "15xx: Set level of operator 4 (0 highest, 7F lowest)"; - break; - case 0x16: - return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; - break; - case 0x18: - return "18xx: Toggle extended channel 3 mode"; - break; - case 0x19: - return "19xx: Set attack of all operators (0 to 1F)"; - break; - case 0x1a: - return "1Axx: Set attack of operator 1 (0 to 1F)"; - break; - case 0x1b: - return "1Bxx: Set attack of operator 2 (0 to 1F)"; - break; - case 0x1c: - return "1Cxx: Set attack of operator 3 (0 to 1F)"; - break; - case 0x1d: - return "1Dxx: Set attack of operator 4 (0 to 1F)"; - break; - case 0x20: - return "20xx: Set SSG channel mode (bit 0: square; bit 1: noise; bit 2: envelope)"; - break; - case 0x21: - return "21xx: Set SSG noise frequency (0 to 1F)"; - break; - case 0x22: - return "22xy: Set SSG envelope mode (x: shape, y: enable for this channel)"; - break; - case 0x23: - return "23xx: Set SSG envelope period low byte"; - break; - case 0x24: - return "24xx: Set SSG envelope period high byte"; - break; - case 0x25: - return "25xx: SSG envelope slide up"; - break; - case 0x26: - return "26xx: SSG envelope slide down"; - break; - case 0x29: - return "29xy: Set SSG auto-envelope (x: numerator; y: denominator)"; - break; - case 0x30: - return "30xx: Toggle hard envelope reset on new notes"; - break; - case 0x50: - return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; - break; - case 0x51: - return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; - break; - case 0x52: - return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; - break; - case 0x53: - return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)"; - break; - case 0x54: - return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)"; - break; - case 0x55: - return "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)"; - break; - case 0x56: - return "56xx: Set decay of all operators (0 to 1F)"; - break; - case 0x57: - return "57xx: Set decay of operator 1 (0 to 1F)"; - break; - case 0x58: - return "58xx: Set decay of operator 2 (0 to 1F)"; - break; - case 0x59: - return "59xx: Set decay of operator 3 (0 to 1F)"; - break; - case 0x5a: - return "5Axx: Set decay of operator 4 (0 to 1F)"; - break; - case 0x5b: - return "5Bxx: Set decay 2 of all operators (0 to 1F)"; - break; - case 0x5c: - return "5Cxx: Set decay 2 of operator 1 (0 to 1F)"; - break; - case 0x5d: - return "5Dxx: Set decay 2 of operator 2 (0 to 1F)"; - break; - case 0x5e: - return "5Exx: Set decay 2 of operator 3 (0 to 1F)"; - break; - case 0x5f: - return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; - break; - } - return NULL; -} - double DivPlatformYM2608::NOTE_OPNB(int ch, int note) { if (ch>8) { // ADPCM-B return NOTE_ADPCMB(note); @@ -450,7 +322,7 @@ void DivPlatformYM2608::acquire(short* bufL, short* bufR, size_t start, size_t l fm->write(0x0+((w.addr>>8)<<1),w.addr); fm->write(0x1+((w.addr>>8)<<1),w.val); regPool[w.addr&0x1ff]=w.val; - writes.pop(); + writes.pop_front(); delay=4; } } @@ -514,18 +386,9 @@ void DivPlatformYM2608::tick(bool sysTick) { if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11); - } else { - chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11); - } + chan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(chan[i].note,chan[i].std.arp.val),11); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11); - chan[i].freqChanged=true; - } } if (chan[i].std.panL.had) { @@ -578,6 +441,10 @@ void DivPlatformYM2608::tick(bool sysTick) { chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } + if (chan[i].std.ex4.had && chan[i].active) { + chan[i].opMask=chan[i].std.ex4.val&15; + chan[i].opMaskChanged=true; + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -666,18 +533,9 @@ void DivPlatformYM2608::tick(bool sysTick) { if (chan[15].std.arp.had) { if (!chan[15].inPorta) { - if (chan[15].std.arp.mode) { - chan[15].baseFreq=NOTE_ADPCMB(chan[15].std.arp.val); - } else { - chan[15].baseFreq=NOTE_ADPCMB(chan[15].note+(signed char)chan[15].std.arp.val); - } + chan[15].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[15].note,chan[15].std.arp.val)); } chan[15].freqChanged=true; - } else { - if (chan[15].std.arp.mode && chan[15].std.arp.finished) { - chan[15].baseFreq=NOTE_ADPCMB(chan[15].note); - chan[15].freqChanged=true; - } } } if (chan[15].freqChanged) { @@ -727,8 +585,9 @@ void DivPlatformYM2608::tick(bool sysTick) { immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); chan[i].freqChanged=false; } - if (chan[i].keyOn) { - immWrite(0x28,0xf0|konOffs[i]); + if (chan[i].keyOn || chan[i].opMaskChanged) { + immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); + chan[i].opMaskChanged=false; chan[i].keyOn=false; } } @@ -769,7 +628,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { immWrite(0x104,(end>>5)&0xff); immWrite(0x105,(end>>13)&0xff); immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); - immWrite(0x100,(s->loopStart>=0)?0xb0:0xa0); // start/repeat + immWrite(0x100,(s->isLoopable())?0xb0:0xa0); // start/repeat if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); @@ -804,7 +663,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { immWrite(0x104,(end>>5)&0xff); immWrite(0x105,(end>>13)&0xff); immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); - immWrite(0x100,(s->loopStart>=0)?0xb0:0xa0); // start/repeat + immWrite(0x100,(s->isLoopable())?0xb0:0xa0); // start/repeat int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0); immWrite(0x109,freq&0xff); immWrite(0x10a,(freq>>8)&0xff); @@ -829,6 +688,11 @@ int DivPlatformYM2608::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; + chan[c.chan].opMask= + (chan[c.chan].state.op[0].enable?1:0)| + (chan[c.chan].state.op[2].enable?2:0)| + (chan[c.chan].state.op[1].enable?4:0)| + (chan[c.chan].state.op[3].enable?8:0); } for (int i=0; i<4; i++) { @@ -996,6 +860,13 @@ int DivPlatformYM2608::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; } + case DIV_CMD_FM_EXTCH: { + if (extSys) { + extMode=c.value; + immWrite(0x27,extMode?0x40:0); + } + break; + } case DIV_CMD_FM_LFO: { rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); break; @@ -1283,7 +1154,7 @@ void DivPlatformYM2608::poke(std::vector& wlist) { } void DivPlatformYM2608::reset() { - while (!writes.empty()) writes.pop(); + while (!writes.empty()) writes.pop_front(); memset(regPool,0,512); if (dumpWrites) { addWrite(0xffffffff,0); @@ -1333,6 +1204,10 @@ void DivPlatformYM2608::reset() { // enable 6 channel mode immWrite(0x29,0x80); + // set prescaler + immWrite(0x2d,0xff); + immWrite(prescale,0xff); + ay->reset(); ay->getRegisterWrites().clear(); ay->flushWrites(); @@ -1402,6 +1277,49 @@ void DivPlatformYM2608::renderSamples() { adpcmBMemLen=memPos+256; } +void DivPlatformYM2608::setFlags(unsigned int flags) { + // Clock flags + switch (flags&0x1f) { + default: + case 0x00: + chipClock=8000000.0; + break; + case 0x01: + chipClock=38400*13*16; // 31948800/4 + break; + } + // Prescaler flags + switch ((flags>>5)&0x3) { + default: + case 0x00: // /6 + prescale=0x2d; + fmFreqBase=9440540.0, + fmDivBase=72, + ayDiv=32; + break; + case 0x01: // /3 + prescale=0x2e; + fmFreqBase=9440540.0/2.0, + fmDivBase=36, + ayDiv=16; + break; + case 0x02: // /2 + prescale=0x2f; + fmFreqBase=9440540.0/3.0, + fmDivBase=24, + ayDiv=8; + break; + } + rate=fm->sample_rate(chipClock); + for (int i=0; i<16; i++) { + oscBuf[i]->rate=rate; + } + immWrite(0x2d,0xff); + immWrite(prescale,0xff); + ay->setExtClockDiv(chipClock,ayDiv); + ay->setFlags(16); +} + int DivPlatformYM2608::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; adpcmBMem=new unsigned char[getSampleMemCapacity(0)]; @@ -1414,17 +1332,13 @@ int DivPlatformYM2608::init(DivEngine* p, int channels, int sugRate, unsigned in isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - chipClock=8000000; fm=new ymfm::ym2608(iface); fm->set_fidelity(ymfm::OPN_FIDELITY_MIN); - rate=fm->sample_rate(chipClock); - for (int i=0; i<16; i++) { - oscBuf[i]->rate=rate; - } // YM2149, 2MHz - ay=new DivPlatformAY8910; - ay->init(p,3,sugRate,19); + ay=new DivPlatformAY8910(true,chipClock,ayDiv); + ay->init(p,3,sugRate,16); ay->toggleRegisterDump(true); + setFlags(flags); reset(); return 16; } diff --git a/src/engine/platform/ym2608.h b/src/engine/platform/ym2608.h index f65c4cd6..ed850bf8 100644 --- a/src/engine/platform/ym2608.h +++ b/src/engine/platform/ym2608.h @@ -19,9 +19,8 @@ #ifndef _YM2608_H #define _YM2608_H -#include "../dispatch.h" +#include "fmshared_OPN.h" #include "../macroInt.h" -#include #include "sound/ymfm/ymfm_opn.h" #include "ay.h" @@ -35,19 +34,23 @@ class DivYM2608Interface: public ymfm::ymfm_interface { DivYM2608Interface(): adpcmBMem(NULL), sampleBank(0) {} }; -class DivPlatformYM2608: public DivDispatch { +class DivPlatformYM2608: public DivPlatformOPN { protected: const unsigned short chanOffs[6]={ 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 }; + const unsigned char konOffs[6]={ + 0, 1, 2, 4, 5, 6 + }; + struct Channel { DivInstrumentFM state; unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; - unsigned char psgMode, autoEnvNum, autoEnvDen; + unsigned char psgMode, autoEnvNum, autoEnvDen, opMask; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged; int vol, outVol; int sample; unsigned char pan; @@ -69,6 +72,7 @@ class DivPlatformYM2608: public DivDispatch { psgMode(1), autoEnvNum(0), autoEnvDen(0), + opMask(15), active(false), insChanged(true), freqChanged(false), @@ -78,6 +82,7 @@ class DivPlatformYM2608: public DivDispatch { inPorta(false), furnacePCM(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(15), sample(-1), @@ -86,17 +91,8 @@ class DivPlatformYM2608: public DivDispatch { Channel chan[16]; DivDispatchOscBuffer* oscBuf[16]; bool isMuted[16]; - struct QueuedWrite { - unsigned short addr; - unsigned char val; - bool addrOrVal; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} - }; - std::queue writes; ymfm::ym2608* fm; ymfm::ym2608::output_data fmout; - unsigned char regPool[512]; - unsigned char lastBusy; unsigned char* adpcmBMem; size_t adpcmBMemLen; @@ -106,13 +102,9 @@ class DivPlatformYM2608: public DivDispatch { unsigned char sampleBank; unsigned char writeRSSOff, writeRSSOn; - int delay; - bool extMode; + unsigned char prescale; - short oldWrites[512]; - short pendingWrites[512]; - double NOTE_OPNB(int ch, int note); double NOTE_ADPCMB(int note); friend void putDispatchChan(void*,int,int); @@ -137,13 +129,16 @@ class DivPlatformYM2608: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); const void* getSampleMem(int index); size_t getSampleMemCapacity(int index); size_t getSampleMemUsage(int index); void renderSamples(); + void setFlags(unsigned int flags); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); + DivPlatformYM2608(): + DivPlatformOPN(9440540.0, 72, 32), + prescale(0x2d) {} ~DivPlatformYM2608(); }; #endif diff --git a/src/engine/platform/ym2608ext.cpp b/src/engine/platform/ym2608ext.cpp index f7d3217f..c6d7e03b 100644 --- a/src/engine/platform/ym2608ext.cpp +++ b/src/engine/platform/ym2608ext.cpp @@ -21,8 +21,8 @@ #include "../engine.h" #include -#include "ym2610shared.h" -#include "fmshared_OPN.h" +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase int DivPlatformYM2608Ext::dispatch(DivCommand c) { if (c.chan<2) { @@ -34,6 +34,10 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) { } int ch=c.chan-2; int ordch=orderedOps[ch]; + if (!extMode) { + c.chan=2; + return DivPlatformYM2608::dispatch(c); + } switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); @@ -55,6 +59,7 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) { rWrite(baseAddr+0x70,op.d2r&31); rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); rWrite(baseAddr+0x90,op.ssgEnv&15); + opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); @@ -151,6 +156,11 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) { opChan[ch].freqChanged=true; break; } + case DIV_CMD_FM_EXTCH: { + extMode=c.value; + immWrite(0x27,extMode?0x40:0); + break; + } case DIV_CMD_FM_LFO: { rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); break; @@ -349,7 +359,7 @@ void DivPlatformYM2608Ext::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -386,10 +396,12 @@ void DivPlatformYM2608Ext::tick(bool sysTick) { immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); } - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn) { writeNoteOn=true; - writeMask|=1<<(4+i); + if (opChan[i].mask) { + writeMask|=1<<(4+i); + } opChan[i].keyOn=false; } } @@ -528,6 +540,7 @@ int DivPlatformYM2608Ext::init(DivEngine* parent, int channels, int sugRate, uns for (int i=0; i<4; i++) { isOpMuted[i]=false; } + extSys=true; reset(); return 19; diff --git a/src/engine/platform/ym2608ext.h b/src/engine/platform/ym2608ext.h index bc3d4f99..21c8a35c 100644 --- a/src/engine/platform/ym2608ext.h +++ b/src/engine/platform/ym2608ext.h @@ -27,12 +27,12 @@ class DivPlatformYM2608Ext: public DivPlatformYM2608 { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask; int vol; unsigned char pan; // UGLY OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), - inPorta(false), vol(0), pan(3) {} + inPorta(false), mask(true), vol(0), pan(3) {} }; OpChannel opChan[4]; bool isOpMuted[4]; diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index ccb9bfda..6a8509d5 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -24,19 +24,8 @@ #include #include -#include "ym2610shared.h" - -#include "fmshared_OPN.h" - -static unsigned char konOffs[4]={ - 1, 2, 5, 6 -}; - -static unsigned char bchOffs[4]={ - 1, 2, 4, 5 -}; - -#define CHIP_DIVIDER 32 +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase const char* regCheatSheetYM2610[]={ // SSG @@ -329,126 +318,6 @@ const char** DivPlatformYM2610::getRegisterSheet() { return regCheatSheetYM2610; } -const char* DivPlatformYM2610::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xy: Setup LFO (x: enable; y: speed)"; - break; - case 0x11: - return "11xx: Set feedback (0 to 7)"; - break; - case 0x12: - return "12xx: Set level of operator 1 (0 highest, 7F lowest)"; - break; - case 0x13: - return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; - break; - case 0x14: - return "14xx: Set level of operator 3 (0 highest, 7F lowest)"; - break; - case 0x15: - return "15xx: Set level of operator 4 (0 highest, 7F lowest)"; - break; - case 0x16: - return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; - break; - case 0x18: - return "18xx: Toggle extended channel 3 mode"; - break; - case 0x19: - return "19xx: Set attack of all operators (0 to 1F)"; - break; - case 0x1a: - return "1Axx: Set attack of operator 1 (0 to 1F)"; - break; - case 0x1b: - return "1Bxx: Set attack of operator 2 (0 to 1F)"; - break; - case 0x1c: - return "1Cxx: Set attack of operator 3 (0 to 1F)"; - break; - case 0x1d: - return "1Dxx: Set attack of operator 4 (0 to 1F)"; - break; - case 0x20: - return "20xx: Set SSG channel mode (bit 0: square; bit 1: noise; bit 2: envelope)"; - break; - case 0x21: - return "21xx: Set SSG noise frequency (0 to 1F)"; - break; - case 0x22: - return "22xy: Set SSG envelope mode (x: shape, y: enable for this channel)"; - break; - case 0x23: - return "23xx: Set SSG envelope period low byte"; - break; - case 0x24: - return "24xx: Set SSG envelope period high byte"; - break; - case 0x25: - return "25xx: SSG envelope slide up"; - break; - case 0x26: - return "26xx: SSG envelope slide down"; - break; - case 0x29: - return "29xy: Set SSG auto-envelope (x: numerator; y: denominator)"; - break; - case 0x30: - return "30xx: Toggle hard envelope reset on new notes"; - break; - case 0x50: - return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; - break; - case 0x51: - return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; - break; - case 0x52: - return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; - break; - case 0x53: - return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)"; - break; - case 0x54: - return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)"; - break; - case 0x55: - return "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)"; - break; - case 0x56: - return "56xx: Set decay of all operators (0 to 1F)"; - break; - case 0x57: - return "57xx: Set decay of operator 1 (0 to 1F)"; - break; - case 0x58: - return "58xx: Set decay of operator 2 (0 to 1F)"; - break; - case 0x59: - return "59xx: Set decay of operator 3 (0 to 1F)"; - break; - case 0x5a: - return "5Axx: Set decay of operator 4 (0 to 1F)"; - break; - case 0x5b: - return "5Bxx: Set decay 2 of all operators (0 to 1F)"; - break; - case 0x5c: - return "5Cxx: Set decay 2 of operator 1 (0 to 1F)"; - break; - case 0x5d: - return "5Dxx: Set decay 2 of operator 2 (0 to 1F)"; - break; - case 0x5e: - return "5Exx: Set decay 2 of operator 3 (0 to 1F)"; - break; - case 0x5f: - return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; - break; - } - return NULL; -} - double DivPlatformYM2610::NOTE_OPNB(int ch, int note) { if (ch>6) { // ADPCM return NOTE_ADPCMB(note); @@ -494,7 +363,7 @@ void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t l fm->write(0x0+((w.addr>>8)<<1),w.addr); fm->write(0x1+((w.addr>>8)<<1),w.val); regPool[w.addr&0x1ff]=w.val; - writes.pop(); + writes.pop_front(); delay=4; } } @@ -558,18 +427,9 @@ void DivPlatformYM2610::tick(bool sysTick) { if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11); - } else { - chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11); - } + chan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(chan[i].note,chan[i].std.arp.val),11); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11); - chan[i].freqChanged=true; - } } if (chan[i].std.panL.had) { @@ -622,6 +482,10 @@ void DivPlatformYM2610::tick(bool sysTick) { chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } + if (chan[i].std.ex4.had && chan[i].active) { + chan[i].opMask=chan[i].std.ex4.val&15; + chan[i].opMaskChanged=true; + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -711,18 +575,9 @@ void DivPlatformYM2610::tick(bool sysTick) { if (chan[13].std.arp.had) { if (!chan[13].inPorta) { - if (chan[13].std.arp.mode) { - chan[13].baseFreq=NOTE_ADPCMB(chan[13].std.arp.val); - } else { - chan[13].baseFreq=NOTE_ADPCMB(chan[13].note+(signed char)chan[13].std.arp.val); - } + chan[13].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[13].note,chan[13].std.arp.val)); } chan[13].freqChanged=true; - } else { - if (chan[13].std.arp.mode && chan[13].std.arp.finished) { - chan[13].baseFreq=NOTE_ADPCMB(chan[13].note); - chan[13].freqChanged=true; - } } } if (chan[13].freqChanged) { @@ -767,8 +622,9 @@ void DivPlatformYM2610::tick(bool sysTick) { immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); chan[i].freqChanged=false; } - if (chan[i].keyOn) { - immWrite(0x28,0xf0|konOffs[i]); + if (chan[i].keyOn || chan[i].opMaskChanged) { + immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); + chan[i].opMaskChanged=false; chan[i].keyOn=false; } } @@ -804,7 +660,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { immWrite(0x14,(end>>8)&0xff); immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat + immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); @@ -839,7 +695,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { immWrite(0x14,(end>>8)&0xff); immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat + immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0); immWrite(0x19,freq&0xff); immWrite(0x1a,(freq>>8)&0xff); @@ -876,6 +732,11 @@ int DivPlatformYM2610::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; + chan[c.chan].opMask= + (chan[c.chan].state.op[0].enable?1:0)| + (chan[c.chan].state.op[2].enable?2:0)| + (chan[c.chan].state.op[1].enable?4:0)| + (chan[c.chan].state.op[3].enable?8:0); } for (int i=0; i<4; i++) { @@ -1043,6 +904,13 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; } + case DIV_CMD_FM_EXTCH: { + if (extSys) { + extMode=c.value; + immWrite(0x27,extMode?0x40:0); + } + break; + } case DIV_CMD_FM_LFO: { rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); break; @@ -1330,7 +1198,7 @@ void DivPlatformYM2610::poke(std::vector& wlist) { } void DivPlatformYM2610::reset() { - while (!writes.empty()) writes.pop(); + while (!writes.empty()) writes.pop_front(); memset(regPool,0,512); if (dumpWrites) { addWrite(0xffffffff,0); @@ -1402,6 +1270,22 @@ void DivPlatformYM2610::setSkipRegisterWrites(bool value) { ay->setSkipRegisterWrites(value); } +void DivPlatformYM2610::setFlags(unsigned int flags) { + switch (flags&0xff) { + default: + case 0x00: + chipClock=8000000.0; + break; + case 0x01: + chipClock=24167829/3; + break; + } + rate=chipClock/16; + for (int i=0; i<14; i++) { + oscBuf[i]->rate=rate; + } +} + int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { DivPlatformYM2610Base::init(p, channels, sugRate, flags); dumpWrites=false; @@ -1410,15 +1294,11 @@ int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, unsigned in isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - chipClock=8000000; - rate=chipClock/16; - for (int i=0; i<14; i++) { - oscBuf[i]->rate=rate; - } fm=new ymfm::ym2610(iface); + setFlags(flags); // YM2149, 2MHz - ay=new DivPlatformAY8910; - ay->init(p,3,sugRate,19); + ay=new DivPlatformAY8910(true,chipClock,32); + ay->init(p,3,sugRate,16); ay->toggleRegisterDump(true); reset(); return 14; diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index 1d807213..4f1a1664 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -19,9 +19,8 @@ #ifndef _YM2610_H #define _YM2610_H -#include "../dispatch.h" +#include "fmshared_OPN.h" #include "../macroInt.h" -#include #include "ay.h" #include "sound/ymfm/ymfm_opn.h" @@ -35,7 +34,7 @@ class DivYM2610Interface: public ymfm::ymfm_interface { DivYM2610Interface(): adpcmAMem(NULL), adpcmBMem(NULL), sampleBank(0) {} }; -class DivPlatformYM2610Base: public DivDispatch { +class DivPlatformYM2610Base: public DivPlatformOPN { protected: unsigned char* adpcmAMem; size_t adpcmAMemLen; @@ -50,6 +49,8 @@ class DivPlatformYM2610Base: public DivDispatch { void renderSamples(); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); + DivPlatformYM2610Base(): + DivPlatformOPN(9440540.0, 72, 32) {} }; class DivPlatformYM2610: public DivPlatformYM2610Base { @@ -58,16 +59,24 @@ class DivPlatformYM2610: public DivPlatformYM2610Base { 0x01, 0x02, 0x101, 0x102 }; + const unsigned char konOffs[4]={ + 1, 2, 5, 6 + }; + + const unsigned char bchOffs[4]={ + 1, 2, 4, 5 + }; + struct Channel { DivInstrumentFM state; unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; unsigned char psgMode, autoEnvNum, autoEnvDen; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged; int vol, outVol; int sample; - unsigned char pan; + unsigned char pan, opMask; DivMacroInt std; void macroInit(DivInstrument* which) { std.init(which); @@ -95,37 +104,25 @@ class DivPlatformYM2610: public DivPlatformYM2610Base { inPorta(false), furnacePCM(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(15), sample(-1), - pan(3) {} + pan(3), + opMask(15) {} }; Channel chan[14]; DivDispatchOscBuffer* oscBuf[14]; bool isMuted[14]; - struct QueuedWrite { - unsigned short addr; - unsigned char val; - bool addrOrVal; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} - }; - std::queue writes; ymfm::ym2610* fm; ymfm::ym2610::output_data fmout; DivPlatformAY8910* ay; - unsigned char regPool[512]; - unsigned char lastBusy; unsigned char sampleBank; - int delay; - bool extMode; - short oldWrites[512]; - short pendingWrites[512]; - double NOTE_OPNB(int ch, int note); double NOTE_ADPCMB(int note); friend void putDispatchChan(void*,int,int); @@ -150,7 +147,7 @@ class DivPlatformYM2610: public DivPlatformYM2610Base { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); + void setFlags(unsigned int flags); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformYM2610(); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index c327645b..31cd7b3f 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -23,15 +23,8 @@ #include #include -#include "ym2610shared.h" - -#include "fmshared_OPN.h" - -static unsigned char konOffs[6]={ - 0, 1, 2, 4, 5, 6 -}; - -#define CHIP_DIVIDER 32 +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase const char* regCheatSheetYM2610B[]={ // SSG @@ -309,126 +302,6 @@ const char** DivPlatformYM2610B::getRegisterSheet() { return regCheatSheetYM2610B; } -const char* DivPlatformYM2610B::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xy: Setup LFO (x: enable; y: speed)"; - break; - case 0x11: - return "11xx: Set feedback (0 to 7)"; - break; - case 0x12: - return "12xx: Set level of operator 1 (0 highest, 7F lowest)"; - break; - case 0x13: - return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; - break; - case 0x14: - return "14xx: Set level of operator 3 (0 highest, 7F lowest)"; - break; - case 0x15: - return "15xx: Set level of operator 4 (0 highest, 7F lowest)"; - break; - case 0x16: - return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; - break; - case 0x18: - return "18xx: Toggle extended channel 3 mode"; - break; - case 0x19: - return "19xx: Set attack of all operators (0 to 1F)"; - break; - case 0x1a: - return "1Axx: Set attack of operator 1 (0 to 1F)"; - break; - case 0x1b: - return "1Bxx: Set attack of operator 2 (0 to 1F)"; - break; - case 0x1c: - return "1Cxx: Set attack of operator 3 (0 to 1F)"; - break; - case 0x1d: - return "1Dxx: Set attack of operator 4 (0 to 1F)"; - break; - case 0x20: - return "20xx: Set SSG channel mode (bit 0: square; bit 1: noise; bit 2: envelope)"; - break; - case 0x21: - return "21xx: Set SSG noise frequency (0 to 1F)"; - break; - case 0x22: - return "22xy: Set SSG envelope mode (x: shape, y: enable for this channel)"; - break; - case 0x23: - return "23xx: Set SSG envelope period low byte"; - break; - case 0x24: - return "24xx: Set SSG envelope period high byte"; - break; - case 0x25: - return "25xx: SSG envelope slide up"; - break; - case 0x26: - return "26xx: SSG envelope slide down"; - break; - case 0x29: - return "29xy: Set SSG auto-envelope (x: numerator; y: denominator)"; - break; - case 0x30: - return "30xx: Toggle hard envelope reset on new notes"; - break; - case 0x50: - return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; - break; - case 0x51: - return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; - break; - case 0x52: - return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; - break; - case 0x53: - return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)"; - break; - case 0x54: - return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)"; - break; - case 0x55: - return "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)"; - break; - case 0x56: - return "56xx: Set decay of all operators (0 to 1F)"; - break; - case 0x57: - return "57xx: Set decay of operator 1 (0 to 1F)"; - break; - case 0x58: - return "58xx: Set decay of operator 2 (0 to 1F)"; - break; - case 0x59: - return "59xx: Set decay of operator 3 (0 to 1F)"; - break; - case 0x5a: - return "5Axx: Set decay of operator 4 (0 to 1F)"; - break; - case 0x5b: - return "5Bxx: Set decay 2 of all operators (0 to 1F)"; - break; - case 0x5c: - return "5Cxx: Set decay 2 of operator 1 (0 to 1F)"; - break; - case 0x5d: - return "5Dxx: Set decay 2 of operator 2 (0 to 1F)"; - break; - case 0x5e: - return "5Exx: Set decay 2 of operator 3 (0 to 1F)"; - break; - case 0x5f: - return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; - break; - } - return NULL; -} - double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) { if (ch>8) { // ADPCM-B return NOTE_ADPCMB(note); @@ -472,7 +345,7 @@ void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t fm->write(0x0+((w.addr>>8)<<1),w.addr); fm->write(0x1+((w.addr>>8)<<1),w.val); regPool[w.addr&0x1ff]=w.val; - writes.pop(); + writes.pop_front(); delay=4; } } @@ -537,18 +410,9 @@ void DivPlatformYM2610B::tick(bool sysTick) { if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11); - } else { - chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11); - } + chan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(chan[i].note,chan[i].std.arp.val),11); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11); - chan[i].freqChanged=true; - } } if (chan[i].std.panL.had) { @@ -601,6 +465,10 @@ void DivPlatformYM2610B::tick(bool sysTick) { chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } + if (chan[i].std.ex4.had && chan[i].active) { + chan[i].opMask=chan[i].std.ex4.val&15; + chan[i].opMaskChanged=true; + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -689,18 +557,9 @@ void DivPlatformYM2610B::tick(bool sysTick) { if (chan[15].std.arp.had) { if (!chan[15].inPorta) { - if (chan[15].std.arp.mode) { - chan[15].baseFreq=NOTE_ADPCMB(chan[15].std.arp.val); - } else { - chan[15].baseFreq=NOTE_ADPCMB(chan[15].note+(signed char)chan[15].std.arp.val); - } + chan[15].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[15].note,chan[15].std.arp.val)); } chan[15].freqChanged=true; - } else { - if (chan[15].std.arp.mode && chan[15].std.arp.finished) { - chan[15].baseFreq=NOTE_ADPCMB(chan[15].note); - chan[15].freqChanged=true; - } } } if (chan[15].freqChanged) { @@ -745,8 +604,9 @@ void DivPlatformYM2610B::tick(bool sysTick) { immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); chan[i].freqChanged=false; } - if (chan[i].keyOn) { - immWrite(0x28,0xf0|konOffs[i]); + if (chan[i].keyOn || chan[i].opMaskChanged) { + immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]); + chan[i].opMaskChanged=false; chan[i].keyOn=false; } } @@ -782,7 +642,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { immWrite(0x14,(end>>8)&0xff); immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat + immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); @@ -817,7 +677,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { immWrite(0x14,(end>>8)&0xff); immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat + immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0); immWrite(0x19,freq&0xff); immWrite(0x1a,(freq>>8)&0xff); @@ -854,6 +714,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; + chan[c.chan].opMask= + (chan[c.chan].state.op[0].enable?1:0)| + (chan[c.chan].state.op[2].enable?2:0)| + (chan[c.chan].state.op[1].enable?4:0)| + (chan[c.chan].state.op[3].enable?8:0); } for (int i=0; i<4; i++) { @@ -1021,6 +886,13 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; } + case DIV_CMD_FM_EXTCH: { + if (extSys) { + extMode=c.value; + immWrite(0x27,extMode?0x40:0); + } + break; + } case DIV_CMD_FM_LFO: { rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); break; @@ -1308,7 +1180,7 @@ void DivPlatformYM2610B::poke(std::vector& wlist) { } void DivPlatformYM2610B::reset() { - while (!writes.empty()) writes.pop(); + while (!writes.empty()) writes.pop_front(); memset(regPool,0,512); if (dumpWrites) { addWrite(0xffffffff,0); @@ -1380,6 +1252,22 @@ void DivPlatformYM2610B::setSkipRegisterWrites(bool value) { ay->setSkipRegisterWrites(value); } +void DivPlatformYM2610B::setFlags(unsigned int flags) { + switch (flags&0xff) { + default: + case 0x00: + chipClock=8000000.0; + break; + case 0x01: + chipClock=24167829/3; + break; + } + rate=chipClock/16; + for (int i=0; i<16; i++) { + oscBuf[i]->rate=rate; + } +} + int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { DivPlatformYM2610Base::init(p, channels, sugRate, flags); dumpWrites=false; @@ -1388,15 +1276,11 @@ int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned i isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - chipClock=8000000; - rate=chipClock/16; - for (int i=0; i<16; i++) { - oscBuf[i]->rate=rate; - } fm=new ymfm::ym2610b(iface); + setFlags(flags); // YM2149, 2MHz - ay=new DivPlatformAY8910; - ay->init(p,3,sugRate,19); + ay=new DivPlatformAY8910(true,chipClock,32); + ay->init(p,3,sugRate,16); ay->toggleRegisterDump(true); reset(); return 16; diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index fe2126dd..1d5fc1f3 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -19,12 +19,10 @@ #ifndef _YM2610B_H #define _YM2610B_H -#include "../dispatch.h" +#include "ym2610.h" #include "../macroInt.h" -#include #include "sound/ymfm/ymfm_opn.h" -#include "ym2610.h" class DivPlatformYM2610B: public DivPlatformYM2610Base { protected: @@ -32,16 +30,20 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base { 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 }; + const unsigned char konOffs[6]={ + 0, 1, 2, 4, 5, 6 + }; + struct Channel { DivInstrumentFM state; unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; unsigned char psgMode, autoEnvNum, autoEnvDen; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged; int vol, outVol; int sample; - unsigned char pan; + unsigned char pan, opMask; DivMacroInt std; void macroInit(DivInstrument* which) { std.init(which); @@ -69,35 +71,25 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base { inPorta(false), furnacePCM(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(15), sample(-1), - pan(3) {} + pan(3), + opMask(15) {} }; Channel chan[16]; DivDispatchOscBuffer* oscBuf[16]; bool isMuted[16]; - struct QueuedWrite { - unsigned short addr; - unsigned char val; - bool addrOrVal; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} - }; - std::queue writes; ymfm::ym2610b* fm; ymfm::ym2610b::output_data fmout; - unsigned char regPool[512]; - unsigned char lastBusy; DivPlatformAY8910* ay; unsigned char sampleBank; - int delay; - bool extMode; - - short oldWrites[512]; - short pendingWrites[512]; + double fmFreqBase=9440540; + unsigned char ayDiv=32; double NOTE_OPNB(int ch, int note); double NOTE_ADPCMB(int note); @@ -123,7 +115,7 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); + void setFlags(unsigned int flags); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformYM2610B(); diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index f208bf18..f55e6561 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -21,8 +21,8 @@ #include "../engine.h" #include -#include "ym2610shared.h" -#include "fmshared_OPN.h" +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase int DivPlatformYM2610BExt::dispatch(DivCommand c) { if (c.chan<2) { @@ -34,6 +34,10 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { } int ch=c.chan-2; int ordch=orderedOps[ch]; + if (!extMode) { + c.chan=2; + return DivPlatformYM2610B::dispatch(c); + } switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); @@ -55,6 +59,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { rWrite(baseAddr+0x70,op.d2r&31); rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); rWrite(baseAddr+0x90,op.ssgEnv&15); + opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); @@ -151,6 +156,11 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { opChan[ch].freqChanged=true; break; } + case DIV_CMD_FM_EXTCH: { + extMode=c.value; + immWrite(0x27,extMode?0x40:0); + break; + } case DIV_CMD_FM_LFO: { rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); break; @@ -349,7 +359,7 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -386,10 +396,12 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); } - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn) { writeNoteOn=true; - writeMask|=1<<(4+i); + if (opChan[i].mask) { + writeMask|=1<<(4+i); + } opChan[i].keyOn=false; } } @@ -427,7 +439,7 @@ void DivPlatformYM2610BExt::forceIns() { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; - if (i==2) { // extended channel + if (i==2 && extMode) { // extended channel if (isOpMuted[j]) { rWrite(baseAddr+0x40,127); } else if (isOutput[chan[i].state.alg][j]) { @@ -528,6 +540,7 @@ int DivPlatformYM2610BExt::init(DivEngine* parent, int channels, int sugRate, un for (int i=0; i<4; i++) { isOpMuted[i]=false; } + extSys=true; reset(); return 19; diff --git a/src/engine/platform/ym2610bext.h b/src/engine/platform/ym2610bext.h index 732678fe..e60f9713 100644 --- a/src/engine/platform/ym2610bext.h +++ b/src/engine/platform/ym2610bext.h @@ -27,12 +27,12 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask; int vol; unsigned char pan; // UGLY OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), - inPorta(false), vol(0), pan(3) {} + inPorta(false), mask(true), vol(0), pan(3) {} }; OpChannel opChan[4]; bool isOpMuted[4]; diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index af9a3da0..6cee242f 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -21,8 +21,8 @@ #include "../engine.h" #include -#include "ym2610shared.h" -#include "fmshared_OPN.h" +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase int DivPlatformYM2610Ext::dispatch(DivCommand c) { if (c.chan<1) { @@ -34,6 +34,10 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { } int ch=c.chan-1; int ordch=orderedOps[ch]; + if (!extMode) { + c.chan=2; + return DivPlatformYM2610::dispatch(c); + } switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); @@ -55,6 +59,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { rWrite(baseAddr+0x70,op.d2r&31); rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); rWrite(baseAddr+0x90,op.ssgEnv&15); + opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? rWrite(chanOffs[1]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); @@ -151,6 +156,11 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { opChan[ch].freqChanged=true; break; } + case DIV_CMD_FM_EXTCH: { + extMode=c.value; + immWrite(0x27,extMode?0x40:0); + break; + } case DIV_CMD_FM_LFO: { rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); break; @@ -349,7 +359,7 @@ void DivPlatformYM2610Ext::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -386,10 +396,12 @@ void DivPlatformYM2610Ext::tick(bool sysTick) { immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); } - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn) { writeNoteOn=true; - writeMask|=1<<(4+i); + if (opChan[i].mask) { + writeMask|=1<<(4+i); + } opChan[i].keyOn=false; } } @@ -427,7 +439,7 @@ void DivPlatformYM2610Ext::forceIns() { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; - if (i==1) { // extended channel + if (i==1 && extMode) { // extended channel if (isOpMuted[j]) { rWrite(baseAddr+0x40,127); } else if (isOutput[chan[i].state.alg][j]) { @@ -528,6 +540,7 @@ int DivPlatformYM2610Ext::init(DivEngine* parent, int channels, int sugRate, uns for (int i=0; i<4; i++) { isOpMuted[i]=false; } + extSys=true; reset(); return 17; diff --git a/src/engine/platform/ym2610ext.h b/src/engine/platform/ym2610ext.h index 119d6356..07d855c0 100644 --- a/src/engine/platform/ym2610ext.h +++ b/src/engine/platform/ym2610ext.h @@ -27,12 +27,12 @@ class DivPlatformYM2610Ext: public DivPlatformYM2610 { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask; int vol; unsigned char pan; // UGLY OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), - inPorta(false), vol(0), pan(3) {} + inPorta(false), mask(true), vol(0), pan(3) {} }; OpChannel opChan[4]; bool isOpMuted[4]; diff --git a/src/engine/platform/ymz280b.cpp b/src/engine/platform/ymz280b.cpp index ed50ccd4..eb65a7bd 100644 --- a/src/engine/platform/ymz280b.cpp +++ b/src/engine/platform/ymz280b.cpp @@ -23,7 +23,7 @@ #include #include -#define CHIP_FREQBASE 98304 +#define CHIP_FREQBASE 25165824 #define rWrite(a,v) {if(!skipRegisterWrites) {ymz280b.write(0,a); ymz280b.write(1,v); regPool[a]=v; if(dumpWrites) addWrite(a,v); }} @@ -60,10 +60,6 @@ const char** DivPlatformYMZ280B::getRegisterSheet() { return regCheatSheetYMZ280B; } -const char* DivPlatformYMZ280B::getEffectName(unsigned char effect) { - return NULL; -} - void DivPlatformYMZ280B::acquire(short* bufL, short* bufR, size_t start, size_t len) { short buf[16][256]; short *bufPtrs[16]={ @@ -99,18 +95,9 @@ void DivPlatformYMZ280B::tick(bool sysTick) { } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.pitch.had) { if (chan[i].std.pitch.mode) { @@ -136,50 +123,54 @@ void DivPlatformYMZ280B::tick(bool sysTick) { DivSample* s=parent->getSample(chan[i].sample); unsigned char ctrl; switch (s->depth) { - case 3: ctrl=0x20; break; - case 8: ctrl=0x40; break; - case 16: ctrl=0x60; break; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: ctrl=0x20; break; + case DIV_SAMPLE_DEPTH_8BIT: ctrl=0x40; break; + case DIV_SAMPLE_DEPTH_16BIT: ctrl=0x60; break; default: ctrl=0; } double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0; - chan[i].freq=(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE))-1; + chan[i].freq=(int)round(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE)/256.0)-1; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>511) chan[i].freq=511; // ADPCM has half the range - if (s->depth==3 && chan[i].freq>255) chan[i].freq=255; - ctrl|=(chan[i].active?0x80:0)|((s->loopStart>=0)?0x10:0)|(chan[i].freq>>8); + if (s->depth==DIV_SAMPLE_DEPTH_YMZ_ADPCM && chan[i].freq>255) chan[i].freq=255; + ctrl|=(chan[i].active?0x80:0)|((s->isLoopable())?0x10:0)|(chan[i].freq>>8); if (chan[i].keyOn) { unsigned int start=s->offYMZ280B; - unsigned int loop=0; + unsigned int loopStart=0; + unsigned int loopEnd=0; unsigned int end=MIN(start+s->getCurBufLen(),getSampleMemCapacity()-1); if (chan[i].audPos>0) { switch (s->depth) { - case 3: start+=chan[i].audPos/2; break; - case 8: start+=chan[i].audPos; break; - case 16: start+=chan[i].audPos*2; break; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: start+=chan[i].audPos/2; break; + case DIV_SAMPLE_DEPTH_8BIT: start+=chan[i].audPos; break; + case DIV_SAMPLE_DEPTH_16BIT: start+=chan[i].audPos*2; break; + default: break; } start=MIN(start,end); } - if (s->loopStart>=0) { + if (s->isLoopable()) { switch (s->depth) { - case 3: loop=start+s->loopStart/2; break; - case 8: loop=start+s->loopStart; break; - case 16: loop=start+s->loopStart*2; break; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: loopStart=start+s->loopStart/2; loopEnd=start+s->loopEnd/2; break; + case DIV_SAMPLE_DEPTH_8BIT: loopStart=start+s->loopStart; loopEnd=start+s->loopEnd; break; + case DIV_SAMPLE_DEPTH_16BIT: loopStart=start+s->loopStart*2; loopEnd=start+s->loopEnd*2; break; + default: break; } - loop=MIN(loop,end); + loopEnd=MIN(loopEnd,end); + loopStart=MIN(loopStart,loopEnd); } rWrite(0x01+i*4,ctrl&~0x80); // force keyoff first rWrite(0x20+i*4,(start>>16)&0xff); - rWrite(0x21+i*4,(loop>>16)&0xff); - rWrite(0x22+i*4,(end>>16)&0xff); + rWrite(0x21+i*4,(loopStart>>16)&0xff); + rWrite(0x22+i*4,(loopEnd>>16)&0xff); rWrite(0x23+i*4,(end>>16)&0xff); rWrite(0x40+i*4,(start>>8)&0xff); - rWrite(0x41+i*4,(loop>>8)&0xff); - rWrite(0x42+i*4,(end>>8)&0xff); + rWrite(0x41+i*4,(loopStart>>8)&0xff); + rWrite(0x42+i*4,(loopEnd>>8)&0xff); rWrite(0x43+i*4,(end>>8)&0xff); rWrite(0x60+i*4,start&0xff); - rWrite(0x61+i*4,loop&0xff); - rWrite(0x62+i*4,end&0xff); + rWrite(0x61+i*4,loopStart&0xff); + rWrite(0x62+i*4,loopEnd&0xff); rWrite(0x63+i*4,end&0xff); if (!chan[i].std.vol.had) { chan[i].outVol=chan[i].vol; @@ -263,14 +254,15 @@ int DivPlatformYMZ280B::dispatch(DivCommand c) { case DIV_CMD_NOTE_PORTA: { int destFreq=NOTE_FREQUENCY(c.value2); bool return2=false; + int multiplier=(parent->song.linearPitch==2)?1:256; if (destFreq>chan[c.chan].baseFreq) { - chan[c.chan].baseFreq+=c.value; + chan[c.chan].baseFreq+=c.value*multiplier; if (chan[c.chan].baseFreq>=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; } } else { - chan[c.chan].baseFreq-=c.value; + chan[c.chan].baseFreq-=c.value*multiplier; if (chan[c.chan].baseFreq<=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; @@ -293,6 +285,7 @@ int DivPlatformYMZ280B::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_SAMPLE_POS: @@ -326,6 +319,8 @@ void DivPlatformYMZ280B::forceIns() { chan[i].insChanged=true; chan[i].freqChanged=true; chan[i].sample=-1; + + rWrite(0x03+i*4,chan[i].panning); } } @@ -435,6 +430,42 @@ void DivPlatformYMZ280B::setChipModel(int type) { chipType=type; } +void DivPlatformYMZ280B::setFlags(unsigned int flags) { + switch (chipType) { + default: + case 280: + switch (flags&0xff) { + case 0x00: + chipClock=16934400; + break; + case 0x01: + chipClock=COLOR_NTSC*4.0; + break; + case 0x02: + chipClock=COLOR_PAL*16.0/5.0; + break; + case 0x03: + chipClock=16000000; + break; + case 0x04: + chipClock=50000000.0/3.0; + break; + case 0x05: + chipClock=14000000; + break; + } + rate=chipClock/384; + break; + case 759: + rate=32000; + chipClock=rate*384; + break; + } + for (int i=0; i<8; i++) { + oscBuf[i]->rate=rate; + } +} + int DivPlatformYMZ280B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; @@ -444,18 +475,12 @@ int DivPlatformYMZ280B::init(DivEngine* p, int channels, int sugRate, unsigned i isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - setFlags(flags); - - rate=(chipType==759)?32000:44100; - chipClock=rate*384; sampleMem=new unsigned char[getSampleMemCapacity()]; sampleMemLen=0; ymz280b.device_start(sampleMem); + setFlags(flags); reset(); - for (int i=0; i<8; i++) { - oscBuf[i]->rate=rate; - } return 8; } diff --git a/src/engine/platform/ymz280b.h b/src/engine/platform/ymz280b.h index fbba3319..6dbe2623 100644 --- a/src/engine/platform/ymz280b.h +++ b/src/engine/platform/ymz280b.h @@ -91,11 +91,11 @@ class DivPlatformYMZ280B: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); const void* getSampleMem(int index = 0); size_t getSampleMemCapacity(int index = 0); size_t getSampleMemUsage(int index = 0); void renderSamples(); + void setFlags(unsigned int flags); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); private: diff --git a/src/engine/platform/zxbeeper.cpp b/src/engine/platform/zxbeeper.cpp index 34265f8a..5fa3840b 100644 --- a/src/engine/platform/zxbeeper.cpp +++ b/src/engine/platform/zxbeeper.cpp @@ -27,18 +27,6 @@ const char** DivPlatformZXBeeper::getRegisterSheet() { return NULL; } -const char* DivPlatformZXBeeper::getEffectName(unsigned char effect) { - switch (effect) { - case 0x12: - return "12xx: Set pulse width"; - break; - case 0x17: - return "17xx: Trigger overlay drum"; - break; - } - return NULL; -} - void DivPlatformZXBeeper::acquire(short* bufL, short* bufR, size_t start, size_t len) { bool o=false; for (size_t h=start; hdata[oscBuf[0]->needle++]=o?16384:-16384; continue; } @@ -76,6 +65,7 @@ void DivPlatformZXBeeper::acquire(short* bufL, short* bufR, size_t start, size_t if (++curChan>=6) curChan=0; bufL[h]=o?16384:0; + oscBuf[0]->data[oscBuf[0]->needle++]=o?16384:-16384; } } @@ -91,18 +81,9 @@ void DivPlatformZXBeeper::tick(bool sysTick) { } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.pitch.had) { if (chan[i].std.pitch.mode) { @@ -222,6 +203,7 @@ int DivPlatformZXBeeper::dispatch(DivCommand c) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER)); } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note); chan[c.chan].inPorta=c.value; break; case DIV_CMD_GET_VOLMAX: @@ -256,7 +238,7 @@ DivMacroInt* DivPlatformZXBeeper::getChanMacroInt(int ch) { } DivDispatchOscBuffer* DivPlatformZXBeeper::getOscBuffer(int ch) { - return oscBuf[ch]; + return (ch<1)?oscBuf[ch]:NULL; } unsigned char* DivPlatformZXBeeper::getRegisterPool() { diff --git a/src/engine/platform/zxbeeper.h b/src/engine/platform/zxbeeper.h index a9b400cb..226f556d 100644 --- a/src/engine/platform/zxbeeper.h +++ b/src/engine/platform/zxbeeper.h @@ -92,7 +92,6 @@ class DivPlatformZXBeeper: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformZXBeeper(); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index ed70da94..898bb2b0 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -24,9 +24,6 @@ #include "engine.h" #include "../ta-log.h" #include -#ifdef HAVE_SNDFILE -#include -#endif constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0; @@ -34,7 +31,9 @@ void DivEngine::nextOrder() { curRow=0; if (repeatPattern) return; if (++curOrder>=curSubSong->ordersLen) { + logV("end of orders reached"); endOfSong=true; + memset(walked,0,8192); curOrder=0; } } @@ -60,6 +59,16 @@ const char* cmdName[]={ "PRE_PORTA", "PRE_NOTE", + "HINT_VIBRATO", + "HINT_VIBRATO_RANGE", + "HINT_VIBRATO_SHAPE", + "HINT_PITCH", + "HINT_ARPEGGIO", + "HINT_VOLUME", + "HINT_VOL_SLIDE", + "HINT_PORTA", + "HINT_LEGATO", + "SAMPLE_MODE", "SAMPLE_FREQ", "SAMPLE_BANK", @@ -204,7 +213,27 @@ const char* formatNote(unsigned char note, unsigned char octave) { int DivEngine::dispatchCmd(DivCommand c) { if (view==DIV_STATUS_COMMANDS) { - printf("%8d | %d: %s(%d, %d)\n",totalTicksR,c.chan,cmdName[c.cmd],c.value,c.value2); + if (!skipping) { + switch (c.cmd) { + // strip away hinted/useless commands + case DIV_ALWAYS_SET_VOLUME: + break; + case DIV_CMD_GET_VOLUME: + break; + case DIV_CMD_VOLUME: + break; + case DIV_CMD_NOTE_PORTA: + break; + case DIV_CMD_LEGATO: + break; + case DIV_CMD_PITCH: + break; + case DIV_CMD_PRE_NOTE: + break; + default: + printf("%8d | %d: %s(%d, %d)\n",totalTicksR,c.chan,cmdName[c.cmd],c.value,c.value2); + } + } } totalCmds++; if (cmdStreamEnabled && cmdStream.size()<2000) { @@ -265,13 +294,39 @@ int DivEngine::dispatchCmd(DivCommand c) { } bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effectVal) { - if (sysDefs[sysOfChan[ch]]==NULL) return false; - return sysDefs[sysOfChan[ch]]->effectFunc(ch,effect,effectVal); + DivSysDef* sysDef=sysDefs[sysOfChan[ch]]; + if (sysDef==NULL) return false; + auto iter=sysDef->effectHandlers.find(effect); + if (iter==sysDef->effectHandlers.end()) return false; + EffectHandler handler=iter->second; + int val=0; + int val2=0; + try { + val=handler.val?handler.val(effect,effectVal):effectVal; + val2=handler.val2?handler.val2(effect,effectVal):0; + } catch (DivDoNotHandleEffect& e) { + return false; + } + // wouldn't this cause problems if it were to return 0? + return dispatchCmd(DivCommand(handler.dispatchCmd,ch,val,val2)); } bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char effectVal) { - if (sysDefs[sysOfChan[ch]]==NULL) return false; - return sysDefs[sysOfChan[ch]]->postEffectFunc(ch,effect,effectVal); + DivSysDef* sysDef=sysDefs[sysOfChan[ch]]; + if (sysDef==NULL) return false; + auto iter=sysDef->postEffectHandlers.find(effect); + if (iter==sysDef->postEffectHandlers.end()) return false; + EffectHandler handler=iter->second; + int val=0; + int val2=0; + try { + val=handler.val?handler.val(effect,effectVal):effectVal; + val2=handler.val2?handler.val2(effect,effectVal):0; + } catch (DivDoNotHandleEffect& e) { + return true; + } + // wouldn't this cause problems if it were to return 0? + return dispatchCmd(DivCommand(handler.dispatchCmd,ch,val,val2)); } void DivEngine::processRow(int i, bool afterDelay) { @@ -295,20 +350,38 @@ void DivEngine::processRow(int i, bool afterDelay) { if (effectVal>0) speed2=effectVal; break; case 0x0b: // change order - if (changeOrd==-1) { + if (changeOrd==-1 || song.jumpTreatment==0) { changeOrd=effectVal; - changePos=0; + if (song.jumpTreatment==1 || song.jumpTreatment==2) { + changePos=0; + } } break; case 0x0d: // next order - if (changeOrd<0 && (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) { - changeOrd=-2; - changePos=effectVal; + if (song.jumpTreatment==2) { + if ((curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) { + changeOrd=-2; + changePos=effectVal; + } + } else if (song.jumpTreatment==1) { + if (changeOrd<0 && (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) { + changeOrd=-2; + changePos=effectVal; + } + } else { + if (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd) { + if (changeOrd<0) { + changeOrd=-2; + } + changePos=effectVal; + } } break; case 0xed: // delay if (effectVal!=0) { - if (effectVal<=nextSpeed) { + bool comparison=(song.delayBehavior==1)?(effectVal<=nextSpeed):(effectVal%d",effectVal,nextSpeed); chan[i].delayLocked=false; } } @@ -326,6 +400,8 @@ void DivEngine::processRow(int i, bool afterDelay) { } } if (returnAfterPre) return; + } else { + logV("honoring delay at position %d",whatRow); } if (chan[i].delayLocked) return; @@ -333,10 +409,16 @@ void DivEngine::processRow(int i, bool afterDelay) { // instrument bool insChanged=false; if (pat->data[whatRow][2]!=-1) { - dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,i,pat->data[whatRow][2])); if (chan[i].lastIns!=pat->data[whatRow][2]) { + dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,i,pat->data[whatRow][2])); chan[i].lastIns=pat->data[whatRow][2]; insChanged=true; + if (song.legacyVolumeSlides && chan[i].volume==chan[i].volMax+1) { + logV("forcing volume"); + chan[i].volume=chan[i].volMax; + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); + } } } // note @@ -348,11 +430,13 @@ void DivEngine::processRow(int i, bool afterDelay) { if (chan[i].stopOnOff) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].stopOnOff=false; } if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { chan[i+1].portaNote=-1; chan[i+1].portaSpeed=-1; @@ -369,11 +453,13 @@ void DivEngine::processRow(int i, bool afterDelay) { if (chan[i].stopOnOff) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].stopOnOff=false; } if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { chan[i+1].portaNote=-1; chan[i+1].portaSpeed=-1; @@ -390,6 +476,7 @@ void DivEngine::processRow(int i, bool afterDelay) { if (!chan[i].keyOn) { if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsArp(dispatchChanOfChan[i])) { chan[i].arp=0; + dispatchCmd(DivCommand(DIV_CMD_HINT_ARPEGGIO,i,chan[i].arp)); } } chan[i].doNote=true; @@ -406,6 +493,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } chan[i].volume=pat->data[whatRow][3]<<8; dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); } } @@ -450,15 +538,18 @@ void DivEngine::processRow(int i, bool afterDelay) { if (effectVal==0) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].inPorta=false; if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); } else { chan[i].portaNote=song.limitSlides?0x60:255; chan[i].portaSpeed=effectVal; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].portaStop=true; chan[i].nowYouCanStop=false; chan[i].stopOnOff=false; chan[i].scheduledSlideReset=false; + chan[i].wasShorthandPorta=false; chan[i].inPorta=false; if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0)); } @@ -469,15 +560,18 @@ void DivEngine::processRow(int i, bool afterDelay) { if (effectVal==0) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].inPorta=false; if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); } else { chan[i].portaNote=song.limitSlides?disCont[dispatchOfChan[i]].dispatch->getPortaFloor(dispatchChanOfChan[i]):-60; chan[i].portaSpeed=effectVal; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].portaStop=true; chan[i].nowYouCanStop=false; chan[i].stopOnOff=false; chan[i].scheduledSlideReset=false; + chan[i].wasShorthandPorta=false; chan[i].inPorta=false; if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0)); } @@ -486,6 +580,7 @@ void DivEngine::processRow(int i, bool afterDelay) { if (effectVal==0) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].inPorta=false; dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); } else { @@ -497,7 +592,9 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].portaNote=chan[i].note; chan[i].portaSpeed=effectVal; chan[i].inPorta=true; + chan[i].wasShorthandPorta=false; } + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].portaStop=true; if (chan[i].keyOn) chan[i].doNote=false; chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?! @@ -509,6 +606,7 @@ void DivEngine::processRow(int i, bool afterDelay) { case 0x04: // vibrato chan[i].vibratoDepth=effectVal&15; chan[i].vibratoRate=effectVal>>4; + dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO,i,chan[i].vibratoDepth,chan[i].vibratoRate)); dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); break; case 0x07: // tremolo @@ -532,12 +630,14 @@ void DivEngine::processRow(int i, bool afterDelay) { } else { chan[i].volSpeed=0; } + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; case 0x00: // arpeggio chan[i].arp=effectVal; if (chan[i].arp==0 && song.arp0Reset) { chan[i].resetArp=true; } + dispatchCmd(DivCommand(DIV_CMD_HINT_ARPEGGIO,i,chan[i].arp)); break; case 0x0c: // retrigger if (effectVal!=0) { @@ -553,14 +653,11 @@ void DivEngine::processRow(int i, bool afterDelay) { break; case 0xc0: case 0xc1: case 0xc2: case 0xc3: // set Hz divider=(double)(((effect&0x3)<<8)|effectVal); - if (divider<10) divider=10; + if (divider<1) divider=1; cycles=got.rate*pow(2,MASTER_CLOCK_PREC)/divider; clockDrift=0; subticks=0; break; - case 0xdf: // set sample direction - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_DIR,i,effectVal)); - break; case 0xe0: // arp speed if (effectVal>0) { curSubSong->arpLen=effectVal; @@ -569,6 +666,7 @@ void DivEngine::processRow(int i, bool afterDelay) { case 0xe1: // portamento up chan[i].portaNote=chan[i].note+(effectVal&15); chan[i].portaSpeed=(effectVal>>4)*4; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].portaStop=true; chan[i].nowYouCanStop=false; chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?! @@ -576,6 +674,7 @@ void DivEngine::processRow(int i, bool afterDelay) { if ((effectVal&15)!=0) { chan[i].inPorta=true; chan[i].shorthandPorta=true; + chan[i].wasShorthandPorta=true; if (!song.brokenShortcutSlides) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0)); if (song.e1e2AlsoTakePriority) lastSlide=0x1337; // ... } else { @@ -586,6 +685,7 @@ void DivEngine::processRow(int i, bool afterDelay) { case 0xe2: // portamento down chan[i].portaNote=chan[i].note-(effectVal&15); chan[i].portaSpeed=(effectVal>>4)*4; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].portaStop=true; chan[i].nowYouCanStop=false; chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?! @@ -593,6 +693,7 @@ void DivEngine::processRow(int i, bool afterDelay) { if ((effectVal&15)!=0) { chan[i].inPorta=true; chan[i].shorthandPorta=true; + chan[i].wasShorthandPorta=true; if (!song.brokenShortcutSlides) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0)); if (song.e1e2AlsoTakePriority) lastSlide=0x1337; // ... } else { @@ -602,9 +703,11 @@ void DivEngine::processRow(int i, bool afterDelay) { break; case 0xe3: // vibrato direction chan[i].vibratoDir=effectVal; + dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO_SHAPE,i,chan[i].vibratoDir)); break; case 0xe4: // vibrato fine chan[i].vibratoFine=effectVal; + dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO_RANGE,i,chan[i].vibratoFine)); break; case 0xe5: // pitch chan[i].pitch=effectVal-0x80; @@ -615,6 +718,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } //chan[i].pitch+=globalPitch; dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); + dispatchCmd(DivCommand(DIV_CMD_HINT_PITCH,i,chan[i].pitch)); break; case 0xea: // legato mode chan[i].legato=effectVal; @@ -623,7 +727,7 @@ void DivEngine::processRow(int i, bool afterDelay) { dispatchCmd(DivCommand(DIV_CMD_SAMPLE_BANK,i,effectVal)); break; case 0xec: // delayed note cut - if (effectVal>0 && effectVal0 && (song.delayBehavior==2 || effectVal>8)); + dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); break; case 0xf9: // single volume ramp down chan[i].volume=MAX(chan[i].volume-effectVal*256,0); dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); break; case 0xfa: // fast volume ramp if (effectVal!=0) { @@ -686,6 +794,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } else { chan[i].volSpeed=0; } + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; case 0xff: // stop song @@ -716,9 +825,19 @@ void DivEngine::processRow(int i, bool afterDelay) { dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); if (chan[i].legato) { dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + dispatchCmd(DivCommand(DIV_CMD_HINT_LEGATO,i,chan[i].note)); } else { if (chan[i].inPorta && chan[i].keyOn && !chan[i].shorthandPorta) { - chan[i].portaNote=chan[i].note; + if (song.e1e2StopOnSameNote && chan[i].wasShorthandPorta) { + chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + if (!song.brokenShortcutSlides) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); + chan[i].wasShorthandPorta=false; + chan[i].inPorta=false; + } else { + chan[i].portaNote=chan[i].note; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + } } else if (!chan[i].noteOnInhibit) { dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,chan[i].note,chan[i].volume>>8)); keyHit[i]=true; @@ -728,12 +847,14 @@ void DivEngine::processRow(int i, bool afterDelay) { if (!chan[i].keyOn && chan[i].scheduledSlideReset) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].scheduledSlideReset=false; chan[i].inPorta=false; } if (!chan[i].keyOn && chan[i].volume>chan[i].volMax) { chan[i].volume=chan[i].volMax; dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); } chan[i].keyOn=true; chan[i].keyOff=false; @@ -757,7 +878,7 @@ void DivEngine::nextRow() { static char pb1[4096]; static char pb2[4096]; static char pb3[4096]; - if (view==DIV_STATUS_PATTERN) { + if (view==DIV_STATUS_PATTERN && !skipping) { strcpy(pb1,""); strcpy(pb3,""); for (int i=0; i>3))&8191]|=1<<(curRow&7); + if (changeOrd!=-1) { if (repeatPattern) { curRow=0; changeOrd=-1; } else { curRow=changePos; + changePos=0; if (changeOrd==-2) changeOrd=curOrder+1; - if (changeOrd<=curOrder) endOfSong=true; + // old loop detection routine + //if (changeOrd<=curOrder) endOfSong=true; curOrder=changeOrd; if (curOrder>=curSubSong->ordersLen) { curOrder=0; endOfSong=true; + memset(walked,0,8192); } changeOrd=-1; } @@ -827,6 +955,13 @@ void DivEngine::nextRow() { if (haltOn==DIV_HALT_PATTERN) halted=true; } + // new loop detection routine + if (!endOfSong && walked[((curOrder<<5)+(curRow>>3))&8191]&(1<<(curRow&7))) { + logV("loop reached"); + endOfSong=true; + memset(walked,0,8192); + } + if (song.brokenSpeedSel) { if ((curSubSong->patLen&1) && curOrder&1) { ticks=((curRow&1)?speed2:speed1)*(curSubSong->timeBase+1); @@ -852,7 +987,9 @@ void DivEngine::nextRow() { if (!(pat->data[curRow][0]==0 && pat->data[curRow][1]==0)) { if (pat->data[curRow][0]!=100 && pat->data[curRow][0]!=101 && pat->data[curRow][0]!=102) { if (!chan[i].legato) { - dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks)); + if (disCont[dispatchOfChan[i]].dispatch!=NULL) { + if (disCont[dispatchOfChan[i]].dispatch->getWantPreNote()) dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks)); + } if (song.oneTickCut) { bool doPrepareCut=true; @@ -882,7 +1019,7 @@ void DivEngine::nextRow() { bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { bool ret=false; - if (divider<10) divider=10; + if (divider<1) divider=1; if (lowLatency && !skipping && !inhibitLowLat) { tickMult=1000/divider; @@ -900,13 +1037,30 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { // MIDI clock if (output) if (!skipping && output->midiOut!=NULL) { - output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0)); + //output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0)); + } + + if (!pendingNotes.empty()) { + bool isOn[DIV_MAX_CHANS]; + memset(isOn,0,DIV_MAX_CHANS*sizeof(bool)); + + for (int i=pendingNotes.size()-1; i>=0; i--) { + if (pendingNotes[i].channel<0 || pendingNotes[i].channel>=chans) continue; + if (pendingNotes[i].on) { + isOn[pendingNotes[i].channel]=true; + } else { + if (isOn[pendingNotes[i].channel]) { + logV("erasing off -> on sequence in %d",pendingNotes[i].channel); + pendingNotes.erase(pendingNotes.begin()+i); + } + } + } } while (!pendingNotes.empty()) { DivNoteEvent& note=pendingNotes.front(); if (note.channel<0 || note.channel>=chans) { - pendingNotes.pop(); + pendingNotes.pop_front(); continue; } if (note.on) { @@ -926,7 +1080,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,note.channel)); } } - pendingNotes.pop(); + pendingNotes.pop_front(); } if (!freelance) { @@ -971,15 +1125,19 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { if (chan[i].volume>chan[i].volMax) { chan[i].volume=chan[i].volMax; chan[i].volSpeed=0; + dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0)); } else if (chan[i].volume<0) { chan[i].volSpeed=0; + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0)); if (song.legacyVolumeSlides) { chan[i].volume=chan[i].volMax+1; } else { chan[i].volume=0; } dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); } else { dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); } @@ -988,6 +1146,10 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { if (chan[i].vibratoDepth>0) { chan[i].vibratoPos+=chan[i].vibratoRate; if (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64; + + chan[i].vibratoPosGiant+=chan[i].vibratoRate; + if (chan[i].vibratoPos>=512) chan[i].vibratoPos-=512; + switch (chan[i].vibratoDir) { case 1: // up dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(MAX(0,(chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); @@ -1004,10 +1166,12 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) { if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) { chan[i].portaSpeed=0; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].oldNote=chan[i].note; chan[i].note=chan[i].portaNote; chan[i].inPorta=false; dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + dispatchCmd(DivCommand(DIV_CMD_HINT_LEGATO,i,chan[i].note)); } } } @@ -1021,11 +1185,13 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { if (chan[i].stopOnOff) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); chan[i].stopOnOff=false; } if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { chan[i+1].portaNote=-1; chan[i+1].portaSpeed=-1; @@ -1039,6 +1205,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } if (chan[i].resetArp) { dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + dispatchCmd(DivCommand(DIV_CMD_HINT_LEGATO,i,chan[i].note)); chan[i].resetArp=false; } if (song.rowResetsArpPos && firstTick) { @@ -1087,7 +1254,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } } - if (consoleMode && subticks<=1) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000,curOrder,curSubSong->ordersLen,curRow,curSubSong->patLen,cmdsPerSecond); + if (consoleMode && subticks<=1 && !skipping) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000,curOrder,curSubSong->ordersLen,curRow,curSubSong->patLen,cmdsPerSecond); } if (haltOn==DIV_HALT_TICK) halted=true; @@ -1125,7 +1292,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi case TA_MIDI_NOTE_OFF: { if (chan<0 || chan>=chans) break; if (midiIsDirect) { - pendingNotes.push(DivNoteEvent(chan,-1,-1,-1,false)); + pendingNotes.push_back(DivNoteEvent(chan,-1,-1,-1,false)); } else { autoNoteOff(msg.type&15,msg.data[0]-12,msg.data[1]); } @@ -1140,13 +1307,13 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi if (chan<0 || chan>=chans) break; if (msg.data[1]==0) { if (midiIsDirect) { - pendingNotes.push(DivNoteEvent(chan,-1,-1,-1,false)); + pendingNotes.push_back(DivNoteEvent(chan,-1,-1,-1,false)); } else { autoNoteOff(msg.type&15,msg.data[0]-12,msg.data[1]); } } else { if (midiIsDirect) { - pendingNotes.push(DivNoteEvent(chan,ins,msg.data[0]-12,msg.data[1],true)); + pendingNotes.push_back(DivNoteEvent(chan,ins,msg.data[0]-12,msg.data[1],true)); } else { autoNoteOn(msg.type&15,ins,msg.data[0]-12,msg.data[1]); } @@ -1186,17 +1353,17 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi blip_add_delta(samp_bb,i,samp_temp-samp_prevSample); samp_prevSample=samp_temp; - if (sPreview.pos>=s->samples || (sPreview.pEnd>=0 && (int)sPreview.pos>=sPreview.pEnd)) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples && (int)sPreview.pos>=s->loopStart) { + if (sPreview.pos>=s->getEndPosition() || (sPreview.pEnd>=0 && (int)sPreview.pos>=sPreview.pEnd)) { + if (s->isLoopable() && (int)sPreview.pos>=s->loopStart) { sPreview.pos=s->loopStart; } } } - if (sPreview.pos>=s->samples || (sPreview.pEnd>=0 && (int)sPreview.pos>=sPreview.pEnd)) { - if (s->loopStart>=0 && s->loopStart<(int)s->samples && (int)sPreview.pos>=s->loopStart) { + if (sPreview.pos>=s->getEndPosition() || (sPreview.pEnd>=0 && (int)sPreview.pos>=sPreview.pEnd)) { + if (s->isLoopable() && (int)sPreview.pos>=s->loopStart) { sPreview.pos=s->loopStart; - } else { + } else if (sPreview.pos>=s->samples) { sPreview.sample=-1; } } @@ -1238,25 +1405,22 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi } // logic starts here - size_t runtotal[32]; - size_t runLeft[32]; - size_t runPos[32]; - size_t lastAvail[32]; for (int i=0; i0) { - disCont[i].flush(lastAvail[i]); + disCont[i].lastAvail=blip_samples_avail(disCont[i].bb[0]); + if (disCont[i].lastAvail>0) { + disCont[i].flush(disCont[i].lastAvail); } - runtotal[i]=blip_clocks_needed(disCont[i].bb[0],size-lastAvail[i]); - if (runtotal[i]>disCont[i].bbInLen) { + disCont[i].runtotal=blip_clocks_needed(disCont[i].bb[0],size-disCont[i].lastAvail); + if (disCont[i].runtotal>disCont[i].bbInLen) { + logV("growing dispatch %d bbIn to %d",i,disCont[i].runtotal+256); delete[] disCont[i].bbIn[0]; delete[] disCont[i].bbIn[1]; - disCont[i].bbIn[0]=new short[runtotal[i]+256]; - disCont[i].bbIn[1]=new short[runtotal[i]+256]; - disCont[i].bbInLen=runtotal[i]+256; + disCont[i].bbIn[0]=new short[disCont[i].runtotal+256]; + disCont[i].bbIn[1]=new short[disCont[i].runtotal+256]; + disCont[i].bbInLen=disCont[i].runtotal+256; } - runLeft[i]=runtotal[i]; - runPos[i]=0; + disCont[i].runLeft=disCont[i].runtotal; + disCont[i].runPos=0; } if (metroTickLen>MASTER_CLOCK_PREC); for (int i=0; i1.0) out[0][i]=1.0; + if (out[1][i]<-1.0) out[1][i]=-1.0; + if (out[1][i]>1.0) out[1][i]=1.0; + } + } isBusy.unlock(); std::chrono::steady_clock::time_point ts_processEnd=std::chrono::steady_clock::now(); diff --git a/src/engine/safeReader.cpp b/src/engine/safeReader.cpp index 173ffd23..76e337a3 100644 --- a/src/engine/safeReader.cpp +++ b/src/engine/safeReader.cpp @@ -77,12 +77,92 @@ signed char SafeReader::readC() { return (signed char)buf[curSeek++]; } +#ifdef TA_BIG_ENDIAN +short SafeReader::readS_BE() { +#ifdef READ_DEBUG + logD("SR: reading short %x:",curSeek); +#endif + if (curSeek+2>len) throw EndOfFileException(this,len); + short ret; + memcpy(&ret,&buf[curSeek],2); +#ifdef READ_DEBUG + logD("SR: %.4x",ret); +#endif + curSeek+=2; + return ret; +} + +short SafeReader::readS() { + if (curSeek+2>len) throw EndOfFileException(this,len); + short ret; + memcpy(&ret,&buf[curSeek],2); + curSeek+=2; + return ((ret>>8)&0xff)|(ret<<8); +} + +int SafeReader::readI_BE() { +#ifdef READ_DEBUG + logD("SR: reading int %x:",curSeek); +#endif + if (curSeek+4>len) throw EndOfFileException(this,len); + int ret; + memcpy(&ret,&buf[curSeek],4); + curSeek+=4; +#ifdef READ_DEBUG + logD("SR: %.8x",ret); +#endif + return ret; +} + +int SafeReader::readI() { + if (curSeek+4>len) throw EndOfFileException(this,len); + unsigned int ret; + memcpy(&ret,&buf[curSeek],4); + curSeek+=4; + return (int)((ret>>24)|((ret&0xff0000)>>8)|((ret&0xff00)<<8)|((ret&0xff)<<24)); +} + +int64_t SafeReader::readL() { + if (curSeek+8>len) throw EndOfFileException(this,len); + unsigned char ret[8]; + memcpy(ret,&buf[curSeek],8); + curSeek+=8; + return (int64_t)(ret[0]|(ret[1]<<8)|(ret[2]<<16)|(ret[3]<<24)|((uint64_t)ret[4]<<32)|((uint64_t)ret[5]<<40)|((uint64_t)ret[6]<<48)|((uint64_t)ret[7]<<56)); +} + +float SafeReader::readF() { + if (curSeek+4>len) throw EndOfFileException(this,len); + unsigned int ret; + memcpy(&ret,&buf[curSeek],4); + curSeek+=4; + ret=((ret>>24)|((ret&0xff0000)>>8)|((ret&0xff00)<<8)|((ret&0xff)<<24)); + return *((float*)(&ret)); +} + +double SafeReader::readD() { + if (curSeek+8>len) throw EndOfFileException(this,len); + unsigned char ret[8]; + unsigned char retB[8]; + memcpy(ret,&buf[curSeek],8); + curSeek+=8; + retB[0]=ret[7]; + retB[1]=ret[6]; + retB[2]=ret[5]; + retB[3]=ret[4]; + retB[4]=ret[3]; + retB[5]=ret[2]; + retB[6]=ret[1]; + retB[7]=ret[0]; + return *((double*)retB); +} +#else short SafeReader::readS() { #ifdef READ_DEBUG logD("SR: reading short %x:",curSeek); #endif if (curSeek+2>len) throw EndOfFileException(this,len); - short ret=*(short*)(&buf[curSeek]); + short ret; + memcpy(&ret,&buf[curSeek],2); #ifdef READ_DEBUG logD("SR: %.4x",ret); #endif @@ -92,7 +172,8 @@ short SafeReader::readS() { short SafeReader::readS_BE() { if (curSeek+2>len) throw EndOfFileException(this,len); - short ret=*(short*)(&buf[curSeek]); + short ret; + memcpy(&ret,&buf[curSeek],2); curSeek+=2; return ((ret>>8)&0xff)|(ret<<8); } @@ -102,7 +183,8 @@ int SafeReader::readI() { logD("SR: reading int %x:",curSeek); #endif if (curSeek+4>len) throw EndOfFileException(this,len); - int ret=*(int*)(&buf[curSeek]); + int ret; + memcpy(&ret,&buf[curSeek],4); curSeek+=4; #ifdef READ_DEBUG logD("SR: %.8x",ret); @@ -112,31 +194,36 @@ int SafeReader::readI() { int SafeReader::readI_BE() { if (curSeek+4>len) throw EndOfFileException(this,len); - unsigned int ret=*(unsigned int*)(&buf[curSeek]); + unsigned int ret; + memcpy(&ret,&buf[curSeek],4); curSeek+=4; return (int)((ret>>24)|((ret&0xff0000)>>8)|((ret&0xff00)<<8)|((ret&0xff)<<24)); } int64_t SafeReader::readL() { if (curSeek+8>len) throw EndOfFileException(this,len); - int64_t ret=*(int64_t*)(&buf[curSeek]); + int64_t ret; + memcpy(&ret,&buf[curSeek],8); curSeek+=8; return ret; } float SafeReader::readF() { if (curSeek+4>len) throw EndOfFileException(this,len); - float ret=*(float*)(&buf[curSeek]); + float ret; + memcpy(&ret,&buf[curSeek],4); curSeek+=4; return ret; } double SafeReader::readD() { if (curSeek+8>len) throw EndOfFileException(this,len); - double ret=*(double*)(&buf[curSeek]); + double ret; + memcpy(&ret,&buf[curSeek],8); curSeek+=8; return ret; } +#endif String SafeReader::readString(size_t stlen) { String ret; diff --git a/src/engine/safeWriter.cpp b/src/engine/safeWriter.cpp index f29800a4..a2a7315a 100644 --- a/src/engine/safeWriter.cpp +++ b/src/engine/safeWriter.cpp @@ -77,9 +77,64 @@ int SafeWriter::writeC(signed char val) { return write(&val,1); } +#ifdef TA_BIG_ENDIAN +int SafeWriter::writeS_BE(short val) { + return write(&val,2); +} + +int SafeWriter::writeS(short val) { + unsigned char bytes[2]{(unsigned char)((val>>8)&0xff), (unsigned char)(val&0xff)}; + return write(bytes,2); +} + +int SafeWriter::writeI(int val) { + unsigned char bytes[4]; + bytes[0]=((unsigned int)val)&0xff; + bytes[1]=(((unsigned int)val)>>8)&0xff; + bytes[2]=(((unsigned int)val)>>16)&0xff; + bytes[3]=(((unsigned int)val)>>24)&0xff; + return write(bytes,4); +} + +int SafeWriter::writeL(int64_t val) { + unsigned char bytes[8]; + bytes[0]=((uint64_t)val)&0xff; + bytes[1]=(((uint64_t)val)>>8)&0xff; + bytes[2]=(((uint64_t)val)>>16)&0xff; + bytes[3]=(((uint64_t)val)>>24)&0xff; + bytes[4]=(((uint64_t)val)>>32)&0xff; + bytes[5]=(((uint64_t)val)>>40)&0xff; + bytes[6]=(((uint64_t)val)>>48)&0xff; + bytes[7]=(((uint64_t)val)>>56)&0xff; + return write(bytes,8); +} + +int SafeWriter::writeF(float val) { + unsigned char bytes[4]; + bytes[0]=((unsigned char*)(&val))[3]; + bytes[1]=((unsigned char*)(&val))[2]; + bytes[2]=((unsigned char*)(&val))[1]; + bytes[3]=((unsigned char*)(&val))[0]; + return write(bytes,4); +} + +int SafeWriter::writeD(double val) { + unsigned char bytes[8]; + bytes[0]=((unsigned char*)(&val))[7]; + bytes[1]=((unsigned char*)(&val))[6]; + bytes[2]=((unsigned char*)(&val))[5]; + bytes[3]=((unsigned char*)(&val))[4]; + bytes[4]=((unsigned char*)(&val))[3]; + bytes[5]=((unsigned char*)(&val))[2]; + bytes[6]=((unsigned char*)(&val))[1]; + bytes[7]=((unsigned char*)(&val))[0]; + return write(bytes,8); +} +#else int SafeWriter::writeS(short val) { return write(&val,2); } + int SafeWriter::writeS_BE(short val) { unsigned char bytes[2]{(unsigned char)((val>>8)&0xff), (unsigned char)(val&0xff)}; return write(bytes,2); @@ -88,23 +143,20 @@ int SafeWriter::writeS_BE(short val) { int SafeWriter::writeI(int val) { return write(&val,4); } + int SafeWriter::writeL(int64_t val) { return write(&val,8); } + int SafeWriter::writeF(float val) { return write(&val,4); } + int SafeWriter::writeD(double val) { return write(&val,8); } -int SafeWriter::writeString(String val, bool pascal) { - if (pascal) { - writeC((unsigned char)val.size()); - return write(val.c_str(),val.size())+1; - } else { - return write(val.c_str(),val.size()+1); - } -} +#endif + int SafeWriter::writeWString(WString val, bool pascal) { if (pascal) { writeS((unsigned short)val.size()); @@ -121,6 +173,19 @@ int SafeWriter::writeWString(WString val, bool pascal) { } } +int SafeWriter::writeText(String val) { + return write(val.c_str(),val.size()); +} + +int SafeWriter::writeString(String val, bool pascal) { + if (pascal) { + writeC((unsigned char)val.size()); + return write(val.c_str(),val.size())+1; + } else { + return write(val.c_str(),val.size()+1); + } +} + void SafeWriter::init() { if (operative) return; buf=new unsigned char[WRITER_BUF_SIZE]; diff --git a/src/engine/safeWriter.h b/src/engine/safeWriter.h index 9072c61d..414417fd 100644 --- a/src/engine/safeWriter.h +++ b/src/engine/safeWriter.h @@ -57,6 +57,7 @@ class SafeWriter { int writeD_BE(double val); int writeWString(WString val, bool pascal); int writeString(String val, bool pascal); + int writeText(String val); void init(); SafeReader* toReader(); diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 53441095..60eb35bb 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -22,7 +22,7 @@ #include #include #ifdef HAVE_SNDFILE -#include +#include "sfWrapper.h" #endif #include "filter.h" @@ -38,6 +38,65 @@ DivSampleHistory::~DivSampleHistory() { if (data!=NULL) delete[] data; } +bool DivSample::isLoopable() { + return (loopStart>=0 && loopStartloopStart && loopEnd<=(int)samples); +} + +unsigned int DivSample::getEndPosition(DivSampleDepth depth) { + int end=loopEnd; + unsigned int len=samples; + switch (depth) { + case DIV_SAMPLE_DEPTH_1BIT: + end=(loopEnd+7)/8; + len=length1; + break; + case DIV_SAMPLE_DEPTH_1BIT_DPCM: + end=(loopEnd+7)/8; + len=lengthDPCM; + break; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: + end=(loopEnd+1)/2; + len=lengthZ; + break; + case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: + end=(loopEnd+1)/2; + len=lengthQSoundA; + break; + case DIV_SAMPLE_DEPTH_ADPCM_A: + end=(loopEnd+1)/2; + len=lengthA; + break; + case DIV_SAMPLE_DEPTH_ADPCM_B: + end=(loopEnd+1)/2; + len=lengthB; + break; + case DIV_SAMPLE_DEPTH_8BIT: + end=loopEnd; + len=length8; + break; + case DIV_SAMPLE_DEPTH_BRR: + end=9*((loopEnd+15)/16); + len=lengthBRR; + break; + case DIV_SAMPLE_DEPTH_VOX: + end=(loopEnd+1)/2; + len=lengthVOX; + break; + case DIV_SAMPLE_DEPTH_16BIT: + end=loopEnd*2; + len=length16; + break; + default: + break; + } + return isLoopable()?end:len; +} + +void DivSample::setSampleCount(unsigned int count) { + samples=count; + if ((!isLoopable()) || loopEnd<0 || loopEnd>(int)samples) loopEnd=samples; +} + bool DivSample::save(const char* path) { #ifndef HAVE_SNDFILE logE("Furnace was not compiled with libsndfile!"); @@ -45,15 +104,23 @@ bool DivSample::save(const char* path) { #else SNDFILE* f; SF_INFO si; + SFWrapper sfWrap; memset(&si,0,sizeof(SF_INFO)); if (length16<1) return false; si.channels=1; si.samplerate=rate; - si.format=SF_FORMAT_PCM_16|SF_FORMAT_WAV; + switch (depth) { + case DIV_SAMPLE_DEPTH_8BIT: // 8-bit + si.format=SF_FORMAT_PCM_U8|SF_FORMAT_WAV; + break; + default: // 16-bit + si.format=SF_FORMAT_PCM_16|SF_FORMAT_WAV; + break; + } - f=sf_open(path,SFM_WRITE,&si); + f=sfWrap.doOpen(path,SFM_WRITE,&si); if (f==NULL) { logE("could not open wave file for saving! %s",sf_error_number(sf_error(f))); @@ -68,83 +135,97 @@ bool DivSample::save(const char* path) { inst.detune = 50 - (pitch % 100); inst.velocity_hi = 0x7f; inst.key_hi = 0x7f; - if(loopStart != -1) + if(isLoopable()) { inst.loop_count = 1; inst.loops[0].mode = SF_LOOP_FORWARD; inst.loops[0].start = loopStart; - inst.loops[0].end = samples; + inst.loops[0].end = loopEnd; } sf_command(f, SFC_SET_INSTRUMENT, &inst, sizeof(inst)); - sf_writef_short(f,data16,samples); + switch (depth) { + case DIV_SAMPLE_DEPTH_8BIT: { + // convert from signed to unsigned + unsigned char* buf=new unsigned char[length8]; + for (size_t i=0; isamples) end=samples; int count=samples-(end-begin); if (count<=0) return resize(0); - if (depth==8) { + if (depth==DIV_SAMPLE_DEPTH_8BIT) { if (data8!=NULL) { signed char* oldData8=data8; data8=NULL; - initInternal(8,count); + initInternal(DIV_SAMPLE_DEPTH_8BIT,count); if (begin>0) { memcpy(data8,oldData8,begin); } @@ -212,13 +293,13 @@ bool DivSample::strip(unsigned int begin, unsigned int end) { // do nothing return true; } - samples=count; + setSampleCount(count); return true; - } else if (depth==16) { + } else if (depth==DIV_SAMPLE_DEPTH_16BIT) { if (data16!=NULL) { short* oldData16=data16; data16=NULL; - initInternal(16,count); + initInternal(DIV_SAMPLE_DEPTH_16BIT,count); if (begin>0) { memcpy(data16,oldData16,sizeof(short)*begin); } @@ -230,7 +311,7 @@ bool DivSample::strip(unsigned int begin, unsigned int end) { // do nothing return true; } - samples=count; + setSampleCount(count); return true; } return false; @@ -240,31 +321,31 @@ bool DivSample::trim(unsigned int begin, unsigned int end) { int count=end-begin; if (count==0) return true; if (begin==0 && end==samples) return true; - if (depth==8) { + if (depth==DIV_SAMPLE_DEPTH_8BIT) { if (data8!=NULL) { signed char* oldData8=data8; data8=NULL; - initInternal(8,count); + initInternal(DIV_SAMPLE_DEPTH_8BIT,count); memcpy(data8,oldData8+begin,count); delete[] oldData8; } else { // do nothing return true; } - samples=count; + setSampleCount(count); return true; - } else if (depth==16) { + } else if (depth==DIV_SAMPLE_DEPTH_16BIT) { if (data16!=NULL) { short* oldData16=data16; data16=NULL; - initInternal(16,count); + initInternal(DIV_SAMPLE_DEPTH_16BIT,count); memcpy(data16,&(oldData16[begin]),sizeof(short)*count); delete[] oldData16; } else { // do nothing return true; } - samples=count; + setSampleCount(count); return true; } return false; @@ -272,11 +353,11 @@ bool DivSample::trim(unsigned int begin, unsigned int end) { bool DivSample::insert(unsigned int pos, unsigned int length) { unsigned int count=samples+length; - if (depth==8) { + if (depth==DIV_SAMPLE_DEPTH_8BIT) { if (data8!=NULL) { signed char* oldData8=data8; data8=NULL; - initInternal(8,count); + initInternal(DIV_SAMPLE_DEPTH_8BIT,count); if (pos>0) { memcpy(data8,oldData8,pos); } @@ -285,15 +366,15 @@ bool DivSample::insert(unsigned int pos, unsigned int length) { } delete[] oldData8; } else { - initInternal(8,count); + initInternal(DIV_SAMPLE_DEPTH_8BIT,count); } - samples=count; + setSampleCount(count); return true; - } else if (depth==16) { + } else if (depth==DIV_SAMPLE_DEPTH_16BIT) { if (data16!=NULL) { short* oldData16=data16; data16=NULL; - initInternal(16,count); + initInternal(DIV_SAMPLE_DEPTH_16BIT,count); if (pos>0) { memcpy(data16,oldData16,sizeof(short)*pos); } @@ -302,9 +383,9 @@ bool DivSample::insert(unsigned int pos, unsigned int length) { } delete[] oldData16; } else { - initInternal(16,count); + initInternal(DIV_SAMPLE_DEPTH_16BIT,count); } - samples=count; + setSampleCount(count); return true; } return false; @@ -315,15 +396,15 @@ bool DivSample::insert(unsigned int pos, unsigned int length) { int finalCount=(double)samples*(r/(double)rate); \ signed char* oldData8=data8; \ short* oldData16=data16; \ - if (depth==16) { \ + if (depth==DIV_SAMPLE_DEPTH_16BIT) { \ if (data16!=NULL) { \ data16=NULL; \ - initInternal(16,finalCount); \ + initInternal(DIV_SAMPLE_DEPTH_16BIT,finalCount); \ } \ - } else if (depth==8) { \ + } else if (depth==DIV_SAMPLE_DEPTH_8BIT) { \ if (data8!=NULL) { \ data8=NULL; \ - initInternal(8,finalCount); \ + initInternal(DIV_SAMPLE_DEPTH_8BIT,finalCount); \ } \ } else { \ return false; \ @@ -331,19 +412,20 @@ bool DivSample::insert(unsigned int pos, unsigned int length) { #define RESAMPLE_END \ if (loopStart>=0) loopStart=(double)loopStart*(r/(double)rate); \ + if (loopEnd>=0) loopEnd=(double)loopEnd*(r/(double)rate); \ centerRate=(int)((double)centerRate*(r/(double)rate)); \ rate=r; \ samples=finalCount; \ - if (depth==16) { \ + if (depth==DIV_SAMPLE_DEPTH_16BIT) { \ delete[] oldData16; \ - } else if (depth==8) { \ + } else if (depth==DIV_SAMPLE_DEPTH_8BIT) { \ delete[] oldData8; \ } bool DivSample::resampleNone(double r) { RESAMPLE_BEGIN; - if (depth==16) { + if (depth==DIV_SAMPLE_DEPTH_16BIT) { for (int i=0; i=samples) { @@ -352,7 +434,7 @@ bool DivSample::resampleNone(double r) { data16[i]=oldData16[pos]; } } - } else if (depth==8) { + } else if (depth==DIV_SAMPLE_DEPTH_8BIT) { for (int i=0; i=samples) { @@ -374,7 +456,7 @@ bool DivSample::resampleLinear(double r) { unsigned int posInt=0; double factor=(double)rate/r; - if (depth==16) { + if (depth==DIV_SAMPLE_DEPTH_16BIT) { for (int i=0; i=samples)?0:oldData16[posInt]; short s2=(posInt+1>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData16[loopStart]:0):oldData16[posInt+1]; @@ -387,7 +469,7 @@ bool DivSample::resampleLinear(double r) { posInt++; } } - } else if (depth==8) { + } else if (depth==DIV_SAMPLE_DEPTH_8BIT) { for (int i=0; i=samples)?0:oldData8[posInt]; short s2=(posInt+1>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData8[loopStart]:0):oldData8[posInt+1]; @@ -414,7 +496,7 @@ bool DivSample::resampleCubic(double r) { double factor=(double)rate/r; float* cubicTable=DivFilterTables::getCubicTable(); - if (depth==16) { + if (depth==DIV_SAMPLE_DEPTH_16BIT) { for (int i=0; i=samples)?0:oldData16[posInt]; } } - } else if (depth==8) { + } else if (depth==DIV_SAMPLE_DEPTH_8BIT) { for (int i=0; i>3]>>(i&7))&1)?0x7fff:-0x7fff; } break; - case 1: { // DPCM + case DIV_SAMPLE_DEPTH_1BIT_DPCM: { // DPCM int accum=0; for (unsigned int i=0; i>3]>>(i&7))&1)?1:-1; @@ -665,27 +747,27 @@ void DivSample::render() { } break; } - case 3: // YMZ ADPCM + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: // YMZ ADPCM ymz_decode(dataZ,data16,samples); break; - case 4: // QSound ADPCM + case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: // QSound ADPCM bs_decode(dataQSoundA,data16,samples); break; - case 5: // ADPCM-A + case DIV_SAMPLE_DEPTH_ADPCM_A: // ADPCM-A yma_decode(dataA,data16,samples); break; - case 6: // ADPCM-B + case DIV_SAMPLE_DEPTH_ADPCM_B: // ADPCM-B ymb_decode(dataB,data16,samples); break; - case 8: // 8-bit PCM + case DIV_SAMPLE_DEPTH_8BIT: // 8-bit PCM for (unsigned int i=0; i0) { data1[i>>3]|=1<<(i&7); } } } - if (depth!=1) { // DPCM - if (!initInternal(1,samples)) return; + if (depth!=DIV_SAMPLE_DEPTH_1BIT_DPCM) { // DPCM + if (!initInternal(DIV_SAMPLE_DEPTH_1BIT_DPCM,samples)) return; int accum=63; for (unsigned int i=0; i>9; @@ -717,84 +799,88 @@ void DivSample::render() { if (accum>127) accum=127; } } - if (depth!=3) { // YMZ ADPCM - if (!initInternal(3,samples)) return; + if (depth!=DIV_SAMPLE_DEPTH_YMZ_ADPCM) { // YMZ ADPCM + if (!initInternal(DIV_SAMPLE_DEPTH_YMZ_ADPCM,samples)) return; ymz_encode(data16,dataZ,(samples+7)&(~0x7)); } - if (depth!=4) { // QSound ADPCM - if (!initInternal(4,samples)) return; + if (depth!=DIV_SAMPLE_DEPTH_QSOUND_ADPCM) { // QSound ADPCM + if (!initInternal(DIV_SAMPLE_DEPTH_QSOUND_ADPCM,samples)) return; bs_encode(data16,dataQSoundA,samples); } // TODO: pad to 256. - if (depth!=5) { // ADPCM-A - if (!initInternal(5,samples)) return; + if (depth!=DIV_SAMPLE_DEPTH_ADPCM_A) { // ADPCM-A + if (!initInternal(DIV_SAMPLE_DEPTH_ADPCM_A,samples)) return; yma_encode(data16,dataA,(samples+511)&(~0x1ff)); } - if (depth!=6) { // ADPCM-B - if (!initInternal(6,samples)) return; + if (depth!=DIV_SAMPLE_DEPTH_ADPCM_B) { // ADPCM-B + if (!initInternal(DIV_SAMPLE_DEPTH_ADPCM_B,samples)) return; ymb_encode(data16,dataB,(samples+511)&(~0x1ff)); } - if (depth!=8) { // 8-bit PCM - if (!initInternal(8,samples)) return; + if (depth!=DIV_SAMPLE_DEPTH_8BIT) { // 8-bit PCM + if (!initInternal(DIV_SAMPLE_DEPTH_8BIT,samples)) return; for (unsigned int i=0; i>8; } } // TODO: BRR! - if (depth!=10) { // VOX - if (!initInternal(10,samples)) return; + if (depth!=DIV_SAMPLE_DEPTH_VOX) { // VOX + if (!initInternal(DIV_SAMPLE_DEPTH_VOX,samples)) return; oki_encode(data16,dataVOX,samples); } } void* DivSample::getCurBuf() { switch (depth) { - case 0: + case DIV_SAMPLE_DEPTH_1BIT: return data1; - case 1: + case DIV_SAMPLE_DEPTH_1BIT_DPCM: return dataDPCM; - case 3: + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: return dataZ; - case 4: + case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: return dataQSoundA; - case 5: + case DIV_SAMPLE_DEPTH_ADPCM_A: return dataA; - case 6: + case DIV_SAMPLE_DEPTH_ADPCM_B: return dataB; - case 8: + case DIV_SAMPLE_DEPTH_8BIT: return data8; - case 9: + case DIV_SAMPLE_DEPTH_BRR: return dataBRR; - case 10: + case DIV_SAMPLE_DEPTH_VOX: return dataVOX; - case 16: + case DIV_SAMPLE_DEPTH_16BIT: return data16; + default: + return NULL; } return NULL; } unsigned int DivSample::getCurBufLen() { switch (depth) { - case 0: + case DIV_SAMPLE_DEPTH_1BIT: return length1; - case 1: + case DIV_SAMPLE_DEPTH_1BIT_DPCM: return lengthDPCM; - case 3: + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: return lengthZ; - case 4: + case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: return lengthQSoundA; - case 5: + case DIV_SAMPLE_DEPTH_ADPCM_A: return lengthA; - case 6: + case DIV_SAMPLE_DEPTH_ADPCM_B: return lengthB; - case 8: + case DIV_SAMPLE_DEPTH_8BIT: return length8; - case 9: + case DIV_SAMPLE_DEPTH_BRR: return lengthBRR; - case 10: + case DIV_SAMPLE_DEPTH_VOX: return lengthVOX; - case 16: + case DIV_SAMPLE_DEPTH_16BIT: return length16; + default: + return 0; } return 0; } @@ -809,9 +895,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); + h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,rate,centerRate,loopStart,loopEnd); } else { - h=new DivSampleHistory(depth,rate,centerRate,loopStart); + h=new DivSampleHistory(depth,rate,centerRate,loopStart,loopEnd); } if (!doNotPush) { while (!redoHist.empty()) { @@ -841,7 +927,8 @@ DivSampleHistory* DivSample::prepareUndo(bool data, bool doNotPush) { } \ rate=h->rate; \ centerRate=h->centerRate; \ - loopStart=h->loopStart; + loopStart=h->loopStart; \ + loopEnd=h->loopEnd; int DivSample::undo() { diff --git a/src/engine/sample.h b/src/engine/sample.h index 5195da1b..0df1f1f7 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -17,9 +17,28 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#ifndef _SAMPLE_H +#define _SAMPLE_H + +#pragma once + #include "../ta-utils.h" #include +enum DivSampleDepth: unsigned char { + DIV_SAMPLE_DEPTH_1BIT=0, + DIV_SAMPLE_DEPTH_1BIT_DPCM=1, + DIV_SAMPLE_DEPTH_YMZ_ADPCM=3, + DIV_SAMPLE_DEPTH_QSOUND_ADPCM=4, + DIV_SAMPLE_DEPTH_ADPCM_A=5, + DIV_SAMPLE_DEPTH_ADPCM_B=6, + DIV_SAMPLE_DEPTH_8BIT=8, + DIV_SAMPLE_DEPTH_BRR=9, + DIV_SAMPLE_DEPTH_VOX=10, + DIV_SAMPLE_DEPTH_16BIT=16, + DIV_SAMPLE_DEPTH_MAX // boundary for sample depth +}; + enum DivResampleFilters { DIV_RESAMPLE_NONE=0, DIV_RESAMPLE_LINEAR, @@ -32,10 +51,10 @@ enum DivResampleFilters { struct DivSampleHistory { unsigned char* data; unsigned int length, samples; - unsigned char depth; - int rate, centerRate, loopStart; + DivSampleDepth depth; + int rate, centerRate, loopStart, loopEnd; bool hasSample; - DivSampleHistory(void* d, unsigned int l, unsigned int s, unsigned char de, int r, int cr, int ls): + DivSampleHistory(void* d, unsigned int l, unsigned int s, DivSampleDepth de, int r, int cr, int ls, int le): data((unsigned char*)d), length(l), samples(s), @@ -43,8 +62,9 @@ struct DivSampleHistory { rate(r), centerRate(cr), loopStart(ls), + loopEnd(le), hasSample(true) {} - DivSampleHistory(unsigned char de, int r, int cr, int ls): + DivSampleHistory(DivSampleDepth de, int r, int cr, int ls, int le): data(NULL), length(0), samples(0), @@ -52,13 +72,14 @@ struct DivSampleHistory { rate(r), centerRate(cr), loopStart(ls), + loopEnd(le), hasSample(false) {} ~DivSampleHistory(); }; struct DivSample { String name; - int rate, centerRate, loopStart, loopOffP; + int rate, centerRate, loopStart, loopEnd, loopOffP; // valid values are: // - 0: ZX Spectrum overlay drum (1-bit) // - 1: 1-bit NES DPCM (1-bit) @@ -70,7 +91,7 @@ struct DivSample { // - 9: BRR (SNES) // - 10: VOX ADPCM // - 16: 16-bit PCM - unsigned char depth; + DivSampleDepth depth; // these are the new data structures. signed char* data8; // 8 @@ -93,6 +114,23 @@ struct DivSample { std::deque undoHist; std::deque redoHist; + /** + * check if sample is loopable. + * @return whether it is loopable. + */ + bool isLoopable(); + + /** + * get sample end position + * @return the samples end position. + */ + unsigned int getEndPosition(DivSampleDepth depth=DIV_SAMPLE_DEPTH_MAX); + + /** + * @warning DO NOT USE - internal functions + */ + void setSampleCount(unsigned int count); + /** * @warning DO NOT USE - internal functions */ @@ -116,7 +154,7 @@ struct DivSample { * @param count number of samples. * @return whether it was successful. */ - bool initInternal(unsigned char d, int count); + bool initInternal(DivSampleDepth d, int count); /** * initialize sample data. make sure you have set `depth` before doing so. @@ -212,8 +250,9 @@ struct DivSample { rate(32000), centerRate(8363), loopStart(-1), + loopEnd(-1), loopOffP(0), - depth(16), + depth(DIV_SAMPLE_DEPTH_16BIT), data8(NULL), data16(NULL), data1(NULL), @@ -248,8 +287,11 @@ struct DivSample { offQSound(0), offX1_010(0), offSU(0), + offYMZ280B(0), offRF5C68(0), offSNES(0), samples(0) {} ~DivSample(); }; + +#endif diff --git a/src/engine/sfWrapper.cpp b/src/engine/sfWrapper.cpp new file mode 100644 index 00000000..f12dc95b --- /dev/null +++ b/src/engine/sfWrapper.cpp @@ -0,0 +1,121 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "sfWrapper.h" +#include "../fileutils.h" +#include "sndfile.h" + +sf_count_t _vioGetSize(void* user) { + return ((SFWrapper*)user)->ioGetSize(); +} + +sf_count_t _vioSeek(sf_count_t offset, int whence, void* user) { + return ((SFWrapper*)user)->ioSeek(offset,whence); +} + +sf_count_t _vioRead(void* ptr, sf_count_t count, void* user) { + return ((SFWrapper*)user)->ioRead(ptr,count); +} + +sf_count_t _vioWrite(const void* ptr, sf_count_t count, void* user) { + return ((SFWrapper*)user)->ioWrite(ptr,count); +} + +sf_count_t _vioTell(void* user) { + return ((SFWrapper*)user)->ioTell(); +} + +sf_count_t SFWrapper::ioGetSize() { + sf_count_t ret=(sf_count_t)len; + if (fileMode==SFM_WRITE || fileMode==SFM_RDWR) { + ssize_t lastTell=ftell(f); + fseek(f,0,SEEK_END); + ret=(sf_count_t)ftell(f); + fseek(f,lastTell,SEEK_SET); + } + return ret; +} + +sf_count_t SFWrapper::ioSeek(sf_count_t offset, int whence) { + return fseek(f,offset,whence); +} + +sf_count_t SFWrapper::ioRead(void* ptr, sf_count_t count) { + return fread(ptr,1,count,f); +} + +sf_count_t SFWrapper::ioWrite(const void* ptr, sf_count_t count) { + return fwrite(ptr,1,count,f); +} + +sf_count_t SFWrapper::ioTell() { + return ftell(f); +} + +int SFWrapper::doClose() { + int ret=sf_close(sf); + fclose(f); + return ret; +} + +SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) { + vio.get_filelen=_vioGetSize; + vio.read=_vioRead; + vio.seek=_vioSeek; + vio.tell=_vioTell; + vio.write=_vioWrite; + + const char* modeC="rb"; + if (mode==SFM_WRITE) { + modeC="wb"; + } + if (mode==SFM_RDWR) { + modeC="rb+"; + } + + f=ps_fopen(path,modeC); + if (f==NULL) { + return NULL; + } + + if (fseek(f,0,SEEK_END)==-1) { + fclose(f); + f=NULL; + return NULL; + } + + len=ftell(f); + if (len==(SIZE_MAX>>1)) { + len=0; + fclose(f); + f=NULL; + return NULL; + } + + if (fseek(f,0,SEEK_SET)==-1) { + len=0; + fclose(f); + f=NULL; + return NULL; + } + + sf=sf_open_virtual(&vio,mode,sfinfo,this); + if (sf!=NULL) fileMode=mode; + return sf; +} \ No newline at end of file diff --git a/src/engine/sfWrapper.h b/src/engine/sfWrapper.h new file mode 100644 index 00000000..6e984a45 --- /dev/null +++ b/src/engine/sfWrapper.h @@ -0,0 +1,56 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +// sfWrapper.h: libsndfile FILE* wrapper to work around Windows issue with +// non-ASCII chars in file path +// I wanted to do this in sndfile directly, but it's a +// submodule... + +#ifndef _SFWRAPPER_H +#define _SFWRAPPER_H +#include +#include +#include +#include +#include "../ta-utils.h" + +class SFWrapper { + FILE* f; + size_t len; + SF_VIRTUAL_IO vio; + SNDFILE* sf; + int fileMode; + + public: + sf_count_t ioGetSize(); + sf_count_t ioSeek(sf_count_t offset, int whence); + sf_count_t ioRead(void* ptr, sf_count_t count); + sf_count_t ioWrite(const void* ptr, sf_count_t count); + sf_count_t ioTell(); + + int doClose(); + SNDFILE* doOpen(const char* path, int mode, SF_INFO* sfinfo); + SFWrapper(): + f(NULL), + len(0), + sf(NULL), + fileMode(0) {} +}; + +#endif diff --git a/src/engine/song.cpp b/src/engine/song.cpp index 216a1bc4..1adb33eb 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -18,6 +18,7 @@ */ #include "song.h" +#include "../ta-log.h" void DivSubSong::clearData() { for (int i=0; i> clearOuts=pat[i].optimize(); + for (auto& j: clearOuts) { + for (int k=0; k<256; k++) { + if (orders.ord[i][k]==j.first) { + orders.ord[i][k]=j.second; + } + } + } + } +} + +void DivSubSong::rearrangePatterns() { + for (int i=0; i> clearOuts=pat[i].rearrange(); + for (auto& j: clearOuts) { + for (int k=0; k<256; k++) { + if (orders.ord[i][k]==j.first) { + orders.ord[i][k]=j.second; + } + } + } + } +} + void DivSong::clearSongData() { for (DivSubSong* i: subsong) { i->clearData(); diff --git a/src/engine/song.h b/src/engine/song.h index 10c00d27..493fc412 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -114,6 +114,8 @@ enum DivSystem { DIV_SYSTEM_YM2612_FRAC, DIV_SYSTEM_YM2612_FRAC_EXT, DIV_SYSTEM_RESERVED_8, + DIV_SYSTEM_T6W28, + DIV_SYSTEM_PCM_DAC, DIV_SYSTEM_DUMMY }; @@ -136,6 +138,8 @@ struct DivSubSong { String chanShortName[DIV_MAX_CHANS]; void clearData(); + void optimizePatterns(); + void rearrangePatterns(); DivSubSong(): hilightA(4), @@ -237,28 +241,45 @@ struct DivSong { // - 1: PAL // - 2: Dendy // - SMS/SN76489: - // - bit 0-1: clock rate - // - 0: NTSC (3.58MHz) - // - 1: PAL (3.55MHz) - // - 2: Other (4MHz) - // - 3: half NTSC (1.79MHz) - // - bit 2-3: noise type - // - 0: Sega VDP (16-bit noise) - // - 1: real SN76489 (15-bit noise) - // - 2: real SN76489 with Atari-like short noise buzz (15-bit noise) - // - 3: Game Gear (16-bit noise, stereo) + // - bit 0-1, 8-15: clock rate + // - 0000: 3.58MHz (NTSC) + // - 0001: 3.55MHz (PAL) + // - 0002: 4MHz (Other) + // - 0003: 1.79MHz (half NTSC) + // - 0100: 3MHz + // - 0101: 2MHz + // - 0102: 447KHz (NTSC / 8) + // - bit 2-3, 6-7: chip type + // - 00: Sega VDP (16-bit noise) + // - 04: real SN76489 (15-bit noise) + // - 08: real SN76489 with Atari-like short noise buzz (15-bit noise) + // - 0c: Game Gear (16-bit noise, stereo) + // - 40: real SN76489A (17-bit noise) + // - 44: real SN76496 (17-bit noise) + // - 48: NCR 8496 (16-bit noise) + // - 4c: Tandy PSSJ-3 (16-bit noise) + // - 80: real SN94624 (15-bit noise) + // - 84: real SN76494 (17-bit noise) // - bit 4: disable noise phase reset - // - YM2612: - // - bit 0-1: clock rate + // - YM2612/YM3438: + // - bit 0-30: clock rate // - 0: Genesis NTSC (7.67MHz) // - 1: Genesis PAL (7.61MHz) - // - 2: 8MHz + // - 2: FM Towns (8MHz) // - 3: AtGames Genesis (6.13MHz) + // - 4: Sega System 32 (8.06MHz) + // - bit 31: DAC distortion + // - 0: disable + // - 1: enable // - YM2151: - // - bit 0-1: clock rate + // - bit 0-7: clock rate // - 0: 3.58MHz (NTSC) // - 1: 3.55MHz (PAL) // - 2: 4MHz + // - YM2610(B): + // - bit 0-7: clock rate + // - 0: 8MHz (Neo Geo MVS) + // - 1: 8.06MHz (Neo Geo AES) // - AY-3-8910/AY8930: // - bit 0-3: clock rate // - 0: 1.79MHz (MSX NTSC) @@ -274,6 +295,8 @@ struct DivSong { // - 10: 2.097152MHz (Game Boy) // - 11: 3.58MHz (Darky) // - 12: 3.6MHz (Darky) + // - 13: 1.25MHz + // - 14: 1.536MHz // - bit 4-5: chip type (ignored on AY8930) // - 0: AY-3-8910 or similar // - 1: YM2149 @@ -287,9 +310,9 @@ struct DivSong { // - 1: low (internally divided to half) // - SAA1099: // - bit 0-1: clock rate - // - 0: 8MHz (SAM Coupé, Game Blaster) - // - 1: 7.15MHz - // - 2: 7.09MHz + // - 0: 8MHz (SAM Coupé) + // - 1: 7.15MHz (Game Blaster, NTSC) + // - 2: 7.09MHz (PAL) // - Amiga: // - bit 0: clock rate // - 0: 7.15MHz (NTSC) @@ -330,10 +353,84 @@ struct DivSong { // - bit 4: stereo // - 0: mono // - 1: stereo + // - YM2203: + // - bit 0-4: clock rate + // - 0: 3.58MHz (NTSC) + // - 1: 3.55MHz (PAL) + // - 2: 4MHz + // - 3: 3MHz + // - 4: 3.9936MHz (PC-88, PC-98) + // - 5: 1.5MHz + // - bit 5-6: output rate + // - 0: FM: clock / 72, SSG: clock / 16 + // - 1: FM: clock / 36, SSG: clock / 8 + // - 2: FM: clock / 24, SSG: clock / 4 + // - YM2608: + // - bit 0-4: clock rate + // - 0: 8MHz + // - 1: 7.987MHz (PC-88, PC-98) + // - bit 5-6: output rate + // - 0: FM: clock / 144, SSG: clock / 32 + // - 1: FM: clock / 72, SSG: clock / 16 + // - 2: FM: clock / 48, SSG: clock / 8 + // - YM3526, YM3812, Y8950: + // - bit 0-7: clock rate + // - 0: 3.58MHz (NTSC) + // - 1: 3.55MHz (PAL) + // - 2: 4MHz + // - 3: 3MHz + // - 4: 3.9936MHz (PC-88, PC-98) + // - 5: 3.5MHz + // - YMF262: + // - bit 0-7: clock rate + // - 0: 14.32MHz (NTSC) + // - 1: 14.19MHz (PAL) + // - 2: 14MHz + // - 3: 16MHz + // - 4: 15MHz + // - YMF289B: (TODO) + // - bit 0-7: clock rate + // - 0: 33.8688MHz + // - 1: 28.64MHz (NTSC) + // - 2: 28.38MHz (PAL) + // - MSM6295: + // - bit 0-6: clock rate + // - 0: 1MHz + // - 1: 1.056MHz + // - 2: 4MHz + // - 3: 4.224MHz + // - 4: 3.58MHz (NTSC) + // - 5: 1.79MHz (Half NTSC) + // - 6: 1.023MHz + // - 7: 0.895MHz (Quarter NTSC) + // - 8: 2MHz + // - 9: 2.112MHz + // - 10: 0.875MHz + // - 11: 0.9375MHz + // - 12: 1.5MHz + // - 13: 3MHz + // - 14: 1.193MHz + // - bit 7: Output rate + // - 0: clock / 132 + // - 1: clock / 165 + // - SCC/+: + // - bit 0-6: clock rate + // - 0: 1.79MHz (MSX NTSC) + // - 1: 1.77MHz (PAL) + // - 2: 1.5MHz + // - 3: 2MHz + // - YMZ280B: + // - bit 0-7: clock rate + // - 0: 16.9344MHz + // - 1: 14.32MHz (NTSC) + // - 2: 14.19MHz (PAL) + // - 3: 16MHz + // - 4: 16.67MHz + // - 5: 14MHz unsigned int systemFlags[32]; // song information - String name, author; + String name, author, systemName; // legacy song information // those will be stored in .fur and mapped to VGM as: @@ -343,7 +440,7 @@ struct DivSong { String carrier, composer, vendor, category, writer, arranger, copyright, manGroup, manInfo, createdDate, revisionDate; // more VGM specific stuff - String nameJ, authorJ, categoryJ; + String nameJ, authorJ, categoryJ, systemNameJ; // other things String notes; @@ -366,6 +463,16 @@ struct DivSong { // 1: fake reset on loop // 2: don't do anything on loop unsigned char loopModality; + // cut/delay effect behavior + // 0: strict (don't allow value higher than or equal to speed) + // 1: broken (don't allow value higher than speed) + // 2: lax (allow value higher than speed) + unsigned char delayBehavior; + // 0B/0D treatment + // 0: normal (0B/0D accepted) + // 1: old Furnace (first one accepted) + // 2: DefleMask (0D takes priority over 0B) + unsigned char jumpTreatment; bool properNoiseLayout; bool waveDutyIsVol; bool resetMacroOnPorta; @@ -401,6 +508,9 @@ struct DivSong { bool newVolumeScaling; bool volMacroLinger; bool brokenOutVol; + bool e1e2StopOnSameNote; + bool brokenPortaArp; + bool snNoLowPeriods; std::vector ins; std::vector wave; @@ -444,6 +554,7 @@ struct DivSong { systemLen(2), name(""), author(""), + systemName(""), carrier(""), composer(""), vendor(""), @@ -463,8 +574,10 @@ struct DivSong { limitSlides(false), linearPitch(2), pitchSlideSpeed(4), - loopModality(0), - properNoiseLayout(false), + loopModality(2), + delayBehavior(2), + jumpTreatment(0), + properNoiseLayout(true), waveDutyIsVol(false), resetMacroOnPorta(false), legacyVolumeSlides(false), @@ -498,7 +611,10 @@ struct DivSong { noOPN2Vol(false), newVolumeScaling(true), volMacroLinger(true), - brokenOutVol(false) { + brokenOutVol(false), + e1e2StopOnSameNote(false), + brokenPortaArp(false), + snNoLowPeriods(false) { for (int i=0; i<32; i++) { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index e5a4888b..78af262a 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -54,14 +54,14 @@ std::vector& DivEngine::getPossibleInsTypes() { return possibleInsTypes; } -// TODO: rewrite this function (again). it's an unreliable mess. -String DivEngine::getSongSystemName(bool isMultiSystemAcceptable) { - switch (song.systemLen) { +// for pre-dev103 modules +String DivEngine::getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAcceptable) { + switch (ds.systemLen) { case 0: return "help! what's going on!"; case 1: - if (song.system[0]==DIV_SYSTEM_AY8910) { - switch (song.systemFlags[0]&0x3f) { + if (ds.system[0]==DIV_SYSTEM_AY8910) { + switch (ds.systemFlags[0]&0x3f) { case 0: // AY-3-8910, 1.79MHz case 1: // AY-3-8910, 1.77MHz case 2: // AY-3-8910, 1.75MHz @@ -88,114 +88,116 @@ String DivEngine::getSongSystemName(bool isMultiSystemAcceptable) { return "Intellivision (PAL)"; default: - if ((song.systemFlags[0]&0x30)==0x00) { + if ((ds.systemFlags[0]&0x30)==0x00) { return "AY-3-8910"; - } else if ((song.systemFlags[0]&0x30)==0x10) { + } else if ((ds.systemFlags[0]&0x30)==0x10) { return "Yamaha YM2149"; - } else if ((song.systemFlags[0]&0x30)==0x20) { + } else if ((ds.systemFlags[0]&0x30)==0x20) { return "Overclocked Sunsoft 5B"; - } else if ((song.systemFlags[0]&0x30)==0x30) { + } else if ((ds.systemFlags[0]&0x30)==0x30) { return "Intellivision"; } } - } else if (song.system[0]==DIV_SYSTEM_SMS) { - switch (song.systemFlags[0]&0x0f) { + } else if (ds.system[0]==DIV_SYSTEM_SMS) { + switch (ds.systemFlags[0]&0x0f) { case 0: case 1: return "Sega Master System"; case 6: return "BBC Micro"; } - } else if (song.system[0]==DIV_SYSTEM_YM2612) { - switch (song.systemFlags[0]&3) { + } else if (ds.system[0]==DIV_SYSTEM_YM2612) { + switch (ds.systemFlags[0]&3) { case 2: return "FM Towns"; } - } else if (song.system[0]==DIV_SYSTEM_YM2151) { - switch (song.systemFlags[0]&3) { + } else if (ds.system[0]==DIV_SYSTEM_YM2151) { + switch (ds.systemFlags[0]&3) { case 2: return "Sharp X68000"; } - } else if (song.system[0]==DIV_SYSTEM_SAA1099) { - switch (song.systemFlags[0]&3) { + } else if (ds.system[0]==DIV_SYSTEM_SAA1099) { + switch (ds.systemFlags[0]&3) { case 0: return "SAM Coupé"; } } - return getSystemName(song.system[0]); + return getSystemName(ds.system[0]); case 2: - if (song.system[0]==DIV_SYSTEM_YM2612 && song.system[1]==DIV_SYSTEM_SMS) { + if (ds.system[0]==DIV_SYSTEM_YM2612 && ds.system[1]==DIV_SYSTEM_SMS) { return "Sega Genesis/Mega Drive"; } - if (song.system[0]==DIV_SYSTEM_YM2612_EXT && song.system[1]==DIV_SYSTEM_SMS) { + if (ds.system[0]==DIV_SYSTEM_YM2612_EXT && ds.system[1]==DIV_SYSTEM_SMS) { return "Sega Genesis Extended Channel 3"; } - if (song.system[0]==DIV_SYSTEM_OPLL && song.system[1]==DIV_SYSTEM_SMS) { + if (ds.system[0]==DIV_SYSTEM_OPLL && ds.system[1]==DIV_SYSTEM_SMS) { return "NTSC-J Sega Master System"; } - if (song.system[0]==DIV_SYSTEM_OPLL_DRUMS && song.system[1]==DIV_SYSTEM_SMS) { + if (ds.system[0]==DIV_SYSTEM_OPLL_DRUMS && ds.system[1]==DIV_SYSTEM_SMS) { return "NTSC-J Sega Master System + drums"; } - if (song.system[0]==DIV_SYSTEM_OPLL && song.system[1]==DIV_SYSTEM_AY8910) { + if (ds.system[0]==DIV_SYSTEM_OPLL && ds.system[1]==DIV_SYSTEM_AY8910) { return "MSX-MUSIC"; } - if (song.system[0]==DIV_SYSTEM_OPLL_DRUMS && song.system[1]==DIV_SYSTEM_AY8910) { + if (ds.system[0]==DIV_SYSTEM_OPLL_DRUMS && ds.system[1]==DIV_SYSTEM_AY8910) { return "MSX-MUSIC + drums"; } - if (song.system[0]==DIV_SYSTEM_C64_6581 && song.system[1]==DIV_SYSTEM_C64_6581) { + if (ds.system[0]==DIV_SYSTEM_C64_6581 && ds.system[1]==DIV_SYSTEM_C64_6581) { return "Commodore 64 with dual 6581"; } - if (song.system[0]==DIV_SYSTEM_C64_8580 && song.system[1]==DIV_SYSTEM_C64_8580) { + if (ds.system[0]==DIV_SYSTEM_C64_8580 && ds.system[1]==DIV_SYSTEM_C64_8580) { return "Commodore 64 with dual 8580"; } - if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_SEGAPCM_COMPAT) { + if (ds.system[0]==DIV_SYSTEM_YM2151 && ds.system[1]==DIV_SYSTEM_SEGAPCM_COMPAT) { return "YM2151 + SegaPCM Arcade (compatibility)"; } - if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_SEGAPCM) { + if (ds.system[0]==DIV_SYSTEM_YM2151 && ds.system[1]==DIV_SYSTEM_SEGAPCM) { return "YM2151 + SegaPCM Arcade"; } - if (song.system[0]==DIV_SYSTEM_SAA1099 && song.system[1]==DIV_SYSTEM_SAA1099) { + if (ds.system[0]==DIV_SYSTEM_SAA1099 && ds.system[1]==DIV_SYSTEM_SAA1099) { return "Creative Music System"; } - if (song.system[0]==DIV_SYSTEM_GB && song.system[1]==DIV_SYSTEM_AY8910) { + if (ds.system[0]==DIV_SYSTEM_GB && ds.system[1]==DIV_SYSTEM_AY8910) { return "Game Boy with AY expansion"; } - if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC6) { - return "NES + Konami VRC6"; + if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_VRC6) { + return "Famicom + Konami VRC6"; } - if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) { - return "NES + Konami VRC7"; + if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_VRC7) { + return "Famicom + Konami VRC7"; } - if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_OPLL) { - return "NES + Yamaha OPLL"; + if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_OPLL) { + return "Family Noraebang"; } - if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_FDS) { + if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_FDS) { return "Famicom Disk System"; } - if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_N163) { - return "NES + Namco C163"; + if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_N163) { + String ret="Famicom + "; + ret+=getConfString("c163Name",DIV_C163_DEFAULT_NAME); + return ret; } - if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_MMC5) { - return "NES + MMC5"; + if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_MMC5) { + return "Famicom + MMC5"; } - if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_AY8910) { - return "NES + Sunsoft 5B"; + if (ds.system[0]==DIV_SYSTEM_NES && ds.system[1]==DIV_SYSTEM_AY8910) { + return "Famicom + Sunsoft 5B"; } - if (song.system[0]==DIV_SYSTEM_AY8910 && song.system[1]==DIV_SYSTEM_AY8910) { + if (ds.system[0]==DIV_SYSTEM_AY8910 && ds.system[1]==DIV_SYSTEM_AY8910) { return "Bally Midway MCR"; } - if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_VERA) { + if (ds.system[0]==DIV_SYSTEM_YM2151 && ds.system[1]==DIV_SYSTEM_VERA) { return "Commander X16"; } break; case 3: - if (song.system[0]==DIV_SYSTEM_AY8910 && song.system[1]==DIV_SYSTEM_AY8910 && song.system[2]==DIV_SYSTEM_BUBSYS_WSG) { + if (ds.system[0]==DIV_SYSTEM_AY8910 && ds.system[1]==DIV_SYSTEM_AY8910 && ds.system[2]==DIV_SYSTEM_BUBSYS_WSG) { return "Konami Bubble System"; } break; @@ -203,9 +205,13 @@ String DivEngine::getSongSystemName(bool isMultiSystemAcceptable) { if (isMultiSystemAcceptable) return "multi-system"; String ret=""; - for (int i=0; i0) ret+=" + "; - ret+=getSystemName(song.system[i]); + if (ds.system[i]==DIV_SYSTEM_N163) { + ret+=getConfString("c163Name",DIV_C163_DEFAULT_NAME); + } else { + ret+=getSystemName(ds.system[i]); + } } return ret; @@ -213,6 +219,11 @@ String DivEngine::getSongSystemName(bool isMultiSystemAcceptable) { const char* DivEngine::getSystemName(DivSystem sys) { if (sysDefs[sys]==NULL) return "Unknown"; + if (sys==DIV_SYSTEM_N163) { + String c1=getConfString("c163Name",DIV_C163_DEFAULT_NAME); + strncpy(c163NameCS,c1.c_str(),1023); + return c163NameCS; + } return sysDefs[sys]->name; } @@ -273,6 +284,10 @@ const char* DivEngine::getSystemNameJ(DivSystem sys) { */ } +const DivSysDef* DivEngine::getSystemDef(DivSystem sys) { + return sysDefs[sys]; +} + bool DivEngine::isFMSystem(DivSystem sys) { if (sysDefs[sys]==NULL) return false; return sysDefs[sys]->isFM; @@ -349,390 +364,222 @@ int DivEngine::minVGMVersion(DivSystem which) { // {chanTypes, ...}, // {chanPreferInsType, ...}, // {chanPreferInsType2, ...}, (optional) -// [this](int ch, unsigned char effect, unsigned char effectVal) -> bool {}, (effect handler, optional) -// [this](int ch, unsigned char effect, unsigned char effectVal) -> bool {} (post effect handler, optional) +// {{effect, {DIV_CMD_xx, "Description"}}, ...}, (effect handler, optional) +// {{effect, {DIV_CMD_xx, "Description"}}, ...} (post effect handler, optional) // ); +template int constVal(unsigned char, unsigned char) { + return val; +}; + +int effectVal(unsigned char, unsigned char val) { + return val; +}; + +int negEffectVal(unsigned char, unsigned char val) { + return -(int)val; +}; + +template int effectValAnd(unsigned char, unsigned char val) { + return val&mask; +}; + +template int effectOpVal(unsigned char, unsigned char val) { + if ((val>>4)>maxOp) throw DivDoNotHandleEffect(); + return (val>>4)-1; +}; + +template int effectOpValNoZero(unsigned char, unsigned char val) { + if ((val>>4)<1 || (val>>4)>maxOp) throw DivDoNotHandleEffect(); + return (val>>4)-1; +}; + +template int effectValLong(unsigned char cmd, unsigned char val) { + return ((((unsigned int)cmd)&((1<<(bits-8))-1))<<8)|((unsigned int)val); +}; + void DivEngine::registerSystems() { logD("registering systems..."); - auto fmPostEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x10: // LFO or noise mode - if (IS_OPM_LIKE) { - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); - } else { - dispatchCmd(DivCommand(DIV_CMD_FM_LFO,ch,effectVal)); - } - break; - case 0x11: // FB - dispatchCmd(DivCommand(DIV_CMD_FM_FB,ch,effectVal&7)); - break; - case 0x12: // TL op1 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,0,effectVal&0x7f)); - break; - case 0x13: // TL op2 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,1,effectVal&0x7f)); - break; - case 0x14: // TL op3 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,2,effectVal&0x7f)); - break; - case 0x15: // TL op4 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,3,effectVal&0x7f)); - break; - case 0x16: // MULT - if ((effectVal>>4)>0 && (effectVal>>4)<5) { - dispatchCmd(DivCommand(DIV_CMD_FM_MULT,ch,(effectVal>>4)-1,effectVal&15)); - } - break; - case 0x17: // arcade LFO - if (IS_OPM_LIKE) { - dispatchCmd(DivCommand(DIV_CMD_FM_LFO,ch,effectVal)); - } - break; - case 0x18: // EXT or LFO waveform - if (IS_OPM_LIKE) { - dispatchCmd(DivCommand(DIV_CMD_FM_LFO_WAVE,ch,effectVal)); - } else { - dispatchCmd(DivCommand(DIV_CMD_FM_EXTCH,ch,effectVal)); - } - break; - case 0x19: // AR global - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,-1,effectVal&31)); - break; - case 0x1a: // AR op1 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,0,effectVal&31)); - break; - case 0x1b: // AR op2 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,1,effectVal&31)); - break; - case 0x1c: // AR op3 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,2,effectVal&31)); - break; - case 0x1d: // AR op4 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,3,effectVal&31)); - break; - case 0x1e: // UNOFFICIAL: Arcade AM depth - dispatchCmd(DivCommand(DIV_CMD_FM_AM_DEPTH,ch,effectVal&127)); - break; - case 0x1f: // UNOFFICIAL: Arcade PM depth - dispatchCmd(DivCommand(DIV_CMD_FM_PM_DEPTH,ch,effectVal&127)); - break; - case 0x20: // Neo Geo PSG mode - if (IS_YM2610) { - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - } - break; - case 0x21: // Neo Geo PSG noise freq - if (IS_YM2610) { - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); - } - break; - case 0x22: // UNOFFICIAL: Neo Geo PSG envelope enable - if (IS_YM2610) { - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SET,ch,effectVal)); - } - break; - case 0x23: // UNOFFICIAL: Neo Geo PSG envelope period low - if (IS_YM2610) { - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_LOW,ch,effectVal)); - } - break; - case 0x24: // UNOFFICIAL: Neo Geo PSG envelope period high - if (IS_YM2610) { - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_HIGH,ch,effectVal)); - } - break; - case 0x25: // UNOFFICIAL: Neo Geo PSG envelope slide up - if (IS_YM2610) { - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,-effectVal)); - } - break; - case 0x26: // UNOFFICIAL: Neo Geo PSG envelope slide down - if (IS_YM2610) { - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,effectVal)); - } - break; - case 0x29: // auto-envelope - if (IS_YM2610) { - dispatchCmd(DivCommand(DIV_CMD_AY_AUTO_ENVELOPE,ch,effectVal)); - } - break; - // fixed frequency effects on OPZ - case 0x30: case 0x31: case 0x32: case 0x33: - case 0x34: case 0x35: case 0x36: case 0x37: - if (sysOfChan[ch]==DIV_SYSTEM_OPZ) { - dispatchCmd(DivCommand(DIV_CMD_FM_FIXFREQ,ch,0,((effect&7)<<8)|effectVal)); - } - break; - case 0x38: case 0x39: case 0x3a: case 0x3b: - case 0x3c: case 0x3d: case 0x3e: case 0x3f: - if (sysOfChan[ch]==DIV_SYSTEM_OPZ) { - dispatchCmd(DivCommand(DIV_CMD_FM_FIXFREQ,ch,1,((effect&7)<<8)|effectVal)); - } - break; - case 0x40: case 0x41: case 0x42: case 0x43: - case 0x44: case 0x45: case 0x46: case 0x47: - if (sysOfChan[ch]==DIV_SYSTEM_OPZ) { - dispatchCmd(DivCommand(DIV_CMD_FM_FIXFREQ,ch,2,((effect&7)<<8)|effectVal)); - } - break; - case 0x48: case 0x49: case 0x4a: case 0x4b: - case 0x4c: case 0x4d: case 0x4e: case 0x4f: - if (sysOfChan[ch]==DIV_SYSTEM_OPZ) { - dispatchCmd(DivCommand(DIV_CMD_FM_FIXFREQ,ch,3,((effect&7)<<8)|effectVal)); - } - break; - // extra FM effects here - OP_EFFECT_SINGLE(0x50,DIV_CMD_FM_AM,4,1); - OP_EFFECT_SINGLE(0x51,DIV_CMD_FM_SL,4,15); - OP_EFFECT_SINGLE(0x52,DIV_CMD_FM_RR,4,15); - OP_EFFECT_SINGLE(0x53,DIV_CMD_FM_DT,4,7); - OP_EFFECT_SINGLE(0x54,DIV_CMD_FM_RS,4,3); - OP_EFFECT_SINGLE(0x55,DIV_CMD_FM_SSG,4,(IS_OPM_LIKE?3:15)); + // Common effect handler maps - OP_EFFECT_MULTI(0x56,DIV_CMD_FM_DR,-1,31); - OP_EFFECT_MULTI(0x57,DIV_CMD_FM_DR,0,31); - OP_EFFECT_MULTI(0x58,DIV_CMD_FM_DR,1,31); - OP_EFFECT_MULTI(0x59,DIV_CMD_FM_DR,2,31); - OP_EFFECT_MULTI(0x5a,DIV_CMD_FM_DR,3,31); - - OP_EFFECT_MULTI(0x5b,DIV_CMD_FM_D2R,-1,31); - OP_EFFECT_MULTI(0x5c,DIV_CMD_FM_D2R,0,31); - OP_EFFECT_MULTI(0x5d,DIV_CMD_FM_D2R,1,31); - OP_EFFECT_MULTI(0x5e,DIV_CMD_FM_D2R,2,31); - OP_EFFECT_MULTI(0x5f,DIV_CMD_FM_D2R,3,31); - - OP_EFFECT_SINGLE(0x28,DIV_CMD_FM_REV,4,7); - OP_EFFECT_SINGLE(0x2a,DIV_CMD_FM_WS,4,7); - OP_EFFECT_SINGLE(0x2b,DIV_CMD_FM_EG_SHIFT,4,3); - OP_EFFECT_SINGLE(0x2c,DIV_CMD_FM_FINE,4,15); - default: - return false; - } - return true; + EffectHandlerMap ayPostEffectHandlerMap={ + {0x20, {DIV_CMD_STD_NOISE_MODE, "20xx: Set channel mode (bit 0: square; bit 1: noise; bit 2: envelope)"}}, + {0x21, {DIV_CMD_STD_NOISE_FREQ, "21xx: Set noise frequency (0 to 1F)"}}, + {0x22, {DIV_CMD_AY_ENVELOPE_SET, "22xy: Set envelope mode (x: shape, y: enable for this channel)"}}, + {0x23, {DIV_CMD_AY_ENVELOPE_LOW, "23xx: Set envelope period low byte"}}, + {0x24, {DIV_CMD_AY_ENVELOPE_HIGH, "24xx: Set envelope period high byte"}}, + {0x25, {DIV_CMD_AY_ENVELOPE_SLIDE, "25xx: Envelope slide up", negEffectVal}}, + {0x26, {DIV_CMD_AY_ENVELOPE_SLIDE, "26xx: Envelope slide down"}}, + {0x29, {DIV_CMD_AY_AUTO_ENVELOPE, "29xy: Set auto-envelope (x: numerator; y: denominator)"}}, + {0x2e, {DIV_CMD_AY_IO_WRITE, "2Exx: Write to I/O port A", constVal<0>, effectVal}}, + {0x2f, {DIV_CMD_AY_IO_WRITE, "2Fxx: Write to I/O port B", constVal<1>, effectVal}}, }; - auto fmOPLLPostEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x11: // FB - dispatchCmd(DivCommand(DIV_CMD_FM_FB,ch,effectVal&7)); - break; - case 0x12: // TL op1 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,0,effectVal&0x3f)); - break; - case 0x13: // TL op2 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,1,effectVal&0x0f)); - break; - case 0x16: // MULT - if ((effectVal>>4)>0 && (effectVal>>4)<3) { - dispatchCmd(DivCommand(DIV_CMD_FM_MULT,ch,(effectVal>>4)-1,effectVal&15)); - } - break; - case 0x19: // AR global - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,-1,effectVal&31)); - break; - case 0x1a: // AR op1 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,0,effectVal&31)); - break; - case 0x1b: // AR op2 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,1,effectVal&31)); - break; + EffectHandlerMap ay8930PostEffectHandlerMap(ayPostEffectHandlerMap); + ay8930PostEffectHandlerMap.insert({ + {0x12, {DIV_CMD_STD_NOISE_MODE, "12xx: Set duty cycle (0 to 8)", + [](unsigned char, unsigned char val) -> int { return 0x10+(val&15); }}}, + {0x27, {DIV_CMD_AY_NOISE_MASK_AND, "27xx: Set noise AND mask"}}, + {0x28, {DIV_CMD_AY_NOISE_MASK_OR, "28xx: Set noise OR mask"}}, + {0x2d, {DIV_CMD_AY_IO_WRITE, "2Dxx: NOT TO BE EMPLOYED BY THE COMPOSER", constVal<255>, effectVal}}, + }); - // extra FM effects here - OP_EFFECT_SINGLE(0x50,DIV_CMD_FM_AM,2,1); - OP_EFFECT_SINGLE(0x51,DIV_CMD_FM_SL,2,15); - OP_EFFECT_SINGLE(0x52,DIV_CMD_FM_RR,2,15); - OP_EFFECT_SINGLE(0x53,DIV_CMD_FM_VIB,2,1); - OP_EFFECT_SINGLE(0x54,DIV_CMD_FM_RS,2,3); - OP_EFFECT_SINGLE(0x55,DIV_CMD_FM_SUS,2,1); - - OP_EFFECT_MULTI(0x56,DIV_CMD_FM_DR,-1,15); - OP_EFFECT_MULTI(0x57,DIV_CMD_FM_DR,0,15); - OP_EFFECT_MULTI(0x58,DIV_CMD_FM_DR,1,15); - - OP_EFFECT_SINGLE(0x5b,DIV_CMD_FM_KSR,2,1); - default: - return false; - } - return true; + EffectHandlerMap fmEffectHandlerMap={ + {0x30, {DIV_CMD_FM_HARD_RESET, "30xx: Toggle hard envelope reset on new notes"}}, }; - auto fmOPLPostEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x10: // DAM - dispatchCmd(DivCommand(DIV_CMD_FM_LFO,ch,effectVal&1)); - break; - case 0x11: // FB - dispatchCmd(DivCommand(DIV_CMD_FM_FB,ch,effectVal&7)); - break; - case 0x12: // TL op1 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,0,effectVal&0x3f)); - break; - case 0x13: // TL op2 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,1,effectVal&0x3f)); - break; - case 0x14: // TL op3 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,2,effectVal&0x3f)); - break; - case 0x15: // TL op4 - dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,3,effectVal&0x3f)); - break; - case 0x16: // MULT - if ((effectVal>>4)>0 && (effectVal>>4)<5) { - dispatchCmd(DivCommand(DIV_CMD_FM_MULT,ch,(effectVal>>4)-1,effectVal&15)); - } - break; - case 0x17: // DVB - dispatchCmd(DivCommand(DIV_CMD_FM_LFO,ch,2+(effectVal&1))); - break; - case 0x19: // AR global - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,-1,effectVal&15)); - break; - case 0x1a: // AR op1 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,0,effectVal&15)); - break; - case 0x1b: // AR op2 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,1,effectVal&15)); - break; - case 0x1c: // AR op3 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,2,effectVal&15)); - break; - case 0x1d: // AR op4 - dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,3,effectVal&15)); - break; - - // extra FM effects here - OP_EFFECT_SINGLE(0x50,DIV_CMD_FM_AM,4,1); - OP_EFFECT_SINGLE(0x51,DIV_CMD_FM_SL,4,15); - OP_EFFECT_SINGLE(0x52,DIV_CMD_FM_RR,4,15); - OP_EFFECT_SINGLE(0x53,DIV_CMD_FM_VIB,4,1); - OP_EFFECT_SINGLE(0x54,DIV_CMD_FM_RS,4,3); - OP_EFFECT_SINGLE(0x55,DIV_CMD_FM_SUS,4,1); + EffectHandlerMap fmOPN2EffectHandlerMap(fmEffectHandlerMap); + fmOPN2EffectHandlerMap.insert({ + {0x17, {DIV_CMD_SAMPLE_MODE, "17xx: Toggle PCM mode"}}, + {0xdf, {DIV_CMD_SAMPLE_DIR, "DFxx: Set sample playback direction (0: normal; 1: reverse)"}}, + }); - OP_EFFECT_MULTI(0x56,DIV_CMD_FM_DR,-1,15); - OP_EFFECT_MULTI(0x57,DIV_CMD_FM_DR,0,15); - OP_EFFECT_MULTI(0x58,DIV_CMD_FM_DR,1,15); - OP_EFFECT_MULTI(0x59,DIV_CMD_FM_DR,2,15); - OP_EFFECT_MULTI(0x5a,DIV_CMD_FM_DR,3,15); + EffectHandlerMap fmOPLDrumsEffectHandlerMap(fmEffectHandlerMap); + fmOPLDrumsEffectHandlerMap.insert({ + {0x18, {DIV_CMD_FM_EXTCH, "18xx: Toggle drums mode (1: enabled; 0: disabled)"}}, + }); - OP_EFFECT_SINGLE(0x5b,DIV_CMD_FM_KSR,4,1); - OP_EFFECT_SINGLE(0x2a,DIV_CMD_FM_WS,4,7); - - default: - return false; - } - return true; + EffectHandlerMap fmOPNPostEffectHandlerMap={ + {0x11, {DIV_CMD_FM_FB, "11xx: Set feedback (0 to 7)"}}, + {0x12, {DIV_CMD_FM_TL, "12xx: Set level of operator 1 (0 highest, 7F lowest)", constVal<0>, effectVal}}, + {0x13, {DIV_CMD_FM_TL, "13xx: Set level of operator 2 (0 highest, 7F lowest)", constVal<1>, effectVal}}, + {0x14, {DIV_CMD_FM_TL, "14xx: Set level of operator 3 (0 highest, 7F lowest)", constVal<2>, effectVal}}, + {0x15, {DIV_CMD_FM_TL, "15xx: Set level of operator 4 (0 highest, 7F lowest)", constVal<3>, effectVal}}, + {0x16, {DIV_CMD_FM_MULT, "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)", effectOpValNoZero<4>, effectValAnd<15>}}, + {0x19, {DIV_CMD_FM_AR, "19xx: Set attack of all operators (0 to 1F)", constVal<-1>, effectValAnd<31>}}, + {0x1a, {DIV_CMD_FM_AR, "1Axx: Set attack of operator 1 (0 to 1F)", constVal<0>, effectValAnd<31>}}, + {0x1b, {DIV_CMD_FM_AR, "1Bxx: Set attack of operator 2 (0 to 1F)", constVal<1>, effectValAnd<31>}}, + {0x1c, {DIV_CMD_FM_AR, "1Cxx: Set attack of operator 3 (0 to 1F)", constVal<2>, effectValAnd<31>}}, + {0x1d, {DIV_CMD_FM_AR, "1Dxx: Set attack of operator 4 (0 to 1F)", constVal<3>, effectValAnd<31>}}, + {0x50, {DIV_CMD_FM_AM, "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)", effectOpVal<4>, effectValAnd<1>}}, + {0x51, {DIV_CMD_FM_SL, "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)", effectOpVal<4>, effectValAnd<15>}}, + {0x52, {DIV_CMD_FM_RR, "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)", effectOpVal<4>, effectValAnd<15>}}, + {0x53, {DIV_CMD_FM_DT, "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)", effectOpVal<4>, effectValAnd<7>}}, + {0x54, {DIV_CMD_FM_RS, "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)", effectOpVal<4>, effectValAnd<3>}}, + {0x56, {DIV_CMD_FM_DR, "56xx: Set decay of all operators (0 to 1F)", constVal<-1>, effectValAnd<31>}}, + {0x57, {DIV_CMD_FM_DR, "57xx: Set decay of operator 1 (0 to 1F)", constVal<0>, effectValAnd<31>}}, + {0x58, {DIV_CMD_FM_DR, "58xx: Set decay of operator 2 (0 to 1F)", constVal<1>, effectValAnd<31>}}, + {0x59, {DIV_CMD_FM_DR, "59xx: Set decay of operator 3 (0 to 1F)", constVal<2>, effectValAnd<31>}}, + {0x5a, {DIV_CMD_FM_DR, "5Axx: Set decay of operator 4 (0 to 1F)", constVal<3>, effectValAnd<31>}}, + {0x5b, {DIV_CMD_FM_D2R, "5Bxx: Set decay 2 of all operators (0 to 1F)", constVal<-1>, effectValAnd<31>}}, + {0x5c, {DIV_CMD_FM_D2R, "5Cxx: Set decay 2 of operator 1 (0 to 1F)", constVal<0>, effectValAnd<31>}}, + {0x5d, {DIV_CMD_FM_D2R, "5Dxx: Set decay 2 of operator 2 (0 to 1F)", constVal<1>, effectValAnd<31>}}, + {0x5e, {DIV_CMD_FM_D2R, "5Exx: Set decay 2 of operator 3 (0 to 1F)", constVal<2>, effectValAnd<31>}}, + {0x5f, {DIV_CMD_FM_D2R, "5Fxx: Set decay 2 of operator 4 (0 to 1F)", constVal<3>, effectValAnd<31>}}, }; - auto c64PostEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x10: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - case 0x11: // cutoff - dispatchCmd(DivCommand(DIV_CMD_C64_CUTOFF,ch,effectVal)); - break; - case 0x12: // duty - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - case 0x13: // resonance - dispatchCmd(DivCommand(DIV_CMD_C64_RESONANCE,ch,effectVal)); - break; - case 0x14: // filter mode - dispatchCmd(DivCommand(DIV_CMD_C64_FILTER_MODE,ch,effectVal)); - break; - case 0x15: // reset time - dispatchCmd(DivCommand(DIV_CMD_C64_RESET_TIME,ch,effectVal)); - break; - case 0x1a: // reset mask - dispatchCmd(DivCommand(DIV_CMD_C64_RESET_MASK,ch,effectVal)); - break; - case 0x1b: // cutoff reset - dispatchCmd(DivCommand(DIV_CMD_C64_FILTER_RESET,ch,effectVal)); - break; - case 0x1c: // duty reset - dispatchCmd(DivCommand(DIV_CMD_C64_DUTY_RESET,ch,effectVal)); - break; - case 0x1e: // extended - dispatchCmd(DivCommand(DIV_CMD_C64_EXTENDED,ch,effectVal)); - break; - case 0x30: case 0x31: case 0x32: case 0x33: - case 0x34: case 0x35: case 0x36: case 0x37: - case 0x38: case 0x39: case 0x3a: case 0x3b: - case 0x3c: case 0x3d: case 0x3e: case 0x3f: // fine duty - dispatchCmd(DivCommand(DIV_CMD_C64_FINE_DUTY,ch,((effect&0x0f)<<8)|effectVal)); - break; - case 0x40: case 0x41: case 0x42: case 0x43: - case 0x44: case 0x45: case 0x46: case 0x47: // fine cutoff - dispatchCmd(DivCommand(DIV_CMD_C64_FINE_CUTOFF,ch,((effect&0x07)<<8)|effectVal)); - break; - default: - return false; - } - return true; + EffectHandlerMap fmOPMPostEffectHandlerMap(fmOPNPostEffectHandlerMap); + fmOPMPostEffectHandlerMap.insert({ + {0x10, {DIV_CMD_STD_NOISE_FREQ, "10xx: Set noise frequency (xx: value; 0 disables noise)"}}, + {0x17, {DIV_CMD_FM_LFO, "17xx: Set LFO speed"}}, + {0x18, {DIV_CMD_FM_LFO_WAVE, "18xx: Set LFO waveform (0 saw, 1 square, 2 triangle, 3 noise)"}}, + {0x1e, {DIV_CMD_FM_AM_DEPTH, "1Exx: Set AM depth (0 to 7F)", effectValAnd<127>}}, + {0x1f, {DIV_CMD_FM_PM_DEPTH, "1Fxx: Set PM depth (0 to 7F)", effectValAnd<127>}}, + {0x55, {DIV_CMD_FM_SSG, "55xy: Set detune 2 (x: operator from 1 to 4 (0 for all ops); y: detune from 0 to 3)", effectOpVal<4>, effectValAnd<3>}}, + }); + + EffectHandlerMap fmOPZPostEffectHandlerMap(fmOPMPostEffectHandlerMap); + fmOPZPostEffectHandlerMap.insert({ + {0x28, {DIV_CMD_FM_REV, "28xy: Set reverb (x: operator from 1 to 4 (0 for all ops); y: reverb from 0 to 7)", effectOpVal<4>, effectValAnd<7>}}, + {0x2a, {DIV_CMD_FM_WS, "2Axy: Set waveform (x: operator from 1 to 4 (0 for all ops); y: waveform from 0 to 7)", effectOpVal<4>, effectValAnd<7>}}, + {0x2b, {DIV_CMD_FM_EG_SHIFT, "2Bxy: Set envelope generator shift (x: operator from 1 to 4 (0 for all ops); y: shift from 0 to 3)", effectOpVal<4>, effectValAnd<3>}}, + {0x2c, {DIV_CMD_FM_FINE, "2Cxy: Set fine multiplier (x: operator from 1 to 4 (0 for all ops); y: fine)", effectOpVal<4>, effectValAnd<15>}}, + }); + const EffectHandler fmOPZFixFreqHandler[4]={ + {DIV_CMD_FM_FIXFREQ, "3xyy: Set fixed frequency of operator 1 (x: octave from 0 to 7; y: frequency)", constVal<0>, effectValLong<11>}, + {DIV_CMD_FM_FIXFREQ, "3xyy: Set fixed frequency of operator 2 (x: octave from 8 to F; y: frequency)", constVal<1>, effectValLong<11>}, + {DIV_CMD_FM_FIXFREQ, "4xyy: Set fixed frequency of operator 3 (x: octave from 0 to 7; y: frequency)", constVal<2>, effectValLong<11>}, + {DIV_CMD_FM_FIXFREQ, "4xyy: Set fixed frequency of operator 4 (x: octave from 8 to F; y: frequency)", constVal<3>, effectValLong<11>}, + }; + for (int i=0; i<32; i++) { + fmOPZPostEffectHandlerMap.emplace(0x30+i,fmOPZFixFreqHandler[i/8]); + } + + fmOPNPostEffectHandlerMap.insert({ + {0x10, {DIV_CMD_FM_LFO, "10xy: Setup LFO (x: enable; y: speed)"}}, + {0x18, {DIV_CMD_FM_EXTCH, "18xx: Toggle extended channel 3 mode"}}, + {0x55, {DIV_CMD_FM_SSG, "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)", effectOpVal<4>, effectValAnd<15>}}, + }); + EffectHandlerMap fmOPN2PostEffectHandlerMap(fmOPNPostEffectHandlerMap); + + fmOPNPostEffectHandlerMap.insert(ayPostEffectHandlerMap.begin(), ayPostEffectHandlerMap.end()); + + EffectHandlerMap fmOPLLPostEffectHandlerMap={ + {0x11, {DIV_CMD_FM_FB, "11xx: Set feedback (0 to 7)"}}, + {0x12, {DIV_CMD_FM_TL, "12xx: Set level of operator 1 (0 highest, 3F lowest)", constVal<0>, effectVal}}, + {0x13, {DIV_CMD_FM_TL, "13xx: Set level of operator 2 (0 highest, 3F lowest)", constVal<1>, effectVal}}, + {0x16, {DIV_CMD_FM_MULT, "16xy: Set operator multiplier (x: operator from 1 to 2; y: multiplier)", effectOpValNoZero<2>, effectValAnd<15>}}, + {0x19, {DIV_CMD_FM_AR, "19xx: Set attack of all operators (0 to F)", constVal<-1>, effectValAnd<15>}}, + {0x1a, {DIV_CMD_FM_AR, "1Axx: Set attack of operator 1 (0 to F)", constVal<0>, effectValAnd<15>}}, + {0x1b, {DIV_CMD_FM_AR, "1Bxx: Set attack of operator 2 (0 to F)", constVal<1>, effectValAnd<15>}}, + {0x50, {DIV_CMD_FM_AM, "50xy: Set AM (x: operator from 1 to 2 (0 for all ops); y: AM)", effectOpVal<2>, effectValAnd<1>}}, + {0x51, {DIV_CMD_FM_SL, "51xy: Set sustain level (x: operator from 1 to 2 (0 for all ops); y: sustain)", effectOpVal<2>, effectValAnd<15>}}, + {0x52, {DIV_CMD_FM_RR, "52xy: Set release (x: operator from 1 to 2 (0 for all ops); y: release)", effectOpVal<2>, effectValAnd<15>}}, + {0x53, {DIV_CMD_FM_VIB, "53xy: Set vibrato (x: operator from 1 to 2 (0 for all ops); y: enabled)", effectOpVal<2>, effectValAnd<1>}}, + {0x54, {DIV_CMD_FM_RS, "54xy: Set envelope scale (x: operator from 1 to 2 (0 for all ops); y: scale from 0 to 3)", effectOpVal<2>, effectValAnd<3>}}, + {0x55, {DIV_CMD_FM_SUS, "55xy: Set envelope sustain (x: operator from 1 to 2 (0 for all ops); y: enabled)", effectOpVal<2>, effectValAnd<1>}}, + {0x56, {DIV_CMD_FM_DR, "56xx: Set decay of all operators (0 to F)", constVal<-1>, effectValAnd<15>}}, + {0x57, {DIV_CMD_FM_DR, "57xx: Set decay of operator 1 (0 to F)", constVal<0>, effectValAnd<15>}}, + {0x58, {DIV_CMD_FM_DR, "58xx: Set decay of operator 2 (0 to F)", constVal<1>, effectValAnd<15>}}, + {0x5b, {DIV_CMD_FM_KSR, "5Bxy: Set whether key will scale envelope (x: operator from 1 to 2 (0 for all ops); y: enabled)", effectOpVal<2>, effectValAnd<1>}}, }; - auto ayPostEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x12: // duty on 8930 - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,0x10+(effectVal&15))); - break; - case 0x20: // mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal&15)); - break; - case 0x21: // noise freq - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); - break; - case 0x22: // envelope enable - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SET,ch,effectVal)); - break; - case 0x23: // envelope period low - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_LOW,ch,effectVal)); - break; - case 0x24: // envelope period high - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_HIGH,ch,effectVal)); - break; - case 0x25: // envelope slide up - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,-effectVal)); - break; - case 0x26: // envelope slide down - dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,effectVal)); - break; - case 0x27: // noise and mask - dispatchCmd(DivCommand(DIV_CMD_AY_NOISE_MASK_AND,ch,effectVal)); - break; - case 0x28: // noise or mask - dispatchCmd(DivCommand(DIV_CMD_AY_NOISE_MASK_OR,ch,effectVal)); - break; - case 0x29: // auto-envelope - dispatchCmd(DivCommand(DIV_CMD_AY_AUTO_ENVELOPE,ch,effectVal)); - break; - case 0x2d: // TEST - dispatchCmd(DivCommand(DIV_CMD_AY_IO_WRITE,ch,255,effectVal)); - break; - case 0x2e: // I/O port A - dispatchCmd(DivCommand(DIV_CMD_AY_IO_WRITE,ch,0,effectVal)); - break; - case 0x2f: // I/O port B - dispatchCmd(DivCommand(DIV_CMD_AY_IO_WRITE,ch,1,effectVal)); - break; - default: - return false; - } - return true; + EffectHandlerMap fmOPLPostEffectHandlerMap={ + {0x10, {DIV_CMD_FM_LFO, "10xx: Set global AM depth (0: 1dB, 1: 4.8dB)", effectValAnd<1>}}, + {0x11, {DIV_CMD_FM_FB, "11xx: Set feedback (0 to 7)"}}, + {0x12, {DIV_CMD_FM_TL, "12xx: Set level of operator 1 (0 highest, 3F lowest)", constVal<0>, effectVal}}, + {0x13, {DIV_CMD_FM_TL, "13xx: Set level of operator 2 (0 highest, 3F lowest)", constVal<1>, effectVal}}, + {0x14, {DIV_CMD_FM_TL, "14xx: Set level of operator 3 (0 highest, 3F lowest)", constVal<2>, effectVal}}, + {0x15, {DIV_CMD_FM_TL, "15xx: Set level of operator 4 (0 highest, 3F lowest)", constVal<3>, effectVal}}, + {0x16, {DIV_CMD_FM_MULT, "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)", effectOpValNoZero<4>, effectValAnd<15>}}, + {0x17, {DIV_CMD_FM_LFO, "17xx: Set global vibrato depth (0: normal, 1: double)", [](unsigned char, unsigned char val) -> int { return (val&1)+2; }}}, + {0x19, {DIV_CMD_FM_AR, "19xx: Set attack of all operators (0 to F)", constVal<-1>, effectValAnd<15>}}, + {0x1a, {DIV_CMD_FM_AR, "1Axx: Set attack of operator 1 (0 to F)", constVal<0>, effectValAnd<15>}}, + {0x1b, {DIV_CMD_FM_AR, "1Bxx: Set attack of operator 2 (0 to F)", constVal<1>, effectValAnd<15>}}, + {0x1c, {DIV_CMD_FM_AR, "1Cxx: Set attack of operator 3 (0 to F)", constVal<2>, effectValAnd<15>}}, + {0x1d, {DIV_CMD_FM_AR, "1Dxx: Set attack of operator 4 (0 to F)", constVal<3>, effectValAnd<15>}}, + {0x2a, {DIV_CMD_FM_WS, "2Axy: Set waveform (x: operator from 1 to 4 (0 for all ops); y: waveform from 0 to 3 in OPL2 and 0 to 7 in OPL3)", effectOpVal<4>, effectValAnd<7>}}, + {0x50, {DIV_CMD_FM_AM, "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)", effectOpVal<4>, effectValAnd<1>}}, + {0x51, {DIV_CMD_FM_SL, "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)", effectOpVal<4>, effectValAnd<15>}}, + {0x52, {DIV_CMD_FM_RR, "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)", effectOpVal<4>, effectValAnd<15>}}, + {0x53, {DIV_CMD_FM_VIB, "53xy: Set vibrato (x: operator from 1 to 4 (0 for all ops); y: enabled)", effectOpVal<4>, effectValAnd<1>}}, + {0x54, {DIV_CMD_FM_RS, "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)", effectOpVal<4>, effectValAnd<3>}}, + {0x55, {DIV_CMD_FM_SUS, "55xy: Set envelope sustain (x: operator from 1 to 4 (0 for all ops); y: enabled)", effectOpVal<4>, effectValAnd<1>}}, + {0x56, {DIV_CMD_FM_DR, "56xx: Set decay of all operators (0 to F)", constVal<-1>, effectValAnd<15>}}, + {0x57, {DIV_CMD_FM_DR, "57xx: Set decay of operator 1 (0 to F)", constVal<0>, effectValAnd<15>}}, + {0x58, {DIV_CMD_FM_DR, "58xx: Set decay of operator 2 (0 to F)", constVal<1>, effectValAnd<15>}}, + {0x59, {DIV_CMD_FM_DR, "59xx: Set decay of operator 3 (0 to F)", constVal<2>, effectValAnd<15>}}, + {0x5a, {DIV_CMD_FM_DR, "5Axx: Set decay of operator 4 (0 to F)", constVal<3>, effectValAnd<15>}}, + {0x5b, {DIV_CMD_FM_KSR, "5Bxy: Set whether key will scale envelope (x: operator from 1 to 4 (0 for all ops); y: enabled)", effectOpVal<4>, effectValAnd<1>}}, }; - auto segaPCMPostEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x20: // PCM frequency - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_FREQ,ch,effectVal)); - break; - default: - return false; - } - return true; + EffectHandlerMap c64PostEffectHandlerMap={ + {0x10, {DIV_CMD_WAVE, "10xx: Set waveform (bit 0: triangle; bit 1: saw; bit 2: pulse; bit 3: noise)"}}, + {0x11, {DIV_CMD_C64_CUTOFF, "11xx: Set coarse cutoff (not recommended; use 4xxx instead)"}}, + {0x12, {DIV_CMD_STD_NOISE_MODE, "12xx: Set coarse pulse width (not recommended; use 3xxx instead)"}}, + {0x13, {DIV_CMD_C64_RESONANCE, "13xx: Set resonance (0 to F)"}}, + {0x14, {DIV_CMD_C64_FILTER_MODE, "14xx: Set filter mode (bit 0: low pass; bit 1: band pass; bit 2: high pass)"}}, + {0x15, {DIV_CMD_C64_RESET_TIME, "15xx: Set envelope reset time"}}, + {0x1a, {DIV_CMD_C64_RESET_MASK, "1Axx: Disable envelope reset for this channel (1 disables; 0 enables)"}}, + {0x1b, {DIV_CMD_C64_FILTER_RESET, "1Bxy: Reset cutoff (x: on new note; y: now)"}}, + {0x1c, {DIV_CMD_C64_DUTY_RESET, "1Cxy: Reset pulse width (x: on new note; y: now)"}}, + {0x1e, {DIV_CMD_C64_EXTENDED, "1Exy: Change additional parameters"}}, }; + const EffectHandler c64FineDutyHandler(DIV_CMD_C64_FINE_DUTY, "3xxx: Set pulse width (0 to FFF)", effectValLong<12>); + const EffectHandler c64FineCutoffHandler(DIV_CMD_C64_FINE_CUTOFF, "4xxx: Set cutoff (0 to 7FF)", effectValLong<11>); + for (int i=0; i<16; i++) c64PostEffectHandlerMap.emplace(0x30+i,c64FineDutyHandler); + for (int i=0; i<8; i++) c64PostEffectHandlerMap.emplace(0x40+i,c64FineCutoffHandler); + + EffectHandlerMap waveOnlyEffectHandlerMap={ + {0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}}, + }; + + EffectHandlerMap segaPCMPostEffectHandlerMap={ + {0x20, {DIV_CMD_SAMPLE_FREQ, "20xx: Set PCM frequency"}} + }; + + // SysDefs sysDefs[DIV_SYSTEM_YMU759]=new DivSysDef( "Yamaha YMU759 (MA-2)", NULL, 0x01, 0x01, 17, true, false, 0, false, @@ -763,15 +610,8 @@ void DivEngine::registerSystems() { {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE}, {DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, {}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x20: // SN noise mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - default: - return false; - } - return true; + { + {0x20, {DIV_CMD_STD_NOISE_MODE, "20xy: Set noise mode (x: preset freq/ch3 freq; y: thin pulse/noise)"}} } ); @@ -789,24 +629,12 @@ void DivEngine::registerSystems() { {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_WAVE, DIV_CH_NOISE}, {DIV_INS_GB, DIV_INS_GB, DIV_INS_GB, DIV_INS_GB}, {}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x10: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - case 0x11: case 0x12: // duty or noise mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - case 0x13: // sweep params - dispatchCmd(DivCommand(DIV_CMD_GB_SWEEP_TIME,ch,effectVal)); - break; - case 0x14: // sweep direction - dispatchCmd(DivCommand(DIV_CMD_GB_SWEEP_DIR,ch,effectVal)); - break; - default: - return false; - } - return true; + { + {0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}}, + {0x11, {DIV_CMD_STD_NOISE_MODE, "11xx: Set noise length (0: long; 1: short)"}}, + {0x12, {DIV_CMD_STD_NOISE_MODE, "12xx: Set duty cycle (0 to 3)"}}, + {0x13, {DIV_CMD_GB_SWEEP_TIME, "13xy: Setup sweep (x: time; y: shift)"}}, + {0x14, {DIV_CMD_GB_SWEEP_DIR, "14xx: Set sweep direction (0: up; 1: down)"}} } ); @@ -818,27 +646,12 @@ void DivEngine::registerSystems() { {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, {DIV_INS_PCE, DIV_INS_PCE, DIV_INS_PCE, DIV_INS_PCE, DIV_INS_PCE, DIV_INS_PCE}, {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x10: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - case 0x11: // noise mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - case 0x12: // LFO mode - dispatchCmd(DivCommand(DIV_CMD_PCE_LFO_MODE,ch,effectVal)); - break; - case 0x13: // LFO speed - dispatchCmd(DivCommand(DIV_CMD_PCE_LFO_SPEED,ch,effectVal)); - break; - case 0x17: // PCM enable - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); - break; - default: - return false; - } - return true; + { + {0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}}, + {0x11, {DIV_CMD_STD_NOISE_MODE, "11xx: Toggle noise mode"}}, + {0x12, {DIV_CMD_PCE_LFO_MODE, "12xx: Setup LFO (0: disabled; 1: 1x depth; 2: 16x depth; 3: 256x depth)"}}, + {0x13, {DIV_CMD_PCE_LFO_SPEED, "13xx: Set LFO speed"}}, + {0x17, {DIV_CMD_SAMPLE_MODE, "17xx: Toggle PCM mode"}} } ); @@ -850,27 +663,12 @@ void DivEngine::registerSystems() { {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_WAVE, DIV_CH_NOISE, DIV_CH_PCM}, {DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_AMIGA}, {}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x11: // DMC write - dispatchCmd(DivCommand(DIV_CMD_NES_DMC,ch,effectVal)); - break; - case 0x12: // duty or noise mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - case 0x13: // sweep up - dispatchCmd(DivCommand(DIV_CMD_NES_SWEEP,ch,0,effectVal)); - break; - case 0x14: // sweep down - dispatchCmd(DivCommand(DIV_CMD_NES_SWEEP,ch,1,effectVal)); - break; - case 0x18: // DPCM mode - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,effectVal)); - break; - default: - return false; - } - return true; + { + {0x11, {DIV_CMD_NES_DMC, "11xx: Write to delta modulation counter (0 to 7F)"}}, + {0x12, {DIV_CMD_STD_NOISE_MODE, "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)"}}, + {0x13, {DIV_CMD_NES_SWEEP, "13xy: Sweep up (x: time; y: shift)",constVal<0>,effectVal}}, + {0x14, {DIV_CMD_NES_SWEEP, "14xy: Sweep down (x: time; y: shift)",constVal<1>,effectVal}}, + {0x18, {DIV_CMD_SAMPLE_MODE, "18xx: Select PCM/DPCM mode (0: PCM; 1: DPCM)"}} } ); @@ -894,8 +692,8 @@ void DivEngine::registerSystems() { {DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, {DIV_INS_C64, DIV_INS_C64, DIV_INS_C64}, {}, - [](int,unsigned char,unsigned char) -> bool {return false;}, - c64PostEffectHandler + {}, + c64PostEffectHandlerMap ); sysDefs[DIV_SYSTEM_C64_8580]=new DivSysDef( @@ -906,8 +704,8 @@ void DivEngine::registerSystems() { {DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, {DIV_INS_C64, DIV_INS_C64, DIV_INS_C64}, {}, - [](int,unsigned char,unsigned char) -> bool {return false;}, - c64PostEffectHandler + {}, + c64PostEffectHandlerMap ); sysDefs[DIV_SYSTEM_ARCADE]=new DivSysDef( @@ -916,17 +714,6 @@ void DivEngine::registerSystems() { {}, {}, {}, {} ); - auto fmHardResetEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x30: // toggle hard-reset - dispatchCmd(DivCommand(DIV_CMD_FM_HARD_RESET,ch,effectVal)); - break; - default: - return false; - } - return true; - }; - sysDefs[DIV_SYSTEM_YM2610]=new DivSysDef( "Neo Geo CD", NULL, 0x09, 0x09, 13, true, true, 0x151, false, "like Neo Geo, but lacking the ADPCM-B channel since they couldn't connect the pins.", @@ -935,8 +722,8 @@ void DivEngine::registerSystems() { {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, {}, - fmHardResetEffectHandler, - fmPostEffectHandler + fmEffectHandlerMap, + fmOPNPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_YM2610_EXT]=new DivSysDef( @@ -947,8 +734,8 @@ void DivEngine::registerSystems() { {DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, {}, - fmHardResetEffectHandler, - fmPostEffectHandler + fmEffectHandlerMap, + fmOPNPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_AY8910]=new DivSysDef( @@ -959,8 +746,8 @@ void DivEngine::registerSystems() { {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, {DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}, {}, - [](int,unsigned char,unsigned char) -> bool {return false;}, - ayPostEffectHandler + {}, + ayPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_AMIGA]=new DivSysDef( @@ -971,22 +758,11 @@ void DivEngine::registerSystems() { {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, {}, - [](int,unsigned char,unsigned char) -> bool {return false;}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x10: // toggle filter - dispatchCmd(DivCommand(DIV_CMD_AMIGA_FILTER,ch,effectVal)); - break; - case 0x11: // toggle AM - dispatchCmd(DivCommand(DIV_CMD_AMIGA_AM,ch,effectVal)); - break; - case 0x12: // toggle PM - dispatchCmd(DivCommand(DIV_CMD_AMIGA_PM,ch,effectVal)); - break; - default: - return false; - } - return true; + {}, + { + {0x10, {DIV_CMD_AMIGA_FILTER, "10xx: Toggle filter (0 disables; 1 enables)"}}, + {0x11, {DIV_CMD_AMIGA_AM, "11xx: Toggle AM with next channel"}}, + {0x12, {DIV_CMD_AMIGA_PM, "12xx: Toggle period modulation with next channel"}}, } ); @@ -998,24 +774,10 @@ void DivEngine::registerSystems() { {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, {}, - fmHardResetEffectHandler, - fmPostEffectHandler + fmEffectHandlerMap, + fmOPMPostEffectHandlerMap ); - auto opn2EffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x17: // DAC enable - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); - break; - case 0x30: // toggle hard-reset - dispatchCmd(DivCommand(DIV_CMD_FM_HARD_RESET,ch,effectVal)); - break; - default: - return false; - } - return true; - }; - sysDefs[DIV_SYSTEM_YM2612]=new DivSysDef( "Yamaha YM2612 (OPN2)", NULL, 0x83, 0, 6, true, false, 0x150, false, "this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).", @@ -1024,8 +786,8 @@ void DivEngine::registerSystems() { {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, {DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA}, - opn2EffectHandler, - fmPostEffectHandler + fmOPN2EffectHandlerMap, + fmOPN2PostEffectHandlerMap ); sysDefs[DIV_SYSTEM_TIA]=new DivSysDef( @@ -1036,17 +798,8 @@ void DivEngine::registerSystems() { {DIV_CH_WAVE, DIV_CH_WAVE}, {DIV_INS_TIA, DIV_INS_TIA}, {}, - [](int,unsigned char,unsigned char) -> bool {return false;}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x10: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - default: - return false; - } - return true; - } + {}, + waveOnlyEffectHandlerMap ); sysDefs[DIV_SYSTEM_SAA1099]=new DivSysDef( @@ -1057,22 +810,11 @@ void DivEngine::registerSystems() { {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, {DIV_INS_SAA1099, DIV_INS_SAA1099, DIV_INS_SAA1099, DIV_INS_SAA1099, DIV_INS_SAA1099, DIV_INS_SAA1099}, {}, - [](int,unsigned char,unsigned char) -> bool {return false;}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x10: // select channel mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - case 0x11: // set noise freq - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); - break; - case 0x12: // setup envelope - dispatchCmd(DivCommand(DIV_CMD_SAA_ENVELOPE,ch,effectVal)); - break; - default: - return false; - } - return true; + {}, + { + {0x10, {DIV_CMD_STD_NOISE_MODE, "10xy: Set channel mode (x: noise; y: tone)"}}, + {0x11, {DIV_CMD_STD_NOISE_FREQ, "11xx: Set noise frequency"}}, + {0x12, {DIV_CMD_SAA_ENVELOPE, "12xx: Setup envelope (refer to docs for more information)"}}, } ); @@ -1084,21 +826,10 @@ void DivEngine::registerSystems() { {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, {DIV_INS_AY8930, DIV_INS_AY8930, DIV_INS_AY8930}, {}, - [](int,unsigned char,unsigned char) -> bool {return false;}, - ayPostEffectHandler + {}, + ay8930PostEffectHandlerMap ); - auto waveOnlyEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x10: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - default: - return false; - } - return true; - }; - sysDefs[DIV_SYSTEM_VIC20]=new DivSysDef( "Commodore VIC-20", NULL, 0x85, 0, 4, false, true, 0, false, "Commodore's successor to the PET.\nits square wave channels are more than just square...", @@ -1107,7 +838,7 @@ void DivEngine::registerSystems() { {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE}, {DIV_INS_VIC, DIV_INS_VIC, DIV_INS_VIC, DIV_INS_VIC}, {}, - waveOnlyEffectHandler + waveOnlyEffectHandlerMap ); sysDefs[DIV_SYSTEM_PET]=new DivSysDef( @@ -1118,7 +849,7 @@ void DivEngine::registerSystems() { {DIV_CH_PULSE}, {DIV_INS_PET}, {}, - waveOnlyEffectHandler + waveOnlyEffectHandlerMap ); sysDefs[DIV_SYSTEM_SNES]=new DivSysDef( @@ -1138,32 +869,12 @@ void DivEngine::registerSystems() { {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_WAVE}, {DIV_INS_VRC6, DIV_INS_VRC6, DIV_INS_VRC6_SAW}, {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_NULL}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x12: // duty or noise mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - case 0x17: // PCM enable - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); - break; - default: - return false; - } - return true; + { + {0x12, {DIV_CMD_STD_NOISE_MODE, "12xx: Set duty cycle (pulse: 0 to 7)"}}, + {0x17, {DIV_CMD_SAMPLE_MODE, "17xx: Toggle PCM mode (pulse channel)"}}, } ); - auto oplEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x30: // toggle hard-reset - dispatchCmd(DivCommand(DIV_CMD_FM_HARD_RESET,ch,effectVal)); - break; - default: - return false; - } - return true; - }; - sysDefs[DIV_SYSTEM_OPLL]=new DivSysDef( "Yamaha YM2413 (OPLL)", NULL, 0x89, 0, 9, true, false, 0x150, false, "cost-reduced version of the OPL with 16 patches and only one of them is user-configurable.", @@ -1172,8 +883,8 @@ void DivEngine::registerSystems() { {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, {DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL}, {}, - oplEffectHandler, - fmOPLLPostEffectHandler + fmEffectHandlerMap, + fmOPLLPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_FDS]=new DivSysDef( @@ -1184,30 +895,13 @@ void DivEngine::registerSystems() { {DIV_CH_WAVE}, {DIV_INS_FDS}, {}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x10: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - case 0x11: // modulation depth - dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_DEPTH,ch,effectVal)); - break; - case 0x12: // modulation enable/high - dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_HIGH,ch,effectVal)); - break; - case 0x13: // modulation low - dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_LOW,ch,effectVal)); - break; - case 0x14: // modulation pos - dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_POS,ch,effectVal)); - break; - case 0x15: // modulation wave - dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_WAVE,ch,effectVal)); - break; - default: - return false; - } - return true; + { + {0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}}, + {0x11, {DIV_CMD_FDS_MOD_DEPTH, "11xx: Set modulation depth"}}, + {0x12, {DIV_CMD_FDS_MOD_HIGH, "12xy: Set modulation speed high byte (x: enable; y: value)"}}, + {0x13, {DIV_CMD_FDS_MOD_LOW, "13xx: Set modulation speed low byte"}}, + {0x14, {DIV_CMD_FDS_MOD_POS, "14xx: Set modulator position"}}, + {0x15, {DIV_CMD_FDS_MOD_WAVE, "15xx: Set modulator table to waveform"}}, } ); @@ -1219,74 +913,33 @@ void DivEngine::registerSystems() { {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM}, {DIV_INS_STD, DIV_INS_STD, DIV_INS_AMIGA}, {}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x11: // DMC write - dispatchCmd(DivCommand(DIV_CMD_NES_DMC,ch,effectVal)); - break; - case 0x12: // duty or noise mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - default: - return false; - } - return true; + { + {0x12, {DIV_CMD_STD_NOISE_MODE, "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)"}}, } ); sysDefs[DIV_SYSTEM_N163]=new DivSysDef( - "Namco C163", NULL, 0x8c, 0, 8, false, true, 0, false, + "Namco 163/C163/129/160/106/whatever", NULL, 0x8c, 0, 8, false, true, 0, false, "an expansion chip for the Famicom, with full wavetable.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"}, {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, {DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163, DIV_INS_N163}, {}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x10: // select instrument waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - case 0x11: // select instrument waveform position in RAM - dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_POSITION,ch,effectVal)); - break; - case 0x12: // select instrument waveform length in RAM - dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LENGTH,ch,effectVal)); - break; - case 0x13: // change instrument waveform update mode - dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_MODE,ch,effectVal)); - break; - case 0x14: // select waveform for load to RAM - dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOAD,ch,effectVal)); - break; - case 0x15: // select waveform position for load to RAM - dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOADPOS,ch,effectVal)); - break; - case 0x16: // select waveform length for load to RAM - dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOADLEN,ch,effectVal)); - break; - case 0x17: // change waveform load mode - dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOADMODE,ch,effectVal)); - break; - case 0x18: // change channel limits - dispatchCmd(DivCommand(DIV_CMD_N163_CHANNEL_LIMIT,ch,effectVal)); - break; - case 0x20: // (global) select waveform for load to RAM - dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOAD,ch,effectVal)); - break; - case 0x21: // (global) select waveform position for load to RAM - dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOADPOS,ch,effectVal)); - break; - case 0x22: // (global) select waveform length for load to RAM - dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOADLEN,ch,effectVal)); - break; - case 0x23: // (global) change waveform load mode - dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOADMODE,ch,effectVal)); - break; - default: - return false; - } - return true; + { + {0x10, {DIV_CMD_WAVE, "10xx: Select waveform"}}, + {0x11, {DIV_CMD_N163_WAVE_POSITION, "11xx: Set waveform position in RAM (single nibble unit)"}}, + {0x12, {DIV_CMD_N163_WAVE_LENGTH, "12xx: Set waveform length in RAM (04 to FC, 4 nibble unit)"}}, + {0x13, {DIV_CMD_N163_WAVE_MODE, "130x: Change waveform update mode (0: off; bit 0: update now; bit 1: update when every waveform changes)"}}, + {0x14, {DIV_CMD_N163_WAVE_LOAD, "14xx: Select waveform for load to RAM"}}, + {0x15, {DIV_CMD_N163_WAVE_LOADPOS, "15xx: Set waveform position for load to RAM (single nibble unit)"}}, + {0x16, {DIV_CMD_N163_WAVE_LOADLEN, "16xx: Set waveform length for load to RAM (04 to FC, 4 nibble unit)"}}, + {0x17, {DIV_CMD_N163_WAVE_LOADMODE, "170x: Change waveform load mode (0: off; bit 0: load now; bit 1: load when every waveform changes)"}}, + {0x18, {DIV_CMD_N163_CHANNEL_LIMIT, "180x: Change channel limits (0 to 7, x + 1)"}}, + {0x20, {DIV_CMD_N163_GLOBAL_WAVE_LOAD, "20xx: (Global) Select waveform for load to RAM"}}, + {0x21, {DIV_CMD_N163_GLOBAL_WAVE_LOADPOS, "21xx: (Global) Set waveform position for load to RAM (single nibble unit)"}}, + {0x22, {DIV_CMD_N163_GLOBAL_WAVE_LOADLEN, "22xx: (Global) Set waveform length for load to RAM (04 to FC, 4 nibble unit)"}}, + {0x23, {DIV_CMD_N163_GLOBAL_WAVE_LOADMODE, "230x: (Global) Change waveform load mode (0: off; bit 0: load now; bit 1: load when every waveform changes)"}}, } ); @@ -1298,20 +951,20 @@ void DivEngine::registerSystems() { {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}, {}, - fmHardResetEffectHandler, - fmPostEffectHandler + fmEffectHandlerMap, + fmOPNPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_OPN_EXT]=new DivSysDef( "Yamaha YM2203 (OPN) Extended Channel 3", NULL, 0xb6, 0, 9, true, true, 0x151, false, - "cost-reduced version of the OPM with a different register layout and no stereo...\n...but it has a built-in AY-3-8910! (actually an YM2149)\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies", + "cost-reduced version of the OPM with a different register layout and no stereo...\n...but it has a built-in AY-3-8910! (actually an YM2149)\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies", {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "PSG 1", "PSG 2", "PSG 3"}, {"F1", "F2", "O1", "O2", "O3", "O4", "S1", "S2", "S3"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}, {}, - fmHardResetEffectHandler, - fmPostEffectHandler + fmEffectHandlerMap, + fmOPNPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_PC98]=new DivSysDef( @@ -1322,20 +975,20 @@ void DivEngine::registerSystems() { {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM}, {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA}, {}, - fmHardResetEffectHandler, - fmPostEffectHandler + fmEffectHandlerMap, + fmOPNPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_PC98_EXT]=new DivSysDef( "Yamaha YM2608 (OPNA) Extended Channel 3", NULL, 0xb7, 0, 19, true, true, 0x151, false, - "OPN but twice the FM channels, stereo makes a come-back and has rhythm and ADPCM channels.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies", + "OPN but twice the FM channels, stereo makes a come-back and has rhythm and ADPCM channels.\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies", {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"}, {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM}, {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA}, {}, - fmHardResetEffectHandler, - fmPostEffectHandler + fmEffectHandlerMap, + fmOPNPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_OPL]=new DivSysDef( @@ -1346,8 +999,8 @@ void DivEngine::registerSystems() { {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, {}, - oplEffectHandler, - fmOPLPostEffectHandler + fmEffectHandlerMap, + fmOPLPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_OPL2]=new DivSysDef( @@ -1358,8 +1011,8 @@ void DivEngine::registerSystems() { {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, {}, - oplEffectHandler, - fmOPLPostEffectHandler + fmEffectHandlerMap, + fmOPLPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_OPL3]=new DivSysDef( @@ -1370,8 +1023,8 @@ void DivEngine::registerSystems() { {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, {}, - oplEffectHandler, - fmOPLPostEffectHandler + fmEffectHandlerMap, + fmOPLPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_MULTIPCM]=new DivSysDef( @@ -1418,27 +1071,12 @@ void DivEngine::registerSystems() { {DIV_CH_WAVE, DIV_CH_PCM, DIV_CH_WAVE, DIV_CH_NOISE}, {DIV_INS_SWAN, DIV_INS_SWAN, DIV_INS_SWAN, DIV_INS_SWAN}, {DIV_INS_NULL, DIV_INS_AMIGA, DIV_INS_NULL, DIV_INS_NULL}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x10: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - case 0x11: // noise mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - case 0x12: // sweep period - dispatchCmd(DivCommand(DIV_CMD_WS_SWEEP_TIME,ch,effectVal)); - break; - case 0x13: // sweep amount - dispatchCmd(DivCommand(DIV_CMD_WS_SWEEP_AMOUNT,ch,effectVal)); - break; - case 0x17: // PCM enable - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); - break; - default: - return false; - } - return true; + { + {0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}}, + {0x11, {DIV_CMD_STD_NOISE_MODE, "11xx: Setup noise mode (0: disabled; 1-8: enabled/tap)"}}, + {0x12, {DIV_CMD_WS_SWEEP_TIME, "12xx: Setup sweep period (0: disabled; 1-20: enabled/period)"}}, + {0x13, {DIV_CMD_WS_SWEEP_AMOUNT, "13xx: Set sweep amount"}}, + {0x17, {DIV_CMD_SAMPLE_MODE, "17xx: Toggle PCM mode"}}, } ); @@ -1450,17 +1088,10 @@ void DivEngine::registerSystems() { {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, {DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ}, {}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x2f: // toggle hard-reset - dispatchCmd(DivCommand(DIV_CMD_FM_HARD_RESET,ch,effectVal)); - break; - default: - return false; - } - return true; + { + {0x2f, {DIV_CMD_FM_HARD_RESET, "2Fxx: Toggle hard envelope reset on new notes"}}, }, - fmPostEffectHandler + fmOPZPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_POKEMINI]=new DivSysDef( @@ -1480,8 +1111,8 @@ void DivEngine::registerSystems() { {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, {}, - [](int,unsigned char,unsigned char) -> bool {return false;}, - segaPCMPostEffectHandler + {}, + segaPCMPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_VBOY]=new DivSysDef( @@ -1501,20 +1132,20 @@ void DivEngine::registerSystems() { {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, {DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL}, {}, - oplEffectHandler, - fmOPLLPostEffectHandler + fmEffectHandlerMap, + fmOPLLPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_YM2610B]=new DivSysDef( - "Yamaha YM2610B (OPNB-B)", NULL, 0x9e, 0, 16, true, false, 0x151, false, + "Yamaha YM2610B (OPNB2)", NULL, 0x9e, 0, 16, true, false, 0x151, false, "so Taito asked Yamaha if they could get the two missing FM channels back, and Yamaha gladly provided them with this chip.", {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, {}, - fmHardResetEffectHandler, - fmPostEffectHandler + fmEffectHandlerMap, + fmOPNPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_SFX_BEEPER]=new DivSysDef( @@ -1525,31 +1156,22 @@ void DivEngine::registerSystems() { {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, {DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER}, {}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x12: // pulse width - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - case 0x17: // overlay sample - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,effectVal)); - break; - default: - return false; - } - return true; + { + {0x12, {DIV_CMD_STD_NOISE_MODE, "12xx: Set pulse width"}}, + {0x17, {DIV_CMD_SAMPLE_MODE, "17xx: Trigger overlay drum"}}, } ); sysDefs[DIV_SYSTEM_YM2612_EXT]=new DivSysDef( "Yamaha YM2612 (OPN2) Extended Channel 3", NULL, 0xa0, 0, 9, true, false, 0x150, false, - "this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.", + "this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies.", {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6"}, {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, {DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA}, - opn2EffectHandler, - fmPostEffectHandler + fmOPN2EffectHandlerMap, + fmOPN2PostEffectHandlerMap ); sysDefs[DIV_SYSTEM_SCC]=new DivSysDef( @@ -1560,23 +1182,9 @@ void DivEngine::registerSystems() { {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, {DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC}, {}, - waveOnlyEffectHandler + waveOnlyEffectHandlerMap ); - auto oplDrumsEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x18: // drum mode toggle - dispatchCmd(DivCommand(DIV_CMD_FM_EXTCH,ch,effectVal)); - break; - case 0x30: // toggle hard-reset - dispatchCmd(DivCommand(DIV_CMD_FM_HARD_RESET,ch,effectVal)); - break; - default: - return false; - } - return true; - }; - sysDefs[DIV_SYSTEM_OPL_DRUMS]=new DivSysDef( "Yamaha YM3526 (OPL) with drums", NULL, 0xa2, 0, 11, true, false, 0x151, false, "the OPL chip but with drums mode enabled.", @@ -1585,8 +1193,8 @@ void DivEngine::registerSystems() { {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS}, {DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, - oplDrumsEffectHandler, - fmOPLPostEffectHandler + fmOPLDrumsEffectHandlerMap, + fmOPLPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_OPL2_DRUMS]=new DivSysDef( @@ -1597,8 +1205,8 @@ void DivEngine::registerSystems() { {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS}, {DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, - oplDrumsEffectHandler, - fmOPLPostEffectHandler + fmOPLDrumsEffectHandlerMap, + fmOPLPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_OPL3_DRUMS]=new DivSysDef( @@ -1609,8 +1217,8 @@ void DivEngine::registerSystems() { {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS}, {DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, - oplDrumsEffectHandler, - fmOPLPostEffectHandler + fmOPLDrumsEffectHandlerMap, + fmOPLPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_YM2610_FULL]=new DivSysDef( @@ -1621,8 +1229,8 @@ void DivEngine::registerSystems() { {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, {}, - fmHardResetEffectHandler, - fmPostEffectHandler + fmEffectHandlerMap, + fmOPNPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_YM2610_FULL_EXT]=new DivSysDef( @@ -1633,8 +1241,8 @@ void DivEngine::registerSystems() { {DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, {}, - fmHardResetEffectHandler, - fmPostEffectHandler + fmEffectHandlerMap, + fmOPNPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_OPLL_DRUMS]=new DivSysDef( @@ -1645,10 +1253,16 @@ void DivEngine::registerSystems() { {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, {DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL, DIV_INS_OPLL}, {}, - oplDrumsEffectHandler, - fmOPLLPostEffectHandler + fmOPLDrumsEffectHandlerMap, + fmOPLLPostEffectHandlerMap ); + EffectHandlerMap lynxEffectHandlerMap; + const EffectHandler lynxLFSRHandler(DIV_CMD_LYNX_LFSR_LOAD, "3xxx: Load LFSR (0 to FFF)", effectValLong<12>); + for (int i=0; i<16; i++) { + lynxEffectHandlerMap.emplace(0x30+i, lynxLFSRHandler); + } + sysDefs[DIV_SYSTEM_LYNX]=new DivSysDef( "Atari Lynx", NULL, 0xa8, 0, 4, false, true, 0, false, "a portable console made by Atari. it has all of Atari's trademark waveforms.", @@ -1657,17 +1271,20 @@ void DivEngine::registerSystems() { {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, {DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY}, {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, - [](int,unsigned char,unsigned char) -> bool {return false;}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - if (effect>=0x30 && effect<0x40) { - int value=((int)(effect&0x0f)<<8)|effectVal; - dispatchCmd(DivCommand(DIV_CMD_LYNX_LFSR_LOAD,ch,value)); - return true; - } - return false; - } + {}, + lynxEffectHandlerMap ); + EffectHandlerMap qSoundEffectHandlerMap={ + {0x10, {DIV_CMD_QSOUND_ECHO_FEEDBACK, "10xx: Set echo feedback level (00 to FF)"}}, + {0x11, {DIV_CMD_QSOUND_ECHO_LEVEL, "11xx: Set channel echo level (00 to FF)"}}, + {0x12, {DIV_CMD_QSOUND_SURROUND, "12xx: Toggle QSound algorithm (0: disabled; 1: enabled)"}}, + }; + const EffectHandler qSoundEchoDelayHandler(DIV_CMD_QSOUND_ECHO_DELAY, "3xxx: Set echo delay buffer length (000 to AA5)", effectValLong<12>); + for (int i=0; i<16; i++) { + qSoundEffectHandlerMap.emplace(0x30+i, qSoundEchoDelayHandler); + } + sysDefs[DIV_SYSTEM_QSOUND]=new DivSysDef( "Capcom QSound", NULL, 0xe0, 0, 19, false, true, 0x161, false, "used in some of Capcom's arcade boards. surround-like sampled sound with echo.", @@ -1676,27 +1293,7 @@ void DivEngine::registerSystems() { {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, {}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x10: // echo feedback - dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_FEEDBACK,ch,effectVal)); - break; - case 0x11: // echo level - dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_LEVEL,ch,effectVal)); - break; - case 0x12: // surround - dispatchCmd(DivCommand(DIV_CMD_QSOUND_SURROUND,ch,effectVal)); - break; - default: - if ((effect&0xf0)==0x30) { - dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_DELAY,ch,((effect & 0x0f) << 8) | effectVal)); - } else { - return false; - } - break; - } - return true; - } + qSoundEffectHandlerMap ); sysDefs[DIV_SYSTEM_VERA]=new DivSysDef( @@ -1707,31 +1304,22 @@ void DivEngine::registerSystems() { {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM}, {DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_AMIGA}, {}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x20: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - case 0x22: // duty - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - default: - return false; - } - return true; + { + {0x20, {DIV_CMD_WAVE, "20xx: Set waveform"}}, + {0x22, {DIV_CMD_STD_NOISE_MODE, "22xx: Set duty cycle (0 to 3F)"}}, } ); sysDefs[DIV_SYSTEM_YM2610B_EXT]=new DivSysDef( - "Yamaha YM2610B (OPNB-B) Extended Channel 3", NULL, 0xde, 0, 19, true, false, 0x151, false, - "so Taito asked Yamaha if they could get the two missing FM channels back, and Yamaha gladly provided them with this chip.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.", + "Yamaha YM2610B (OPNB2) Extended Channel 3", NULL, 0xde, 0, 19, true, false, 0x151, false, + "so Taito asked Yamaha if they could get the two missing FM channels back, and Yamaha gladly provided them with this chip.\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies.", {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, {}, - fmHardResetEffectHandler, - fmPostEffectHandler + fmEffectHandlerMap, + fmOPNPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_SEGAPCM_COMPAT]=new DivSysDef( @@ -1742,8 +1330,8 @@ void DivEngine::registerSystems() { {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, {}, - [](int,unsigned char,unsigned char) -> bool {return false;}, - segaPCMPostEffectHandler + {}, + segaPCMPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_X1_010]=new DivSysDef( @@ -1754,46 +1342,18 @@ void DivEngine::registerSystems() { {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, {DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010}, {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x10: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - case 0x11: // select envelope shape - dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SHAPE,ch,effectVal)); - break; - case 0x17: // PCM enable - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); - break; - default: - return false; - } - return true; + { + {0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}}, + {0x11, {DIV_CMD_X1_010_ENVELOPE_SHAPE, "11xx: Set envelope shape"}}, + {0x17, {DIV_CMD_SAMPLE_MODE, "17xx: Toggle PCM mode"}}, }, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x20: // PCM frequency - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_FREQ,ch,effectVal)); - break; - case 0x22: // envelope mode - dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_MODE,ch,effectVal)); - break; - case 0x23: // envelope period - dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_PERIOD,ch,effectVal)); - break; - case 0x25: // envelope slide up - dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SLIDE,ch,effectVal)); - break; - case 0x26: // envelope slide down - dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SLIDE,ch,-effectVal)); - break; - case 0x29: // auto-envelope - dispatchCmd(DivCommand(DIV_CMD_X1_010_AUTO_ENVELOPE,ch,effectVal)); - break; - default: - return false; - } - return true; + { + {0x20, {DIV_CMD_SAMPLE_FREQ, "20xx: Set PCM frequency (1 to FF)"}}, + {0x22, {DIV_CMD_X1_010_ENVELOPE_MODE, "22xx: Set envelope mode (bit 0: enable; bit 1: one-shot; bit 2: split shape to L/R; bit 3/5: H.invert right/left; bit 4/6: V.invert right/left)"}}, + {0x23, {DIV_CMD_X1_010_ENVELOPE_PERIOD, "23xx: Set envelope period"}}, + {0x25, {DIV_CMD_X1_010_ENVELOPE_SLIDE, "25xx: Envelope slide up"}}, + {0x26, {DIV_CMD_X1_010_ENVELOPE_SLIDE, "26xx: Envelope slide down", negEffectVal}}, + {0x29, {DIV_CMD_X1_010_AUTO_ENVELOPE, "29xy: Set auto-envelope (x: numerator; y: denominator)"}}, } ); @@ -1805,12 +1365,12 @@ void DivEngine::registerSystems() { {DIV_CH_WAVE, DIV_CH_WAVE}, {DIV_INS_SCC, DIV_INS_SCC}, {}, - waveOnlyEffectHandler + waveOnlyEffectHandlerMap ); // to Grauw: feel free to change this to 24 during development of OPL4's PCM part. sysDefs[DIV_SYSTEM_OPL4]=new DivSysDef( - "Yamaha OPL4", NULL, 0xae, 0, 42, true, true, 0, false, + "Yamaha YMF278B (OPL4)", NULL, 0xae, 0, 42, true, true, 0, false, "like OPL3, but this time it also has a 24-channel version of MultiPCM.", {"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18", "PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "PCM 17", "PCM 18", "PCM 19", "PCM 20", "PCM 21", "PCM 22", "PCM 23", "PCM 24"}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P8", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"}, @@ -1819,7 +1379,7 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_OPL4_DRUMS]=new DivSysDef( - "Yamaha OPL4 with drums", NULL, 0xaf, 0, 44, true, true, 0, false, + "Yamaha YMF278B (OPL4) with drums", NULL, 0xaf, 0, 44, true, true, 0, false, "the OPL4 but with drums mode turned on.", {"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick/FM 16", "Snare", "Tom", "Top", "HiHat", "PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "PCM 17", "PCM 18", "PCM 19", "PCM 20", "PCM 21", "PCM 22", "PCM 23", "PCM 24"}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "BD", "SD", "TM", "TP", "HH", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P8", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"}, @@ -1829,7 +1389,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_ES5506]=new DivSysDef( "Ensoniq ES5506", NULL, 0xb1, 0, 32, false, true, 0, false, - "a sample chip used in the Gravis Ultrasound, popular in the PC (DOS) demoscene.", + "a sample chip used in the Ensoniq's unique TransWave synthesizers, and SoundScape series PC ISA soundcards (which are yet another (partially) Sound Blaster compatible ones with emulated OPL3 and MIDI ROMpler).", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28", "Channel 29", "Channel 30", "Channel 31", "Channel 32"}, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32"}, {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, @@ -1839,25 +1399,25 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_Y8950]=new DivSysDef( "Yamaha Y8950", NULL, 0xb2, 0, 10, true, false, 0x151, false, "like OPL but with an ADPCM channel.", - {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "PCM"}, - {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "PCM"}, + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "ADPCM"}, + {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "P"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM}, {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA}, {}, - oplEffectHandler, - fmOPLPostEffectHandler + fmEffectHandlerMap, + fmOPLPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_Y8950_DRUMS]=new DivSysDef( "Yamaha Y8950 with drums", NULL, 0xb3, 0, 12, true, false, 0x151, false, "the Y8950 chip, in drums mode.", - {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick/FM 7", "Snare", "Tom", "Top", "HiHat", "PCM"}, - {"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH", "PCM"}, + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick/FM 7", "Snare", "Tom", "Top", "HiHat", "ADPCM"}, + {"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH", "P"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM}, {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_AMIGA}, {DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_NULL}, - oplEffectHandler, - fmOPLPostEffectHandler + fmEffectHandlerMap, + fmOPLPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_SCC_PLUS]=new DivSysDef( @@ -1868,9 +1428,34 @@ void DivEngine::registerSystems() { {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, {DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC}, {}, - waveOnlyEffectHandler + waveOnlyEffectHandlerMap ); + EffectHandlerMap suEffectHandlerMap={ + {0x10, {DIV_CMD_WAVE, "10xx: Set waveform (0 to 7)"}}, + {0x12, {DIV_CMD_STD_NOISE_MODE, "12xx: Set pulse width (0 to 7F)"}}, + {0x13, {DIV_CMD_C64_RESONANCE, "13xx: Set resonance (0 to FF)"}}, + {0x14, {DIV_CMD_C64_FILTER_MODE, "14xx: Set filter mode (bit 0: ring mod; bit 1: low pass; bit 2: high pass; bit 3: band pass)"}}, + {0x15, {DIV_CMD_SU_SWEEP_PERIOD_LOW, "15xx: Set frequency sweep period low byte", constVal<0>, effectVal}}, + {0x16, {DIV_CMD_SU_SWEEP_PERIOD_HIGH, "16xx: Set frequency sweep period high byte", constVal<0>, effectVal}}, + {0x17, {DIV_CMD_SU_SWEEP_PERIOD_LOW, "17xx: Set volume sweep period low byte", constVal<1>, effectVal}}, + {0x18, {DIV_CMD_SU_SWEEP_PERIOD_HIGH, "18xx: Set volume sweep period high byte", constVal<1>, effectVal}}, + {0x19, {DIV_CMD_SU_SWEEP_PERIOD_LOW, "19xx: Set cutoff sweep period low byte", constVal<2>, effectVal}}, + {0x1a, {DIV_CMD_SU_SWEEP_PERIOD_HIGH, "1Axx: Set cutoff sweep period high byte", constVal<2>, effectVal}}, + {0x1b, {DIV_CMD_SU_SWEEP_BOUND, "1Bxx: Set frequency sweep boundary", constVal<0>, effectVal}}, + {0x1c, {DIV_CMD_SU_SWEEP_BOUND, "1Cxx: Set volume sweep boundary", constVal<1>, effectVal}}, + {0x1d, {DIV_CMD_SU_SWEEP_BOUND, "1Dxx: Set cutoff sweep boundary", constVal<2>, effectVal}}, + {0x1e, {DIV_CMD_SU_SYNC_PERIOD_LOW, "1Exx: Set phase reset period low byte"}}, + {0x1f, {DIV_CMD_SU_SYNC_PERIOD_HIGH, "1Fxx: Set phase reset period high byte"}}, + {0x20, {DIV_CMD_SU_SWEEP_ENABLE, "20xx: Toggle frequency sweep (bit 0-6: speed; bit 7: direction is up)", constVal<0>, effectVal}}, + {0x21, {DIV_CMD_SU_SWEEP_ENABLE, "21xx: Toggle volume sweep (bit 0-4: speed; bit 5: direciton is up; bit 6: loop; bit 7: alternate)", constVal<1>, effectVal}}, + {0x22, {DIV_CMD_SU_SWEEP_ENABLE, "22xx: Toggle cutoff sweep (bit 0-6: speed; bit 7: direction is up)", constVal<2>, effectVal}}, + }; + const EffectHandler suCutoffHandler(DIV_CMD_C64_FINE_CUTOFF, "4xxx: Set cutoff (0 to FFF)", effectValLong<12>); + for (int i=0; i<16; i++) { + suEffectHandlerMap.emplace(0x40+i, suCutoffHandler); + } + sysDefs[DIV_SYSTEM_SOUND_UNIT]=new DivSysDef( "tildearrow Sound Unit", NULL, 0xb5, 0, 8, false, true, 0, false, "tildearrow's fantasy sound chip. put SID, AY and VERA in a blender, and you get this!", @@ -1879,74 +1464,8 @@ void DivEngine::registerSystems() { {DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, {DIV_INS_SU, DIV_INS_SU, DIV_INS_SU, DIV_INS_SU, DIV_INS_SU, DIV_INS_SU, DIV_INS_SU, DIV_INS_SU}, {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, - [](int,unsigned char,unsigned char) -> bool {return false;}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x10: // waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - case 0x12: // duty cycle - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - case 0x13: // resonance - dispatchCmd(DivCommand(DIV_CMD_C64_RESONANCE,ch,effectVal)); - break; - case 0x14: // filter mode - dispatchCmd(DivCommand(DIV_CMD_C64_FILTER_MODE,ch,effectVal)); - break; - case 0x15: // freq sweep - dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_PERIOD_LOW,ch,0,effectVal)); - break; - case 0x16: // freq sweep - dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_PERIOD_HIGH,ch,0,effectVal)); - break; - case 0x17: // vol sweep - dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_PERIOD_LOW,ch,1,effectVal)); - break; - case 0x18: // vol sweep - dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_PERIOD_HIGH,ch,1,effectVal)); - break; - case 0x19: // cut sweep - dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_PERIOD_LOW,ch,2,effectVal)); - break; - case 0x1a: // cut sweep - dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_PERIOD_HIGH,ch,2,effectVal)); - break; - case 0x1b: // freq sweep bound - dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_BOUND,ch,0,effectVal)); - break; - case 0x1c: // vol sweep bound - dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_BOUND,ch,1,effectVal)); - break; - case 0x1d: // cut sweep bound - dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_BOUND,ch,2,effectVal)); - break; - case 0x1e: // sync low - dispatchCmd(DivCommand(DIV_CMD_SU_SYNC_PERIOD_LOW,ch,effectVal)); - break; - case 0x1f: // sync high - dispatchCmd(DivCommand(DIV_CMD_SU_SYNC_PERIOD_HIGH,ch,effectVal)); - break; - case 0x20: // freq sweep enable - dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_ENABLE,ch,0,effectVal)); - break; - case 0x21: // vol sweep enable - dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_ENABLE,ch,1,effectVal)); - break; - case 0x22: // cut sweep enable - dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_ENABLE,ch,2,effectVal)); - break; - case 0x40: case 0x41: case 0x42: case 0x43: - case 0x44: case 0x45: case 0x46: case 0x47: - case 0x48: case 0x49: case 0x4a: case 0x4b: - case 0x4c: case 0x4d: case 0x4e: case 0x4f: // cutoff - dispatchCmd(DivCommand(DIV_CMD_C64_FINE_CUTOFF,ch,(((effect&0x0f)<<8)|effectVal)*4)); - break; - default: - return false; - } - return true; - } + {}, + suEffectHandlerMap ); sysDefs[DIV_SYSTEM_MSM6295]=new DivSysDef( @@ -1957,15 +1476,8 @@ void DivEngine::registerSystems() { {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, {}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x20: // select rate - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_FREQ,ch,effectVal)); - break; - default: - return false; - } - return true; + { + {0x20, {DIV_CMD_SAMPLE_FREQ, "20xx: Set chip output rate (0: clock/132; 1: clock/165)"}}, } ); @@ -1977,18 +1489,9 @@ void DivEngine::registerSystems() { {DIV_CH_PCM}, {DIV_INS_AMIGA}, {}, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x20: // select rate - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_FREQ,ch,effectVal)); - break; - case 0x21: // select clock - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,effectVal)); - break; - default: - return false; - } - return true; + { + {0x20, {DIV_CMD_SAMPLE_FREQ, "20xx: Set frequency divider (0-2)"}}, + {0x21, {DIV_CMD_SAMPLE_MODE, "21xx: Select clock rate (0: full; 1: half)"}}, } ); @@ -2001,18 +1504,9 @@ void DivEngine::registerSystems() { {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA} ); - auto namcoEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x10: // select waveform - dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); - break; - case 0x11: // noise mode - dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); - break; - default: - return false; - } - return true; + EffectHandlerMap namcoEffectHandlerMap={ + {0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}}, + {0x11, {DIV_CMD_STD_NOISE_MODE, "11xx: Toggle noise mode"}}, }; sysDefs[DIV_SYSTEM_NAMCO]=new DivSysDef( @@ -2023,7 +1517,7 @@ void DivEngine::registerSystems() { {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, {DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO}, {}, - namcoEffectHandler + namcoEffectHandlerMap ); sysDefs[DIV_SYSTEM_NAMCO_15XX]=new DivSysDef( @@ -2034,7 +1528,7 @@ void DivEngine::registerSystems() { {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, {DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO}, {}, - namcoEffectHandler + namcoEffectHandlerMap ); sysDefs[DIV_SYSTEM_NAMCO_CUS30]=new DivSysDef( @@ -2045,7 +1539,7 @@ void DivEngine::registerSystems() { {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, {DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO}, {}, - namcoEffectHandler + namcoEffectHandlerMap ); // replace with an 8-channel chip in a future @@ -2066,20 +1560,42 @@ void DivEngine::registerSystems() { {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM, DIV_CH_PCM}, {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AMIGA}, {DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA, DIV_INS_NULL}, - opn2EffectHandler, - fmPostEffectHandler + fmOPN2EffectHandlerMap, + fmOPN2PostEffectHandlerMap ); sysDefs[DIV_SYSTEM_YM2612_FRAC_EXT]=new DivSysDef( "Yamaha YM2612 (OPN2) Extended Channel 3 with DualPCM and CSM", NULL, 0xbd, 0, 11, true, false, 0, false, - "this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis system uses software mixing to provide two sample channels.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.", + "this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis system uses software mixing to provide two sample channels.\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies.", {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6/PCM 1", "PCM 2", "CSM Timer"}, {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "P1", "P2", "CSM"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_NOISE}, {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AMIGA, DIV_INS_FM}, {DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA, DIV_INS_NULL, DIV_INS_NULL}, - opn2EffectHandler, - fmPostEffectHandler + fmOPN2EffectHandlerMap, + fmOPN2PostEffectHandlerMap + ); + + sysDefs[DIV_SYSTEM_T6W28]=new DivSysDef( + "T6W28", NULL, 0xbf, 0, 4, false, true, 0, false, + "an SN76489 derivative used in Neo Geo Pocket, has independent stereo volume and noise channel frequency.", + {"Square 1", "Square 2", "Square 3", "Noise"}, + {"S1", "S2", "S3", "NO"}, + {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE}, + {DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, + {}, + { + {0x20, {DIV_CMD_STD_NOISE_MODE, "20xy: Set noise mode (x: preset/variable; y: thin pulse/noise)"}} + } + ); + + sysDefs[DIV_SYSTEM_PCM_DAC]=new DivSysDef( + "Generic PCM DAC", NULL, 0xc0, 0, 1, false, true, 0, false, + "as generic sample playback as it gets.", + {"Sample"}, + {"PCM"}, + {DIV_CH_PCM}, + {DIV_INS_AMIGA} ); sysDefs[DIV_SYSTEM_DUMMY]=new DivSysDef( diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 6e2bc82c..15de0156 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -512,7 +512,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(streamID); w->writeS(write.val); // sample number w->writeC((sample->loopStart==0)|(sampleDir[streamID]?0x10:0)); // flags - if (sample->loopStart>0 && !sampleDir[streamID]) { + if (sample->isLoopable() && !sampleDir[streamID]) { loopTimer[streamID]=sample->length8; loopSample[streamID]=write.val; } @@ -802,7 +802,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write } } -SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { +SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool patternHints) { if (version<0x150) { lastError="VGM version is too low"; return NULL; @@ -1549,7 +1549,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { size_t memPos=0; for (int i=0; ilength8+0xff)&(~0xff); + unsigned int alignedSize=(sample->getEndPosition(DIV_SAMPLE_DEPTH_8BIT)+0xff)&(~0xff); if (alignedSize>65536) alignedSize=65536; if ((memPos&0xff0000)!=((memPos+alignedSize)&0xff0000)) { memPos=(memPos+0xffff)&0xff0000; @@ -1559,8 +1559,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { sample->offSegaPCM=memPos; unsigned int readPos=0; for (unsigned int j=0; j=sample->length8) { - if (sample->loopStart>=0 && sample->loopStart<(int)sample->length8) { + if (readPos>=sample->getEndPosition(DIV_SAMPLE_DEPTH_8BIT)) { + if (sample->isLoopable()) { readPos=sample->loopStart; pcmMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80); } else { @@ -1653,13 +1653,34 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { w->write(writeX1010[i]->getSampleMem(),writeX1010[i]->getSampleMemUsage()); } if (writeZ280[i]!=NULL && writeZ280[i]->getSampleMemUsage()>0) { + // In VGM, YMZ280B's 16-bit PCM has an endianness swapped + // which have been fixed in the upstream MAME since 2013 + // in order to get Konami FireBeat working + // The reason given for VGM not applying this change was + // "It matches OPL4 and MAME probably did an endianness optimization" + size_t sampleMemLen=writeZ280[i]->getSampleMemUsage(); + unsigned char* sampleMem=new unsigned char[sampleMemLen]; + memcpy(sampleMem,writeZ280[i]->getSampleMem(),sampleMemLen); + for (int i=0; idepth==DIV_SAMPLE_DEPTH_16BIT) { + unsigned int pos=s->offYMZ280B; + for (unsigned int j=0; jsamples; j++) { + unsigned char lo=sampleMem[pos+j*2]; + unsigned char hi=sampleMem[pos+j*2+1]; + sampleMem[pos+j*2]=hi; + sampleMem[pos+j*2+1]=lo; + } + } + } w->writeC(0x67); w->writeC(0x66); w->writeC(0x86); w->writeI((writeZ280[i]->getSampleMemUsage()+8)|(i*0x80000000)); w->writeI(writeZ280[i]->getSampleMemCapacity()); w->writeI(0); - w->write(writeZ280[i]->getSampleMem(),writeZ280[i]->getSampleMemUsage()); + w->write(sampleMem,sampleMemLen); + delete[] sampleMem; } } @@ -1774,6 +1795,12 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { playSub(false); size_t tickCount=0; bool writeLoop=false; + int ord=-1; + int exportChans=0; + for (int i=0; iwriteC(0x67); + w->writeC(0x66); + w->writeC(0xfe); + w->writeI(3+exportChans); + w->writeC(0x01); + w->writeC(prevOrder); + w->writeC(prevRow); + for (int i=0; iwriteC(curSubSong->orders.ord[i][prevOrder]); + } + } + } } // get register dumps for (int i=0; iloopStart<(int)sample->length8) { + if (sample->loopStart<(int)sample->getEndPosition(DIV_SAMPLE_DEPTH_8BIT)) { w->writeC(0x93); w->writeC(nextToTouch); w->writeI(sample->off8+sample->loopStart); w->writeC(0x81); - w->writeI(sample->length8-sample->loopStart); + w->writeI(sample->getEndPosition(DIV_SAMPLE_DEPTH_8BIT)-sample->loopStart); } } loopSample[nextToTouch]=-1; @@ -1899,24 +1946,20 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { WString ws; ws=utf8To16(song.name.c_str()); w->writeWString(ws,false); // name - w->writeS(0); // japanese name - w->writeS(0); // game name - w->writeS(0); // japanese game name - if (song.systemLen>1) { - ws=L"Multiple Systems"; - } else { - ws=utf8To16(getSystemName(song.system[0])); - } + ws=utf8To16(song.nameJ.c_str()); + w->writeWString(ws,false); // japanese name + ws=utf8To16(song.category.c_str()); + w->writeWString(ws,false); // game name + ws=utf8To16(song.categoryJ.c_str()); + w->writeWString(ws,false); // japanese game name + ws=utf8To16(song.systemName.c_str()); w->writeWString(ws,false); // system name - if (song.systemLen>1) { - ws=L"複数システム"; - } else { - ws=utf8To16(getSystemNameJ(song.system[0])); - } + ws=utf8To16(song.systemNameJ.c_str()); w->writeWString(ws,false); // japanese system name ws=utf8To16(song.author.c_str()); w->writeWString(ws,false); // author name - w->writeS(0); // japanese author name + ws=utf8To16(song.authorJ.c_str()); + w->writeWString(ws,false); // japanese author name w->writeS(0); // date w->writeWString(L"Furnace Tracker",false); // ripper w->writeS(0); // notes diff --git a/src/engine/wavetable.cpp b/src/engine/wavetable.cpp index db6c33b2..9de8544f 100644 --- a/src/engine/wavetable.cpp +++ b/src/engine/wavetable.cpp @@ -24,7 +24,10 @@ #include "../fileutils.h" void DivWavetable::putWaveData(SafeWriter* w) { + size_t blockStartSeek, blockEndSeek; + w->write("WAVE",4); + blockStartSeek=w->tell(); w->writeI(0); w->writeC(0); // name @@ -34,6 +37,11 @@ void DivWavetable::putWaveData(SafeWriter* w) { for (int j=0; jwriteI(data[j]); } + + blockEndSeek=w->tell(); + w->seek(blockStartSeek,SEEK_SET); + w->writeI(blockEndSeek-blockStartSeek-4); + w->seek(0,SEEK_END); } DivDataErrors DivWavetable::readWaveData(SafeReader& reader, short version) { @@ -51,7 +59,9 @@ DivDataErrors DivWavetable::readWaveData(SafeReader& reader, short version) { if (len>256 || min!=0 || max>255) return DIV_DATA_INVALID_DATA; - reader.read(data,4*len); + for (int i=0; ifinish(); return true; } + +bool DivWavetable::saveDMW(const char* path) { + SafeWriter* w=new SafeWriter(); + w->init(); + + // write width + w->writeI(len); + + // check height + w->writeC(max); + if (max==255) { + // write as new format (because 0xff means that) + w->writeC(1); // format version + w->writeC(max); // actual height + + // waveform data + for (int i=0; iwriteI(data[i]&0xff); + } + } else { + // write as old format + for (int i=0; iwriteC(data[i]); + } + } + + FILE* outFile=ps_fopen(path,"wb"); + if (outFile==NULL) { + logE("could not save wavetable: %s!",strerror(errno)); + w->finish(); + return false; + } + if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { + logW("did not write entire wavetable!"); + } + fclose(outFile); + w->finish(); + return true; +} + +bool DivWavetable::saveRaw(const char* path) { + SafeWriter* w=new SafeWriter(); + w->init(); + + // waveform data + for (int i=0; iwriteC(data[i]); + } + + FILE* outFile=ps_fopen(path,"wb"); + if (outFile==NULL) { + logE("could not save wavetable: %s!",strerror(errno)); + w->finish(); + return false; + } + if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { + logW("did not write entire wavetable!"); + } + fclose(outFile); + w->finish(); + return true; +} diff --git a/src/engine/wavetable.h b/src/engine/wavetable.h index 616b048c..0f518ab5 100644 --- a/src/engine/wavetable.h +++ b/src/engine/wavetable.h @@ -46,6 +46,20 @@ struct DivWavetable { * @return whether it was successful. */ bool save(const char* path); + + /** + * save this wavetable to a file in .dmw format. + * @param path file path. + * @return whether it was successful. + */ + bool saveDMW(const char* path); + + /** + * save this wavetable to a file in raw format. + * @param path file path. + * @return whether it was successful. + */ + bool saveRaw(const char* path); DivWavetable(): len(32), min(0), @@ -56,4 +70,4 @@ struct DivWavetable { } }; -#endif \ No newline at end of file +#endif diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 76fc80ad..b763ab97 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -27,8 +27,8 @@ const char* aboutLine[]={ "", ("Furnace " DIV_VERSION), "", - "the free software multi-system chiptune tracker,", - "compatible with DefleMask modules.", + "the biggest multi-system chiptune tracker!", + "featuring DefleMask song compatibility.", "", "zero disassembly.", "just clean-room design,", @@ -48,6 +48,8 @@ const char* aboutLine[]={ "-- graphics/UI design --", "tildearrow", "BlastBrothers", + "Mahbod Karamoozian", + "nicco1690", "Raijin", "", "-- documentation --", @@ -59,22 +61,50 @@ const char* aboutLine[]={ "", "-- demo songs --", "0x5066", + "Abstract 64", "ActualNK358", + "akumanatt", + "AmigaX", + "AURORA*FIELDS", + "BlueElectric05", "breakthetargets", + "brickblock369", "CaptainMalware", + "DeMOSic", + "DevEd", + "Dippy", + "Forte", + "Fragmare", + "freq-mod", + "iyatemu", "kleeder", + "jaezu", + "Laggy", + "LovelyA72", + "LunaMoth", + "Lunathir", + "LVintageNerd", "Mahbod Karamoozian", + "Miker", "nicco1690", "NikonTeen", + "psxdominator", + "Raijin", + "SnugglyBun", "SuperJet Spade", "TheDuccinator", + "theloredev", "TheRealHedgehogSonic", "tildearrow", "Ultraprogramer", + "Weeppiko", + "ZoomTen (Zumi)", "", "-- additional feedback/fixes --", "fd", "GENATARi", + "host12prog", + "Lunathir", "plane", "TheEssem", "", @@ -85,29 +115,44 @@ const char* aboutLine[]={ "and Mark Adler", "libsndfile by Erik de Castro Lopo", "Portable File Dialogs by Sam Hocevar", + "Native File Dialog by Frogtoss Games", "RtMidi by Gary P. Scavone", + "FFTW by Matteo Frigo and Steven G. Johnson", + "backward-cpp by Google", "adpcm by superctr", - "Nuked-OPL3/OPLL/OPM/OPN2 by Nuke.YKT", + "Nuked-OPL3/OPLL/OPM/OPN2/PSG by Nuke.YKT", "ymfm by Aaron Giles", "MAME SN76496 by Nicola Salmoria", "MAME AY-3-8910 by Couriersud", "with AY8930 fixes by Eulous, cam900 and Grauw", "MAME SAA1099 by Juergen Buchmueller and Manuel Abadia", + "MAME Namco WSG by Nicola Salmoria and Aaron Giles", + "MAME RF5C68 core by Olivier Galibert and Aaron Giles", + "MAME MSM6258 core by Barry Rodewald", + "MAME YMZ280B core by Aaron Giles", "SAASound by Dave Hooper and Simon Owen", "SameBoy by Lior Halphon", "Mednafen PCE and WonderSwan audio cores", "puNES (NES, MMC5 and FDS) by FHorse", + "NSFPlay (NES and FDS) by Brad Smith and Brezza", "reSID by Dag Lem", + "reSIDfp by Dag Lem, Antti Lankila", + "and Leandro Nini", "Stella by Stella Team", - "QSound emulator by Ian Karlsson and Valley Bell", + "QSound emulator by superctr and Valley Bell", "VICE VIC-20 sound core by Rami Rasanen and viznut", "VERA sound core by Frank van den Hoef", "K005289 emulator by cam900", "Namco 163 emulator by cam900", "Seta X1-010 emulator by cam900", "Konami VRC6 emulator by cam900", + "Konami SCC emulator by cam900", + "MSM6295 emulator by cam900", "", - "greetings to all members of Deflers of Noice!", + "greetings to:", + "Fractal Sound team", + "NEOART Costa Rica", + "all members of Deflers of Noice!", "", "copyright © 2021-2022 tildearrow", "(and contributors).", diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index 5bd945f1..547e59a1 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -26,6 +26,49 @@ #define FURNACE_FFT_RATE 80.0 #define FURNACE_FFT_CUTOFF 0.1 +const char* chanOscRefs[]={ + "None (0%)", + "None (50%)", + "None (100%)", + + "Frequency", + "Volume", + "Channel", + "Brightness", + + "Note Trigger" +}; + +float FurnaceGUI::computeGradPos(int type, int chan) { + switch (type) { + case GUI_OSCREF_NONE: + return 0.0f; + break; + case GUI_OSCREF_CENTER: + return 0.5f; + break; + case GUI_OSCREF_MAX: + return 1.0f; + break; + case GUI_OSCREF_FREQUENCY: + return chanOscPitch[chan]; + break; + case GUI_OSCREF_VOLUME: + return chanOscVol[chan]; + break; + case GUI_OSCREF_CHANNEL: + return (float)chan/(float)(e->getTotalChannelCount()-1); + break; + case GUI_OSCREF_BRIGHT: + return chanOscBright[chan]; + break; + case GUI_OSCREF_NOTE_TRIGGER: + return keyHit[chan]*5.0f; + break; + } + return 0.0f; +} + void FurnaceGUI::drawChanOsc() { if (nextWindow==GUI_WINDOW_CHAN_OSC) { chanOscOpen=true; @@ -34,36 +77,178 @@ void FurnaceGUI::drawChanOsc() { } if (!chanOscOpen) return; ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); - if (ImGui::Begin("Oscilloscope (per-channel)",&chanOscOpen,globalWinFlags)) { + if (ImGui::Begin("Oscilloscope (per-channel)",&chanOscOpen,globalWinFlags|((chanOscOptions)?0:ImGuiWindowFlags_NoTitleBar))) { bool centerSettingReset=false; - if (ImGui::BeginTable("ChanOscSettings",3)) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Columns"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##COSColumns",&chanOscCols,1,1)) { - if (chanOscCols<1) chanOscCols=1; - if (chanOscCols>64) chanOscCols=64; + ImDrawList* dl=ImGui::GetWindowDrawList(); + if (chanOscOptions) { + if (ImGui::BeginTable("ChanOscSettings",3)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Columns"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##COSColumns",&chanOscCols,1,1)) { + if (chanOscCols<1) chanOscCols=1; + if (chanOscCols>64) chanOscCols=64; + } + + ImGui::TableNextColumn(); + ImGui::Text("Size (ms)"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputFloat("##COSWinSize",&chanOscWindowSize,1.0f,1.0f)) { + if (chanOscWindowSize<1.0f) chanOscWindowSize=1.0f; + if (chanOscWindowSize>50.0f) chanOscWindowSize=50.0f; + } + + ImGui::TableNextColumn(); + if (ImGui::Checkbox("Center waveform",&chanOscWaveCorr)) { + centerSettingReset=true; + } + + ImGui::EndTable(); } - ImGui::TableNextColumn(); - ImGui::Text("Size (ms)"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputFloat("##COSWinSize",&chanOscWindowSize,1.0f,1.0f)) { - if (chanOscWindowSize<1.0f) chanOscWindowSize=1.0f; - if (chanOscWindowSize>50.0f) chanOscWindowSize=50.0f; + ImGui::Checkbox("Gradient",&chanOscUseGrad); + + if (chanOscUseGrad) { + if (chanOscGradTex==NULL) { + chanOscGradTex=SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ABGR8888,SDL_TEXTUREACCESS_STREAMING,chanOscGrad.width,chanOscGrad.height); + + if (chanOscGradTex==NULL) { + logE("error while creating gradient texture! %s",SDL_GetError()); + } else { + updateChanOscGradTex=true; + } + } + + if (ImGui::BeginTable("ChanOscGradSet",2)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (chanOscGradTex!=NULL) { + if (updateChanOscGradTex) { + chanOscGrad.render(); + if (SDL_UpdateTexture(chanOscGradTex,NULL,chanOscGrad.grad.get(),chanOscGrad.width*4)==0) { + updateChanOscGradTex=false; + } else { + logE("error while updating gradient texture! %s",SDL_GetError()); + } + } + + ImVec2 gradLeft=ImGui::GetCursorPos(); + ImVec2 gradSize=ImVec2(400.0f*dpiScale,400.0f*dpiScale); + ImGui::Image(chanOscGradTex,gradSize); + ImVec2 gradLeftAbs=ImGui::GetItemRectMin(); + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + if (chanOscGrad.points.size()<32) { + chanOscGrad.points.push_back(Gradient2DPoint( + (ImGui::GetMousePos().x-gradLeftAbs.x)/gradSize.x, + (ImGui::GetMousePos().y-gradLeftAbs.y)/gradSize.y + )); + updateChanOscGradTex=true; + } + } + + ImVec2 oldCurPos=ImGui::GetCursorPos(); + int index=0; + int removePoint=-1; + for (Gradient2DPoint& i: chanOscGrad.points) { + ImGui::PushID(index+16); + ImGui::SetCursorPos(ImVec2(gradLeft.x+i.x*gradSize.x-8.0*dpiScale,gradLeft.y+i.y*gradSize.y-8.0*dpiScale)); + if (ImGui::InvisibleButton("gradPoint",ImVec2(16.0*dpiScale,16.0*dpiScale))) { + if (!i.grab) { + ImGui::OpenPopup("gradPointSettings"); + } + } + if (ImGui::IsItemHovered() || ImGui::IsItemActive()) { + ImGui::SetTooltip("(%.1f, %.1f)",i.x*100.0f,(1.0f-i.y)*100.0f); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) { + removePoint=index; + } + if (ImGui::IsItemActive()) { + float mX=(ImGui::GetMousePos().x-gradLeftAbs.x)/gradSize.x; + float mY=(ImGui::GetMousePos().y-gradLeftAbs.y)/gradSize.y; + + if (i.grab || (fabs(i.x-mX)>0.015 || fabs(i.y-mY)>0.015)) { + i.x=mX; + i.y=mY; + i.grab=true; + + if (i.x<0) i.x=0; + if (i.x>1) i.x=1; + if (i.y<0) i.y=0; + if (i.y>1) i.y=1; + updateChanOscGradTex=true; + } + } else { + i.grab=false; + i.prevX=i.x; + i.prevY=i.y; + } + if (ImGui::BeginPopup("gradPointSettings",ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::ColorPicker4("Color",(float*)&i.color)) { + updateChanOscGradTex=true; + } + ImGui::Text("Distance"); + ImGui::SameLine(); + float pDist=i.distance*100.0f; + if (ImGui::SliderFloat("##PDistance",&pDist,0.0f,150.0f,"%.1f%%")) { + i.distance=pDist/100.0f; + updateChanOscGradTex=true; + } + + ImGui::Text("Spread"); + ImGui::SameLine(); + float pSpread=i.spread*100.0f; + if (ImGui::SliderFloat("##PSpread",&pSpread,0.0f,150.0f,"%.1f%%")) { + i.spread=pSpread/100.0f; + updateChanOscGradTex=true; + } + + if (ImGui::Button("Remove")) { + removePoint=index; + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + + dl->AddCircle(ImVec2(gradLeftAbs.x+i.x*gradSize.x,gradLeftAbs.y+i.y*gradSize.y),8.0*dpiScale,ImGui::ColorConvertFloat4ToU32(ImVec4(0.5,0.5,0.5,1.0)),6,2.0f*dpiScale); + dl->AddCircle(ImVec2(gradLeftAbs.x+i.x*gradSize.x,gradLeftAbs.y+i.y*gradSize.y),5.0*dpiScale,ImGui::ColorConvertFloat4ToU32(ImVec4(0.1,0.1,0.1,1.0)),6,2.0f*dpiScale); + + ImGui::PopID(); + index++; + } + ImGui::SetCursorPos(oldCurPos); + + if (removePoint>=0) { + chanOscGrad.points.erase(chanOscGrad.points.begin()+removePoint); + updateChanOscGradTex=true; + } + } + + ImGui::TableNextColumn(); + if (ImGui::ColorEdit4("Background",(float*)&chanOscGrad.bgColor)) { + updateChanOscGradTex=true; + } + ImGui::Combo("X Axis##AxisX",&chanOscColorX,chanOscRefs,GUI_OSCREF_MAX); + ImGui::Combo("Y Axis##AxisY",&chanOscColorY,chanOscRefs,GUI_OSCREF_MAX); + + ImGui::EndTable(); + } + } else { + ImGui::SetNextItemWidth(400.0f*dpiScale); + ImGui::ColorPicker4("Color",(float*)&chanOscColor); } - ImGui::TableNextColumn(); - if (ImGui::Checkbox("Center waveform",&chanOscWaveCorr)) { - centerSettingReset=true; + if (ImGui::Button("OK")) { + chanOscOptions=false; } - - ImGui::EndTable(); } - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f)); float availY=ImGui::GetContentRegionAvail().y; @@ -72,16 +257,14 @@ void FurnaceGUI::drawChanOsc() { std::vector oscFFTs; std::vector oscChans; int chans=e->getTotalChannelCount(); - ImDrawList* dl=ImGui::GetWindowDrawList(); ImGuiWindow* window=ImGui::GetCurrentWindow(); ImVec2 waveform[512]; ImGuiStyle& style=ImGui::GetStyle(); - ImU32 color=ImGui::GetColorU32(uiColors[GUI_COLOR_OSC_WAVE]); for (int i=0; igetOscBuffer(i); - if (buf!=NULL) { + if (buf!=NULL && e->curSubSong->chanShow[i]) { oscBufs.push_back(buf); oscFFTs.push_back(&chanOscChan[i]); oscChans.push_back(i); @@ -129,94 +312,83 @@ void FurnaceGUI::drawChanOsc() { inRect.Max.y-=dpiScale; ImGui::ItemSize(size,style.FramePadding.y); if (ImGui::ItemAdd(rect,ImGui::GetID("chOscDisplay"))) { - if (!e->isPlaying()) { + if (!e->isRunning()) { for (unsigned short i=0; i<512; i++) { float x=(float)i/512.0f; waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f)); } } else { + float minLevel=1.0f; + float maxLevel=-1.0f; + float dcOff=0.0f; unsigned short needlePos=buf->needle; - if (chanOscWaveCorr) { - /* - double fftDataRate=(FURNACE_FFT_SIZE*FURNACE_FFT_RATE)/((double)buf->rate); - while (buf->readNeedle!=needlePos) { - fft->inBufPosFrac+=fftDataRate; - while (fft->inBufPosFrac>=1.0) { - chanOscLP0[ch]+=FURNACE_FFT_CUTOFF*((float)buf->data[buf->readNeedle]-chanOscLP0[ch]); - chanOscLP1[ch]+=FURNACE_FFT_CUTOFF*(chanOscLP0[ch]-chanOscLP1[ch]); - fft->inBuf[fft->inBufPos]=(double)chanOscLP1[ch]/32768.0; - if (++fft->inBufPos>=FURNACE_FFT_SIZE) { - fftw_execute(fft->plan); - fft->inBufPos=0; - fft->needle=buf->readNeedle; - } - fft->inBufPosFrac-=1.0; - } - buf->readNeedle++; - }*/ - - for (int i=0; iinBuf[i]=(double)buf->data[(unsigned short)(needlePos-displaySize*2+((i*displaySize*2)/FURNACE_FFT_SIZE))]/32768.0; - } - fftw_execute(fft->plan); - - // find origin frequency - int point=1; - double candAmp=0.0; - for (unsigned short i=1; i<512; i++) { - fftw_complex& f=fft->outBuf[i]; - // AMPLITUDE - double amp=sqrt(pow(f[0],2.0)+pow(f[1],2.0))/pow((double)i,0.8); - if (amp>candAmp) { - point=i; - candAmp=amp; - } - } - - // PHASE - fftw_complex& candPoint=fft->outBuf[point]; - double phase=((double)(displaySize*2)/(double)point)*(0.5+(atan2(candPoint[1],candPoint[0])/(M_PI*2))); - - //needlePos=fft->needle; - needlePos-=phase; - - /* - int alignment=0; - for (unsigned short i=0; idata[(unsigned short)(needlePos-i)])>fabs(buf->data[(unsigned short)(needlePos-alignment)])) { - alignment=i; - } - } - needlePos-=alignment; - */ - - //String cPhase=fmt::sprintf("%d cphase: %f",point,phase); - //dl->AddText(inRect.Min,0xffffffff,cPhase.c_str()); - - needlePos-=displaySize; - for (unsigned short i=0; i<512; i++) { - float x=(float)i/512.0f; - float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f; - if (y<-0.5f) y=-0.5f; - if (y>0.5f) y=0.5f; - waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y)); - } - } else { - needlePos-=displaySize; - for (unsigned short i=0; i<512; i++) { - float x=(float)i/512.0f; - float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f; - if (y<-0.5f) y=-0.5f; - if (y>0.5f) y=0.5f; - waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y)); + for (int i=0; iinBuf[i]=(double)buf->data[(unsigned short)(needlePos-displaySize*2+((i*displaySize*2)/FURNACE_FFT_SIZE))]/32768.0; + } + fftw_execute(fft->plan); + + // find origin frequency + int point=1; + double candAmp=0.0; + for (unsigned short i=1; i<512; i++) { + fftw_complex& f=fft->outBuf[i]; + // AMPLITUDE + double amp=sqrt(pow(f[0],2.0)+pow(f[1],2.0))/pow((double)i,0.8); + if (amp>candAmp) { + point=i; + candAmp=amp; } } + + // PHASE + fftw_complex& candPoint=fft->outBuf[point]; + double phase=((double)(displaySize*2)/(double)point)*(0.5+(atan2(candPoint[1],candPoint[0])/(M_PI*2))); + + if (chanOscWaveCorr) { + needlePos-=phase; + } + chanOscPitch[ch]=(float)point/32.0f; + + /* + String cPhase=fmt::sprintf("%d cphase: %f vol: %f",point,phase,chanOscVol[ch]); + dl->AddText(inRect.Min,0xffffffff,cPhase.c_str()); + */ + + needlePos-=displaySize; + for (unsigned short i=0; i<512; i++) { + float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f; + if (minLevel>y) minLevel=y; + if (maxLeveldata[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f; + if (y<-0.5f) y=-0.5f; + if (y>0.5f) y=0.5f; + waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-(y-dcOff))); + } + } + ImU32 color=ImGui::GetColorU32(chanOscColor); + if (chanOscUseGrad) { + float xVal=computeGradPos(chanOscColorX,ch); + float yVal=computeGradPos(chanOscColorY,ch); + + xVal=CLAMP(xVal,0.0f,1.0f); + yVal=CLAMP(yVal,0.0f,1.0f); + + color=chanOscGrad.get(xVal,1.0f-yVal); } dl->AddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale); } } } ImGui::EndTable(); + + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + chanOscOptions=!chanOscOptions; + } } ImGui::PopStyleVar(); } diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index d15f1722..63571dca 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -115,6 +115,10 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("does this make any sense by now?"); } + ImGui::Checkbox("E1xy/E2xy stop when repeating the same note",&e->song.e1e2StopOnSameNote); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("ugh, if only this wasn't a thing..."); + } ImGui::Checkbox("SN76489 duty macro always resets phase",&e->song.snDutyReset); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("when enabled, duty macro will always reset phase, even if its value hasn't changed."); @@ -135,6 +139,10 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("if enabled, no checks for the presence of a volume macro will be made.\nthis will cause the last macro value to linger unless a value in the volume column is present."); } + ImGui::Checkbox("Treat SN76489 periods under 8 as 1",&e->song.snNoLowPeriods); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, any SN period under 8 will be written as 1 instead.\nthis replicates DefleMask behavior, but reduces available period range."); + } ImGui::Text("Pitch linearity:"); if (ImGui::RadioButton("None",e->song.linearPitch==0)) { @@ -185,6 +193,46 @@ void FurnaceGUI::drawCompatFlags() { ImGui::SetTooltip("select to not reset channels on loop."); } + ImGui::Text("Cut/delay effect policy:"); + if (ImGui::RadioButton("Strict",e->song.delayBehavior==0)) { + e->song.delayBehavior=0; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("only when time is less than speed (like DefleMask/ProTracker)"); + } + if (ImGui::RadioButton("Strict (old)",e->song.delayBehavior==1)) { + e->song.delayBehavior=1; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("only when time is less than or equal to speed (original buggy behavior)"); + } + if (ImGui::RadioButton("Lax",e->song.delayBehavior==2)) { + e->song.delayBehavior=2; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("no checks (like FamiTracker)"); + } + + ImGui::Text("Simultaneous jump (0B+0D) treatment:"); + if (ImGui::RadioButton("Normal",e->song.jumpTreatment==0)) { + e->song.jumpTreatment=0; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("accept 0B+0D to jump to a specific row of an order"); + } + if (ImGui::RadioButton("Old Furnace",e->song.jumpTreatment==1)) { + e->song.jumpTreatment=1; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("only accept the first jump effect"); + } + if (ImGui::RadioButton("DefleMask",e->song.jumpTreatment==2)) { + e->song.jumpTreatment=2; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("only accept 0Dxx"); + } + ImGui::Separator(); ImGui::TextWrapped("the following flags are for compatibility with older Furnace versions."); @@ -203,31 +251,35 @@ void FurnaceGUI::drawCompatFlags() { } ImGui::Checkbox("Stop portamento on note off",&e->song.stopPortaOnNoteOff); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.6"); + ImGui::SetTooltip("behavior changed in 0.6pre1"); } ImGui::Checkbox("Allow instrument change during slides",&e->song.newInsTriggersInPorta); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.6"); + ImGui::SetTooltip("behavior changed in 0.6pre1"); } ImGui::Checkbox("Reset note to base on arpeggio stop",&e->song.arp0Reset); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.6"); + ImGui::SetTooltip("behavior changed in 0.6pre1"); } ImGui::Checkbox("ExtCh channel status is shared among operators",&e->song.sharedExtStat); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.6"); + ImGui::SetTooltip("behavior changed in 0.6pre1"); } ImGui::Checkbox("New SegaPCM features (macros and better panning)",&e->song.newSegaPCM); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.6"); + ImGui::SetTooltip("behavior changed in 0.6pre1"); } ImGui::Checkbox("Old FM octave boundary behavior",&e->song.oldOctaveBoundary); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.6"); + ImGui::SetTooltip("behavior changed in 0.6pre1"); } ImGui::Checkbox("No OPN2 DAC volume control",&e->song.noOPN2Vol); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.6"); + ImGui::SetTooltip("behavior changed in 0.6pre1"); + } + ImGui::Checkbox("Broken initial position of portamento after arpeggio",&e->song.brokenPortaArp); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.6pre1.5"); } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_COMPAT_FLAGS; diff --git a/src/gui/cursor.cpp b/src/gui/cursor.cpp index e47dfd62..5a98a78c 100644 --- a/src/gui/cursor.cpp +++ b/src/gui/cursor.cpp @@ -42,6 +42,22 @@ void FurnaceGUI::startSelection(int xCoarse, int xFine, int y, bool fullRow) { return; } } + + if ((settings.dragMovesSelection==1 || (settings.dragMovesSelection==2 && (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)))) && !fullRow) { + if (xCoarse>=selStart.xCoarse && (xFine>=selStart.xFine || xCoarse>selStart.xCoarse) && y>=selStart.y && + xCoarse<=selEnd.xCoarse && (xFine<=selEnd.xFine || xCoarsecurPat[selEnd.xCoarse].effectCols*2; - selEnd.y=y; + if (dragging) { + dragDestinationX=xCoarse; + dragDestinationY=y; + cursorDrag.xCoarse=xCoarse; + cursorDrag.xFine=xFine; + cursorDrag.y=y; + + int len=dragEnd.xCoarse-dragStart.xCoarse+1; + if (len<0) len=0; + + DETERMINE_FIRST_LAST; + + if (dragStart.xCoarse+(dragDestinationX-dragSourceX)=lastChannel) { + dragDestinationX=lastChannel-(dragEnd.xCoarse-dragSourceX)-1; + } + + if (dragStart.y+(dragDestinationY-dragSourceY)<0) { + dragDestinationY=dragSourceY-dragStart.y; + } + + if (dragEnd.y+(dragDestinationY-dragSourceY)>=e->curSubSong->patLen) { + dragDestinationY=e->curSubSong->patLen-(dragEnd.y-dragSourceY)-1; + } + + selStart.xCoarse=dragStart.xCoarse+(dragDestinationX-dragSourceX); + selStart.xFine=dragStart.xFine; + selStart.y=dragStart.y+(dragDestinationY-dragSourceY); + selEnd.xCoarse=dragEnd.xCoarse+(dragDestinationX-dragSourceX); + selEnd.xFine=dragEnd.xFine; + selEnd.y=dragEnd.y+(dragDestinationY-dragSourceY); } else { - selEnd.xCoarse=xCoarse; - selEnd.xFine=xFine; - selEnd.y=y; + if (selectingFull) { + DETERMINE_LAST; + selEnd.xCoarse=lastChannel-1; + selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2; + selEnd.y=y; + } else { + selEnd.xCoarse=xCoarse; + selEnd.xFine=xFine; + selEnd.y=y; + } } } @@ -103,6 +155,18 @@ void FurnaceGUI::finishSelection() { selecting=false; selectingFull=false; + if (dragging) { + if (dragSourceX==dragDestinationX && dragSourceY==dragDestinationY) { + cursor=cursorDrag; + selStart=cursorDrag; + selEnd=cursorDrag; + } else { // perform drag + doDrag(); + } + + dragging=false; + } + // boundary check int chanCount=e->getTotalChannelCount(); diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 62b93479..0519fca3 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -29,14 +29,20 @@ const char* sampleNote[12]={ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; -void FurnaceGUI::drawInsList() { +void FurnaceGUI::drawInsList(bool asChild) { if (nextWindow==GUI_WINDOW_INS_LIST) { insListOpen=true; ImGui::SetNextWindowFocus(); nextWindow=GUI_WINDOW_NOTHING; } - if (!insListOpen) return; - if (ImGui::Begin("Instruments",&insListOpen,globalWinFlags)) { + if (!insListOpen && !asChild) return; + bool began=false; + if (asChild) { + began=ImGui::BeginChild("Instruments"); + } else { + began=ImGui::Begin("Instruments",&insListOpen,globalWinFlags); + } + if (began) { if (settings.unifiedDataView) settings.horizontalDataView=0; if (ImGui::Button(ICON_FA_PLUS "##InsAdd")) { if (!settings.unifiedDataView) doAction(GUI_ACTION_INS_LIST_ADD); @@ -121,16 +127,30 @@ void FurnaceGUI::drawInsList() { if (ImGui::MenuItem("instrument")) { doAction(GUI_ACTION_INS_LIST_SAVE); } + if (ImGui::MenuItem("instrument (.dmp)")) { + doAction(GUI_ACTION_INS_LIST_SAVE_DMP); + } if (ImGui::MenuItem("wavetable")) { doAction(GUI_ACTION_WAVE_LIST_SAVE); } + if (ImGui::MenuItem("wavetable (.dmw)")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_DMW); + } + if (ImGui::MenuItem("wavetable (raw)")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_RAW); + } if (ImGui::MenuItem("sample")) { doAction(GUI_ACTION_SAMPLE_LIST_SAVE); } ImGui::EndPopup(); } - } - if (!settings.unifiedDataView) { + } else { + if (ImGui::BeginPopupContextItem("InsSaveFormats",ImGuiMouseButton_Right)) { + if (ImGui::MenuItem("save as .dmp...")) { + doAction(GUI_ACTION_INS_LIST_SAVE_DMP); + } + ImGui::EndPopup(); + } ImGui::SameLine(); if (ImGui::ArrowButton("InsUp",ImGuiDir_Up)) { doAction(GUI_ACTION_INS_LIST_MOVE_UP); @@ -184,6 +204,7 @@ void FurnaceGUI::drawInsList() { if (i>=0) { DivInstrument* ins=e->song.ins[i]; insType=(ins->type>DIV_INS_MAX)?"Unknown":insTypes[ins->type]; + if (ins->type==DIV_INS_N163) insType=settings.c163Name.c_str(); switch (ins->type) { case DIV_INS_FM: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FM]); @@ -358,6 +379,9 @@ void FurnaceGUI::drawInsList() { if (ImGui::MenuItem("save")) { doAction(GUI_ACTION_INS_LIST_SAVE); } + if (ImGui::MenuItem("save (.dmp)")) { + doAction(GUI_ACTION_INS_LIST_SAVE_DMP); + } if (ImGui::MenuItem("delete")) { doAction(GUI_ACTION_INS_LIST_DELETE); } @@ -391,11 +415,15 @@ void FurnaceGUI::drawInsList() { ImGui::EndTable(); } } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_LIST; - ImGui::End(); + if (asChild) { + ImGui::EndChild(); + } else { + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_LIST; + ImGui::End(); + } } -void FurnaceGUI::drawWaveList() { +void FurnaceGUI::drawWaveList(bool asChild) { if (nextWindow==GUI_WINDOW_WAVE_LIST) { waveListOpen=true; if (settings.unifiedDataView) { @@ -406,8 +434,14 @@ void FurnaceGUI::drawWaveList() { nextWindow=GUI_WINDOW_NOTHING; } if (settings.unifiedDataView) return; - if (!waveListOpen) return; - if (ImGui::Begin("Wavetables",&waveListOpen,globalWinFlags)) { + if (!waveListOpen && !asChild) return; + bool began=false; + if (asChild) { + began=ImGui::BeginChild("Wavetables"); + } else { + began=ImGui::Begin("Wavetables",&waveListOpen,globalWinFlags); + } + if (began) { if (ImGui::Button(ICON_FA_PLUS "##WaveAdd")) { doAction(GUI_ACTION_WAVE_LIST_ADD); } @@ -419,10 +453,27 @@ void FurnaceGUI::drawWaveList() { if (ImGui::Button(ICON_FA_FOLDER_OPEN "##WaveLoad")) { doAction(GUI_ACTION_WAVE_LIST_OPEN); } + if (ImGui::BeginPopupContextItem("WaveOpenOpt")) { + if (ImGui::MenuItem("replace...")) { + doAction((curWave>=0 && curWave<(int)e->song.wave.size())?GUI_ACTION_WAVE_LIST_OPEN_REPLACE:GUI_ACTION_WAVE_LIST_OPEN); + } + ImGui::EndPopup(); + } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FLOPPY_O "##WaveSave")) { doAction(GUI_ACTION_WAVE_LIST_SAVE); } + if (!settings.unifiedDataView) { + if (ImGui::BeginPopupContextItem("WaveSaveFormats",ImGuiMouseButton_Right)) { + if (ImGui::MenuItem("save as .dmw...")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_DMW); + } + if (ImGui::MenuItem("save raw...")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_RAW); + } + ImGui::EndPopup(); + } + } ImGui::SameLine(); if (ImGui::ArrowButton("WaveUp",ImGuiDir_Up)) { doAction(GUI_ACTION_WAVE_LIST_MOVE_UP); @@ -441,11 +492,15 @@ void FurnaceGUI::drawWaveList() { ImGui::EndTable(); } } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_LIST; - ImGui::End(); + if (asChild) { + ImGui::EndChild(); + } else { + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_LIST; + ImGui::End(); + } } -void FurnaceGUI::drawSampleList() { +void FurnaceGUI::drawSampleList(bool asChild) { if (nextWindow==GUI_WINDOW_SAMPLE_LIST) { sampleListOpen=true; if (settings.unifiedDataView) { @@ -456,8 +511,14 @@ void FurnaceGUI::drawSampleList() { nextWindow=GUI_WINDOW_NOTHING; } if (settings.unifiedDataView) return; - if (!sampleListOpen) return; - if (ImGui::Begin("Samples",&sampleListOpen,globalWinFlags)) { + if (!sampleListOpen && !asChild) return; + bool began=false; + if (asChild) { + began=ImGui::BeginChild("Samples"); + } else { + began=ImGui::Begin("Samples",&sampleListOpen,globalWinFlags); + } + if (began) { if (ImGui::Button(ICON_FA_FILE "##SampleAdd")) { doAction(GUI_ACTION_SAMPLE_LIST_ADD); } @@ -469,6 +530,19 @@ void FurnaceGUI::drawSampleList() { if (ImGui::Button(ICON_FA_FOLDER_OPEN "##SampleLoad")) { doAction(GUI_ACTION_SAMPLE_LIST_OPEN); } + if (ImGui::BeginPopupContextItem("SampleOpenOpt")) { + if (ImGui::MenuItem("replace...")) { + doAction((curSample>=0 && curSample<(int)e->song.sample.size())?GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE:GUI_ACTION_SAMPLE_LIST_OPEN); + } + ImGui::Separator(); + if (ImGui::MenuItem("import raw...")) { + doAction(GUI_ACTION_SAMPLE_LIST_OPEN_RAW); + } + if (ImGui::MenuItem("import raw (replace)...")) { + doAction((curSample>=0 && curSample<(int)e->song.sample.size())?GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE_RAW:GUI_ACTION_SAMPLE_LIST_OPEN_RAW); + } + ImGui::EndPopup(); + } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FLOPPY_O "##SampleSave")) { doAction(GUI_ACTION_SAMPLE_LIST_SAVE); @@ -500,8 +574,12 @@ void FurnaceGUI::drawSampleList() { } ImGui::Unindent(); } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_LIST; - ImGui::End(); + if (asChild) { + ImGui::EndChild(); + } else { + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_LIST; + ImGui::End(); + } } void FurnaceGUI::actualWaveList() { diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index f1c974e8..5ae596bc 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -334,7 +334,7 @@ void putDispatchChan(void* data, int chanNum, int type) { break; } default: - ImGui::Text("Unknown system! Help!"); + ImGui::Text("Unimplemented chip! Help!"); break; } } diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 7d6d057f..8fe02b11 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -18,6 +18,7 @@ */ #include "gui.h" +#include "guiConst.h" #include "debug.h" #include "IconsFontAwesome4.h" #include @@ -154,12 +155,19 @@ void FurnaceGUI::drawDebug() { ImGui::Text("rate: %d",sample->rate); ImGui::Text("centerRate: %d",sample->centerRate); ImGui::Text("loopStart: %d",sample->loopStart); + ImGui::Text("loopEnd: %d", sample->loopEnd); ImGui::Text("loopOffP: %d",sample->loopOffP); - ImGui::Text("depth: %d",sample->depth); + if (sampleDepths[sample->depth]!=NULL) { + ImGui::Text("depth: %d (%s)",(unsigned char)sample->depth,sampleDepths[sample->depth]); + } else { + ImGui::Text("depth: %d ()",(unsigned char)sample->depth); + } + ImGui::Text("length8: %d",sample->length8); ImGui::Text("length16: %d",sample->length16); ImGui::Text("length1: %d",sample->length1); ImGui::Text("lengthDPCM: %d",sample->lengthDPCM); + ImGui::Text("lengthZ: %d",sample->lengthZ); ImGui::Text("lengthQSoundA: %d",sample->lengthQSoundA); ImGui::Text("lengthA: %d",sample->lengthA); ImGui::Text("lengthB: %d",sample->lengthB); @@ -170,6 +178,7 @@ void FurnaceGUI::drawDebug() { ImGui::Text("off16: %x",sample->off16); ImGui::Text("off1: %x",sample->off1); ImGui::Text("offDPCM: %x",sample->offDPCM); + ImGui::Text("offZ: %x",sample->offZ); ImGui::Text("offQSoundA: %x",sample->offQSoundA); ImGui::Text("offA: %x",sample->offA); ImGui::Text("offB: %x",sample->offB); @@ -179,6 +188,8 @@ void FurnaceGUI::drawDebug() { ImGui::Text("offQSound: %x",sample->offQSound); ImGui::Text("offX1_010: %x",sample->offX1_010); ImGui::Text("offSU: %x",sample->offSU); + ImGui::Text("offYMZ280B: %x",sample->offYMZ280B); + ImGui::Text("offRF5C68: %x",sample->offRF5C68); ImGui::Text("samples: %d",sample->samples); ImGui::TreePop(); @@ -209,24 +220,34 @@ void FurnaceGUI::drawDebug() { ImGui::Text("Data"); for (int j=0; jgetChannelCount(system); j++, c++) { + DivDispatchOscBuffer* oscBuf=e->getOscBuffer(c); + if (oscBuf==NULL) { + ImGui::TableNextRow(); + // channel + ImGui::TableNextColumn(); + ImGui::Text("%d",j); + ImGui::TableNextColumn(); + ImGui::Text(""); + continue; + } ImGui::TableNextRow(); // channel ImGui::TableNextColumn(); ImGui::Text("%d",j); // follow ImGui::TableNextColumn(); - ImGui::Checkbox(fmt::sprintf("##%d_OSCFollow_%d",i,c).c_str(),&e->getOscBuffer(c)->follow); + ImGui::Checkbox(fmt::sprintf("##%d_OSCFollow_%d",i,c).c_str(),&oscBuf->follow); // address ImGui::TableNextColumn(); - int needle=e->getOscBuffer(c)->follow?e->getOscBuffer(c)->needle:e->getOscBuffer(c)->followNeedle; - ImGui::BeginDisabled(e->getOscBuffer(c)->follow); + int needle=oscBuf->follow?oscBuf->needle:oscBuf->followNeedle; + ImGui::BeginDisabled(oscBuf->follow); if (ImGui::InputInt(fmt::sprintf("##%d_OSCFollowNeedle_%d",i,c).c_str(),&needle,1,100)) { - e->getOscBuffer(c)->followNeedle=MIN(MAX(needle,0),65535); + oscBuf->followNeedle=MIN(MAX(needle,0),65535); } ImGui::EndDisabled(); // data ImGui::TableNextColumn(); - ImGui::Text("%d",e->getOscBuffer(c)->data[needle]); + ImGui::Text("%d",oscBuf->data[needle]); } ImGui::EndTable(); } @@ -260,9 +281,23 @@ void FurnaceGUI::drawDebug() { ImGui::Unindent(); ImGui::TreePop(); } + if (ImGui::TreeNode("File Selection Test")) { + if (ImGui::Button("Test Open")) { + openFileDialog(GUI_FILE_TEST_OPEN); + } + ImGui::SameLine(); + if (ImGui::Button("Test Open Multi")) { + openFileDialog(GUI_FILE_TEST_OPEN_MULTI); + } + ImGui::SameLine(); + if (ImGui::Button("Test Save")) { + openFileDialog(GUI_FILE_TEST_SAVE); + } + ImGui::TreePop(); + } if (ImGui::TreeNode("Playground")) { if (pgSys<0 || pgSys>=e->song.systemLen) pgSys=0; - if (ImGui::BeginCombo("System",fmt::sprintf("%d. %s",pgSys+1,e->getSystemName(e->song.system[pgSys])).c_str())) { + if (ImGui::BeginCombo("Chip",fmt::sprintf("%d. %s",pgSys+1,e->getSystemName(e->song.system[pgSys])).c_str())) { for (int i=0; isong.systemLen; i++) { if (ImGui::Selectable(fmt::sprintf("%d. %s",i+1,e->getSystemName(e->song.system[i])).c_str())) { pgSys=i; @@ -323,7 +358,7 @@ void FurnaceGUI::drawDebug() { if (ImGui::TreeNode("Register Cheatsheet")) { const char** sheet=e->getRegisterSheet(pgSys); if (sheet==NULL) { - ImGui::Text("no cheatsheet available for this system."); + ImGui::Text("no cheatsheet available for this chip."); } else { if (ImGui::BeginTable("RegisterSheet",2,ImGuiTableFlags_SizingFixedSame)) { ImGui::TableNextRow(); @@ -369,6 +404,9 @@ void FurnaceGUI::drawDebug() { if (ImGui::Button("Inspect")) { inspectorOpen=!inspectorOpen; } + if (ImGui::Button("Spoiler")) { + spoilerOpen=!spoilerOpen; + } ImGui::TreePop(); } if (ImGui::TreeNode("Performance")) { diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 60c9d5c3..468b7e37 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -238,6 +238,12 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_WINDOW_CHANNELS: nextWindow=GUI_WINDOW_CHANNELS; break; + case GUI_ACTION_WINDOW_PAT_MANAGER: + nextWindow=GUI_WINDOW_PAT_MANAGER; + break; + case GUI_ACTION_WINDOW_SYS_MANAGER: + nextWindow=GUI_WINDOW_SYS_MANAGER; + break; case GUI_ACTION_WINDOW_REGISTER_VIEW: nextWindow=GUI_WINDOW_REGISTER_VIEW; break; @@ -322,6 +328,12 @@ void FurnaceGUI::doAction(int what) { case GUI_WINDOW_CHANNELS: channelsOpen=false; break; + case GUI_WINDOW_PAT_MANAGER: + patManagerOpen=false; + break; + case GUI_WINDOW_SYS_MANAGER: + sysManagerOpen=false; + break; case GUI_WINDOW_REGISTER_VIEW: regViewOpen=false; break; @@ -587,6 +599,9 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_INS_LIST_SAVE: if (curIns>=0 && curIns<(int)e->song.ins.size()) openFileDialog(GUI_FILE_INS_SAVE); break; + case GUI_ACTION_INS_LIST_SAVE_DMP: + if (curIns>=0 && curIns<(int)e->song.ins.size()) openFileDialog(GUI_FILE_INS_SAVE_DMP); + break; case GUI_ACTION_INS_LIST_MOVE_UP: if (e->moveInsUp(curIns)) { curIns--; @@ -630,6 +645,7 @@ void FurnaceGUI::doAction(int what) { } else { wantScrollList=true; MARK_MODIFIED; + RESET_WAVE_MACRO_ZOOM; } break; case GUI_ACTION_WAVE_LIST_DUPLICATE: @@ -642,15 +658,25 @@ void FurnaceGUI::doAction(int what) { (*e->song.wave[curWave])=(*e->song.wave[prevWave]); wantScrollList=true; MARK_MODIFIED; + RESET_WAVE_MACRO_ZOOM; } } break; case GUI_ACTION_WAVE_LIST_OPEN: openFileDialog(GUI_FILE_WAVE_OPEN); break; + case GUI_ACTION_WAVE_LIST_OPEN_REPLACE: + openFileDialog(GUI_FILE_WAVE_OPEN_REPLACE); + break; case GUI_ACTION_WAVE_LIST_SAVE: if (curWave>=0 && curWave<(int)e->song.wave.size()) openFileDialog(GUI_FILE_WAVE_SAVE); break; + case GUI_ACTION_WAVE_LIST_SAVE_DMW: + if (curWave>=0 && curWave<(int)e->song.wave.size()) openFileDialog(GUI_FILE_WAVE_SAVE_DMW); + break; + case GUI_ACTION_WAVE_LIST_SAVE_RAW: + if (curWave>=0 && curWave<(int)e->song.wave.size()) openFileDialog(GUI_FILE_WAVE_SAVE_RAW); + break; case GUI_ACTION_WAVE_LIST_MOVE_UP: if (e->moveWaveUp(curWave)) { curWave--; @@ -709,6 +735,7 @@ void FurnaceGUI::doAction(int what) { sample->centerRate=prevSample->centerRate; sample->name=prevSample->name; sample->loopStart=prevSample->loopStart; + sample->loopEnd=prevSample->loopEnd; sample->depth=prevSample->depth; if (sample->init(prevSample->samples)) { if (prevSample->getCurBuf()!=NULL) { @@ -727,6 +754,15 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_LIST_OPEN: openFileDialog(GUI_FILE_SAMPLE_OPEN); break; + case GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE: + openFileDialog(GUI_FILE_SAMPLE_OPEN_REPLACE); + break; + case GUI_ACTION_SAMPLE_LIST_OPEN_RAW: + openFileDialog(GUI_FILE_SAMPLE_OPEN_RAW); + break; + case GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE_RAW: + openFileDialog(GUI_FILE_SAMPLE_OPEN_REPLACE_RAW); + break; case GUI_ACTION_SAMPLE_LIST_SAVE: if (curSample>=0 && curSample<(int)e->song.sample.size()) openFileDialog(GUI_FILE_SAMPLE_SAVE); break; @@ -838,7 +874,7 @@ void FurnaceGUI::doAction(int what) { if (!sample->insert(pos,sampleClipboardLen)) { showError("couldn't paste! make sure your sample is 8 or 16-bit."); } else { - if (sample->depth==8) { + if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (size_t i=0; idata8[pos+i]=sampleClipboard[i]>>8; } @@ -864,7 +900,7 @@ void FurnaceGUI::doAction(int what) { if (pos<0) pos=0; e->lockEngine([this,sample,pos]() { - if (sample->depth==8) { + if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (size_t i=0; i=sample->samples) break; sample->data8[pos+i]=sampleClipboard[i]>>8; @@ -894,7 +930,7 @@ void FurnaceGUI::doAction(int what) { if (pos<0) pos=0; e->lockEngine([this,sample,pos]() { - if (sample->depth==8) { + if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (size_t i=0; i=sample->samples) break; int val=sample->data8[pos+i]+(sampleClipboard[i]>>8); @@ -948,7 +984,7 @@ void FurnaceGUI::doAction(int what) { SAMPLE_OP_BEGIN; float maxVal=0.0f; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[i]/32767.0f); if (val>maxVal) maxVal=val; @@ -963,7 +999,7 @@ void FurnaceGUI::doAction(int what) { sample->data16[i]=val; } } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; idata8[i]/127.0f); if (val>maxVal) maxVal=val; @@ -994,14 +1030,14 @@ void FurnaceGUI::doAction(int what) { e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[i]*float(i-start)/float(end-start); if (val<-32768) val=-32768; if (val>32767) val=32767; sample->data16[i]=val; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; idata8[i]*float(i-start)/float(end-start); if (val<-128) val=-128; @@ -1024,14 +1060,14 @@ void FurnaceGUI::doAction(int what) { e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[i]*float(end-i)/float(end-start); if (val<-32768) val=-32768; if (val>32767) val=32767; sample->data16[i]=val; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; idata8[i]*float(end-i)/float(end-start); if (val<-128) val=-128; @@ -1058,11 +1094,11 @@ void FurnaceGUI::doAction(int what) { e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[i]=0; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; idata8[i]=0; } @@ -1116,7 +1152,7 @@ void FurnaceGUI::doAction(int what) { e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[ri]^=sample->data16[i]; sample->data16[i]^=sample->data16[ri]; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; ilockEngine([this,sample]() { SAMPLE_OP_BEGIN; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[i]=-sample->data16[i]; if (sample->data16[i]==-32768) sample->data16[i]=32767; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; idata8[i]=-sample->data8[i]; if (sample->data16[i]==-128) sample->data16[i]=127; @@ -1174,11 +1210,11 @@ void FurnaceGUI::doAction(int what) { e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[i]^=0x8000; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; idata8[i]^=0x80; } @@ -1261,8 +1297,8 @@ void FurnaceGUI::doAction(int what) { e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; - sample->trim(0,end); sample->loopStart=start; + sample->loopEnd=end; updateSampleTex=true; e->renderSamples(); @@ -1270,6 +1306,33 @@ void FurnaceGUI::doAction(int what) { MARK_MODIFIED; break; } + case GUI_ACTION_SAMPLE_CREATE_WAVE: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + SAMPLE_OP_BEGIN; + if (end-start<1) { + showError("select at least one sample!"); + } else if (end-start>256) { + showError("maximum size is 256 samples!"); + } else { + curWave=e->addWave(); + if (curWave==-1) { + showError("too many wavetables!"); + } else { + DivWavetable* wave=e->song.wave[curWave]; + wave->min=0; + wave->max=255; + wave->len=end-start; + for (unsigned int i=start; idata[i-start]=(sample->data8[i]&0xff)^0x80; + } + nextWindow=GUI_WINDOW_WAVE_EDIT; + MARK_MODIFIED; + RESET_WAVE_MACRO_ZOOM; + } + } + break; + } case GUI_ACTION_ORDERS_UP: if (curOrder>0) { diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 912bd1c0..1d0e2a25 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -22,54 +22,242 @@ #include void FurnaceGUI::drawMobileControls() { + float timeScale=1.0f/(60.0f*ImGui::GetIO().DeltaTime); + if (mobileMenuOpen) { + if (mobileMenuPos<0.999f) { + WAKE_UP; + mobileMenuPos+=MIN(0.1,(1.0-mobileMenuPos)*0.65)*timeScale; + } else { + mobileMenuPos=1.0f; + } + } else { + if (mobileMenuPos>0.001f) { + WAKE_UP; + mobileMenuPos-=MIN(0.1,mobileMenuPos*0.65)*timeScale; + } else { + mobileMenuPos=0.0f; + } + } + ImGui::SetNextWindowPos(portrait?ImVec2(0.0f,((1.0-mobileMenuPos*0.65)*scrH*dpiScale)-(0.16*scrW*dpiScale)):ImVec2(0.5*scrW*dpiScale*mobileMenuPos,0.0f)); + ImGui::SetNextWindowSize(portrait?ImVec2(scrW*dpiScale,0.16*scrW*dpiScale):ImVec2(0.16*scrH*dpiScale,scrH*dpiScale)); if (ImGui::Begin("Mobile Controls",NULL,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) { - float availX=ImGui::GetContentRegionAvail().x; - ImVec2 buttonSize=ImVec2(availX,availX); + float avail=portrait?ImGui::GetContentRegionAvail().y:ImGui::GetContentRegionAvail().x; + ImVec2 buttonSize=ImVec2(avail,avail); - if (ImGui::Button(ICON_FA_CHEVRON_RIGHT "##MobileMenu",buttonSize)) { + const char* mobButtonName=ICON_FA_CHEVRON_RIGHT "##MobileMenu"; + if (portrait) mobButtonName=ICON_FA_CHEVRON_UP "##MobileMenu"; + if (mobileMenuOpen) { + if (portrait) { + mobButtonName=ICON_FA_CHEVRON_DOWN "##MobileMenu"; + } else { + mobButtonName=ICON_FA_CHEVRON_LEFT "##MobileMenu"; + } + } + if (ImGui::Button(mobButtonName,buttonSize)) { + mobileMenuOpen=!mobileMenuOpen; } - ImGui::Separator(); + if (!portrait) ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); + pushToggleColors(e->isPlaying()); + if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_PLAY "##Play",buttonSize)) { play(); } - ImGui::PopStyleColor(); + popToggleColors(); + if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_STOP "##Stop",buttonSize)) { stop(); } + if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne",buttonSize)) { e->stepOne(cursor.y); pendingStepUpdate=true; } bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); + pushToggleColors(repeatPattern); + if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern",buttonSize)) { e->setRepeatPattern(!repeatPattern); } - ImGui::PopStyleColor(); + popToggleColors(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); + pushToggleColors(edit); + if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_CIRCLE "##Edit",buttonSize)) { edit=!edit; } - ImGui::PopStyleColor(); + popToggleColors(); bool metro=e->getMetronome(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); + pushToggleColors(metro); + if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_BELL_O "##Metronome",buttonSize)) { e->setMetronome(!metro); } - ImGui::PopStyleColor(); - - if (ImGui::Button("Get me out of here")) { - toggleMobileUI(false); - } + popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); + + ImGui::SetNextWindowPos(portrait?ImVec2(0.0f,((1.0-mobileMenuPos*0.65)*scrH*dpiScale)):ImVec2(0.5*scrW*dpiScale*(mobileMenuPos-1.0),0.0f)); + ImGui::SetNextWindowSize(portrait?ImVec2(scrW*dpiScale,0.65*scrH*dpiScale):ImVec2(0.5*scrW*dpiScale,scrH*dpiScale)); + if (ImGui::Begin("Mobile Menu",NULL,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) { + if (ImGui::BeginTable("SceneSel",5)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,1.0f); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,1.0f); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,1.0f); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,1.0f); + ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,1.0f); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImVec2 buttonSize=ImGui::GetContentRegionAvail(); + buttonSize.y=30.0f*dpiScale; + + if (ImGui::Button("Pattern",buttonSize)) { + mobScene=GUI_SCENE_PATTERN; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Orders",buttonSize)) { + mobScene=GUI_SCENE_ORDERS; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Ins",buttonSize)) { + mobScene=GUI_SCENE_INSTRUMENT; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Wave",buttonSize)) { + mobScene=GUI_SCENE_WAVETABLE; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Sample",buttonSize)) { + mobScene=GUI_SCENE_SAMPLE; + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Button("Song",buttonSize)) { + mobScene=GUI_SCENE_SONG; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Channels",buttonSize)) { + mobScene=GUI_SCENE_CHANNELS; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Chips",buttonSize)) { + mobScene=GUI_SCENE_CHIPS; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Other",buttonSize)) { + mobScene=GUI_SCENE_OTHER; + } + ImGui::EndTable(); + } + + ImGui::Separator(); + + if (settings.unifiedDataView) { + drawInsList(true); + } else { + switch (mobScene) { + case GUI_SCENE_PATTERN: + case GUI_SCENE_ORDERS: + case GUI_SCENE_INSTRUMENT: + drawInsList(true); + break; + case GUI_SCENE_WAVETABLE: + drawWaveList(true); + break; + case GUI_SCENE_SAMPLE: + drawSampleList(true); + break; + case GUI_SCENE_SONG: { + if (ImGui::Button("New")) { + mobileMenuOpen=false; + //doAction(GUI_ACTION_NEW); + if (modified) { + showWarning("Unsaved changes! Save changes before creating a new song?",GUI_WARN_NEW); + } else { + displayNew=true; + } + } + ImGui::SameLine(); + if (ImGui::Button("Open")) { + mobileMenuOpen=false; + doAction(GUI_ACTION_OPEN); + } + ImGui::SameLine(); + if (ImGui::Button("Save")) { + mobileMenuOpen=false; + doAction(GUI_ACTION_SAVE); + } + ImGui::SameLine(); + if (ImGui::Button("Save as...")) { + mobileMenuOpen=false; + doAction(GUI_ACTION_SAVE_AS); + } + + ImGui::Button("1.1+ .dmf"); + ImGui::SameLine(); + ImGui::Button("Legacy .dmf"); + ImGui::SameLine(); + ImGui::Button("Export Audio"); + ImGui::SameLine(); + ImGui::Button("Export VGM"); + + ImGui::Button("CmdStream"); + + ImGui::Separator(); + + ImGui::Text("Song info here..."); + break; + } + case GUI_SCENE_CHANNELS: + ImGui::Text("Channels here..."); + break; + case GUI_SCENE_CHIPS: + ImGui::Text("Chips here..."); + break; + case GUI_SCENE_OTHER: { + if (ImGui::Button("Osc")) { + oscOpen=!oscOpen; + } + ImGui::SameLine(); + if (ImGui::Button("ChanOsc")) { + chanOscOpen=!chanOscOpen; + } + ImGui::SameLine(); + if (ImGui::Button("RegView")) { + regViewOpen=!regViewOpen; + } + ImGui::SameLine(); + if (ImGui::Button("Stats")) { + statsOpen=!statsOpen; + } + + ImGui::Separator(); + + ImGui::Button("Panic"); + ImGui::SameLine(); + if (ImGui::Button("Settings")) { + mobileMenuOpen=false; + } + ImGui::SameLine(); + if (ImGui::Button("About")) { + mobileMenuOpen=false; + mobileMenuPos=0.0f; + aboutOpen=true; + } + if (ImGui::Button("Switch to Desktop Mode")) { + toggleMobileUI(!mobileUI); + } + break; + } + } + } + } + ImGui::End(); } void FurnaceGUI::drawEditControls() { @@ -118,11 +306,11 @@ void FurnaceGUI::drawEditControls() { ImGui::EndTable(); } - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); + pushToggleColors(e->isPlaying()); if (ImGui::Button(ICON_FA_PLAY "##Play")) { play(); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); if (ImGui::Button(ICON_FA_STOP "##Stop")) { stop(); @@ -152,12 +340,12 @@ void FurnaceGUI::drawEditControls() { } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly)); + pushToggleColors(noteInputPoly); if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) { noteInputPoly=!noteInputPoly; e->setAutoNotePoly(noteInputPoly); } - ImGui::PopStyleColor(); + popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); @@ -168,11 +356,11 @@ void FurnaceGUI::drawEditControls() { stop(); } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); + pushToggleColors(e->isPlaying()); if (ImGui::Button(ICON_FA_PLAY "##Play")) { play(); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { e->stepOne(cursor.y); @@ -181,26 +369,26 @@ void FurnaceGUI::drawEditControls() { ImGui::SameLine(); bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); + pushToggleColors(repeatPattern); if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { e->setRepeatPattern(!repeatPattern); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); + pushToggleColors(edit); if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { edit=!edit; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); bool metro=e->getMetronome(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); + pushToggleColors(metro); if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { e->setMetronome(!metro); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); ImGui::Text("Octave"); @@ -237,12 +425,12 @@ void FurnaceGUI::drawEditControls() { unimportant(ImGui::Checkbox("Pattern",&followPattern)); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly)); + pushToggleColors(noteInputPoly); if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) { noteInputPoly=!noteInputPoly; e->setAutoNotePoly(noteInputPoly); } - ImGui::PopStyleColor(); + popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); @@ -250,11 +438,11 @@ void FurnaceGUI::drawEditControls() { case 2: // compact vertical if (ImGui::Begin("Play/Edit Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) { ImVec2 buttonSize=ImVec2(ImGui::GetContentRegionAvail().x,0.0f); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); + pushToggleColors(e->isPlaying()); if (ImGui::Button(ICON_FA_PLAY "##Play",buttonSize)) { play(); } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::Button(ICON_FA_STOP "##Stop",buttonSize)) { stop(); } @@ -264,24 +452,24 @@ void FurnaceGUI::drawEditControls() { } bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); + pushToggleColors(repeatPattern); if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern",buttonSize)) { e->setRepeatPattern(!repeatPattern); } - ImGui::PopStyleColor(); + popToggleColors(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); + pushToggleColors(edit); if (ImGui::Button(ICON_FA_CIRCLE "##Edit",buttonSize)) { edit=!edit; } - ImGui::PopStyleColor(); + popToggleColors(); bool metro=e->getMetronome(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); + pushToggleColors(metro); if (ImGui::Button(ICON_FA_BELL_O "##Metronome",buttonSize)) { e->setMetronome(!metro); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::Text("Oct."); float avail=ImGui::GetContentRegionAvail().x; @@ -308,23 +496,23 @@ void FurnaceGUI::drawEditControls() { } ImGui::Text("Foll."); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(followOrders)); + pushToggleColors(followOrders); if (ImGui::Button("Ord##FollowOrders",buttonSize)) { handleUnimportant followOrders=!followOrders; } - ImGui::PopStyleColor(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(followPattern)); + popToggleColors(); + pushToggleColors(followPattern); if (ImGui::Button("Pat##FollowPattern",buttonSize)) { handleUnimportant followPattern=!followPattern; } - ImGui::PopStyleColor(); + popToggleColors(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly)); + pushToggleColors(noteInputPoly); if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) { noteInputPoly=!noteInputPoly; e->setAutoNotePoly(noteInputPoly); } - ImGui::PopStyleColor(); + popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); @@ -332,11 +520,11 @@ void FurnaceGUI::drawEditControls() { case 3: // split if (ImGui::Begin("Play Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) { if (e->isPlaying()) { - ImGui::PushStyleColor(ImGuiCol_Button,uiColors[GUI_COLOR_TOGGLE_ON]); + pushToggleColors(true); if (ImGui::Button(ICON_FA_STOP "##Stop")) { stop(); } - ImGui::PopStyleColor(); + popToggleColors(); } else { if (ImGui::Button(ICON_FA_PLAY "##Play")) { play(oldRow); @@ -359,35 +547,35 @@ void FurnaceGUI::drawEditControls() { } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); + pushToggleColors(edit); if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { edit=!edit; } - ImGui::PopStyleColor(); + popToggleColors(); bool metro=e->getMetronome(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); + pushToggleColors(metro); if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { e->setMetronome(!metro); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); + pushToggleColors(repeatPattern); if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { e->setRepeatPattern(!repeatPattern); } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly)); + pushToggleColors(noteInputPoly); if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) { noteInputPoly=!noteInputPoly; e->setAutoNotePoly(noteInputPoly); } - ImGui::PopStyleColor(); + popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index ea12eb59..201c9be2 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -24,7 +24,7 @@ #include "actionUtil.h" -const char* noteNameNormal(short note, short octave) { +const char* FurnaceGUI::noteNameNormal(short note, short octave) { if (note==100) { // note cut return "OFF"; } else if (note==101) { // note off and envelope release @@ -62,10 +62,13 @@ void FurnaceGUI::prepareUndo(ActionType action) { case GUI_UNDO_PATTERN_FLIP: case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: + case GUI_UNDO_PATTERN_DRAG: for (int i=0; igetTotalChannelCount(); i++) { e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false)->copyOn(oldPat[i]); } break; + case GUI_UNDO_REPLACE: // this is handled by doReplace() + break; } } @@ -112,6 +115,7 @@ void FurnaceGUI::makeUndo(ActionType action) { case GUI_UNDO_PATTERN_FLIP: case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: + case GUI_UNDO_PATTERN_DRAG: for (int i=0; igetTotalChannelCount(); i++) { DivPattern* p=e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false); for (int j=0; jcurSubSong->patLen; j++) { @@ -126,6 +130,8 @@ void FurnaceGUI::makeUndo(ActionType action) { doPush=true; } break; + case GUI_UNDO_REPLACE: // this is handled by doReplace() + break; } if (doPush) { MARK_MODIFIED; @@ -425,7 +431,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { int startOff=-1; bool invalidData=false; if (data.size()<2) return; - if (data[0]!=fmt::sprintf("org.tildearrow.furnace - Pattern Data (%d)",DIV_ENGINE_VERSION)) return; + if (data[0].find("org.tildearrow.furnace - Pattern Data")!=0) return; if (sscanf(data[1].c_str(),"%d",&startOff)!=1) return; if (startOff<0) return; @@ -571,7 +577,7 @@ void FurnaceGUI::doChangeIns(int ins) { if (!e->curSubSong->chanShow[iCoarse]) continue; DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); for (int j=selStart.y; j<=selEnd.y; j++) { - if (pat->data[j][2]!=-1 || !(pat->data[j][0]==0 && pat->data[j][1]==0)) { + if (pat->data[j][2]!=-1 || !((pat->data[j][0]==0 || pat->data[j][0]==100 || pat->data[j][0]==101 || pat->data[j][0]==102) && pat->data[j][1]==0)) { pat->data[j][2]=ins; } } @@ -610,9 +616,9 @@ void FurnaceGUI::doInterpolate() { } } else { for (int j=selStart.y; j<=selEnd.y; j++) { - if (pat->data[j][0]!=0 && pat->data[j][1]!=0) { + if (pat->data[j][0]!=0 || pat->data[j][1]!=0) { if (pat->data[j][0]!=100 && pat->data[j][0]!=101 && pat->data[j][0]!=102) { - points.emplace(points.end(),j,pat->data[j][0]+pat->data[j][1]*12); + points.emplace(points.end(),j,pat->data[j][0]+(signed char)pat->data[j][1]*12); } } } @@ -914,6 +920,76 @@ void FurnaceGUI::doExpand(int multiplier) { makeUndo(GUI_UNDO_PATTERN_EXPAND); } +void FurnaceGUI::doDrag() { + DivPattern* patBuffer=NULL; + int len=dragEnd.xCoarse-dragStart.xCoarse+1; + + DETERMINE_FIRST_LAST; + + if (len<1) return; + + patBuffer=new DivPattern[len]; + prepareUndo(GUI_UNDO_PATTERN_DRAG); + + // copy and clear + { + int iCoarse=dragStart.xCoarse; + int iFine=dragStart.xFine; + int iCoarseP=0; + for (; iCoarse<=dragEnd.xCoarse; iCoarse++) { + if (!e->curSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsedata[j][iFine]; + pat->data[j][iFine]=0; + if (dragStart.y==dragEnd.y) pat->data[j][2]=-1; + } + patBuffer[iCoarseP].data[row][iFine+1]=pat->data[j][iFine+1]; + pat->data[j][iFine+1]=(iFine<1)?0:-1; + + if (dragStart.y==dragEnd.y && iFine>2 && iFine&1 && settings.effectDeletionAltersValue) { + pat->data[j][iFine+2]=-1; + } + row++; + } + } + iFine=0; + iCoarseP++; + } + } + + // replace + { + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int iCoarseP=0; + for (; iCoarse<=selEnd.xCoarse && iCoarsePlastChannel) continue; + if (!e->curSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse=e->curSubSong->patLen) continue; + if (iFine==0) { + pat->data[j][iFine]=patBuffer[iCoarseP].data[row][iFine]; + } + pat->data[j][iFine+1]=patBuffer[iCoarseP].data[row][iFine+1]; + } + } + iFine=0; + iCoarseP++; + } + } + + delete[] patBuffer; + makeUndo(GUI_UNDO_PATTERN_DRAG); +} + void FurnaceGUI::doUndo() { if (undoHist.empty()) return; UndoStep& us=undoHist.back(); @@ -943,18 +1019,22 @@ void FurnaceGUI::doUndo() { case GUI_UNDO_PATTERN_FLIP: case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: + case GUI_UNDO_PATTERN_DRAG: + case GUI_UNDO_REPLACE: for (UndoPatternData& i: us.pat) { e->changeSongP(i.subSong); DivPattern* p=e->curPat[i.chan].getPattern(i.pat,true); p->data[i.row][i.col]=i.oldVal; } - if (!e->isPlaying() || !followPattern) { - cursor=us.cursor; - selStart=us.selStart; - selEnd=us.selEnd; - curNibble=us.nibble; - updateScroll(cursor.y); - setOrder(us.order); + if (us.type!=GUI_UNDO_REPLACE) { + if (!e->isPlaying() || !followPattern) { + cursor=us.cursor; + selStart=us.selStart; + selEnd=us.selEnd; + curNibble=us.nibble; + updateScroll(cursor.y); + setOrder(us.order); + } } break; } @@ -991,18 +1071,22 @@ void FurnaceGUI::doRedo() { case GUI_UNDO_PATTERN_FLIP: case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: + case GUI_UNDO_PATTERN_DRAG: + case GUI_UNDO_REPLACE: for (UndoPatternData& i: us.pat) { e->changeSongP(i.subSong); DivPattern* p=e->curPat[i.chan].getPattern(i.pat,true); p->data[i.row][i.col]=i.newVal; } - if (!e->isPlaying()) { - cursor=us.cursor; - selStart=us.selStart; - selEnd=us.selEnd; - curNibble=us.nibble; - updateScroll(cursor.y); - setOrder(us.order); + if (us.type!=GUI_UNDO_REPLACE) { + if (!e->isPlaying() || !followPattern) { + cursor=us.cursor; + selStart=us.selStart; + selEnd=us.selEnd; + curNibble=us.nibble; + updateScroll(cursor.y); + setOrder(us.order); + } } break; diff --git a/src/gui/effectList.cpp b/src/gui/effectList.cpp index a71a2fec..488d4e79 100644 --- a/src/gui/effectList.cpp +++ b/src/gui/effectList.cpp @@ -10,7 +10,7 @@ void FurnaceGUI::drawEffectList() { } if (!effectListOpen) return; if (ImGui::Begin("Effect List",&effectListOpen,globalWinFlags)) { - ImGui::Text("System at cursor: %s",e->getSystemName(e->sysOfChan[cursor.xCoarse])); + ImGui::Text("Chip at cursor: %s",e->getSystemName(e->sysOfChan[cursor.xCoarse])); if (ImGui::BeginTable("effectList",2)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index e50298c1..8d524ab4 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -2,18 +2,99 @@ #include "ImGuiFileDialog.h" #include "../ta-log.h" +#ifdef USE_NFD +#include +#else #include "../../extern/pfd-fixed/portable-file-dialogs.h" +#endif -bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale, FileDialogSelectCallback clickCallback) { +#ifdef USE_NFD +struct NFDState { + bool isSave, allowMultiple; + String header; + std::vector filter; + String path; + FileDialogSelectCallback clickCallback; + NFDState(bool save, String h, std::vector filt, String pa, FileDialogSelectCallback cc, bool multi): + isSave(save), + allowMultiple(multi), + header(h), + filter(filt), + path(pa), + clickCallback(cc) { + } +}; + +// TODO: filter +void _nfdThread(const NFDState state, std::atomic* ok, std::vector* result, bool* errorOutput) { + nfdchar_t* out=NULL; + nfdresult_t ret=NFD_CANCEL; + (*errorOutput)=false; + nfdpathset_t paths; + + result->clear(); + + if (state.isSave) { + ret=NFD_SaveDialog(state.filter,state.path.c_str(),&out,state.clickCallback); + } else { + if (state.allowMultiple) { + ret=NFD_OpenDialogMultiple(state.filter,state.path.c_str(),&paths,state.clickCallback); + } else { + ret=NFD_OpenDialog(state.filter,state.path.c_str(),&out,state.clickCallback); + } + } + + switch (ret) { + case NFD_OKAY: + if (state.allowMultiple) { + logD("pushing multi path"); + for (size_t i=0; ipush_back(String(NFD_PathSet_GetPath(&paths,i))); + } + NFD_PathSet_Free(&paths); + } else { + logD("pushing single path"); + if (out!=NULL) { + logD("we have it"); + result->push_back(String(out)); + } + } + break; + case NFD_CANCEL: + break; + case NFD_ERROR: + logE("NFD error! %s\n",NFD_GetError()); + (*errorOutput)=true; + break; + default: + logE("NFD unknown return code %d!\n",ret); + break; + } + (*ok)=true; +} +#endif + +bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale, FileDialogSelectCallback clickCallback, bool allowMultiple) { if (opened) return false; saving=false; curPath=path; logD("opening load file dialog with curPath %s",curPath.c_str()); if (sysDialog) { - dialogO=new pfd::open_file(header,path,filter); +#ifdef USE_NFD + dialogOK=false; +#ifdef NFD_NON_THREADED + _nfdThread(NFDState(false,header,filter,path,clickCallback,allowMultiple),&dialogOK,&nfdResult,&hasError); +#else + dialogO=new std::thread(_nfdThread,NFDState(false,header,filter,path,clickCallback,allowMultiple),&dialogOK,&nfdResult,&hasError); +#endif +#else + dialogO=new pfd::open_file(header,path,filter,allowMultiple?(pfd::opt::multiselect):(pfd::opt::none)); + hasError=!pfd::settings::available(); +#endif } else { + hasError=false; ImGuiFileDialog::Instance()->DpiScale=dpiScale; - ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,1,nullptr,0,clickCallback); + ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,allowMultiple?999:1,nullptr,0,clickCallback); } opened=true; return true; @@ -25,8 +106,19 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, c curPath=path; logD("opening save file dialog with curPath %s",curPath.c_str()); if (sysDialog) { +#ifdef USE_NFD + dialogOK=false; +#ifdef NFD_NON_THREADED + _nfdThread(NFDState(true,header,filter,path,NULL,false),&dialogOK,&nfdResult,&hasError); +#else + dialogS=new std::thread(_nfdThread,NFDState(true,header,filter,path,NULL,false),&dialogOK,&nfdResult,&hasError); +#endif +#else dialogS=new pfd::save_file(header,path,filter); + hasError=!pfd::settings::available(); +#endif } else { + hasError=false; ImGuiFileDialog::Instance()->DpiScale=dpiScale; ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); } @@ -36,7 +128,7 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, c bool FurnaceGUIFileDialog::accepted() { if (sysDialog) { - return (fileName!=""); + return (!fileName.empty()); } else { return ImGuiFileDialog::Instance()->IsOk(); } @@ -46,15 +138,24 @@ void FurnaceGUIFileDialog::close() { if (sysDialog) { if (saving) { if (dialogS!=NULL) { +#ifdef USE_NFD + dialogS->join(); +#endif delete dialogS; dialogS=NULL; } } else { if (dialogO!=NULL) { +#ifdef USE_NFD + dialogO->join(); +#endif delete dialogO; dialogO=NULL; } } +#ifdef USE_NFD + dialogOK=false; +#endif } else { ImGuiFileDialog::Instance()->Close(); } @@ -63,13 +164,30 @@ void FurnaceGUIFileDialog::close() { bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { if (sysDialog) { +#ifdef USE_NFD + if (dialogOK) { + fileName.clear(); + fileName=nfdResult; + if (!fileName.empty()) { + size_t dsPos=fileName[0].rfind(DIR_SEPARATOR); + if (dsPos!=String::npos) curPath=fileName[0].substr(0,dsPos); + } + for (String& i: fileName) { + logD("- returning %s",i); + } + dialogOK=false; + return true; + } + return false; +#else if (saving) { if (dialogS!=NULL) { if (dialogS->ready(0)) { - fileName=dialogS->result(); - size_t dsPos=fileName.rfind(DIR_SEPARATOR); - if (dsPos!=String::npos) curPath=fileName.substr(0,dsPos); - logD("returning %s",fileName.c_str()); + fileName.clear(); + fileName.push_back(dialogS->result()); + size_t dsPos=fileName[0].rfind(DIR_SEPARATOR); + if (dsPos!=String::npos) curPath=fileName[0].substr(0,dsPos); + logD("returning %s",fileName[0]); return true; } } @@ -77,19 +195,26 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { if (dialogO!=NULL) { if (dialogO->ready(0)) { if (dialogO->result().empty()) { - fileName=""; + fileName.clear(); logD("returning nothing"); } else { - fileName=dialogO->result()[0]; - size_t dsPos=fileName.rfind(DIR_SEPARATOR); - if (dsPos!=String::npos) curPath=fileName.substr(0,dsPos); - logD("returning %s",fileName.c_str()); + fileName=dialogO->result(); + if (fileName.empty()) { + // don't touch + } else { + size_t dsPos=fileName[0].rfind(DIR_SEPARATOR); + if (dsPos!=String::npos) curPath=fileName[0].substr(0,dsPos); + for (String& i: fileName) { + logD("- returning %s",i); + } + } } return true; } } } return false; +#endif } else { return ImGuiFileDialog::Instance()->Display("FileDialog",ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove,min,max); } @@ -99,6 +224,10 @@ bool FurnaceGUIFileDialog::isOpen() { return opened; } +bool FurnaceGUIFileDialog::isError() { + return hasError; +} + String FurnaceGUIFileDialog::getPath() { if (sysDialog) { if (curPath.size()>1) { @@ -113,10 +242,19 @@ String FurnaceGUIFileDialog::getPath() { } } -String FurnaceGUIFileDialog::getFileName() { +std::vector& FurnaceGUIFileDialog::getFileName() { if (sysDialog) { return fileName; } else { - return ImGuiFileDialog::Instance()->GetFilePathName(); + fileName.clear(); + if (saving) { + fileName.push_back(ImGuiFileDialog::Instance()->GetFilePathName()); + } else { + for (auto& i: ImGuiFileDialog::Instance()->GetSelection()) { + fileName.push_back(i.second); + } + } + // + return fileName; } } diff --git a/src/gui/fileDialog.h b/src/gui/fileDialog.h index 5eb67d85..7990b037 100644 --- a/src/gui/fileDialog.h +++ b/src/gui/fileDialog.h @@ -3,10 +3,24 @@ #include #include +#if defined(_WIN32) || defined(__APPLE__) +#define USE_NFD +#endif + +#ifdef USE_NFD +#include +#include + +#ifdef __APPLE__ +#define NFD_NON_THREADED +#endif + +#else namespace pfd { class open_file; class save_file; } +#endif typedef std::function FileDialogSelectCallback; @@ -14,23 +28,33 @@ class FurnaceGUIFileDialog { bool sysDialog; bool opened; bool saving; + bool hasError; String curPath; - String fileName; + std::vector fileName; +#ifdef USE_NFD + std::thread* dialogO; + std::thread* dialogS; + std::atomic dialogOK; + std::vector nfdResult; +#else pfd::open_file* dialogO; pfd::save_file* dialogS; +#endif public: - bool openLoad(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale, FileDialogSelectCallback clickCallback=NULL); + bool openLoad(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale, FileDialogSelectCallback clickCallback=NULL, bool allowMultiple=false); bool openSave(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale); bool accepted(); void close(); bool render(const ImVec2& min, const ImVec2& max); bool isOpen(); + bool isError(); String getPath(); - String getFileName(); + std::vector& getFileName(); explicit FurnaceGUIFileDialog(bool system): sysDialog(system), opened(false), saving(false), + hasError(false), dialogO(NULL), dialogS(NULL) {} }; diff --git a/src/gui/findReplace.cpp b/src/gui/findReplace.cpp index 36048737..428fce5a 100644 --- a/src/gui/findReplace.cpp +++ b/src/gui/findReplace.cpp @@ -1,9 +1,29 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + #include "gui.h" #include "imgui.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" #include "guiConst.h" #include "intConst.h" +#include "../ta-log.h" const char* queryModes[GUI_QUERY_MAX]={ "ignore", @@ -19,9 +39,451 @@ const char* queryReplaceModes[GUI_QUERY_REPLACE_MAX]={ "set", "add", "add (overflow)", + "scale", "clear" }; +int queryNote(int note, int octave) { + if (note==100) { + return 128; + } else if (note==101) { // note off and envelope release + return 129; + } else if (note==102) { // envelope release only + return 130; + } else if (octave==0 && note==0) { + return -61; + } else if (note==0 && octave!=0) { + return -61; // bug note? + } + int seek=(note+(signed char)octave*12); + if (seek<-60 || seek>=120) { + return -61; // out of range note + } + return seek; +} + +bool checkCondition(int mode, int arg, int argMax, int val, bool noteMode=false) { + const int emptyVal=noteMode?-61:-1; + switch (mode) { + case GUI_QUERY_IGNORE: + return true; + break; + case GUI_QUERY_MATCH: + return (val==arg); + break; + case GUI_QUERY_MATCH_NOT: + return (val!=emptyVal && val!=arg); + break; + case GUI_QUERY_RANGE: + return (val>=arg && val<=argMax); + break; + case GUI_QUERY_RANGE_NOT: + return (val!=emptyVal && (valargMax) && (!noteMode || val<120)); + break; + case GUI_QUERY_ANY: + return (val!=emptyVal); + break; + case GUI_QUERY_NONE: + return (val==emptyVal); + break; + } + return false; +} + +void FurnaceGUI::doFind() { + int firstOrder=0; + int lastOrder=e->curSubSong->ordersLen-1; + + if (curQueryRangeY==1 || curQueryRangeY==2) { + firstOrder=curOrder; + lastOrder=curOrder; + } + + int firstRow=0; + int lastRow=e->curSubSong->patLen-1; + + if (curQueryRangeY==1) { + firstRow=selStart.y; + lastRow=selEnd.y; + } + + int firstChan=0; + int lastChan=e->getTotalChannelCount()-1; + + if (curQueryRangeX) { + firstChan=curQueryRangeXMin; + lastChan=curQueryRangeXMax; + } + + curQueryResults.clear(); + + for (int i=firstOrder; i<=lastOrder; i++) { + for (int j=firstRow; j<=lastRow; j++) { + for (int k=firstChan; k<=lastChan; k++) { + DivPattern* p=e->curPat[k].getPattern(e->curOrders->ord[k][i],false); + bool matched=false; + for (FurnaceGUIFindQuery& l: curQuery) { + if (matched) break; + + if (!checkCondition(l.noteMode,l.note,l.noteMax,queryNote(p->data[j][0],p->data[j][1]),true)) continue; + if (!checkCondition(l.insMode,l.ins,l.insMax,p->data[j][2])) continue; + if (!checkCondition(l.volMode,l.vol,l.volMax,p->data[j][3])) continue; + + if (l.effectCount>0) { + bool notMatched=false; + switch (curQueryEffectPos) { + case 0: // no + for (int m=0; mcurPat[k].effectCols; n++) { + if (!checkCondition(l.effectMode[m],l.effect[m],l.effectMax[m],p->data[j][4+n*2])) continue; + if (!checkCondition(l.effectValMode[m],l.effectVal[m],l.effectValMax[m],p->data[j][5+n*2])) continue; + allGood=true; + break; + } + if (!allGood) { + notMatched=true; + break; + } + } + break; + case 1: { // lax + // locate first effect + int posOfFirst=-1; + for (int m=0; mcurPat[k].effectCols; m++) { + if (!checkCondition(l.effectMode[0],l.effect[0],l.effectMax[0],p->data[j][4+m*2])) continue; + if (!checkCondition(l.effectValMode[0],l.effectVal[0],l.effectValMax[0],p->data[j][5+m*2])) continue; + posOfFirst=m; + break; + } + if (posOfFirst<0) { + notMatched=true; + break; + } + // make sure we aren't too far to the right + if ((posOfFirst+l.effectCount)>e->curPat[k].effectCols) { + notMatched=true; + break; + } + // search from first effect location + for (int m=0; mdata[j][4+(m+posOfFirst)*2])) { + notMatched=true; + break; + } + if (!checkCondition(l.effectValMode[m],l.effectVal[m],l.effectValMax[m],p->data[j][5+(m+posOfFirst)*2])) { + notMatched=true; + break; + } + } + break; + } + case 2: // strict + int effectMax=l.effectCount; + if (effectMax>e->curPat[k].effectCols) { + notMatched=true; + } else { + for (int m=0; mdata[j][4+m*2])) { + notMatched=true; + break; + } + if (!checkCondition(l.effectValMode[m],l.effectVal[m],l.effectValMax[m],p->data[j][5+m*2])) { + notMatched=true; + break; + } + } + } + break; + } + if (notMatched) continue; + } + + matched=true; + } + if (matched) { + curQueryResults.push_back(FurnaceGUIQueryResult(e->getCurrentSubSong(),i,k,j)); + } + } + } + } + queryViewingResults=true; +} + +void FurnaceGUI::doReplace() { + doFind(); + queryViewingResults=false; + + bool* touched[DIV_MAX_CHANS]; + memset(touched,0,DIV_MAX_CHANS*sizeof(bool*)); + + UndoStep us; + us.type=GUI_UNDO_REPLACE; + + short prevVal[32]; + memset(prevVal,0,32*sizeof(short)); + + for (FurnaceGUIQueryResult& i: curQueryResults) { + int patIndex=e->song.subsong[i.subsong]->orders.ord[i.x][i.order]; + DivPattern* p=e->song.subsong[i.subsong]->pat[i.x].getPattern(patIndex,true); + if (touched[i.x]==NULL) { + touched[i.x]=new bool[256*256]; + memset(touched[i.x],0,256*256*sizeof(bool)); + } + if (touched[i.x][(patIndex<<8)|i.y]) continue; + touched[i.x][(patIndex<<8)|i.y]=true; + + memcpy(prevVal,p->data[i.y],32*sizeof(short)); + + if (queryReplaceNoteDo) { + switch (queryReplaceNoteMode) { + case GUI_QUERY_REPLACE_SET: + if (queryReplaceNote==130) { // macro release + p->data[i.y][0]=102; + p->data[i.y][1]=0; + } else if (queryReplaceNote==129) { // note release + p->data[i.y][0]=101; + p->data[i.y][1]=0; + } else if (queryReplaceNote==128) { // note off + p->data[i.y][0]=100; + p->data[i.y][1]=0; + } else if (queryReplaceNote>=-60 && queryReplaceNote<120) { // note + p->data[i.y][0]=(queryReplaceNote+60)%12; + if (p->data[i.y][0]==0) p->data[i.y][0]=12; + p->data[i.y][1]=(unsigned char)((queryReplaceNote-1)/12); + } else { // invalid + p->data[i.y][0]=0; + p->data[i.y][1]=0; + } + break; + case GUI_QUERY_REPLACE_ADD: + if (p->data[i.y][0]<100) { + int note=queryNote(p->data[i.y][0],p->data[i.y][1]); + if (note>=-60 && note<120) { + note+=queryReplaceNote; + if (note<-60) note=-60; + if (note>119) note=119; + + p->data[i.y][0]=(note+60)%12; + p->data[i.y][1]=(unsigned char)(((note+60)/12)-5); + if (p->data[i.y][0]==0) { + p->data[i.y][0]=12; + p->data[i.y][1]=(unsigned char)(p->data[i.y][1]-1); + } + } + } + break; + case GUI_QUERY_REPLACE_ADD_OVERFLOW: + if (p->data[i.y][0]<100) { + int note=queryNote(p->data[i.y][0],p->data[i.y][1]); + if (note>=-60 && note<120) { + note+=queryReplaceNote; + if (note<-60) { + while (note<-60) note+=180; + } else if (note>119) { + while (note>119) note-=180; + } + + p->data[i.y][0]=(note+60)%12; + p->data[i.y][1]=(unsigned char)(((note+60)/12)-5); + if (p->data[i.y][0]==0) { + p->data[i.y][0]=12; + p->data[i.y][1]=(unsigned char)(p->data[i.y][1]-1); + } + } + } + break; + case GUI_QUERY_REPLACE_SCALE: + break; + case GUI_QUERY_REPLACE_CLEAR: + p->data[i.y][0]=0; + p->data[i.y][1]=0; + break; + } + } + + if (queryReplaceInsDo) { + switch (queryReplaceInsMode) { + case GUI_QUERY_REPLACE_SET: + p->data[i.y][2]=queryReplaceIns; + break; + case GUI_QUERY_REPLACE_ADD: + if (p->data[i.y][2]>=0) { + p->data[i.y][2]+=queryReplaceIns; + if (p->data[i.y][2]<0) p->data[i.y][2]=0; + if (p->data[i.y][2]>255) p->data[i.y][2]=255; + } + break; + case GUI_QUERY_REPLACE_ADD_OVERFLOW: + if (p->data[i.y][2]>=0) p->data[i.y][2]=(p->data[i.y][2]+queryReplaceIns)&0xff; + break; + case GUI_QUERY_REPLACE_SCALE: + if (p->data[i.y][2]>=0) { + p->data[i.y][2]=(p->data[i.y][2]*queryReplaceIns)/100; + if (p->data[i.y][2]<0) p->data[i.y][2]=0; + if (p->data[i.y][2]>255) p->data[i.y][2]=255; + } + break; + case GUI_QUERY_REPLACE_CLEAR: + p->data[i.y][2]=-1; + break; + } + } + + if (queryReplaceVolDo) { + switch (queryReplaceVolMode) { + case GUI_QUERY_REPLACE_SET: + p->data[i.y][3]=queryReplaceVol; + break; + case GUI_QUERY_REPLACE_ADD: + if (p->data[i.y][3]>=0) { + p->data[i.y][3]+=queryReplaceVol; + if (p->data[i.y][3]<0) p->data[i.y][3]=0; + if (p->data[i.y][3]>255) p->data[i.y][3]=255; + } + break; + case GUI_QUERY_REPLACE_ADD_OVERFLOW: + if (p->data[i.y][3]>=0) p->data[i.y][3]=(p->data[i.y][3]+queryReplaceVol)&0xff; + break; + case GUI_QUERY_REPLACE_SCALE: + if (p->data[i.y][3]>=0) { + p->data[i.y][3]=(p->data[i.y][3]*queryReplaceVol)/100; + if (p->data[i.y][3]<0) p->data[i.y][3]=0; + if (p->data[i.y][3]>255) p->data[i.y][3]=255; + } + break; + case GUI_QUERY_REPLACE_CLEAR: + p->data[i.y][3]=-1; + break; + } + } + + signed char effectOrder[8]; + memset(effectOrder,-1,8); + + switch (queryReplaceEffectPos) { + case 0: // clear + for (int j=0; jsong.subsong[i.subsong]->pat[i.x].effectCols; j++) { + effectOrder[j]=j; + } + break; + case 1: { // replace matches + int placementIndex=0; + for (int j=0; jsong.subsong[i.subsong]->pat[i.x].effectCols; j++) { + if (p->data[i.y][4+j*2]!=-1 || p->data[i.y][5+j*2]!=-1) { + effectOrder[placementIndex++]=j; + } + } + break; + } + case 2: { // replace matches then free spaces + int placementIndex=0; + for (int j=0; jsong.subsong[i.subsong]->pat[i.x].effectCols; j++) { + if (p->data[i.y][4+j*2]!=-1 || p->data[i.y][5+j*2]!=-1) { + effectOrder[placementIndex++]=j; + } + } + for (int j=0; jsong.subsong[i.subsong]->pat[i.x].effectCols; j++) { + if (p->data[i.y][4+j*2]==-1 && p->data[i.y][5+j*2]==-1) { + effectOrder[placementIndex++]=j; + } + } + break; + } + case 3: { // insert in free spaces + int placementIndex=0; + for (int j=0; jsong.subsong[i.subsong]->pat[i.x].effectCols; j++) { + if (p->data[i.y][4+j*2]==-1 && p->data[i.y][5+j*2]==-1) { + effectOrder[placementIndex++]=j; + } + } + break; + } + } + + for (int j=0; jdata[i.y][4+pos*2]=queryReplaceEffect[j]; + break; + case GUI_QUERY_REPLACE_ADD: + if (p->data[i.y][4+pos*2]>=0) { + p->data[i.y][4+pos*2]+=queryReplaceEffect[j]; + if (p->data[i.y][4+pos*2]<0) p->data[i.y][4+pos*2]=0; + if (p->data[i.y][4+pos*2]>255) p->data[i.y][4+pos*2]=255; + } + break; + case GUI_QUERY_REPLACE_ADD_OVERFLOW: + if (p->data[i.y][4+pos*2]>=0) p->data[i.y][4+pos*2]=(p->data[i.y][4+pos*2]+queryReplaceEffect[j])&0xff; + break; + case GUI_QUERY_REPLACE_SCALE: + if (p->data[i.y][4+pos*2]>=0) { + p->data[i.y][4+pos*2]=(p->data[i.y][4+pos*2]*queryReplaceEffect[j])/100; + if (p->data[i.y][4+pos*2]<0) p->data[i.y][4+pos*2]=0; + if (p->data[i.y][4+pos*2]>255) p->data[i.y][4+pos*2]=255; + } + break; + case GUI_QUERY_REPLACE_CLEAR: + p->data[i.y][4+pos*2]=-1; + break; + } + } + + if (queryReplaceEffectValDo[j]) { + switch (queryReplaceEffectValMode[j]) { + case GUI_QUERY_REPLACE_SET: + p->data[i.y][5+pos*2]=queryReplaceEffectVal[j]; + break; + case GUI_QUERY_REPLACE_ADD: + if (p->data[i.y][5+pos*2]>=0) { + p->data[i.y][5+pos*2]+=queryReplaceEffectVal[j]; + if (p->data[i.y][5+pos*2]<0) p->data[i.y][5+pos*2]=0; + if (p->data[i.y][5+pos*2]>255) p->data[i.y][5+pos*2]=255; + } + break; + case GUI_QUERY_REPLACE_ADD_OVERFLOW: + if (p->data[i.y][5+pos*2]>=0) p->data[i.y][5+pos*2]=(p->data[i.y][5+pos*2]+queryReplaceEffectVal[j])&0xff; + break; + case GUI_QUERY_REPLACE_SCALE: + if (p->data[i.y][5+pos*2]>=0) { + p->data[i.y][5+pos*2]=(p->data[i.y][5+pos*2]*queryReplaceEffectVal[j])/100; + if (p->data[i.y][5+pos*2]<0) p->data[i.y][5+pos*2]=0; + if (p->data[i.y][5+pos*2]>255) p->data[i.y][5+pos*2]=255; + } + break; + case GUI_QUERY_REPLACE_CLEAR: + p->data[i.y][5+pos*2]=-1; + break; + } + } + } + + // issue undo step + for (int j=0; j<32; j++) { + if (p->data[i.y][j]!=prevVal[j]) { + us.pat.push_back(UndoPatternData(i.subsong,i.x,patIndex,i.y,j,prevVal[j],p->data[i.y][j])); + } + } + } + + for (int i=0; isettings.maxUndoSteps) undoHist.pop_front(); + } +} + #define FIRST_VISIBLE(x) (x==GUI_QUERY_MATCH || x==GUI_QUERY_MATCH_NOT || x==GUI_QUERY_RANGE || x==GUI_QUERY_RANGE_NOT) #define SECOND_VISIBLE(x) (x==GUI_QUERY_RANGE || x==GUI_QUERY_RANGE_NOT) @@ -40,515 +502,616 @@ void FurnaceGUI::drawFindReplace() { int index=0; int eraseIndex=-1; char tempID[1024]; - for (FurnaceGUIFindQuery& i: curQuery) { - if (ImGui::BeginTable("FindRep",4,ImGuiTableFlags_BordersOuter)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.5); - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.25); - ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.25); - ImGui::PushID(index); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Note"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::Combo("##NCondition",&i.noteMode,queryModes,GUI_QUERY_MAX); - ImGui::TableNextColumn(); - if (FIRST_VISIBLE(i.noteMode)) { - if ((i.noteMode==GUI_QUERY_RANGE || i.noteMode==GUI_QUERY_RANGE_NOT) && i.note>=120) { - i.note=0; - } - if (i.note==130) { - snprintf(tempID,1024,"REL"); - } else if (i.note==129) { - snprintf(tempID,1024,"==="); - } else if (i.note==128) { - snprintf(tempID,1024,"OFF"); - } else if (i.note>=-60 && i.note<120) { - snprintf(tempID,1024,"%s",noteNames[i.note+60]); + if (ImGui::BeginTabBar("FindOrReplace")) { + if (ImGui::BeginTabItem("Find")) { + if (queryViewingResults) { + if (!curQueryResults.empty()) { + ImVec2 avail=ImGui::GetContentRegionAvail(); + avail.y-=ImGui::GetFrameHeightWithSpacing(); + if (ImGui::BeginTable("FindResults",4,ImGuiTableFlags_Borders|ImGuiTableFlags_ScrollY,avail)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,ImGui::CalcTextSize("order").x); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,ImGui::CalcTextSize("row").x); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed); + + ImGui::TableSetupScrollFreeze(0,1); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("order"); + ImGui::TableNextColumn(); + ImGui::Text("row"); + ImGui::TableNextColumn(); + ImGui::Text("channel"); + ImGui::TableNextColumn(); + ImGui::Text("go"); + + int index=0; + for (FurnaceGUIQueryResult& i: curQueryResults) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (settings.orderRowsBase==1) { + ImGui::Text("%.2X",i.order); + } else { + ImGui::Text("%d",i.order); + } + ImGui::TableNextColumn(); + if (settings.patRowsBase==1) { + ImGui::Text("%.2X",i.y); + } else { + ImGui::Text("%d",i.y); + } + ImGui::TableNextColumn(); + ImGui::Text("%d (%s)",i.x+1,e->getChannelName(i.x)); + if (ImGui::TableNextColumn()) { + snprintf(tempID,1024,ICON_FA_CHEVRON_RIGHT "##_FR%d",index); + if (ImGui::Selectable(tempID)) { + e->changeSongP(i.subsong); + if (e->isPlaying()) { + followPattern=false; + } else { + e->setOrder(i.order); + } + curOrder=i.order; + cursor.xCoarse=i.x; + cursor.xFine=0; + cursor.y=i.y; + selStart=cursor; + selEnd=cursor; + demandScrollX=true; + updateScroll(cursor.y); + nextWindow=GUI_WINDOW_PATTERN; + } + } + index++; + } + ImGui::EndTable(); + } } else { - snprintf(tempID,1024,"???"); - i.note=0; + ImGui::Text("no matches found!"); } - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("##NN1",tempID)) { - for (int j=0; j<180; j++) { - snprintf(tempID,1024,"%s",noteNames[j]); - if (ImGui::Selectable(tempID,i.note==(j-60))) { - i.note=j-60; - } - } - if (i.noteMode!=GUI_QUERY_RANGE && i.noteMode!=GUI_QUERY_RANGE_NOT) { - if (ImGui::Selectable("OFF",i.note==128)) { - i.note=128; - } - if (ImGui::Selectable("===",i.note==129)) { - i.note=129; - } - if (ImGui::Selectable("REL",i.note==130)) { - i.note=130; - } - } - ImGui::EndCombo(); + if (ImGui::Button("Back")) { + queryViewingResults=false; } - } - ImGui::TableNextColumn(); - if (SECOND_VISIBLE(i.noteMode)) { - if (i.noteMax<-60 || i.noteMax>=120) { - i.noteMax=0; - } - if (i.noteMax>=-60 && i.noteMax<120) { - snprintf(tempID,1024,"%s",noteNames[i.noteMax+60]); - } else { - snprintf(tempID,1024,"???"); - } - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("##NN2",tempID)) { - for (int j=0; j<180; j++) { - snprintf(tempID,1024,"%s",noteNames[j]); - if (ImGui::Selectable(tempID,i.noteMax==(j-60))) { - i.noteMax=j-60; + } else { + for (FurnaceGUIFindQuery& i: curQuery) { + ImGui::PushID(index+0x100); + if (ImGui::BeginTable("FindRep",4,ImGuiTableFlags_BordersOuter)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.5); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.25); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.25); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Note"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##NCondition",&i.noteMode,queryModes,GUI_QUERY_MAX); + ImGui::TableNextColumn(); + if (FIRST_VISIBLE(i.noteMode)) { + if ((i.noteMode==GUI_QUERY_RANGE || i.noteMode==GUI_QUERY_RANGE_NOT) && i.note>=120) { + i.note=0; + } + if (i.note==130) { + snprintf(tempID,1024,"%s##MREL",macroRelLabel); + } else if (i.note==129) { + snprintf(tempID,1024,"%s##NREL",noteRelLabel); + } else if (i.note==128) { + snprintf(tempID,1024,"%s##NOFF",noteOffLabel); + } else if (i.note>=-60 && i.note<120) { + snprintf(tempID,1024,"%s",noteNames[i.note+60]); + } else { + snprintf(tempID,1024,"???"); + i.note=0; + } + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("##NN1",tempID)) { + for (int j=0; j<180; j++) { + snprintf(tempID,1024,"%s",noteNames[j]); + if (ImGui::Selectable(tempID,i.note==(j-60))) { + i.note=j-60; + } + } + if (i.noteMode!=GUI_QUERY_RANGE && i.noteMode!=GUI_QUERY_RANGE_NOT) { + if (ImGui::Selectable(noteOffLabel,i.note==128)) { + i.note=128; + } + if (ImGui::Selectable(noteRelLabel,i.note==129)) { + i.note=129; + } + if (ImGui::Selectable(macroRelLabel,i.note==130)) { + i.note=130; + } + } + ImGui::EndCombo(); + } + } + ImGui::TableNextColumn(); + if (SECOND_VISIBLE(i.noteMode)) { + if (i.noteMax<-60 || i.noteMax>=120) { + i.noteMax=0; + } + if (i.noteMax>=-60 && i.noteMax<120) { + snprintf(tempID,1024,"%s",noteNames[i.noteMax+60]); + } else { + snprintf(tempID,1024,"???"); + } + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("##NN2",tempID)) { + for (int j=0; j<180; j++) { + snprintf(tempID,1024,"%s",noteNames[j]); + if (ImGui::Selectable(tempID,i.noteMax==(j-60))) { + i.noteMax=j-60; + } + } + ImGui::EndCombo(); + } } - } - ImGui::EndCombo(); - } - } - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Ins"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::Combo("##ICondition",&i.insMode,queryModes,GUI_QUERY_MAX); - ImGui::TableNextColumn(); - if (FIRST_VISIBLE(i.insMode)) { - snprintf(tempID,1024,"%.2X",i.ins); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("II1",tempID)) { - for (int j=0; j<256; j++) { - snprintf(tempID,1024,"%.2X",j); - if (ImGui::Selectable(tempID,i.ins==j)) { - i.ins=j; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Ins"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##ICondition",&i.insMode,queryModes,GUI_QUERY_MAX); + ImGui::TableNextColumn(); + if (FIRST_VISIBLE(i.insMode)) { + snprintf(tempID,1024,"%.2X",i.ins); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("II1",tempID)) { + for (int j=0; j<256; j++) { + snprintf(tempID,1024,"%.2X",j); + if (ImGui::Selectable(tempID,i.ins==j)) { + i.ins=j; + } + } + ImGui::EndCombo(); + } } - } - ImGui::EndCombo(); - } - } - ImGui::TableNextColumn(); - if (SECOND_VISIBLE(i.insMode)) { - snprintf(tempID,1024,"%.2X",i.insMax); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("II2",tempID)) { - for (int j=0; j<256; j++) { - snprintf(tempID,1024,"%.2X",j); - if (ImGui::Selectable(tempID,i.insMax==j)) { - i.insMax=j; + ImGui::TableNextColumn(); + if (SECOND_VISIBLE(i.insMode)) { + snprintf(tempID,1024,"%.2X",i.insMax); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("II2",tempID)) { + for (int j=0; j<256; j++) { + snprintf(tempID,1024,"%.2X",j); + if (ImGui::Selectable(tempID,i.insMax==j)) { + i.insMax=j; + } + } + ImGui::EndCombo(); + } } - } - ImGui::EndCombo(); - } - } - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Volume"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::Combo("##VCondition",&i.volMode,queryModes,GUI_QUERY_MAX); - ImGui::TableNextColumn(); - if (FIRST_VISIBLE(i.volMode)) { - snprintf(tempID,1024,"%.2X",i.vol); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("VV1",tempID)) { - for (int j=0; j<256; j++) { - snprintf(tempID,1024,"%.2X",j); - if (ImGui::Selectable(tempID,i.vol==j)) { - i.vol=j; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Volume"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##VCondition",&i.volMode,queryModes,GUI_QUERY_MAX); + ImGui::TableNextColumn(); + if (FIRST_VISIBLE(i.volMode)) { + snprintf(tempID,1024,"%.2X",i.vol); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("VV1",tempID)) { + for (int j=0; j<256; j++) { + snprintf(tempID,1024,"%.2X",j); + if (ImGui::Selectable(tempID,i.vol==j)) { + i.vol=j; + } + } + ImGui::EndCombo(); + } } - } - ImGui::EndCombo(); - } - } - ImGui::TableNextColumn(); - if (SECOND_VISIBLE(i.volMode)) { - snprintf(tempID,1024,"%.2X",i.volMax); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("VV2",tempID)) { - for (int j=0; j<256; j++) { - snprintf(tempID,1024,"%.2X",j); - if (ImGui::Selectable(tempID,i.volMax==j)) { - i.volMax=j; + ImGui::TableNextColumn(); + if (SECOND_VISIBLE(i.volMode)) { + snprintf(tempID,1024,"%.2X",i.volMax); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("VV2",tempID)) { + for (int j=0; j<256; j++) { + snprintf(tempID,1024,"%.2X",j); + if (ImGui::Selectable(tempID,i.volMax==j)) { + i.volMax=j; + } + } + ImGui::EndCombo(); + } } - } - ImGui::EndCombo(); - } - } - for (int j=0; j0) { + if (ImGui::Button("Remove effect")) { + i.effectCount--; + } + } + ImGui::EndTable(); + } + ImGui::PopID(); + index++; + } + if (eraseIndex>=0) { + curQuery.erase(curQuery.begin()+eraseIndex); + } + if (ImGui::Button(ICON_FA_PLUS "##AddQuery")) { + curQuery.push_back(FurnaceGUIFindQuery()); + } + + if (ImGui::BeginTable("QueryLimits",3)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.5f); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.5f); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + ImGui::Text("Search range:"); + + if (ImGui::RadioButton("Song",curQueryRangeY==0)) { + curQueryRangeY=0; + } + if (ImGui::RadioButton("Selection",curQueryRangeY==1)) { + curQueryRangeY=1; + } + if (ImGui::RadioButton("Pattern",curQueryRangeY==2)) { + curQueryRangeY=2; + } + + ImGui::TableNextColumn(); + ImGui::Checkbox("Confine to channels",&curQueryRangeX); + + ImGui::BeginDisabled(!curQueryRangeX); + snprintf(tempID,1024,"%d: %s",curQueryRangeXMin+1,e->getChannelName(curQueryRangeXMin)); + if (ImGui::BeginCombo("From",tempID)) { + for (int i=0; igetTotalChannelCount(); i++) { + snprintf(tempID,1024,"%d: %s",i+1,e->getChannelName(i)); + if (ImGui::Selectable(tempID,curQueryRangeXMin==i)) { + curQueryRangeXMin=i; + } + } + ImGui::EndCombo(); + } + + snprintf(tempID,1024,"%d: %s",curQueryRangeXMax+1,e->getChannelName(curQueryRangeXMax)); + if (ImGui::BeginCombo("To",tempID)) { + for (int i=0; igetTotalChannelCount(); i++) { + snprintf(tempID,1024,"%d: %s",i+1,e->getChannelName(i)); + if (ImGui::Selectable(tempID,curQueryRangeXMax==i)) { + curQueryRangeXMax=i; + } + } + ImGui::EndCombo(); + } + ImGui::EndDisabled(); + + ImGui::TableNextColumn(); + ImGui::Text("Match effect position:"); + + if (ImGui::RadioButton("No",curQueryEffectPos==0)) { + curQueryEffectPos=0; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("match effects regardless of position."); + } + if (ImGui::RadioButton("Lax",curQueryEffectPos==1)) { + curQueryEffectPos=1; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("match effects only if they appear in-order."); + } + if (ImGui::RadioButton("Strict",curQueryEffectPos==2)) { + curQueryEffectPos=2; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("match effects only if they appear exactly as specified."); + } + + ImGui::EndTable(); + } + + if (ImGui::Button("Find")) { + doFind(); + } + } + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Replace")) { + if (ImGui::BeginTable("QueryReplace",3,ImGuiTableFlags_BordersOuter)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.5); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.5); + ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::Text("Effect"); + ImGui::Checkbox("Note",&queryReplaceNoteDo); + ImGui::TableNextColumn(); + ImGui::BeginDisabled(!queryReplaceNoteDo); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##NRMode",&queryReplaceNoteMode,queryReplaceModes,GUI_QUERY_REPLACE_MAX); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::Combo("##ECondition",&i.effectMode[j],queryModes,GUI_QUERY_MAX); - ImGui::TableNextColumn(); - if (FIRST_VISIBLE(i.effectMode[j])) { - snprintf(tempID,1024,"%.2X",i.effect[j]); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("EE1",tempID)) { - for (int k=0; k<256; k++) { - snprintf(tempID,1024,"%.2X",k); - if (ImGui::Selectable(tempID,i.effect[j]==k)) { - i.effect[j]=k; + if (queryReplaceNoteMode==GUI_QUERY_REPLACE_SET) { + if (queryReplaceNote==130) { + snprintf(tempID,1024,"%s##MREL",macroRelLabel); + } else if (queryReplaceNote==129) { + snprintf(tempID,1024,"%s##NREL",noteRelLabel); + } else if (queryReplaceNote==128) { + snprintf(tempID,1024,"%s##NOFF",noteOffLabel); + } else if (queryReplaceNote>=-60 && queryReplaceNote<120) { + snprintf(tempID,1024,"%s",noteNames[queryReplaceNote+60]); + } else { + snprintf(tempID,1024,"???"); + queryReplaceNote=0; + } + if (ImGui::BeginCombo("##NRValueC",tempID)) { + for (int j=0; j<180; j++) { + snprintf(tempID,1024,"%s",noteNames[j]); + if (ImGui::Selectable(tempID,queryReplaceNote==(j-60))) { + queryReplaceNote=j-60; } } + if (ImGui::Selectable(noteOffLabel,queryReplaceNote==128)) { + queryReplaceNote=128; + } + if (ImGui::Selectable(noteRelLabel,queryReplaceNote==129)) { + queryReplaceNote=129; + } + if (ImGui::Selectable(macroRelLabel,queryReplaceNote==130)) { + queryReplaceNote=130; + } ImGui::EndCombo(); } - } - ImGui::TableNextColumn(); - if (SECOND_VISIBLE(i.effectMode[j])) { - snprintf(tempID,1024,"%.2X",i.effectMax[j]); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("EE2",tempID)) { - for (int k=0; k<256; k++) { - snprintf(tempID,1024,"%.2X",k); - if (ImGui::Selectable(tempID,i.effectMax[j]==k)) { - i.effectMax[j]=k; - } - } - ImGui::EndCombo(); + } else if (queryReplaceNoteMode==GUI_QUERY_REPLACE_ADD || queryReplaceNoteMode==GUI_QUERY_REPLACE_ADD_OVERFLOW) { + if (ImGui::InputInt("##NRValue",&queryReplaceNote,1,12)) { + if (queryReplaceNote<-180) queryReplaceNote=-180; + if (queryReplaceNote>180) queryReplaceNote=180; } + } else if (queryReplaceNoteMode==GUI_QUERY_REPLACE_SCALE) { + ImGui::Text("INVALID"); } - + ImGui::EndDisabled(); + ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::Text("Value"); + ImGui::Checkbox("Ins",&queryReplaceInsDo); + ImGui::TableNextColumn(); + ImGui::BeginDisabled(!queryReplaceInsDo); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##IRMode",&queryReplaceInsMode,queryReplaceModes,GUI_QUERY_REPLACE_MAX); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::Combo("##EVCondition",&i.effectValMode[j],queryModes,GUI_QUERY_MAX); - ImGui::TableNextColumn(); - if (FIRST_VISIBLE(i.effectValMode[j])) { - snprintf(tempID,1024,"%.2X",i.effectVal[j]); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("EV1",tempID)) { - for (int k=0; k<256; k++) { - snprintf(tempID,1024,"%.2X",k); - if (ImGui::Selectable(tempID,i.effectVal[j]==k)) { - i.effectVal[j]=k; - } - } - ImGui::EndCombo(); + if (queryReplaceInsMode==GUI_QUERY_REPLACE_SET) { + if (ImGui::InputScalar("##IRValueH",ImGuiDataType_S32,&queryReplaceIns,&_ONE,&_SIXTEEN,"%.2X",ImGuiInputTextFlags_CharsHexadecimal)) { + if (queryReplaceIns<0) queryReplaceIns=0; + if (queryReplaceIns>255) queryReplaceIns=255; } - } - ImGui::TableNextColumn(); - if (SECOND_VISIBLE(i.effectValMode[j])) { - snprintf(tempID,1024,"%.2X",i.effectValMax[j]); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("EV2",tempID)) { - for (int k=0; k<256; k++) { - snprintf(tempID,1024,"%.2X",k); - if (ImGui::Selectable(tempID,i.effectValMax[j]==k)) { - i.effectValMax[j]=k; - } - } - ImGui::EndCombo(); + } else if (queryReplaceInsMode==GUI_QUERY_REPLACE_ADD || queryReplaceInsMode==GUI_QUERY_REPLACE_ADD_OVERFLOW) { + if (ImGui::InputInt("##IRValue",&queryReplaceIns,1,12)) { + if (queryReplaceIns<-255) queryReplaceIns=-255; + if (queryReplaceIns>255) queryReplaceIns=255; } - } - - ImGui::PopID(); - } - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Button(ICON_FA_MINUS "##DelQuery")) { - eraseIndex=index; - } - ImGui::TableNextColumn(); - if (i.effectCount<8) { - if (ImGui::Button("Add effect")) { - i.effectCount++; - } - } - ImGui::TableNextColumn(); - if (i.effectCount>0) { - if (ImGui::Button("Remove effect")) { - i.effectCount--; - } - } - ImGui::PopID(); - ImGui::EndTable(); - } - index++; - } - if (ImGui::Button("Find")) { - - } - ImGui::SameLine(); - if (eraseIndex>=0) { - curQuery.erase(curQuery.begin()+eraseIndex); - } - if (ImGui::Button(ICON_FA_PLUS "##AddQuery")) { - curQuery.push_back(FurnaceGUIFindQuery()); - } - - if (ImGui::BeginTable("QueryLimits",3)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.5f); - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.5f); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - - ImGui::Text("Search range:"); - - if (ImGui::RadioButton("Song",curQueryRangeY==0)) { - curQueryRangeY=0; - } - if (ImGui::RadioButton("Selection",curQueryRangeY==1)) { - curQueryRangeY=1; - } - if (ImGui::RadioButton("Pattern",curQueryRangeY==2)) { - curQueryRangeY=2; - } - - ImGui::TableNextColumn(); - ImGui::Checkbox("Confine to channels",&curQueryRangeX); - - ImGui::BeginDisabled(!curQueryRangeX); - snprintf(tempID,1024,"%d: %s",curQueryRangeXMin+1,e->getChannelName(curQueryRangeXMin)); - if (ImGui::BeginCombo("From",tempID)) { - for (int i=0; igetTotalChannelCount(); i++) { - snprintf(tempID,1024,"%d: %s",i+1,e->getChannelName(i)); - if (ImGui::Selectable(tempID,curQueryRangeXMin==i)) { - curQueryRangeXMin=i; - } - } - ImGui::EndCombo(); - } - - snprintf(tempID,1024,"%d: %s",curQueryRangeXMax+1,e->getChannelName(curQueryRangeXMax)); - if (ImGui::BeginCombo("To",tempID)) { - for (int i=0; igetTotalChannelCount(); i++) { - snprintf(tempID,1024,"%d: %s",i+1,e->getChannelName(i)); - if (ImGui::Selectable(tempID,curQueryRangeXMax==i)) { - curQueryRangeXMax=i; - } - } - ImGui::EndCombo(); - } - ImGui::EndDisabled(); - - ImGui::TableNextColumn(); - ImGui::Text("Match effect position:"); - - if (ImGui::RadioButton("No",curQueryEffectPos==0)) { - curQueryEffectPos=0; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("match effects regardless of position."); - } - if (ImGui::RadioButton("Lax",curQueryEffectPos==1)) { - curQueryEffectPos=1; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("match effects only if they appear in-order."); - } - if (ImGui::RadioButton("Strict",curQueryEffectPos==2)) { - curQueryEffectPos=2; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("match effects only if they appear exactly as specified."); - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Checkbox("Backwards",&curQueryBackwards); - - ImGui::EndTable(); - } - - if (ImGui::TreeNode("Replace")) { - if (ImGui::BeginTable("QueryReplace",3,ImGuiTableFlags_BordersOuter)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.5); - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.5); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Checkbox("Note",&queryReplaceNoteDo); - ImGui::TableNextColumn(); - ImGui::BeginDisabled(!queryReplaceNoteDo); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::Combo("##NRMode",&queryReplaceNoteMode,queryReplaceModes,GUI_QUERY_REPLACE_MAX); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (queryReplaceNoteMode==GUI_QUERY_REPLACE_SET) { - if (queryReplaceNote==130) { - snprintf(tempID,1024,"REL"); - } else if (queryReplaceNote==129) { - snprintf(tempID,1024,"==="); - } else if (queryReplaceNote==128) { - snprintf(tempID,1024,"OFF"); - } else if (queryReplaceNote>=-60 && queryReplaceNote<120) { - snprintf(tempID,1024,"%s",noteNames[queryReplaceNote+60]); - } else { - snprintf(tempID,1024,"???"); - queryReplaceNote=0; - } - if (ImGui::BeginCombo("##NRValueC",tempID)) { - for (int j=0; j<180; j++) { - snprintf(tempID,1024,"%s",noteNames[j]); - if (ImGui::Selectable(tempID,queryReplaceNote==(j-60))) { - queryReplaceNote=j-60; - } - } - if (ImGui::Selectable("OFF",queryReplaceNote==128)) { - queryReplaceNote=128; - } - if (ImGui::Selectable("===",queryReplaceNote==129)) { - queryReplaceNote=129; - } - if (ImGui::Selectable("REL",queryReplaceNote==130)) { - queryReplaceNote=130; - } - ImGui::EndCombo(); - } - } else if (queryReplaceNoteMode==GUI_QUERY_REPLACE_ADD || queryReplaceNoteMode==GUI_QUERY_REPLACE_ADD_OVERFLOW) { - if (ImGui::InputInt("##NRValue",&queryReplaceNote,1,12)) { - if (queryReplaceNote<-180) queryReplaceNote=-180; - if (queryReplaceNote>180) queryReplaceNote=180; - } - } - ImGui::EndDisabled(); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Checkbox("Ins",&queryReplaceInsDo); - ImGui::TableNextColumn(); - ImGui::BeginDisabled(!queryReplaceInsDo); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::Combo("##IRMode",&queryReplaceInsMode,queryReplaceModes,GUI_QUERY_REPLACE_MAX); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (queryReplaceInsMode==GUI_QUERY_REPLACE_SET) { - if (ImGui::InputScalar("##IRValueH",ImGuiDataType_S32,&queryReplaceIns,&_ONE,&_SIXTEEN,"%.2X",ImGuiInputTextFlags_CharsHexadecimal)) { + } else if (queryReplaceInsMode==GUI_QUERY_REPLACE_SCALE) { if (queryReplaceIns<0) queryReplaceIns=0; - if (queryReplaceIns>255) queryReplaceIns=255; + if (queryReplaceIns>400) queryReplaceIns=400; + if (ImGui::InputInt("##IRValue",&queryReplaceIns,1,12)) { + if (queryReplaceIns<0) queryReplaceIns=0; + if (queryReplaceIns>400) queryReplaceIns=400; + } } - } else if (queryReplaceInsMode==GUI_QUERY_REPLACE_ADD || queryReplaceInsMode==GUI_QUERY_REPLACE_ADD_OVERFLOW) { - if (ImGui::InputInt("##IRValue",&queryReplaceIns,1,12)) { - if (queryReplaceIns<-255) queryReplaceIns=-255; - if (queryReplaceIns>255) queryReplaceIns=255; - } - } - ImGui::EndDisabled(); + ImGui::EndDisabled(); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Checkbox("Volume",&queryReplaceVolDo); - ImGui::TableNextColumn(); - ImGui::BeginDisabled(!queryReplaceVolDo); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::Combo("##VRMode",&queryReplaceVolMode,queryReplaceModes,GUI_QUERY_REPLACE_MAX); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (queryReplaceVolMode==GUI_QUERY_REPLACE_SET) { - if (ImGui::InputScalar("##VRValueH",ImGuiDataType_S32,&queryReplaceVol,&_ONE,&_SIXTEEN,"%.2X",ImGuiInputTextFlags_CharsHexadecimal)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Checkbox("Volume",&queryReplaceVolDo); + ImGui::TableNextColumn(); + ImGui::BeginDisabled(!queryReplaceVolDo); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##VRMode",&queryReplaceVolMode,queryReplaceModes,GUI_QUERY_REPLACE_MAX); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (queryReplaceVolMode==GUI_QUERY_REPLACE_SET) { + if (ImGui::InputScalar("##VRValueH",ImGuiDataType_S32,&queryReplaceVol,&_ONE,&_SIXTEEN,"%.2X",ImGuiInputTextFlags_CharsHexadecimal)) { + if (queryReplaceVol<0) queryReplaceVol=0; + if (queryReplaceVol>255) queryReplaceVol=255; + } + } else if (queryReplaceVolMode==GUI_QUERY_REPLACE_ADD || queryReplaceVolMode==GUI_QUERY_REPLACE_ADD_OVERFLOW) { + if (ImGui::InputInt("##VRValue",&queryReplaceVol,1,12)) { + if (queryReplaceVol<-255) queryReplaceVol=-255; + if (queryReplaceVol>255) queryReplaceVol=255; + } + } else if (queryReplaceVolMode==GUI_QUERY_REPLACE_SCALE) { if (queryReplaceVol<0) queryReplaceVol=0; - if (queryReplaceVol>255) queryReplaceVol=255; + if (queryReplaceVol>400) queryReplaceVol=400; + if (ImGui::InputInt("##VRValue",&queryReplaceVol,1,12)) { + if (queryReplaceVol<0) queryReplaceVol=0; + if (queryReplaceVol>400) queryReplaceVol=400; + } } - } else if (queryReplaceVolMode==GUI_QUERY_REPLACE_ADD || queryReplaceVolMode==GUI_QUERY_REPLACE_ADD_OVERFLOW) { - if (ImGui::InputInt("##VRValue",&queryReplaceVol,1,12)) { - if (queryReplaceVol<-255) queryReplaceVol=-255; - if (queryReplaceVol>255) queryReplaceVol=255; - } - } - ImGui::EndDisabled(); + ImGui::EndDisabled(); - for (int i=0; i255) queryReplaceEffect[i]=255; + } + } else if (queryReplaceEffectMode[i]==GUI_QUERY_REPLACE_ADD || queryReplaceEffectMode[i]==GUI_QUERY_REPLACE_ADD_OVERFLOW) { + if (ImGui::InputInt("##ERValue",&queryReplaceEffect[i],1,12)) { + if (queryReplaceEffect[i]<-255) queryReplaceEffect[i]=-255; + if (queryReplaceEffect[i]>255) queryReplaceEffect[i]=255; + } + } else if (queryReplaceEffectMode[i]==GUI_QUERY_REPLACE_SCALE) { if (queryReplaceEffect[i]<0) queryReplaceEffect[i]=0; - if (queryReplaceEffect[i]>255) queryReplaceEffect[i]=255; + if (queryReplaceEffect[i]>400) queryReplaceEffect[i]=400; + if (ImGui::InputInt("##ERValue",&queryReplaceEffect[i],1,12)) { + if (queryReplaceEffect[i]<0) queryReplaceEffect[i]=0; + if (queryReplaceEffect[i]>400) queryReplaceEffect[i]=400; + } } - } else if (queryReplaceEffectMode[i]==GUI_QUERY_REPLACE_ADD || queryReplaceEffectMode[i]==GUI_QUERY_REPLACE_ADD_OVERFLOW) { - if (ImGui::InputInt("##ERValue",&queryReplaceEffect[i],1,12)) { - if (queryReplaceEffect[i]<-255) queryReplaceEffect[i]=-255; - if (queryReplaceEffect[i]>255) queryReplaceEffect[i]=255; + ImGui::EndDisabled(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Checkbox("Value",&queryReplaceEffectValDo[i]); + ImGui::TableNextColumn(); + ImGui::BeginDisabled(!queryReplaceEffectValDo[i]); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##ERModeV",&queryReplaceEffectValMode[i],queryReplaceModes,GUI_QUERY_REPLACE_MAX); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (queryReplaceEffectValMode[i]==GUI_QUERY_REPLACE_SET) { + if (ImGui::InputScalar("##ERValueVH",ImGuiDataType_S32,&queryReplaceEffectVal[i],&_ONE,&_SIXTEEN,"%.2X",ImGuiInputTextFlags_CharsHexadecimal)) { + if (queryReplaceEffectVal[i]<0) queryReplaceEffectVal[i]=0; + if (queryReplaceEffectVal[i]>255) queryReplaceEffectVal[i]=255; + } + } else if (queryReplaceEffectValMode[i]==GUI_QUERY_REPLACE_ADD || queryReplaceEffectValMode[i]==GUI_QUERY_REPLACE_ADD_OVERFLOW) { + if (ImGui::InputInt("##ERValueV",&queryReplaceEffectVal[i],1,12)) { + if (queryReplaceEffectVal[i]<-255) queryReplaceEffectVal[i]=-255; + if (queryReplaceEffectVal[i]>255) queryReplaceEffectVal[i]=255; + } + } else if (queryReplaceEffectValMode[i]==GUI_QUERY_REPLACE_SCALE) { + if (queryReplaceEffectVal[i]<0) queryReplaceEffectVal[i]=0; + if (queryReplaceEffectVal[i]>400) queryReplaceEffectVal[i]=400; + if (ImGui::InputInt("##ERValueV",&queryReplaceEffectVal[i],1,12)) { + if (queryReplaceEffectVal[i]<0) queryReplaceEffectVal[i]=0; + if (queryReplaceEffectVal[i]>400) queryReplaceEffectVal[i]=400; + } } + ImGui::EndDisabled(); + + ImGui::PopID(); } - ImGui::EndDisabled(); ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::Checkbox("Value",&queryReplaceEffectValDo[i]); ImGui::TableNextColumn(); - ImGui::BeginDisabled(!queryReplaceEffectValDo[i]); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::Combo("##ERMode",&queryReplaceEffectValMode[i],queryReplaceModes,GUI_QUERY_REPLACE_MAX); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (queryReplaceEffectValMode[i]==GUI_QUERY_REPLACE_SET) { - if (ImGui::InputScalar("##ERValueH",ImGuiDataType_S32,&queryReplaceEffectVal[i],&_ONE,&_SIXTEEN,"%.2X",ImGuiInputTextFlags_CharsHexadecimal)) { - if (queryReplaceEffectVal[i]<0) queryReplaceEffectVal[i]=0; - if (queryReplaceEffectVal[i]>255) queryReplaceEffectVal[i]=255; - } - } else if (queryReplaceEffectValMode[i]==GUI_QUERY_REPLACE_ADD || queryReplaceEffectValMode[i]==GUI_QUERY_REPLACE_ADD_OVERFLOW) { - if (ImGui::InputInt("##ERValue",&queryReplaceEffectVal[i],1,12)) { - if (queryReplaceEffectVal[i]<-255) queryReplaceEffectVal[i]=-255; - if (queryReplaceEffectVal[i]>255) queryReplaceEffectVal[i]=255; + if (queryReplaceEffectCount<8) { + if (ImGui::Button("Add effect")) { + queryReplaceEffectCount++; } } - ImGui::EndDisabled(); - - - ImGui::PopID(); - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::TableNextColumn(); - if (queryReplaceEffectCount<8) { - if (ImGui::Button("Add effect")) { - queryReplaceEffectCount++; + ImGui::TableNextColumn(); + if (queryReplaceEffectCount>0) { + if (ImGui::Button("Remove effect")) { + queryReplaceEffectCount--; + } } - } - ImGui::TableNextColumn(); - if (queryReplaceEffectCount>0) { - if (ImGui::Button("Remove effect")) { - queryReplaceEffectCount--; - } - } - ImGui::EndTable(); + ImGui::EndTable(); + } + ImGui::Text("Effect replace mode:"); + if (ImGui::RadioButton("Clear effects",queryReplaceEffectPos==0)) { + queryReplaceEffectPos=0; + } + if (ImGui::RadioButton("Replace matches only",queryReplaceEffectPos==1)) { + queryReplaceEffectPos=1; + } + if (ImGui::RadioButton("Replace matches, then free spaces",queryReplaceEffectPos==2)) { + queryReplaceEffectPos=2; + } + if (ImGui::RadioButton("Insert in free spaces",queryReplaceEffectPos==3)) { + queryReplaceEffectPos=3; + } + if (ImGui::Button("Replace##QueryReplace")) { + doReplace(); + } + ImGui::EndTabItem(); } - ImGui::Text("Effect replace mode:"); - if (ImGui::RadioButton("Clear effects",queryReplaceEffectPos==0)) { - queryReplaceEffectPos=0; - } - if (ImGui::RadioButton("Replace matches only",queryReplaceEffectPos==1)) { - queryReplaceEffectPos=1; - } - if (ImGui::RadioButton("Replace matches, then free spaces",queryReplaceEffectPos==2)) { - queryReplaceEffectPos=2; - } - if (ImGui::RadioButton("Insert in free spaces",queryReplaceEffectPos==3)) { - queryReplaceEffectPos=3; - } - if (ImGui::Button("Replace##QueryReplace")) { - // TODO - } - ImGui::TreePop(); + ImGui::EndTabBar(); } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_FIND; diff --git a/src/gui/gradient.cpp b/src/gui/gradient.cpp new file mode 100644 index 00000000..e4af10b5 --- /dev/null +++ b/src/gui/gradient.cpp @@ -0,0 +1,130 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" +#include "imgui.h" +#include +#include + +ImU32 Gradient2D::get(float x, float y) { + int xi=round(x*width); + int yi=round(y*height); + if (xi<0) xi=0; + if (xi>=(int)width) xi=width-1; + if (yi<0) yi=0; + if (yi>=(int)height) yi=height-1; + return grad[yi*width+xi]; +} + +String Gradient2D::toString() { + String ret=fmt::sprintf("GRAD #%.2X%.2X%.2X%.2X",(unsigned char)(bgColor.x*255.0f),(unsigned char)(bgColor.y*255.0f),(unsigned char)(bgColor.z*255.0f),(unsigned char)(bgColor.w*255.0f)); + for (Gradient2DPoint& i: points) { + ret+=fmt::sprintf(" %f,%f:%f,%f:#%.2X%.2X%.2X%.2X",i.x,i.y,i.distance,i.spread,(unsigned char)(i.color.x*255.0f),(unsigned char)(i.color.y*255.0f),(unsigned char)(i.color.z*255.0f),(unsigned char)(i.color.w*255.0f)); + } + return ret; +} + +bool Gradient2D::fromString(String val) { + std::vector split; + String cur; + for (char i: val) { + if (i==' ') { + if (!cur.empty()) { + split.push_back(cur); + cur=""; + } + } else { + cur+=i; + } + } + if (!cur.empty()) { + split.push_back(cur); + } + + if (split.size()<2) return false; + + if (split[0]!="GRAD") return false; + + ImU32 bgColorH=0; + if (sscanf(split[1].c_str(),"#%X",&bgColorH)!=1) return false; + bgColorH=(bgColorH>>24)|((bgColorH>>8)&0xff00)|((bgColorH<<8)&0xff0000)|(bgColorH<<24); + + bgColor=ImGui::ColorConvertU32ToFloat4(bgColorH); + + for (size_t i=2; i>24)|((colorH>>8)&0xff00)|((colorH<<8)&0xff0000)|(colorH<<24); + + point.color=ImGui::ColorConvertU32ToFloat4(colorH); + points.push_back(point); + } + return true; +} + +void Gradient2D::render() { + ImU32* g=grad.get(); + ImU32 bgColorU=ImGui::ColorConvertFloat4ToU32(bgColor); + + // 1. fill with background color + for (size_t i=0; i=pDistSquared) continue; + + float dist=(1.0-(sqrt(distSquared)/i.distance)); + if (dist<0) dist=0; + if (dist>1) dist=1; + + ImU32 shadeColor=ImGui::ColorConvertFloat4ToU32( + ImVec4( + i.color.x*i.color.w*dist, + i.color.y*i.color.w*dist, + i.color.z*i.color.w*dist, + 1.0f + ) + ); + + ImU32 origColor=g[j*width+k]; + g[j*width+k]=( + (MIN( 0xff, (origColor&0xff) + (shadeColor&0xff) )) | // R + (MIN( 0xff00, (origColor&0xff00) + (shadeColor&0xff00) )) | // G + (MIN(0xff0000,(origColor&0xff0000)+(shadeColor&0xff0000))) | // B + (origColor&0xff000000) // A + ); + } + } + } +} diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index c1107d2e..40869c42 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -17,6 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +// I hate you clangd extension! +// how about you DON'T insert random headers before this freaking important +// define!!!!!! #define _USE_MATH_DEFINES #include "gui.h" #include "util.h" @@ -84,13 +87,13 @@ void FurnaceGUI::bindEngine(DivEngine* eng) { const char* FurnaceGUI::noteName(short note, short octave) { if (note==100) { - return "OFF"; + return noteOffLabel; } else if (note==101) { // note off and envelope release - return "==="; + return noteRelLabel; } else if (note==102) { // envelope release only - return "REL"; + return macroRelLabel; } else if (octave==0 && note==0) { - return "..."; + return emptyLabel; } else if (note==0 && octave!=0) { return "BUG"; } @@ -195,30 +198,37 @@ void FurnaceGUI::decodeKeyMap(std::map& map, String source) { } } -void FurnaceGUI::encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel, bool hex) { +void FurnaceGUI::encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel, bool hex, bool bit30) { target=""; char buf[32]; for (int i=0; imacroMax) macro[macroLen]=macroMax; macroLen++; buf=0; @@ -267,22 +277,23 @@ void FurnaceGUI::decodeMMLStrW(String& source, int* macro, int& macroLen, int ma } if (hasVal && macroLen<256) { hasVal=false; - negaBuf=false; macro[macroLen]=negaBuf?-buf:buf; - if (macro[macroLen]<0) macro[macroLen]=0; + negaBuf=false; + if (macro[macroLen]macroMax) macro[macroLen]=macroMax; macroLen++; buf=0; } } -void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLen, signed char& macroLoop, int macroMin, int macroMax, signed char& macroRel) { +void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLen, unsigned char& macroLoop, int macroMin, int macroMax, unsigned char& macroRel, bool bit30) { int buf=0; bool negaBuf=false; + bool setBit30=false; bool hasVal=false; macroLen=0; - macroLoop=-1; - macroRel=-1; + macroLoop=255; + macroRel=255; for (char& i: source) { switch (i) { case '0': case '1': case '2': case '3': case '4': @@ -297,6 +308,11 @@ void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLe negaBuf=true; } break; + case '@': + if (bit30) { + setBit30=true; + } + break; case ' ': if (hasVal) { hasVal=false; @@ -304,6 +320,8 @@ void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLe negaBuf=false; if (macro[macroLen]macroMax) macro[macroLen]=macroMax; + if (setBit30) macro[macroLen]^=0x40000000; + setBit30=false; macroLen++; buf=0; } @@ -315,10 +333,12 @@ void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLe negaBuf=false; if (macro[macroLen]macroMax) macro[macroLen]=macroMax; + if (setBit30) macro[macroLen]^=0x40000000; + setBit30=false; macroLen++; buf=0; } - if (macroLoop==-1) { + if (macroLoop==255) { macroLoop=macroLen; } break; @@ -329,22 +349,26 @@ void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLe negaBuf=false; if (macro[macroLen]macroMax) macro[macroLen]=macroMax; + if (setBit30) macro[macroLen]^=0x40000000; + setBit30=false; macroLen++; buf=0; } - if (macroRel==-1) { + if (macroRel==255) { macroRel=macroLen; } break; } - if (macroLen>=128) break; + if (macroLen>=255) break; } - if (hasVal && macroLen<128) { + if (hasVal && macroLen<255) { hasVal=false; macro[macroLen]=negaBuf?-buf:buf; negaBuf=false; if (macro[macroLen]macroMax) macro[macroLen]=macroMax; + if (setBit30) macro[macroLen]^=0x40000000; + setBit30=false; macroLen++; buf=0; } @@ -512,6 +536,7 @@ void FurnaceGUI::setFileName(String name) { } #endif updateWindowTitle(); + pushRecentFile(curFileName); } void FurnaceGUI::updateWindowTitle() { @@ -551,12 +576,46 @@ void FurnaceGUI::updateWindowTitle() { } if (settings.titleBarSys) { - title+=fmt::sprintf(" (%s)",e->getSongSystemName(!settings.noMultiSystem)); + if (e->song.systemName!="") { + title+=fmt::sprintf(" (%s)",e->song.systemName); + } } if (sdlWin!=NULL) SDL_SetWindowTitle(sdlWin,title.c_str()); } +ImVec4 FurnaceGUI::channelColor(int ch) { + switch (settings.channelColors) { + case 0: + return uiColors[GUI_COLOR_CHANNEL_BG]; + break; + case 1: + return uiColors[GUI_COLOR_CHANNEL_FM+e->getChannelType(ch)]; + break; + case 2: + return uiColors[GUI_COLOR_INSTR_STD+e->getPreferInsType(ch)]; + break; + } + // invalid + return uiColors[GUI_COLOR_TEXT]; +} + +ImVec4 FurnaceGUI::channelTextColor(int ch) { + switch (settings.channelTextColors) { + case 0: + return uiColors[GUI_COLOR_CHANNEL_FG]; + break; + case 1: + return uiColors[GUI_COLOR_CHANNEL_FM+e->getChannelType(ch)]; + break; + case 2: + return uiColors[GUI_COLOR_INSTR_STD+e->getPreferInsType(ch)]; + break; + } + // invalid + return uiColors[GUI_COLOR_TEXT]; +} + const char* defaultLayout="[Window][DockSpaceViewport_11111111]\n\ Pos=0,24\n\ Size=1280,731\n\ @@ -860,7 +919,7 @@ void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode, bool autoNote) { void FurnaceGUI::noteInput(int num, int key, int vol) { DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true); - + prepareUndo(GUI_UNDO_PATTERN_EDIT); if (key==100) { // note off @@ -1128,6 +1187,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { e->lockSave([this,num]() { e->curOrders->ord[orderCursor][curOrder]=((e->curOrders->ord[orderCursor][curOrder]<<4)|num); }); + MARK_MODIFIED; if (orderEditMode==2 || orderEditMode==3) { curNibble=!curNibble; if (!curNibble) { @@ -1224,9 +1284,9 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openLoad( "Open File", - {"compatible files", "*.fur *.dmf *.mod", + {"compatible files", "*.fur *.dmf *.mod *.fc13 *.fc14 *.smod", "all files", ".*"}, - "compatible files{.fur,.dmf,.mod},.*", + "compatible files{.fur,.dmf,.mod,.fc13,.fc14,.smod},.*", workingDirSong, dpiScale ); @@ -1235,9 +1295,18 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openSave( "Save File", - {"Furnace song", "*.fur", - "DefleMask 1.1.3 module", "*.dmf"}, - "Furnace song{.fur},DefleMask 1.1.3 module{.dmf}", + {"Furnace song", "*.fur"}, + "Furnace song{.fur}", + workingDirSong, + dpiScale + ); + break; + case GUI_FILE_SAVE_DMF: + if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); + hasOpened=fileDialog->openSave( + "Save File", + {"DefleMask 1.1.3 module", "*.dmf"}, + "DefleMask 1.1.3 module{.dmf}", workingDirSong, dpiScale ); @@ -1265,6 +1334,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { hasOpened=fileDialog->openLoad( "Load Instrument", // TODO supply loadable formats in a dynamic, scalable, "DRY" way. + // thank the author of IGFD for making things impossible {"all compatible files", "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi *.opli *.opni *.y12 *.bnk *.ff *.gyb *.opm *.wopl *.wopn", "Furnace instrument", "*.fui", "DefleMask preset", "*.dmp", @@ -1317,7 +1387,18 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; + case GUI_FILE_INS_SAVE_DMP: + if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); + hasOpened=fileDialog->openSave( + "Save Instrument", + {"DefleMask preset", "*.dmp"}, + "DefleMask preset{.dmp}", + workingDirIns, + dpiScale + ); + break; case GUI_FILE_WAVE_OPEN: + case GUI_FILE_WAVE_OPEN_REPLACE: if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); hasOpened=fileDialog->openLoad( "Load Wavetable", @@ -1338,7 +1419,28 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; + case GUI_FILE_WAVE_SAVE_DMW: + if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); + hasOpened=fileDialog->openSave( + "Save Wavetable", + {"DefleMask wavetable", ".dmw"}, + "DefleMask wavetable{.dmw}", + workingDirWave, + dpiScale + ); + break; + case GUI_FILE_WAVE_SAVE_RAW: + if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); + hasOpened=fileDialog->openSave( + "Save Wavetable", + {"raw data", ".raw"}, + "raw data{.raw}", + workingDirWave, + dpiScale + ); + break; case GUI_FILE_SAMPLE_OPEN: + case GUI_FILE_SAMPLE_OPEN_REPLACE: if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); hasOpened=fileDialog->openLoad( "Load Sample", @@ -1349,6 +1451,17 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; + case GUI_FILE_SAMPLE_OPEN_RAW: + case GUI_FILE_SAMPLE_OPEN_REPLACE_RAW: + if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); + hasOpened=fileDialog->openLoad( + "Load Raw Sample", + {"all files", ".*"}, + ".*", + workingDirSample, + dpiScale + ); + break; case GUI_FILE_SAMPLE_SAVE: if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); hasOpened=fileDialog->openSave( @@ -1399,6 +1512,17 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; + case GUI_FILE_EXPORT_CMDSTREAM: + if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir(); + hasOpened=fileDialog->openSave( + "Export Command Stream", + {"text file", "*.txt", + "binary file", "*.bin"}, + "text file{.txt},binary file{.bin}", + workingDirROMExport, + dpiScale + ); + break; case GUI_FILE_EXPORT_ROM: showError("Coming soon!"); break; @@ -1495,6 +1619,43 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; + case GUI_FILE_TEST_OPEN: + if (!dirExists(workingDirTest)) workingDirTest=getHomeDir(); + hasOpened=fileDialog->openLoad( + "Open Test", + {"compatible files", "*.fur *.dmf *.mod", + "another option", "*.wav *.ttf", + "all files", ".*"}, + "compatible files{.fur,.dmf,.mod},another option{.wav,.ttf},.*", + workingDirTest, + dpiScale + ); + break; + case GUI_FILE_TEST_OPEN_MULTI: + if (!dirExists(workingDirTest)) workingDirTest=getHomeDir(); + hasOpened=fileDialog->openLoad( + "Open Test (Multi)", + {"compatible files", "*.fur *.dmf *.mod", + "another option", "*.wav *.ttf", + "all files", ".*"}, + "compatible files{.fur,.dmf,.mod},another option{.wav,.ttf},.*", + workingDirTest, + dpiScale, + NULL, + true + ); + break; + case GUI_FILE_TEST_SAVE: + if (!dirExists(workingDirTest)) workingDirTest=getHomeDir(); + hasOpened=fileDialog->openSave( + "Save Test", + {"Furnace song", "*.fur", + "DefleMask module", "*.dmf"}, + "Furnace song{.fur},DefleMask module{.dmf}", + workingDirTest, + dpiScale + ); + break; } if (hasOpened) curFileDialog=type; //ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard; @@ -1505,6 +1666,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { int FurnaceGUI::save(String path, int dmfVersion) { SafeWriter* w; if (dmfVersion) { + if (dmfVersion<24) dmfVersion=24; w=e->saveDMF(dmfVersion); } else { w=e->saveFur(); @@ -1595,6 +1757,7 @@ int FurnaceGUI::save(String path, int dmfVersion) { if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } + pushRecentFile(path); return 0; } @@ -1672,9 +1835,26 @@ int FurnaceGUI::load(String path) { if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } + pushRecentFile(path); return 0; } +void FurnaceGUI::pushRecentFile(String path) { + if (path.empty()) return; + if (path==backupPath) return; + for (int i=0; i<(int)recentFile.size(); i++) { + if (recentFile[i]==path) { + recentFile.erase(recentFile.begin()+i); + i--; + } + } + recentFile.push_front(path); + + while (!recentFile.empty() && (int)recentFile.size()>settings.maxRecentFile) { + recentFile.pop_back(); + } +} + void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) { e->saveAudio(path.c_str(),exportLoops+1,mode,exportFadeOut); displayExporting=true; @@ -1691,8 +1871,35 @@ void FurnaceGUI::showError(String what) { displayError=true; } +// what monster did I just create here? +#define B30(tt) (macroDragBit30?((((tt)&0xc0000000)==0x40000000 || ((tt)&0xc0000000)==0x80000000)?0x40000000:0):0) + #define MACRO_DRAG(t) \ - if (macroDragBitMode) { \ + if (macroDragSettingBit30) { \ + if (macroDragLastX!=x || macroDragLastY!=y) { \ + macroDragLastX=x; \ + macroDragLastY=y; \ + if (macroDragInitialValueSet) { \ + if (!macroDragInitialValue) { \ + if (t[x]&0x80000000) { \ + t[x]&=~0x40000000; \ + } else { \ + t[x]|=0x40000000; \ + } \ + } else { \ + if (t[x]&0x80000000) { \ + t[x]|=0x40000000; \ + } else { \ + t[x]&=~0x40000000; \ + } \ + } \ + } else { \ + macroDragInitialValue=(((t[x])&0xc0000000)==0x40000000 || ((t[x])&0xc0000000)==0x80000000); \ + macroDragInitialValueSet=true; \ + t[x]^=0x40000000; \ + } \ + } \ + } else if (macroDragBitMode) { \ if (macroDragLastX!=x || macroDragLastY!=y) { \ macroDragLastX=x; \ macroDragLastY=y; \ @@ -1723,25 +1930,25 @@ void FurnaceGUI::showError(String what) { } \ if (macroDragMouseMoved) { \ if ((int)round(x-macroDragLineInitial.x)==0) { \ - t[x]=macroDragLineInitial.y; \ + t[x]=B30(t[x])^(int)(macroDragLineInitial.y); \ } else { \ if ((int)round(x-macroDragLineInitial.x)<0) { \ for (int i=0; i<=(int)round(macroDragLineInitial.x-x); i++) { \ int index=(int)round(x+i); \ if (index<0) continue; \ - t[index]=y+(macroDragLineInitial.y-y)*((float)i/(float)(macroDragLineInitial.x-x)); \ + t[index]=B30(t[index])^(int)(y+(macroDragLineInitial.y-y)*((float)i/(float)(macroDragLineInitial.x-x))); \ } \ } else { \ for (int i=0; i<=(int)round(x-macroDragLineInitial.x); i++) { \ int index=(int)round(i+macroDragLineInitial.x); \ if (index<0) continue; \ - t[index]=macroDragLineInitial.y+(y-macroDragLineInitial.y)*((float)i/(x-macroDragLineInitial.x)); \ + t[index]=B30(t[index])^(int)(macroDragLineInitial.y+(y-macroDragLineInitial.y)*((float)i/(x-macroDragLineInitial.x))); \ } \ } \ } \ } \ } else { \ - t[x]=y; \ + t[x]=B30(t[x])^(y); \ } \ } @@ -1826,20 +2033,6 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { } } -#define sysAddOption(x) \ - if (ImGui::MenuItem(getSystemName(x))) { \ - if (!e->addSystem(x)) { \ - showError("cannot add system! ("+e->getLastError()+")"); \ - } \ - updateWindowTitle(); \ - } - -#define sysChangeOption(x,y) \ - if (ImGui::MenuItem(getSystemName(y),NULL,e->song.system[x]==y)) { \ - e->changeSystem(x,y,preserveChanPos); \ - updateWindowTitle(); \ - } - #define checkExtension(x) \ String lowerCase=fileName; \ for (char& i: lowerCase) { \ @@ -1858,6 +2051,15 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { fileName+=fallback; \ } +#define checkExtensionTriple(x,y,z,fallback) \ + String lowerCase=fileName; \ + for (char& i: lowerCase) { \ + if (i>='A' && i<='Z') i+='a'-'A'; \ + } \ + if (lowerCase.size()<4 || (lowerCase.rfind(x)!=lowerCase.size()-4 && lowerCase.rfind(y)!=lowerCase.size()-4 && lowerCase.rfind(z)!=lowerCase.size()-4)) { \ + fileName+=fallback; \ + } + #define drawOpMask(m) \ ImGui::PushFont(patFont); \ ImGui::PushID("om_" #m); \ @@ -2027,7 +2229,7 @@ void FurnaceGUI::editOptions(bool topMenu) { snprintf(id,63,"%.2x##LatchFX",data); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[fxColors[data]]); } - + if (ImGui::Selectable(id,latchTarget==3,ImGuiSelectableFlags_DontClosePopups)) { latchTarget=3; latchNibble=false; @@ -2100,7 +2302,7 @@ void FurnaceGUI::editOptions(bool topMenu) { doTranspose(transposeAmount,opMaskTransposeValue); ImGui::CloseCurrentPopup(); } - + ImGui::Separator(); if (ImGui::MenuItem("interpolate",BIND_FOR(GUI_ACTION_PAT_INTERPOLATE))) doInterpolate(); if (ImGui::BeginMenu("change instrument...")) { @@ -2227,15 +2429,44 @@ void FurnaceGUI::toggleMobileUI(bool enable, bool force) { if (mobileUI!=enable || force) { if (!mobileUI && enable) { ImGui::SaveIniSettingsToDisk(finalLayoutPath); - } + } mobileUI=enable; if (mobileUI) { ImGui::GetIO().IniFilename=NULL; } else { - ImGui::GetIO().IniFilename=finalLayoutPath; + ImGui::GetIO().IniFilename=NULL; ImGui::LoadIniSettingsFromDisk(finalLayoutPath); } - } + } +} + +void FurnaceGUI::pushToggleColors(bool status) { + ImVec4 toggleColor=status?uiColors[GUI_COLOR_TOGGLE_ON]:uiColors[GUI_COLOR_TOGGLE_OFF]; + ImGui::PushStyleColor(ImGuiCol_Button,toggleColor); + if (settings.guiColorsBase) { + toggleColor.x*=0.8f; + toggleColor.y*=0.8f; + toggleColor.z*=0.8f; + } else { + toggleColor.x=CLAMP(toggleColor.x*1.3f,0.0f,1.0f); + toggleColor.y=CLAMP(toggleColor.y*1.3f,0.0f,1.0f); + toggleColor.z=CLAMP(toggleColor.z*1.3f,0.0f,1.0f); + } + ImGui::PushStyleColor(ImGuiCol_ButtonHovered,toggleColor); + if (settings.guiColorsBase) { + toggleColor.x*=0.8f; + toggleColor.y*=0.8f; + toggleColor.z*=0.8f; + } else { + toggleColor.x=CLAMP(toggleColor.x*1.5f,0.0f,1.0f); + toggleColor.y=CLAMP(toggleColor.y*1.5f,0.0f,1.0f); + toggleColor.z=CLAMP(toggleColor.z*1.5f,0.0f,1.0f); + } + ImGui::PushStyleColor(ImGuiCol_ButtonActive,toggleColor); +} + +void FurnaceGUI::popToggleColors() { + ImGui::PopStyleColor(3); } int _processEvent(void* instance, SDL_Event* event) { @@ -2393,12 +2624,15 @@ void FurnaceGUI::processPoint(SDL_Event& ev) { TouchPoint* point=NULL; FIND_POINT(point,ev.tfinger.fingerId); if (point!=NULL) { + float prevX=point->x; + float prevY=point->y; point->x=ev.tfinger.x*scrW*dpiScale; point->y=ev.tfinger.y*scrH*dpiScale; point->z=ev.tfinger.pressure; if (point->id==0) { ImGui::GetIO().AddMousePosEvent(point->x,point->y); + pointMotion(point->x,point->y,point->x-prevX,point->y-prevY); } } break; @@ -2419,6 +2653,7 @@ void FurnaceGUI::processPoint(SDL_Event& ev) { if (newPoint.id==0) { ImGui::GetIO().AddMousePosEvent(newPoint.x,newPoint.y); ImGui::GetIO().AddMouseButtonEvent(ImGuiMouseButton_Left,true); + pointDown(newPoint.x,newPoint.y,0); } break; } @@ -2426,13 +2661,15 @@ void FurnaceGUI::processPoint(SDL_Event& ev) { for (size_t i=0; irenderSamplesP(); + } else { + if (sampleSelStart>sampleSelEnd) { + sampleSelStart^=sampleSelEnd; + sampleSelEnd^=sampleSelStart; + sampleSelStart^=sampleSelEnd; + } + } + } + sampleDragActive=false; + if (selecting) { + if (!selectingFull) cursor=selEnd; + finishSelection(); + demandScrollX=true; + if (cursor.xCoarse==selStart.xCoarse && cursor.xFine==selStart.xFine && cursor.y==selStart.y && + cursor.xCoarse==selEnd.xCoarse && cursor.xFine==selEnd.xFine && cursor.y==selEnd.y) { + if (!settings.cursorMoveNoScroll) { + updateScroll(cursor.y); + } + } + } +} + +void FurnaceGUI::pointMotion(int x, int y, int xrel, int yrel) { + if (selecting) { + // detect whether we have to scroll + if (ypatWindowPos.y+patWindowSize.y-2.0f*dpiScale) { + addScroll(1); + } + } + if (macroDragActive || macroLoopDragActive || waveDragActive || sampleDragActive) { + int distance=fabs((double)xrel); + if (distance<1) distance=1; + float start=x-xrel; + float end=x; + float startY=y-yrel; + float endY=y; + for (int i=0; i<=distance; i++) { + float fraction=(float)i/(float)distance; + float x=start+(end-start)*fraction; + float y=startY+(endY-startY)*fraction; + processDrags(x,y); + } + } +} + +// how many pixels should be visible at least at x/y dir +#define OOB_PIXELS_SAFETY 25 + +bool FurnaceGUI::detectOutOfBoundsWindow() { + int count=SDL_GetNumVideoDisplays(); + if (count<1) { + logW("bounds check: error %s",SDL_GetError()); + return false; + } + + SDL_Rect rect; + for (int i=0; i=scrX); + bool ybound=((rect.y+OOB_PIXELS_SAFETY)<=(scrY+scrH)) && ((rect.y+rect.h-OOB_PIXELS_SAFETY)>=scrY); + logD("bounds check: display %d is at %dx%dx%dx%d: %s%s",i,rect.x+OOB_PIXELS_SAFETY,rect.y+OOB_PIXELS_SAFETY,rect.x+rect.w-OOB_PIXELS_SAFETY,rect.y+rect.h-OOB_PIXELS_SAFETY,xbound?"x":"",ybound?"y":""); + + if (xbound && ybound) { + return true; + } + } + + return false; +} + bool FurnaceGUI::loop() { - SDL_SetEventFilter(_processEvent,this); + bool doThreadedInput=!settings.noThreadedInput; + if (doThreadedInput) { + logD("key input: event filter"); + SDL_SetEventFilter(_processEvent,this); + } else { + logD("key input: main thread"); + } while (!quit) { SDL_Event ev; @@ -2454,10 +2804,12 @@ bool FurnaceGUI::loop() { if (settings.powerSave) SDL_WaitEventTimeout(NULL,500); } eventTimeBegin=SDL_GetPerformanceCounter(); + bool updateWindow=false; while (SDL_PollEvent(&ev)) { WAKE_UP; ImGui_ImplSDL2_ProcessEvent(&ev); processPoint(ev); + if (!doThreadedInput) processEvent(&ev); switch (ev.type) { case SDL_MOUSEMOTION: { int motionX=ev.motion.x; @@ -2470,80 +2822,14 @@ bool FurnaceGUI::loop() { motionXrel*=dpiScale; motionYrel*=dpiScale; #endif - if (selecting) { - // detect whether we have to scroll - if (motionYpatWindowPos.y+patWindowSize.y-2.0f*dpiScale) { - addScroll(1); - } - } - if (macroDragActive || macroLoopDragActive || waveDragActive || sampleDragActive) { - int distance=fabs((double)motionXrel); - if (distance<1) distance=1; - float start=motionX-motionXrel; - float end=motionX; - float startY=motionY-motionYrel; - float endY=motionY; - for (int i=0; i<=distance; i++) { - float fraction=(float)i/(float)distance; - float x=start+(end-start)*fraction; - float y=startY+(endY-startY)*fraction; - processDrags(x,y); - } - } + pointMotion(motionX,motionY,motionXrel,motionYrel); break; } case SDL_MOUSEBUTTONUP: - if (macroDragActive || macroLoopDragActive || waveDragActive || (sampleDragActive && sampleDragMode)) { - MARK_MODIFIED; - } - if (macroDragActive && macroDragLineMode && !macroDragMouseMoved) { - displayMacroMenu=true; - } - macroDragActive=false; - macroDragBitMode=false; - macroDragInitialValue=false; - macroDragInitialValueSet=false; - macroDragLastX=-1; - macroDragLastY=-1; - macroLoopDragActive=false; - waveDragActive=false; - if (sampleDragActive) { - logD("stopping sample drag"); - if (sampleDragMode) { - e->renderSamplesP(); - } else { - if (sampleSelStart>sampleSelEnd) { - sampleSelStart^=sampleSelEnd; - sampleSelEnd^=sampleSelStart; - sampleSelStart^=sampleSelEnd; - } - } - } - sampleDragActive=false; - if (selecting) { - if (!selectingFull) cursor=selEnd; - finishSelection(); - demandScrollX=true; - if (cursor.xCoarse==selStart.xCoarse && cursor.xFine==selStart.xFine && cursor.y==selStart.y && - cursor.xCoarse==selEnd.xCoarse && cursor.xFine==selEnd.xFine && cursor.y==selEnd.y) { - if (!settings.cursorMoveNoScroll) { - updateScroll(cursor.y); - } - } - } + pointUp(ev.button.x,ev.button.y,ev.button.button); break; case SDL_MOUSEBUTTONDOWN: - aboutOpen=false; - if (bindSetActive) { - bindSetActive=false; - bindSetPending=false; - actionKeys[bindSetTarget]=bindSetPrevValue; - bindSetTarget=0; - bindSetPrevValue=0; - } + pointDown(ev.button.x,ev.button.y,ev.button.button); break; case SDL_MOUSEWHEEL: wheelX+=ev.wheel.x; @@ -2559,6 +2845,22 @@ bool FurnaceGUI::loop() { scrW=ev.window.data1/dpiScale; scrH=ev.window.data2/dpiScale; #endif + portrait=(scrW instruments=e->instrumentFromFile(ev.drop.file); + DivWavetable* droppedWave=NULL; + DivSample* droppedSample=NULL;; if (!instruments.empty()) { if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); @@ -2582,10 +2886,12 @@ bool FurnaceGUI::loop() { } nextWindow=GUI_WINDOW_INS_LIST; MARK_MODIFIED; - } else if (e->addWaveFromFile(ev.drop.file,false)) { + } else if ((droppedWave=e->waveFromFile(ev.drop.file,false))!=NULL) { + e->addWavePtr(droppedWave); nextWindow=GUI_WINDOW_WAVE_LIST; MARK_MODIFIED; - } else if (e->addSampleFromFile(ev.drop.file)!=-1) { + } else if ((droppedSample=e->sampleFromFile(ev.drop.file))!=NULL) { + e->addSamplePtr(droppedSample); nextWindow=GUI_WINDOW_SAMPLE_LIST; MARK_MODIFIED; } else if (modified) { @@ -2610,6 +2916,16 @@ bool FurnaceGUI::loop() { } } + // update config x/y/w/h values based on scrMax state + if (updateWindow) { + if (!scrMax) { + scrConfX=scrX; + scrConfY=scrY; + scrConfW=scrW; + scrConfH=scrH; + } + } + wantCaptureKeyboard=ImGui::GetIO().WantTextInput; if (wantCaptureKeyboard!=oldWantCaptureKeyboard) { @@ -2627,7 +2943,7 @@ bool FurnaceGUI::loop() { if (ImGui::GetIO().MouseDown[0] || ImGui::GetIO().MouseDown[1] || ImGui::GetIO().MouseDown[2] || ImGui::GetIO().MouseDown[3] || ImGui::GetIO().MouseDown[4]) { WAKE_UP; } - + while (true) { midiLock.lock(); if (midiQueue.empty()) { @@ -2783,7 +3099,7 @@ bool FurnaceGUI::loop() { eventTimeEnd=SDL_GetPerformanceCounter(); layoutTimeBegin=SDL_GetPerformanceCounter(); - + ImGui_ImplSDLRenderer_NewFrame(); ImGui_ImplSDL2_NewFrame(sdlWin); ImGui::NewFrame(); @@ -2809,6 +3125,27 @@ bool FurnaceGUI::loop() { openFileDialog(GUI_FILE_OPEN); } } + if (ImGui::BeginMenu("open recent")) { + for (int i=0; i<(int)recentFile.size(); i++) { + String item=recentFile[i]; + if (ImGui::MenuItem(item.c_str())) { + if (modified) { + nextFile=item; + showWarning("Unsaved changes! Save changes before opening file?",GUI_WARN_OPEN_DROP); + } else { + recentFile.erase(recentFile.begin()+i); + i--; + if (load(item)>0) { + showError(fmt::sprintf("Error while loading file! (%s)",lastError)); + } + } + } + } + if (recentFile.empty()) { + ImGui::Text("nothing here yet"); + } + ImGui::EndMenu(); + } ImGui::Separator(); if (ImGui::MenuItem("save",BIND_FOR(GUI_ACTION_SAVE))) { if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { @@ -2822,7 +3159,10 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("save as...",BIND_FOR(GUI_ACTION_SAVE_AS))) { openFileDialog(GUI_FILE_SAVE); } - if (ImGui::MenuItem("save as .dmf (1.0/legacy)...",BIND_FOR(GUI_ACTION_SAVE_AS))) { + if (ImGui::MenuItem("save as .dmf (1.1.3+)...")) { + openFileDialog(GUI_FILE_SAVE_DMF); + } + if (ImGui::MenuItem("save as .dmf (1.0/legacy)...")) { openFileDialog(GUI_FILE_SAVE_DMF_LEGACY); } ImGui::Separator(); @@ -2830,7 +3170,7 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("one file")) { openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); } - if (ImGui::MenuItem("multiple files (one per system)")) { + if (ImGui::MenuItem("multiple files (one per chip)")) { openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS); } if (ImGui::MenuItem("multiple files (one per channel)")) { @@ -2855,7 +3195,23 @@ bool FurnaceGUI::loop() { ImGui::EndCombo(); } ImGui::Checkbox("loop",&vgmExportLoop); - ImGui::Text("systems to export:"); + ImGui::Checkbox("add pattern change hints",&vgmExportPatternHints); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "inserts data blocks on pattern changes.\n" + "useful if you are writing a playback routine.\n\n" + + "the format of a pattern change data block is:\n" + "67 66 FE ll ll ll ll 01 oo rr pp pp pp ...\n" + "- ll: length, a 32-bit little-endian number\n" + "- oo: order\n" + "- rr: initial row (a 0Dxx effect is able to select a different row)\n" + "- pp: pattern index (one per channel)\n\n" + + "pattern indexes are ordered as they appear in the song." + ); + } + ImGui::Text("chips to export:"); bool hasOneAtLeast=false; for (int i=0; isong.systemLen; i++) { int minVersion=e->minVGMVersion(e->song.system[i]); @@ -2864,17 +3220,17 @@ bool FurnaceGUI::loop() { ImGui::EndDisabled(); if (minVersion>vgmExportVersion) { if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { - ImGui::SetTooltip("this system is only available in VGM %d.%.2x and higher!",minVersion>>8,minVersion&0xff); + ImGui::SetTooltip("this chip is only available in VGM %d.%.2x and higher!",minVersion>>8,minVersion&0xff); } } else if (minVersion==0) { if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { - ImGui::SetTooltip("this system is not supported by the VGM format!"); + ImGui::SetTooltip("this chip is not supported by the VGM format!"); } } else { if (willExport[i]) hasOneAtLeast=true; } } - ImGui::Text("select the systems you wish to export,"); + ImGui::Text("select the chip you wish to export,"); ImGui::Text("but only up to %d of each type.",(vgmExportVersion>=0x151)?2:1); if (hasOneAtLeast) { if (ImGui::MenuItem("click to export")) { @@ -2885,15 +3241,32 @@ bool FurnaceGUI::loop() { } ImGui::EndMenu(); } - ImGui::Separator(); - if (ImGui::BeginMenu("add system...")) { - for (int j=0; availableSystems[j]; j++) { - if (!settings.hiddenSystems && (availableSystems[j]==DIV_SYSTEM_YMU759 || availableSystems[j]==DIV_SYSTEM_DUMMY || availableSystems[j]==DIV_SYSTEM_SOUND_UNIT)) continue; - sysAddOption((DivSystem)availableSystems[j]); + if (ImGui::BeginMenu("export command stream...")) { + ImGui::Text( + "this option exports a text or binary file which\n" + "contains a dump of the internal command stream\n" + "produced when playing the song.\n\n" + + "technical/development use only!" + ); + if (ImGui::Button("export")) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); } ImGui::EndMenu(); } - if (ImGui::BeginMenu("configure system...")) { + ImGui::Separator(); + if (ImGui::BeginMenu("add chip...")) { + DivSystem picked=systemPicker(); + if (picked!=DIV_SYSTEM_NULL) { + if (!e->addSystem(picked)) { + showError("cannot add chip! ("+e->getLastError()+")"); + } + ImGui::CloseCurrentPopup(); + updateWindowTitle(); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("configure chip...")) { for (int i=0; isong.systemLen; i++) { if (ImGui::TreeNode(fmt::sprintf("%d. %s##_SYSP%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { drawSysConf(i,e->song.system[i],e->song.systemFlags[i],true); @@ -2902,25 +3275,27 @@ bool FurnaceGUI::loop() { } ImGui::EndMenu(); } - if (ImGui::BeginMenu("change system...")) { + if (ImGui::BeginMenu("change chip...")) { ImGui::Checkbox("Preserve channel positions",&preserveChanPos); for (int i=0; isong.systemLen; i++) { if (ImGui::BeginMenu(fmt::sprintf("%d. %s##_SYSC%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { - for (int j=0; availableSystems[j]; j++) { - if (!settings.hiddenSystems && (availableSystems[j]==DIV_SYSTEM_YMU759 || availableSystems[j]==DIV_SYSTEM_DUMMY || availableSystems[j]==DIV_SYSTEM_SOUND_UNIT)) continue; - sysChangeOption(i,(DivSystem)availableSystems[j]); + DivSystem picked=systemPicker(); + if (picked!=DIV_SYSTEM_NULL) { + e->changeSystem(i,picked,preserveChanPos); + updateWindowTitle(); + ImGui::CloseCurrentPopup(); } ImGui::EndMenu(); } } ImGui::EndMenu(); } - if (ImGui::BeginMenu("remove system...")) { + if (ImGui::BeginMenu("remove chip...")) { ImGui::Checkbox("Preserve channel positions",&preserveChanPos); for (int i=0; isong.systemLen; i++) { if (ImGui::MenuItem(fmt::sprintf("%d. %s##_SYSR%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { if (!e->removeSystem(i,preserveChanPos)) { - showError("cannot remove system! ("+e->getLastError()+")"); + showError("cannot remove chip! ("+e->getLastError()+")"); } } } @@ -2971,6 +3346,11 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("reset layout")) { showWarning("Are you sure you want to reset the workspace layout?",GUI_WARN_RESET_LAYOUT); } +#ifdef IS_MOBILE + if (ImGui::MenuItem("switch to mobile view")) { + toggleMobileUI(!mobileUI); + } +#endif if (ImGui::MenuItem("settings...",BIND_FOR(GUI_ACTION_WINDOW_SETTINGS))) { syncSettings(); settingsOpen=true; @@ -2991,6 +3371,8 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("pattern",BIND_FOR(GUI_ACTION_WINDOW_PATTERN),patternOpen)) patternOpen=!patternOpen; if (ImGui::MenuItem("mixer",BIND_FOR(GUI_ACTION_WINDOW_MIXER),mixerOpen)) mixerOpen=!mixerOpen; if (ImGui::MenuItem("channels",BIND_FOR(GUI_ACTION_WINDOW_CHANNELS),channelsOpen)) channelsOpen=!channelsOpen; + if (ImGui::MenuItem("pattern manager",BIND_FOR(GUI_ACTION_WINDOW_PAT_MANAGER),patManagerOpen)) patManagerOpen=!patManagerOpen; + if (ImGui::MenuItem("chip manager",BIND_FOR(GUI_ACTION_WINDOW_SYS_MANAGER),sysManagerOpen)) sysManagerOpen=!sysManagerOpen; if (ImGui::MenuItem("compatibility flags",BIND_FOR(GUI_ACTION_WINDOW_COMPAT_FLAGS),compatFlagsOpen)) compatFlagsOpen=!compatFlagsOpen; if (ImGui::MenuItem("song comments",BIND_FOR(GUI_ACTION_WINDOW_NOTES),notesOpen)) notesOpen=!notesOpen; ImGui::Separator(); @@ -3006,7 +3388,8 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("register view",BIND_FOR(GUI_ACTION_WINDOW_REGISTER_VIEW),regViewOpen)) regViewOpen=!regViewOpen; if (ImGui::MenuItem("log viewer",BIND_FOR(GUI_ACTION_WINDOW_LOG),logOpen)) logOpen=!logOpen; if (ImGui::MenuItem("statistics",BIND_FOR(GUI_ACTION_WINDOW_STATS),statsOpen)) statsOpen=!statsOpen; - + if (spoilerOpen) if (ImGui::MenuItem("spoiler",NULL,spoilerOpen)) spoilerOpen=!spoilerOpen; + ImGui::EndMenu(); } if (ImGui::BeginMenu("help")) { @@ -3090,15 +3473,48 @@ bool FurnaceGUI::loop() { if (mobileUI) { globalWinFlags=ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoBringToFrontOnFocus; + //globalWinFlags=ImGuiWindowFlags_NoTitleBar; + // scene handling goes here! + pianoOpen=true; drawMobileControls(); - drawPattern(); - drawPiano(); + switch (mobScene) { + case GUI_SCENE_ORDERS: + ordersOpen=true; + curWindow=GUI_WINDOW_ORDERS; + drawOrders(); + break; + case GUI_SCENE_INSTRUMENT: + insEditOpen=true; + curWindow=GUI_WINDOW_INS_EDIT; + drawInsEdit(); + drawPiano(); + break; + case GUI_SCENE_WAVETABLE: + waveEditOpen=true; + curWindow=GUI_WINDOW_WAVE_EDIT; + drawWaveEdit(); + drawPiano(); + break; + case GUI_SCENE_SAMPLE: + sampleEditOpen=true; + curWindow=GUI_WINDOW_SAMPLE_EDIT; + drawSampleEdit(); + drawPiano(); + break; + default: + patternOpen=true; + curWindow=GUI_WINDOW_PATTERN; + drawPattern(); + drawPiano(); + break; + } } else { globalWinFlags=0; ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoWindowMenuButton|ImGuiDockNodeFlags_NoMove|ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0); drawSubSongs(); drawFindReplace(); + drawSpoiler(); drawPattern(); drawEditControls(); drawSongInfo(); @@ -3123,6 +3539,8 @@ bool FurnaceGUI::loop() { drawPiano(); drawNotes(); drawChannels(); + drawPatManager(); + drawSysManager(); drawRegView(); drawLog(); drawEffectList(); @@ -3132,12 +3550,20 @@ bool FurnaceGUI::loop() { if (firstFrame) { firstFrame=false; +#ifdef IS_MOBILE + SDL_GetWindowSize(sdlWin,&scrW,&scrH); + scrW/=dpiScale; + scrH/=dpiScale; + portrait=(scrWisOpen() && settings.sysFileDialog) { ImGui::OpenPopup("System File Dialog Pending"); } @@ -3150,6 +3576,7 @@ bool FurnaceGUI::loop() { dl->AddRectFilled(ImVec2(0.0f,0.0f),ImVec2(scrW*dpiScale,scrH*dpiScale),ImGui::ColorConvertFloat4ToU32(uiColors[GUI_COLOR_MODAL_BACKDROP])); ImGui::EndPopup(); } +#endif if (fileDialog->render(ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale))) { bool openOpen=false; @@ -3170,19 +3597,27 @@ bool FurnaceGUI::loop() { switch (curFileDialog) { case GUI_FILE_OPEN: case GUI_FILE_SAVE: + case GUI_FILE_SAVE_DMF: case GUI_FILE_SAVE_DMF_LEGACY: workingDirSong=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_INS_OPEN: case GUI_FILE_INS_OPEN_REPLACE: case GUI_FILE_INS_SAVE: + case GUI_FILE_INS_SAVE_DMP: workingDirIns=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_WAVE_OPEN: + case GUI_FILE_WAVE_OPEN_REPLACE: case GUI_FILE_WAVE_SAVE: + case GUI_FILE_WAVE_SAVE_DMW: + case GUI_FILE_WAVE_SAVE_RAW: workingDirWave=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_SAMPLE_OPEN: + case GUI_FILE_SAMPLE_OPEN_RAW: + case GUI_FILE_SAMPLE_OPEN_REPLACE: + case GUI_FILE_SAMPLE_OPEN_REPLACE_RAW: case GUI_FILE_SAMPLE_SAVE: workingDirSample=fileDialog->getPath()+DIR_SEPARATOR_STR; break; @@ -3192,9 +3627,12 @@ bool FurnaceGUI::loop() { workingDirAudioExport=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_EXPORT_VGM: - case GUI_FILE_EXPORT_ROM: workingDirVGMExport=fileDialog->getPath()+DIR_SEPARATOR_STR; break; + case GUI_FILE_EXPORT_ROM: + case GUI_FILE_EXPORT_CMDSTREAM: + workingDirROMExport=fileDialog->getPath()+DIR_SEPARATOR_STR; + break; case GUI_FILE_LOAD_MAIN_FONT: case GUI_FILE_LOAD_PAT_FONT: workingDirFont=fileDialog->getPath()+DIR_SEPARATOR_STR; @@ -3216,14 +3654,31 @@ bool FurnaceGUI::loop() { case GUI_FILE_MU5_ROM_OPEN: workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR; break; + case GUI_FILE_TEST_OPEN: + case GUI_FILE_TEST_OPEN_MULTI: + case GUI_FILE_TEST_SAVE: + workingDirTest=fileDialog->getPath()+DIR_SEPARATOR_STR; + break; + } + if (fileDialog->isError()) { +#if defined(_WIN32) || defined(__APPLE__) + showError("there was an error in the file dialog! you may want to report this issue to:\nhttps://github.com/tildearrow/furnace/issues\ncheck the Log Viewer (window > log viewer) for more information.\n\nfor now please disable the system file picker in Settings > General."); +#else + showError("Zenity/KDialog not available!\nplease install one of these, or disable the system file picker in Settings > General."); +#endif } if (fileDialog->accepted()) { - fileName=fileDialog->getFileName(); + if (fileDialog->getFileName().empty()) { + fileName=""; + } else { + fileName=fileDialog->getFileName()[0]; + } if (fileName!="") { if (curFileDialog==GUI_FILE_SAVE) { - // we can't tell whether the user chose .dmf or .fur in the system file picker - const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace song")?".fur":".dmf"; - checkExtensionDual(".fur",".dmf",fallbackExt); + checkExtension(".fur"); + } + if (curFileDialog==GUI_FILE_SAVE_DMF) { + checkExtension(".dmf"); } if (curFileDialog==GUI_FILE_SAVE_DMF_LEGACY) { checkExtension(".dmf"); @@ -3237,12 +3692,26 @@ bool FurnaceGUI::loop() { if (curFileDialog==GUI_FILE_INS_SAVE) { checkExtension(".fui"); } + if (curFileDialog==GUI_FILE_INS_SAVE_DMP) { + checkExtension(".dmp"); + } if (curFileDialog==GUI_FILE_WAVE_SAVE) { checkExtension(".fuw"); } + if (curFileDialog==GUI_FILE_WAVE_SAVE_DMW) { + checkExtension(".dmw"); + } + if (curFileDialog==GUI_FILE_WAVE_SAVE_RAW) { + checkExtension(".raw"); + } if (curFileDialog==GUI_FILE_EXPORT_VGM) { checkExtension(".vgm"); } + if (curFileDialog==GUI_FILE_EXPORT_CMDSTREAM) { + // we can't tell whether the user chose .txt or .bin in the system file picker + const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="text file")?".txt":".bin"; + checkExtensionDual(".txt",".bin",fallbackExt); + } if (curFileDialog==GUI_FILE_EXPORT_COLORS) { checkExtension(".cfgc"); } @@ -3261,21 +3730,10 @@ bool FurnaceGUI::loop() { break; case GUI_FILE_SAVE: { logD("saving: %s",copyOfName.c_str()); - String lowerCase=fileName; - for (char& i: lowerCase) { - if (i>='A' && i<='Z') i+='a'-'A'; - } bool saveWasSuccessful=true; - if ((lowerCase.size()<4 || lowerCase.rfind(".dmf")!=lowerCase.size()-4)) { - if (save(copyOfName,0)>0) { - showError(fmt::sprintf("Error while saving file! (%s)",lastError)); - saveWasSuccessful=false; - } - } else { - if (save(copyOfName,26)>0) { - showError(fmt::sprintf("Error while saving file! (%s)",lastError)); - saveWasSuccessful=false; - } + if (save(copyOfName,0)>0) { + showError(fmt::sprintf("Error while saving file! (%s)",lastError)); + saveWasSuccessful=false; } if (saveWasSuccessful && postWarnAction!=GUI_WARN_GENERIC) { switch (postWarnAction) { @@ -3308,6 +3766,12 @@ bool FurnaceGUI::loop() { } break; } + case GUI_FILE_SAVE_DMF: + logD("saving: %s",copyOfName.c_str()); + if (save(copyOfName,26)>0) { + showError(fmt::sprintf("Error while saving file! (%s)",lastError)); + } + break; case GUI_FILE_SAVE_DMF_LEGACY: logD("saving: %s",copyOfName.c_str()); if (save(copyOfName,24)>0) { @@ -3319,18 +3783,67 @@ bool FurnaceGUI::loop() { e->song.ins[curIns]->save(copyOfName.c_str()); } break; + case GUI_FILE_INS_SAVE_DMP: + if (curIns>=0 && curIns<(int)e->song.ins.size()) { + if (!e->song.ins[curIns]->saveDMP(copyOfName.c_str())) { + showError("error while saving instrument! make sure your instrument is compatible."); + } + } + break; case GUI_FILE_WAVE_SAVE: if (curWave>=0 && curWave<(int)e->song.wave.size()) { e->song.wave[curWave]->save(copyOfName.c_str()); } break; - case GUI_FILE_SAMPLE_OPEN: - if (e->addSampleFromFile(copyOfName.c_str())==-1) { + case GUI_FILE_WAVE_SAVE_DMW: + if (curWave>=0 && curWave<(int)e->song.wave.size()) { + e->song.wave[curWave]->saveDMW(copyOfName.c_str()); + } + break; + case GUI_FILE_WAVE_SAVE_RAW: + if (curWave>=0 && curWave<(int)e->song.wave.size()) { + e->song.wave[curWave]->saveRaw(copyOfName.c_str()); + } + break; + case GUI_FILE_SAMPLE_OPEN: { + DivSample* s=e->sampleFromFile(copyOfName.c_str()); + if (s==NULL) { showError(e->getLastError()); } else { - MARK_MODIFIED; + if (e->addSamplePtr(s)==-1) { + showError(e->getLastError()); + } else { + MARK_MODIFIED; + } } break; + } + case GUI_FILE_SAMPLE_OPEN_REPLACE: { + DivSample* s=e->sampleFromFile(copyOfName.c_str()); + if (s==NULL) { + showError(e->getLastError()); + } else { + if (curSample>=0 && curSample<(int)e->song.sample.size()) { + e->lockEngine([this,s]() { + // if it crashes here please tell me... + DivSample* oldSample=e->song.sample[curSample]; + e->song.sample[curSample]=s; + delete oldSample; + e->renderSamples(); + MARK_MODIFIED; + }); + } else { + showError("...but you haven't selected a sample!"); + delete s; + } + } + break; + } + case GUI_FILE_SAMPLE_OPEN_RAW: + case GUI_FILE_SAMPLE_OPEN_REPLACE_RAW: + pendingRawSample=copyOfName; + displayPendingRawSample=true; + break; case GUI_FILE_SAMPLE_SAVE: if (curSample>=0 && curSample<(int)e->song.sample.size()) { e->song.sample[curSample]->save(copyOfName.c_str()); @@ -3394,15 +3907,39 @@ bool FurnaceGUI::loop() { } break; } - case GUI_FILE_WAVE_OPEN: - if (!e->addWaveFromFile(copyOfName.c_str())) { + case GUI_FILE_WAVE_OPEN: { + DivWavetable* wave=e->waveFromFile(copyOfName.c_str()); + if (wave==NULL) { showError("cannot load wavetable! ("+e->getLastError()+")"); } else { - MARK_MODIFIED; + if (e->addWavePtr(wave)==-1) { + showError("cannot load wavetable! ("+e->getLastError()+")"); + } else { + MARK_MODIFIED; + RESET_WAVE_MACRO_ZOOM; + } } break; + } + case GUI_FILE_WAVE_OPEN_REPLACE: { + DivWavetable* wave=e->waveFromFile(copyOfName.c_str()); + if (wave==NULL) { + showError("cannot load wavetable! ("+e->getLastError()+")"); + } else { + if (curWave>=0 && curWave<(int)e->song.wave.size()) { + e->lockEngine([this,wave]() { + *e->song.wave[curWave]=*wave; + MARK_MODIFIED; + }); + } else { + showError("...but you haven't selected a wavetable!"); + } + delete wave; + } + break; + } case GUI_FILE_EXPORT_VGM: { - SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion); + SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion,vgmExportPatternHints); if (w!=NULL) { FILE* f=ps_fopen(copyOfName.c_str(),"wb"); if (f!=NULL) { @@ -3424,6 +3961,35 @@ bool FurnaceGUI::loop() { case GUI_FILE_EXPORT_ROM: showError("Coming soon!"); break; + case GUI_FILE_EXPORT_CMDSTREAM: { + String lowerCase=fileName; + for (char& i: lowerCase) { + if (i>='A' && i<='Z') i+='a'-'A'; + } + bool isBinary=true; + if ((lowerCase.size()<4 || lowerCase.rfind(".bin")!=lowerCase.size()-4)) { + isBinary=false; + } + + SafeWriter* w=e->saveCommand(isBinary); + if (w!=NULL) { + FILE* f=ps_fopen(copyOfName.c_str(),"wb"); + if (f!=NULL) { + fwrite(w->getFinalBuf(),1,w->size(),f); + fclose(f); + } else { + showError("could not open file!"); + } + w->finish(); + delete w; + if (!e->getWarnings().empty()) { + showWarning(e->getWarnings(),GUI_WARN_GENERIC); + } + } else { + showError(fmt::sprintf("could not write command stream! (%s)",e->getLastError())); + } + break; + } case GUI_FILE_LOAD_MAIN_FONT: settings.mainFontPath=copyOfName; break; @@ -3457,6 +4023,20 @@ bool FurnaceGUI::loop() { case GUI_FILE_MU5_ROM_OPEN: settings.mu5Path=copyOfName; break; + case GUI_FILE_TEST_OPEN: + showWarning(fmt::sprintf("You opened: %s",copyOfName),GUI_WARN_GENERIC); + break; + case GUI_FILE_TEST_OPEN_MULTI: { + String msg="You opened:"; + for (String i: fileDialog->getFileName()) { + msg+=fmt::sprintf("\n- %s",i); + } + showWarning(msg,GUI_WARN_GENERIC); + break; + } + case GUI_FILE_TEST_SAVE: + showWarning(fmt::sprintf("You saved: %s",copyOfName),GUI_WARN_GENERIC); + break; } curFileDialog=GUI_FILE_OPEN; } @@ -3484,6 +4064,11 @@ bool FurnaceGUI::loop() { ImGui::OpenPopup("Select Instrument"); } + if (displayPendingRawSample) { + displayPendingRawSample=false; + ImGui::OpenPopup("Import Raw Sample"); + } + if (displayExporting) { displayExporting=false; ImGui::OpenPopup("Rendering..."); @@ -3825,6 +4410,16 @@ bool FurnaceGUI::loop() { ImGui::CloseCurrentPopup(); } break; + case GUI_WARN_SYSTEM_DEL: + if (ImGui::Button("Yes")) { + e->removeSystem(sysToDelete,preserveChanPos); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("No")) { + ImGui::CloseCurrentPopup(); + } + break; case GUI_WARN_GENERIC: if (ImGui::Button("OK")) { ImGui::CloseCurrentPopup(); @@ -3914,6 +4509,53 @@ bool FurnaceGUI::loop() { ImGui::EndPopup(); } + if (ImGui::BeginPopupModal("Import Raw Sample",NULL,ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("Data type:"); + for (int i=0; isampleFromFileRaw(pendingRawSample.c_str(),(DivSampleDepth)pendingRawSampleDepth,pendingRawSampleChannels,pendingRawSampleBigEndian,pendingRawSampleUnsigned); + if (s==NULL) { + showError(e->getLastError()); + } else { + if (e->addSamplePtr(s)==-1) { + showError(e->getLastError()); + } else { + MARK_MODIFIED; + } + } + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + layoutTimeEnd=SDL_GetPerformanceCounter(); // backup trigger @@ -3928,7 +4570,7 @@ bool FurnaceGUI::loop() { } logD("saving backup..."); SafeWriter* w=e->saveFur(true); - + if (w!=NULL) { FILE* outFile=ps_fopen(backupPath.c_str(),"wb"); if (outFile!=NULL) { @@ -4003,10 +4645,12 @@ bool FurnaceGUI::init() { workingDirSample=e->getConfString("lastDirSample",workingDir); workingDirAudioExport=e->getConfString("lastDirAudioExport",workingDir); workingDirVGMExport=e->getConfString("lastDirVGMExport",workingDir); + workingDirROMExport=e->getConfString("lastDirROMExport",workingDir); workingDirFont=e->getConfString("lastDirFont",workingDir); workingDirColors=e->getConfString("lastDirColors",workingDir); workingDirKeybinds=e->getConfString("lastDirKeybinds",workingDir); workingDirLayout=e->getConfString("lastDirLayout",workingDir); + workingDirTest=e->getConfString("lastDirTest",workingDir); editControlsOpen=e->getConfBool("editControlsOpen",true); ordersOpen=e->getConfBool("ordersOpen",true); @@ -4028,14 +4672,20 @@ bool FurnaceGUI::init() { pianoOpen=e->getConfBool("pianoOpen",false); notesOpen=e->getConfBool("notesOpen",false); channelsOpen=e->getConfBool("channelsOpen",false); + patManagerOpen=e->getConfBool("patManagerOpen",false); + sysManagerOpen=e->getConfBool("sysManagerOpen",false); regViewOpen=e->getConfBool("regViewOpen",false); logOpen=e->getConfBool("logOpen",false); effectListOpen=e->getConfBool("effectListOpen",false); subSongsOpen=e->getConfBool("subSongsOpen",true); findOpen=e->getConfBool("findOpen",false); + spoilerOpen=e->getConfBool("spoilerOpen",false); tempoView=e->getConfBool("tempoView",true); waveHex=e->getConfBool("waveHex",false); + waveSigned=e->getConfBool("waveSigned",false); + waveGenVisible=e->getConfBool("waveGenVisible",false); + waveEditStyle=e->getConfInt("waveEditStyle",0); lockLayout=e->getConfBool("lockLayout",false); #ifdef IS_MOBILE fullScreen=true; @@ -4065,8 +4715,29 @@ bool FurnaceGUI::init() { pianoView=e->getConfInt("pianoView",pianoView); pianoInputPadMode=e->getConfInt("pianoInputPadMode",pianoInputPadMode); + chanOscCols=e->getConfInt("chanOscCols",3); + chanOscColorX=e->getConfInt("chanOscColorX",GUI_OSCREF_CENTER); + chanOscColorY=e->getConfInt("chanOscColorY",GUI_OSCREF_CENTER); + chanOscWindowSize=e->getConfFloat("chanOscWindowSize",20.0f); + chanOscWaveCorr=e->getConfBool("chanOscWaveCorr",true); + chanOscOptions=e->getConfBool("chanOscOptions",false); + chanOscColor.x=e->getConfFloat("chanOscColorR",1.0f); + chanOscColor.y=e->getConfFloat("chanOscColorG",1.0f); + chanOscColor.z=e->getConfFloat("chanOscColorB",1.0f); + chanOscColor.w=e->getConfFloat("chanOscColorA",1.0f); + chanOscUseGrad=e->getConfBool("chanOscUseGrad",false); + chanOscGrad.fromString(e->getConfString("chanOscGrad","")); + chanOscGrad.render(); + syncSettings(); + for (int i=0; igetConfString(fmt::sprintf("recentFile%d",i),""); + if (!r.empty()) { + recentFile.push_back(r); + } + } + if (settings.dpiScale>=0.5f) { dpiScale=settings.dpiScale; } @@ -4080,10 +4751,22 @@ bool FurnaceGUI::init() { SDL_Surface* icon=SDL_CreateRGBSurfaceFrom(furIcon,256,256,32,256*4,0xff,0xff00,0xff0000,0xff000000); #endif - scrW=e->getConfInt("lastWindowWidth",1280); - scrH=e->getConfInt("lastWindowHeight",800); +#ifdef IS_MOBILE + scrW=960; + scrH=540; + scrX=0; + scrY=0; +#else + scrW=scrConfW=e->getConfInt("lastWindowWidth",1280); + scrH=scrConfH=e->getConfInt("lastWindowHeight",800); + scrX=scrConfX=e->getConfInt("lastWindowX",SDL_WINDOWPOS_CENTERED); + scrY=scrConfY=e->getConfInt("lastWindowY",SDL_WINDOWPOS_CENTERED); + scrMax=e->getConfBool("lastWindowMax",false); +#endif + portrait=(scrWdisplaySize.w/dpiScale) scrW=(displaySize.w/dpiScale)-32; if (scrH>displaySize.h/dpiScale) scrH=(displaySize.h/dpiScale)-32; + portrait=(scrWsetConf("lastDirSample",workingDirSample); e->setConf("lastDirAudioExport",workingDirAudioExport); e->setConf("lastDirVGMExport",workingDirVGMExport); + e->setConf("lastDirROMExport",workingDirROMExport); e->setConf("lastDirFont",workingDirFont); e->setConf("lastDirColors",workingDirColors); e->setConf("lastDirKeybinds",workingDirKeybinds); e->setConf("lastDirLayout",workingDirLayout); + e->setConf("lastDirTest",workingDirTest); // commit last open windows e->setConf("editControlsOpen",editControlsOpen); @@ -4250,18 +4951,27 @@ bool FurnaceGUI::finish() { e->setConf("pianoOpen",pianoOpen); e->setConf("notesOpen",notesOpen); e->setConf("channelsOpen",channelsOpen); + e->setConf("patManagerOpen",patManagerOpen); + e->setConf("sysManagerOpen",sysManagerOpen); e->setConf("regViewOpen",regViewOpen); e->setConf("logOpen",logOpen); e->setConf("effectListOpen",effectListOpen); e->setConf("subSongsOpen",subSongsOpen); e->setConf("findOpen",findOpen); + e->setConf("spoilerOpen",spoilerOpen); // commit last window size - e->setConf("lastWindowWidth",scrW); - e->setConf("lastWindowHeight",scrH); + e->setConf("lastWindowWidth",scrConfW); + e->setConf("lastWindowHeight",scrConfH); + e->setConf("lastWindowX",settings.saveWindowPos?scrConfX:(int)SDL_WINDOWPOS_CENTERED); + e->setConf("lastWindowY",settings.saveWindowPos?scrConfY:(int)SDL_WINDOWPOS_CENTERED); + e->setConf("lastWindowMax",scrMax); e->setConf("tempoView",tempoView); e->setConf("waveHex",waveHex); + e->setConf("waveSigned",waveSigned); + e->setConf("waveGenVisible",waveGenVisible); + e->setConf("waveEditStyle",waveEditStyle); e->setConf("lockLayout",lockLayout); e->setConf("fullScreen",fullScreen); e->setConf("mobileUI",mobileUI); @@ -4287,6 +4997,30 @@ bool FurnaceGUI::finish() { e->setConf("pianoView",pianoView); e->setConf("pianoInputPadMode",pianoInputPadMode); + // commit per-chan osc state + e->setConf("chanOscCols",chanOscCols); + e->setConf("chanOscColorX",chanOscColorX); + e->setConf("chanOscColorY",chanOscColorY); + e->setConf("chanOscWindowSize",chanOscWindowSize); + e->setConf("chanOscWaveCorr",chanOscWaveCorr); + e->setConf("chanOscOptions",chanOscOptions); + e->setConf("chanOscColorR",chanOscColor.x); + e->setConf("chanOscColorG",chanOscColor.y); + e->setConf("chanOscColorB",chanOscColor.z); + e->setConf("chanOscColorA",chanOscColor.w); + e->setConf("chanOscUseGrad",chanOscUseGrad); + e->setConf("chanOscGrad",chanOscGrad.toString()); + + // commit recent files + for (int i=0; i<30; i++) { + String key=fmt::sprintf("recentFile%d",i); + if (i>=settings.maxRecentFile || i>=(int)recentFile.size()) { + e->setConf(key,""); + } else { + e->setConf(key,recentFile[i]); + } + } + for (int i=0; i #include #include +#include #include #include #include @@ -46,7 +47,11 @@ #define MARK_MODIFIED modified=true; #define WAKE_UP drawHalt=16; -#define TOGGLE_COLOR(x) ((x)?uiColors[GUI_COLOR_TOGGLE_ON]:uiColors[GUI_COLOR_TOGGLE_OFF]) +#define RESET_WAVE_MACRO_ZOOM \ + for (DivInstrument* _wi: e->song.ins) { \ + _wi->std.waveMacro.vZoom=-1; \ + _wi->std.waveMacro.vScroll=-1; \ + } #define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str() @@ -156,6 +161,8 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_OPL_DRUMS, GUI_COLOR_INSTR_UNKNOWN, + GUI_COLOR_CHANNEL_BG, + GUI_COLOR_CHANNEL_FG, GUI_COLOR_CHANNEL_FM, GUI_COLOR_CHANNEL_PULSE, GUI_COLOR_CHANNEL_NOISE, @@ -199,6 +206,13 @@ enum FurnaceGUIColors { GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_PAT_MANAGER_NULL, + GUI_COLOR_PAT_MANAGER_USED, + GUI_COLOR_PAT_MANAGER_OVERUSED, + GUI_COLOR_PAT_MANAGER_EXTREMELY_OVERUSED, + GUI_COLOR_PAT_MANAGER_COMBO_BREAKER, + GUI_COLOR_PAT_MANAGER_UNUSED, + GUI_COLOR_PIANO_BACKGROUND, GUI_COLOR_PIANO_KEY_BOTTOM, GUI_COLOR_PIANO_KEY_TOP, @@ -241,29 +255,53 @@ enum FurnaceGUIWindows { GUI_WINDOW_PIANO, GUI_WINDOW_NOTES, GUI_WINDOW_CHANNELS, + GUI_WINDOW_PAT_MANAGER, + GUI_WINDOW_SYS_MANAGER, GUI_WINDOW_REGISTER_VIEW, GUI_WINDOW_LOG, GUI_WINDOW_EFFECT_LIST, GUI_WINDOW_CHAN_OSC, GUI_WINDOW_SUBSONGS, - GUI_WINDOW_FIND + GUI_WINDOW_FIND, + GUI_WINDOW_SPOILER +}; + +enum FurnaceGUIMobileScenes { + GUI_SCENE_PATTERN, + GUI_SCENE_ORDERS, + GUI_SCENE_INSTRUMENT, + GUI_SCENE_WAVETABLE, + GUI_SCENE_SAMPLE, + GUI_SCENE_SONG, + GUI_SCENE_CHANNELS, + GUI_SCENE_CHIPS, + GUI_SCENE_OTHER, }; enum FurnaceGUIFileDialogs { GUI_FILE_OPEN, GUI_FILE_SAVE, + GUI_FILE_SAVE_DMF, GUI_FILE_SAVE_DMF_LEGACY, GUI_FILE_INS_OPEN, GUI_FILE_INS_OPEN_REPLACE, GUI_FILE_INS_SAVE, + GUI_FILE_INS_SAVE_DMP, GUI_FILE_WAVE_OPEN, + GUI_FILE_WAVE_OPEN_REPLACE, GUI_FILE_WAVE_SAVE, + GUI_FILE_WAVE_SAVE_DMW, + GUI_FILE_WAVE_SAVE_RAW, GUI_FILE_SAMPLE_OPEN, + GUI_FILE_SAMPLE_OPEN_RAW, + GUI_FILE_SAMPLE_OPEN_REPLACE, + GUI_FILE_SAMPLE_OPEN_REPLACE_RAW, GUI_FILE_SAMPLE_SAVE, GUI_FILE_EXPORT_AUDIO_ONE, GUI_FILE_EXPORT_AUDIO_PER_SYS, GUI_FILE_EXPORT_AUDIO_PER_CHANNEL, GUI_FILE_EXPORT_VGM, + GUI_FILE_EXPORT_CMDSTREAM, GUI_FILE_EXPORT_ROM, GUI_FILE_LOAD_MAIN_FONT, GUI_FILE_LOAD_PAT_FONT, @@ -275,7 +313,11 @@ enum FurnaceGUIFileDialogs { GUI_FILE_EXPORT_LAYOUT, GUI_FILE_YRW801_ROM_OPEN, GUI_FILE_TG100_ROM_OPEN, - GUI_FILE_MU5_ROM_OPEN + GUI_FILE_MU5_ROM_OPEN, + + GUI_FILE_TEST_OPEN, + GUI_FILE_TEST_OPEN_MULTI, + GUI_FILE_TEST_SAVE }; enum FurnaceGUIWarnings { @@ -290,6 +332,7 @@ enum FurnaceGUIWarnings { GUI_WARN_CLOSE_SETTINGS, GUI_WARN_CLEAR, GUI_WARN_SUBSONG_DEL, + GUI_WARN_SYSTEM_DEL, GUI_WARN_GENERIC }; @@ -350,6 +393,8 @@ enum FurnaceGUIActions { GUI_ACTION_WINDOW_PIANO, GUI_ACTION_WINDOW_NOTES, GUI_ACTION_WINDOW_CHANNELS, + GUI_ACTION_WINDOW_PAT_MANAGER, + GUI_ACTION_WINDOW_SYS_MANAGER, GUI_ACTION_WINDOW_REGISTER_VIEW, GUI_ACTION_WINDOW_LOG, GUI_ACTION_WINDOW_EFFECT_LIST, @@ -432,6 +477,7 @@ enum FurnaceGUIActions { GUI_ACTION_INS_LIST_OPEN, GUI_ACTION_INS_LIST_OPEN_REPLACE, GUI_ACTION_INS_LIST_SAVE, + GUI_ACTION_INS_LIST_SAVE_DMP, GUI_ACTION_INS_LIST_MOVE_UP, GUI_ACTION_INS_LIST_MOVE_DOWN, GUI_ACTION_INS_LIST_DELETE, @@ -444,7 +490,10 @@ enum FurnaceGUIActions { GUI_ACTION_WAVE_LIST_ADD, GUI_ACTION_WAVE_LIST_DUPLICATE, GUI_ACTION_WAVE_LIST_OPEN, + GUI_ACTION_WAVE_LIST_OPEN_REPLACE, GUI_ACTION_WAVE_LIST_SAVE, + GUI_ACTION_WAVE_LIST_SAVE_DMW, + GUI_ACTION_WAVE_LIST_SAVE_RAW, GUI_ACTION_WAVE_LIST_MOVE_UP, GUI_ACTION_WAVE_LIST_MOVE_DOWN, GUI_ACTION_WAVE_LIST_DELETE, @@ -457,6 +506,9 @@ enum FurnaceGUIActions { GUI_ACTION_SAMPLE_LIST_ADD, GUI_ACTION_SAMPLE_LIST_DUPLICATE, GUI_ACTION_SAMPLE_LIST_OPEN, + GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE, + GUI_ACTION_SAMPLE_LIST_OPEN_RAW, + GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE_RAW, GUI_ACTION_SAMPLE_LIST_SAVE, GUI_ACTION_SAMPLE_LIST_MOVE_UP, GUI_ACTION_SAMPLE_LIST_MOVE_DOWN, @@ -498,6 +550,7 @@ enum FurnaceGUIActions { GUI_ACTION_SAMPLE_ZOOM_AUTO, GUI_ACTION_SAMPLE_MAKE_INS, GUI_ACTION_SAMPLE_SET_LOOP, + GUI_ACTION_SAMPLE_CREATE_WAVE, GUI_ACTION_SAMPLE_MAX, GUI_ACTION_ORDERS_MIN, @@ -523,6 +576,21 @@ enum FurnaceGUIActions { GUI_ACTION_MAX }; +enum FurnaceGUIChanOscRef { + GUI_OSCREF_NONE=0, + GUI_OSCREF_CENTER, + GUI_OSCREF_FULL, + + GUI_OSCREF_FREQUENCY, + GUI_OSCREF_VOLUME, + GUI_OSCREF_CHANNEL, + GUI_OSCREF_BRIGHT, + + GUI_OSCREF_NOTE_TRIGGER, + + GUI_OSCREF_MAX +}; + enum PasteMode { GUI_PASTE_MODE_NORMAL=0, GUI_PASTE_MODE_MIX_FG, @@ -566,7 +634,9 @@ enum ActionType { GUI_UNDO_PATTERN_INVERT_VAL, GUI_UNDO_PATTERN_FLIP, GUI_UNDO_PATTERN_COLLAPSE, - GUI_UNDO_PATTERN_EXPAND + GUI_UNDO_PATTERN_EXPAND, + GUI_UNDO_PATTERN_DRAG, + GUI_UNDO_REPLACE }; struct UndoPatternData { @@ -754,6 +824,49 @@ struct TouchPoint { z(pressure) {} }; +struct Gradient2DPoint { + ImVec4 color; + float x, y, prevX, prevY; + float spread, distance; + bool selected, grab; + Gradient2DPoint(float xPos, float yPos): + color(1,1,1,1), + x(xPos), + y(yPos), + prevX(0.0f), + prevY(0.0f), + spread(0.0f), + distance(0.5f), + selected(false), + grab(false) {} + Gradient2DPoint(): + color(1,1,1,1), + x(0.0f), + y(0.0f), + spread(0.0f), + distance(0.5f), + selected(false), + grab(false) {} +}; + +struct Gradient2D { + ImVec4 bgColor; + std::vector points; + std::unique_ptr grad; + size_t width, height; + + String toString(); + bool fromString(String val); + void render(); + ImU32 get(float x, float y); + Gradient2D(size_t w, size_t h): + bgColor(0.0f,0.0f,0.0f,0.0f), + width(w), + height(h) { + grad=std::make_unique(width*height); + } +}; + struct FurnaceGUISysDef { const char* name; std::vector definition; @@ -783,10 +896,11 @@ struct FurnaceGUIMacroDesc { const char* modeName; ImVec4 color; unsigned int bitOffset; - bool isBitfield, blockMode; - String (*hoverFunc)(int,float); + bool isBitfield, blockMode, bit30; + String (*hoverFunc)(int,float,void*); + void* hoverFuncUser; - FurnaceGUIMacroDesc(const char* name, DivInstrumentMacro* m, int macroMin, int macroMax, float macroHeight, ImVec4 col=ImVec4(1.0f,1.0f,1.0f,1.0f), bool block=false, const char* mName=NULL, String (*hf)(int,float)=NULL, bool bitfield=false, const char** bfVal=NULL, unsigned int bitOff=0): + FurnaceGUIMacroDesc(const char* name, DivInstrumentMacro* m, int macroMin, int macroMax, float macroHeight, ImVec4 col=ImVec4(1.0f,1.0f,1.0f,1.0f), bool block=false, const char* mName=NULL, String (*hf)(int,float,void*)=NULL, bool bitfield=false, const char** bfVal=NULL, unsigned int bitOff=0, bool bit30Special=false, void* hfu=NULL): macro(m), height(macroHeight), displayName(name), @@ -796,7 +910,9 @@ struct FurnaceGUIMacroDesc { bitOffset(bitOff), isBitfield(bitfield), blockMode(block), - hoverFunc(hf) { + bit30(bit30Special), + hoverFunc(hf), + hoverFuncUser(hfu) { // MSVC -> hell this->min=macroMin; this->max=macroMax; @@ -819,6 +935,7 @@ enum FurnaceGUIFindQueryReplaceModes { GUI_QUERY_REPLACE_SET=0, GUI_QUERY_REPLACE_ADD, GUI_QUERY_REPLACE_ADD_OVERFLOW, + GUI_QUERY_REPLACE_SCALE, GUI_QUERY_REPLACE_CLEAR, GUI_QUERY_REPLACE_MAX @@ -856,6 +973,20 @@ struct FurnaceGUIFindQuery { } }; +struct FurnaceGUIQueryResult { + int subsong, order, x, y; + FurnaceGUIQueryResult(): + subsong(0), + order(0), + x(0), + y(0) {} + FurnaceGUIQueryResult(int ss, int o, int xPos, int yPos): + subsong(ss), + order(o), + x(xPos), + y(yPos) {} +}; + class FurnaceGUI { DivEngine* e; @@ -866,28 +997,46 @@ class FurnaceGUI { int sampleTexW, sampleTexH; bool updateSampleTex; - String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile; - String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout, workingDirROM; + String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile, sysSearchQuery, newSongQuery; + String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport; + String workingDirVGMExport, workingDirROMExport, workingDirFont, workingDirColors, workingDirKeybinds; + String workingDirLayout, workingDirROM, workingDirTest; String mmlString[32]; String mmlStringW; - bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; + std::vector sysSearchResults; + std::vector newSongSearchResults; + std::deque recentFile; + + bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints; + bool portrait, mobileMenuOpen; + bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly; - bool displayPendingIns, pendingInsSingle; + bool displayPendingIns, pendingInsSingle, displayPendingRawSample; bool willExport[32]; int vgmExportVersion; int drawHalt; int macroPointSize; + int waveEditStyle; + float mobileMenuPos; + const int* curSysSection; + + String pendingRawSample; + int pendingRawSampleDepth, pendingRawSampleChannels; + bool pendingRawSampleUnsigned, pendingRawSampleBigEndian; ImGuiWindowFlags globalWinFlags; FurnaceGUIFileDialogs curFileDialog; FurnaceGUIWarnings warnAction; FurnaceGUIWarnings postWarnAction; + FurnaceGUIMobileScenes mobScene; FurnaceGUIFileDialog* fileDialog; - int scrW, scrH; + int scrW, scrH, scrConfW, scrConfH; + int scrX, scrY, scrConfX, scrConfY; + bool scrMax; double dpiScale; @@ -919,6 +1068,12 @@ class FurnaceGUI { ImU32 sysCmd1Grad[256]; ImU32 sysCmd2Grad[256]; + char noteOffLabel[32]; + char noteRelLabel[32]; + char macroRelLabel[32]; + char emptyLabel[32]; + char emptyLabel2[32]; + struct Settings { int mainFontSize, patFontSize, iconSize; int audioEngine; @@ -928,6 +1083,7 @@ class FurnaceGUI { int snCore; int nesCore; int fdsCore; + int c64Core; int pcSpeakerOutMethod; String yrw801Path; String tg100Path; @@ -970,6 +1126,8 @@ class FurnaceGUI { int roundedMenus; int loadJapanese; int loadChinese; + int loadChineseTraditional; + int loadKorean; int fmLayout; int sampleLayout; int waveLayout; @@ -1009,12 +1167,32 @@ class FurnaceGUI { int effectValCellSpacing; int doubleClickColumn; int blankIns; + int dragMovesSelection; + int unsignedDetune; + int noThreadedInput; + int saveWindowPos; + int clampSamples; + int saveUnusedPatterns; + int channelColors; + int channelTextColors; + int channelStyle; + int channelVolStyle; + int channelFeedbackStyle; + int channelFont; + int maxRecentFile; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; String audioDevice; String midiInDevice; String midiOutDevice; + String c163Name; + String initialSysName; + String noteOffLabel; + String noteRelLabel; + String macroRelLabel; + String emptyLabel; + String emptyLabel2; std::vector initialSys; Settings(): @@ -1028,6 +1206,7 @@ class FurnaceGUI { snCore(0), nesCore(0), fdsCore(0), + c64Core(1), pcSpeakerOutMethod(0), yrw801Path(""), tg100Path(""), @@ -1070,6 +1249,8 @@ class FurnaceGUI { roundedMenus(0), loadJapanese(0), loadChinese(0), + loadChineseTraditional(0), + loadKorean(0), fmLayout(0), sampleLayout(0), waveLayout(0), @@ -1109,12 +1290,31 @@ class FurnaceGUI { effectValCellSpacing(0), doubleClickColumn(1), blankIns(0), + dragMovesSelection(1), + unsignedDetune(0), + noThreadedInput(0), + clampSamples(0), + saveUnusedPatterns(0), + channelColors(1), + channelTextColors(0), + channelStyle(0), + channelVolStyle(0), + channelFeedbackStyle(1), + channelFont(1), + maxRecentFile(10), maxUndoSteps(100), mainFontPath(""), patFontPath(""), audioDevice(""), midiInDevice(""), - midiOutDevice("") {} + midiOutDevice(""), + c163Name(""), + initialSysName("Sega Genesis/Mega Drive"), + noteOffLabel("OFF"), + noteRelLabel("==="), + macroRelLabel("REL"), + emptyLabel("..."), + emptyLabel2("..") {} } settings; char finalLayoutPath[4096]; @@ -1123,7 +1323,7 @@ class FurnaceGUI { int curIns, curWave, curSample, curOctave, curOrder, prevIns, oldRow, oldOrder, oldOrder1, editStep, exportLoops, soloChan, soloTimeout, orderEditMode, orderCursor; int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, patNameTarget, newSongCategory, latchTarget; - int wheelX, wheelY; + int wheelX, wheelY, dragSourceX, dragSourceY, dragDestinationX, dragDestinationY; double exportFadeOut; @@ -1131,16 +1331,17 @@ class FurnaceGUI { bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen; bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; bool pianoOpen, notesOpen, channelsOpen, regViewOpen, logOpen, effectListOpen, chanOscOpen; - bool subSongsOpen, findOpen; + bool subSongsOpen, findOpen, spoilerOpen, patManagerOpen, sysManagerOpen; - SelectionPoint selStart, selEnd, cursor; - bool selecting, selectingFull, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI; - bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, lockLayout, editOptsVisible, latchNibble, nonLatchNibble; + SelectionPoint selStart, selEnd, cursor, cursorDrag, dragStart, dragEnd; + bool selecting, selectingFull, dragging, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI; + bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, waveSigned, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble; FurnaceGUIWindows curWindow, nextWindow, curWindowLast; float peak[2]; float patChanX[DIV_MAX_CHANS+1]; float patChanSlideY[DIV_MAX_CHANS+1]; const int* nextDesc; + String nextDescName; OperationMask opMaskDelete, opMaskPullDelete, opMaskInsert, opMaskPaste, opMaskTransposeNote, opMaskTransposeValue; OperationMask opMaskInterpolate, opMaskFade, opMaskInvertVal, opMaskScale; @@ -1171,6 +1372,7 @@ class FurnaceGUI { int pgSys, pgAddr, pgVal; std::vector curQuery; + std::vector curQueryResults; bool curQueryRangeX, curQueryBackwards; int curQueryRangeXMin, curQueryRangeXMax; int curQueryRangeY; @@ -1193,6 +1395,7 @@ class FurnaceGUI { bool queryReplaceVolDo; bool queryReplaceEffectDo[8]; bool queryReplaceEffectValDo[8]; + bool queryViewingResults; struct ActiveNote { int chan; @@ -1243,6 +1446,8 @@ class FurnaceGUI { bool macroDragInitialValueSet; bool macroDragInitialValue; bool macroDragChar; + bool macroDragBit30; + bool macroDragSettingBit30; bool macroDragLineMode; bool macroDragMouseMoved; ImVec2 macroDragLineInitial; @@ -1251,10 +1456,11 @@ class FurnaceGUI { FurnaceGUIMacroDesc lastMacroDesc; int macroOffX, macroOffY; float macroScaleX, macroScaleY; + int macroRandMin, macroRandMax; ImVec2 macroLoopDragStart; ImVec2 macroLoopDragAreaSize; - signed char* macroLoopDragTarget; + unsigned char* macroLoopDragTarget; int macroLoopDragLen; bool macroLoopDragActive; @@ -1274,10 +1480,10 @@ class FurnaceGUI { int renderTimeBegin, renderTimeEnd, renderTimeDelta; int eventTimeBegin, eventTimeEnd, eventTimeDelta; - int chanToMove; + int chanToMove, sysToMove, sysToDelete, opToMove; ImVec2 patWindowPos, patWindowSize; - + // pattern view specific ImVec2 fourChars, threeChars, twoChars; ImVec2 noteCellSize, insCellSize, volCellSize, effectCellSize, effectValCellSize; @@ -1321,11 +1527,17 @@ class FurnaceGUI { bool oscZoomSlider; // per-channel oscilloscope - int chanOscCols; + int chanOscCols, chanOscColorX, chanOscColorY; float chanOscWindowSize; - bool chanOscWaveCorr; + bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex, chanOscUseGrad; + ImVec4 chanOscColor; + Gradient2D chanOscGrad; + SDL_Texture* chanOscGradTex; float chanOscLP0[DIV_MAX_CHANS]; float chanOscLP1[DIV_MAX_CHANS]; + float chanOscVol[DIV_MAX_CHANS]; + float chanOscPitch[DIV_MAX_CHANS]; + float chanOscBright[DIV_MAX_CHANS]; unsigned short lastNeedlePos[DIV_MAX_CHANS]; unsigned short lastCorrPos[DIV_MAX_CHANS]; struct ChanOscStatus { @@ -1347,7 +1559,7 @@ class FurnaceGUI { // visualizer float keyHit[DIV_MAX_CHANS]; int lastIns[DIV_MAX_CHANS]; - + // log window bool followLog; @@ -1363,6 +1575,23 @@ class FurnaceGUI { bool hasACED; unsigned char acedData[23]; + // wave generator + int waveGenBaseShape; + float waveGenDuty; + int waveGenPower; + float waveGenInvertPoint; + float waveGenAmp[16]; + float waveGenPhase[16]; + float waveGenTL[4]; + int waveGenMult[4]; + int waveGenFB[4]; + int waveGenScaleX, waveGenScaleY, waveGenOffsetX, waveGenOffsetY, waveGenSmooth; + float waveGenAmplify; + bool waveGenFMCon1[4]; + bool waveGenFMCon2[3]; + bool waveGenFMCon3[2]; + bool waveGenFM; + void drawSSGEnv(unsigned char type, const ImVec2& size); void drawWaveform(unsigned char type, bool opz, const ImVec2& size); void drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size); @@ -1379,6 +1608,8 @@ class FurnaceGUI { void updateWindowTitle(); void prepareLayout(); + ImVec4 channelColor(int ch); + ImVec4 channelTextColor(int ch); void readOsc(); @@ -1396,16 +1627,19 @@ class FurnaceGUI { void toggleMobileUI(bool enable, bool force=false); + void pushToggleColors(bool status); + void popToggleColors(); + void drawMobileControls(); void drawEditControls(); void drawSongInfo(); void drawOrders(); void drawPattern(); - void drawInsList(); + void drawInsList(bool asChild=false); void drawInsEdit(); - void drawWaveList(); + void drawWaveList(bool asChild=false); void drawWaveEdit(); - void drawSampleList(); + void drawSampleList(bool asChild=false); void drawSampleEdit(); void drawMixer(); void drawOsc(); @@ -1416,6 +1650,8 @@ class FurnaceGUI { void drawPiano(); void drawNotes(); void drawChannels(); + void drawPatManager(); + void drawSysManager(); void drawRegView(); void drawAbout(); void drawSettings(); @@ -1425,6 +1661,7 @@ class FurnaceGUI { void drawEffectList(); void drawSubSongs(); void drawFindReplace(); + void drawSpoiler(); void parseKeybinds(); void promptKey(int which); @@ -1437,6 +1674,8 @@ class FurnaceGUI { bool importLayout(String path); bool exportLayout(String path); + float computeGradPos(int type, int chan); + void resetColors(); void resetKeybinds(); @@ -1448,6 +1687,7 @@ class FurnaceGUI { void startSelection(int xCoarse, int xFine, int y, bool fullRow=false); void updateSelection(int xCoarse, int xFine, int y, bool fullRow=false); void finishSelection(); + void finishDrag(); void moveCursor(int x, int y, bool select); void moveCursorPrevChannel(bool overflow); @@ -1475,10 +1715,16 @@ class FurnaceGUI { void doExpand(int multiplier); void doUndo(); void doRedo(); + void doFind(); + void doReplace(); + void doDrag(); void editOptions(bool topMenu); + DivSystem systemPicker(); void noteInput(int num, int key, int vol=-1); void valueInput(int num, bool direct=false, int target=-1); + void doGenerateWave(); + void doUndoSample(); void doRedoSample(); @@ -1492,9 +1738,14 @@ class FurnaceGUI { void keyDown(SDL_Event& ev); void keyUp(SDL_Event& ev); + void pointDown(int x, int y, int button); + void pointUp(int x, int y, int button); + void pointMotion(int x, int y, int xrel, int yrel); + void openFileDialog(FurnaceGUIFileDialogs type); int save(String path, int dmfVersion); int load(String path); + void pushRecentFile(String path); void exportAudio(String path, DivAudioExportModes mode); bool parseSysEx(unsigned char* data, size_t len); @@ -1502,9 +1753,9 @@ class FurnaceGUI { void applyUISettings(bool updateFonts=true); void initSystemPresets(); - void encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel, bool hex=false); - void decodeMMLStr(String& source, int* macro, unsigned char& macroLen, signed char& macroLoop, int macroMin, int macroMax, signed char& macroRel); - void decodeMMLStrW(String& source, int* macro, int& macroLen, int macroMax, bool hex=false); + void encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel, bool hex=false, bool bit30=false); + void decodeMMLStr(String& source, int* macro, unsigned char& macroLen, unsigned char& macroLoop, int macroMin, int macroMax, unsigned char& macroRel, bool bit30=false); + void decodeMMLStrW(String& source, int* macro, int& macroLen, int macroMin, int macroMax, bool hex=false); String encodeKeyMap(std::map& map); void decodeKeyMap(std::map& map, String source); @@ -1514,6 +1765,7 @@ class FurnaceGUI { public: void showWarning(String what, FurnaceGUIWarnings type); void showError(String what); + const char* noteNameNormal(short note, short octave); const char* noteName(short note, short octave); bool decodeNote(const char* what, short& note, short& octave); void bindEngine(DivEngine* eng); @@ -1521,6 +1773,9 @@ class FurnaceGUI { void addScroll(int amount); void setFileName(String name); void runBackupThread(); + void pushPartBlend(); + void popPartBlend(); + bool detectOutOfBoundsWindow(); int processEvent(SDL_Event* ev); bool loop(); bool finish(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 532df4e1..37882d74 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -97,7 +97,7 @@ const char* insTypes[DIV_INS_MAX+1]={ "FM (OPL)", "FDS", "Virtual Boy", - "Namco C163", + "Namco 163", "Konami SCC/Bubble System WSG", "FM (OPZ)", "POKEY", @@ -116,7 +116,7 @@ const char* insTypes[DIV_INS_MAX+1]={ NULL }; -const char* sampleDepths[17]={ +const char* sampleDepths[DIV_SAMPLE_DEPTH_MAX]={ "1-bit PCM", "1-bit DPCM", NULL, @@ -487,11 +487,13 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WINDOW_PIANO", "Piano", 0), D("WINDOW_NOTES", "Song Comments", 0), D("WINDOW_CHANNELS", "Channels", 0), + D("WINDOW_PAT_MANAGER", "Pattern Manager", 0), + D("WINDOW_SYS_MANAGER", "Chip Manager", 0), D("WINDOW_REGISTER_VIEW", "Register View", 0), D("WINDOW_LOG", "Log Viewer", 0), - D("WINDOW_SUBSONGS", "Subsongs", 0), D("EFFECT_LIST", "Effect List", 0), D("WINDOW_CHAN_OSC", "Oscilloscope (per-channel)", 0), + D("WINDOW_SUBSONGS", "Subsongs", 0), D("WINDOW_FIND", "Find/Replace", FURKMOD_CMD|SDLK_f), D("COLLAPSE_WINDOW", "Collapse/expand current window", 0), @@ -569,6 +571,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("INS_LIST_OPEN", "Open", 0), D("INS_LIST_OPEN_REPLACE", "Open (replace current)", 0), D("INS_LIST_SAVE", "Save", 0), + D("INS_LIST_SAVE_DMP", "Save (.dmp)", 0), D("INS_LIST_MOVE_UP", "Move up", FURKMOD_SHIFT|SDLK_UP), D("INS_LIST_MOVE_DOWN", "Move down", FURKMOD_SHIFT|SDLK_DOWN), D("INS_LIST_DELETE", "Delete", 0), @@ -581,7 +584,10 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WAVE_LIST_ADD", "Add", SDLK_INSERT), D("WAVE_LIST_DUPLICATE", "Duplicate", FURKMOD_CMD|SDLK_d), D("WAVE_LIST_OPEN", "Open", 0), + D("WAVE_LIST_OPEN_REPLACE", "Open (replace current)", 0), D("WAVE_LIST_SAVE", "Save", 0), + D("WAVE_LIST_SAVE_DMW", "Save (.dmw)", 0), + D("WAVE_LIST_SAVE_RAW", "Save (raw)", 0), D("WAVE_LIST_MOVE_UP", "Move up", FURKMOD_SHIFT|SDLK_UP), D("WAVE_LIST_MOVE_DOWN", "Move down", FURKMOD_SHIFT|SDLK_DOWN), D("WAVE_LIST_DELETE", "Delete", 0), @@ -594,6 +600,9 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("SAMPLE_LIST_ADD", "Add", SDLK_INSERT), D("SAMPLE_LIST_DUPLICATE", "Duplicate", FURKMOD_CMD|SDLK_d), D("SAMPLE_LIST_OPEN", "Open", 0), + D("SAMPLE_LIST_OPEN_REPLACE", "Open (replace current)", 0), + D("SAMPLE_LIST_OPEN_RAW", "Import raw data", 0), + D("SAMPLE_LIST_OPEN_REPLACE_RAW", "Import raw data (replace current)", 0), D("SAMPLE_LIST_SAVE", "Save", 0), D("SAMPLE_LIST_MOVE_UP", "Move up", FURKMOD_SHIFT|SDLK_UP), D("SAMPLE_LIST_MOVE_DOWN", "Move down", FURKMOD_SHIFT|SDLK_DOWN), @@ -635,6 +644,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("SAMPLE_ZOOM_AUTO", "Toggle auto-zoom", FURKMOD_CMD|SDLK_0), D("SAMPLE_MAKE_INS", "Create instrument from sample", 0), D("SAMPLE_SET_LOOP", "Set loop to selection", FURKMOD_CMD|SDLK_l), + D("SAMPLE_CREATE_WAVE", "Create wavetable from selection", FURKMOD_CMD|SDLK_w), D("SAMPLE_MAX", "", NOT_AN_ACTION), D("ORDERS_MIN", "---Orders", NOT_AN_ACTION), @@ -764,6 +774,8 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_INSTR_OPL_DRUMS,"",ImVec4(0.3f,1.0f,0.9f,1.0f)), D(GUI_COLOR_INSTR_UNKNOWN,"",ImVec4(0.3f,0.3f,0.3f,1.0f)), + D(GUI_COLOR_CHANNEL_BG,"",ImVec4(0.4f,0.6f,0.8f,1.0f)), + D(GUI_COLOR_CHANNEL_FG,"",ImVec4(1.0f,1.0f,1.0f,1.0f)), D(GUI_COLOR_CHANNEL_FM,"",ImVec4(0.2f,0.8f,1.0f,1.0f)), D(GUI_COLOR_CHANNEL_PULSE,"",ImVec4(0.4f,1.0f,0.2f,1.0f)), D(GUI_COLOR_CHANNEL_NOISE,"",ImVec4(0.8f,0.8f,0.8f,1.0f)), @@ -807,6 +819,13 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY,"",ImVec4(0.0f,1.0f,0.5f,1.0f)), D(GUI_COLOR_PATTERN_EFFECT_MISC,"",ImVec4(0.3f,0.3f,1.0f,1.0f)), + D(GUI_COLOR_PAT_MANAGER_NULL,"",ImVec4(0.15f,0.15f,0.15f,1.0f)), + D(GUI_COLOR_PAT_MANAGER_USED,"",ImVec4(0.15f,1.0f,0.15f,1.0f)), + D(GUI_COLOR_PAT_MANAGER_OVERUSED,"",ImVec4(1.0f,1.0f,0.15f,1.0f)), + D(GUI_COLOR_PAT_MANAGER_EXTREMELY_OVERUSED,"",ImVec4(1.0f,0.5f,0.15f,1.0f)), + D(GUI_COLOR_PAT_MANAGER_COMBO_BREAKER,"",ImVec4(1.0f,0.15f,1.0f,1.0f)), + D(GUI_COLOR_PAT_MANAGER_UNUSED,"",ImVec4(1.0f,0.15f,0.15f,1.0f)), + D(GUI_COLOR_PIANO_BACKGROUND,"",ImVec4(0.0f,0.0f,0.0f,1.0f)), D(GUI_COLOR_PIANO_KEY_BOTTOM,"",ImVec4(1.0f,1.0f,1.0f,1.0f)), D(GUI_COLOR_PIANO_KEY_TOP,"",ImVec4(0.0f,0.0f,0.0f,1.0f)), @@ -826,7 +845,9 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ }; #undef D -// define systems. +// define chips here + +// all chips const int availableSystems[]={ DIV_SYSTEM_YM2612, DIV_SYSTEM_YM2612_EXT, @@ -895,6 +916,119 @@ const int availableSystems[]={ DIV_SYSTEM_MSM6295, DIV_SYSTEM_RF5C68, DIV_SYSTEM_SNES, + DIV_SYSTEM_PCM_DAC, 0 // don't remove this last one! }; +// FM +const int chipsFM[]={ + DIV_SYSTEM_YM2612, + DIV_SYSTEM_YM2612_EXT, + DIV_SYSTEM_YM2612_FRAC, + DIV_SYSTEM_YM2612_FRAC_EXT, + DIV_SYSTEM_YM2151, + DIV_SYSTEM_YM2610, + DIV_SYSTEM_YM2610_EXT, + DIV_SYSTEM_YM2610_FULL, + DIV_SYSTEM_YM2610_FULL_EXT, + DIV_SYSTEM_YM2610B, + DIV_SYSTEM_YM2610B_EXT, + DIV_SYSTEM_YMU759, + DIV_SYSTEM_OPN, + DIV_SYSTEM_OPN_EXT, + DIV_SYSTEM_PC98, + DIV_SYSTEM_PC98_EXT, + DIV_SYSTEM_OPLL, + DIV_SYSTEM_OPLL_DRUMS, + DIV_SYSTEM_VRC7, + DIV_SYSTEM_OPL, + DIV_SYSTEM_OPL_DRUMS, + DIV_SYSTEM_Y8950, + DIV_SYSTEM_Y8950_DRUMS, + DIV_SYSTEM_OPL2, + DIV_SYSTEM_OPL2_DRUMS, + DIV_SYSTEM_OPL3, + DIV_SYSTEM_OPL3_DRUMS, + DIV_SYSTEM_OPZ, + 0 // don't remove this last one! +}; + +// square +const int chipsSquare[]={ + DIV_SYSTEM_SMS, + DIV_SYSTEM_AY8910, + DIV_SYSTEM_PCSPKR, + DIV_SYSTEM_SAA1099, + DIV_SYSTEM_VIC20, + 0 // don't remove this last one! +}; + +// wavetable +const int chipsWave[]={ + DIV_SYSTEM_PCE, + DIV_SYSTEM_X1_010, + DIV_SYSTEM_SWAN, + DIV_SYSTEM_BUBSYS_WSG, + DIV_SYSTEM_N163, + DIV_SYSTEM_FDS, + DIV_SYSTEM_SCC, + DIV_SYSTEM_SCC_PLUS, + DIV_SYSTEM_NAMCO, + DIV_SYSTEM_NAMCO_15XX, + DIV_SYSTEM_NAMCO_CUS30, + 0 // don't remove this last one! +}; + +// specialized +const int chipsSpecial[]={ + DIV_SYSTEM_GB, + DIV_SYSTEM_NES, + DIV_SYSTEM_C64_8580, + DIV_SYSTEM_C64_6581, + DIV_SYSTEM_SFX_BEEPER, + DIV_SYSTEM_DUMMY, + DIV_SYSTEM_SOUND_UNIT, + DIV_SYSTEM_TIA, + DIV_SYSTEM_AY8930, + DIV_SYSTEM_LYNX, + DIV_SYSTEM_VERA, + DIV_SYSTEM_PET, + DIV_SYSTEM_VRC6, + DIV_SYSTEM_MMC5, + 0 // don't remove this last one! +}; + +// sample +const int chipsSample[]={ + DIV_SYSTEM_SEGAPCM, + DIV_SYSTEM_SEGAPCM_COMPAT, + DIV_SYSTEM_AMIGA, + DIV_SYSTEM_QSOUND, + DIV_SYSTEM_X1_010, + DIV_SYSTEM_YMZ280B, + DIV_SYSTEM_MSM6258, + DIV_SYSTEM_MSM6295, + DIV_SYSTEM_RF5C68, + DIV_SYSTEM_PCM_DAC, + 0 // don't remove this last one! +}; + +const int* chipCategories[]={ + availableSystems, + chipsFM, + chipsSquare, + chipsWave, + chipsSpecial, + chipsSample, + NULL +}; + +const char* chipCategoryNames[]={ + "All chips", + "FM", + "Square", + "Wavetable", + "Special", + "Sample", + NULL +}; \ No newline at end of file diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index 69085c32..24bdac47 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -40,9 +40,16 @@ extern const char* noteNames[180]; extern const char* noteNamesG[180]; extern const char* pitchLabel[11]; extern const char* insTypes[]; -extern const char* sampleDepths[17]; +extern const char* sampleDepths[]; extern const char* resampleStrats[]; +extern const char* chipCategoryNames[]; extern const int availableSystems[]; +extern const int chipsFM[]; +extern const int chipsSquare[]; +extern const int chipsWavetable[]; +extern const int chipsSpecial[]; +extern const int chipsSample[]; +extern const int* chipCategories[]; extern const FurnaceGUIActionDef guiActions[]; extern const FurnaceGUIColorDef guiColors[]; extern const int altValues[24]; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 7d7fa050..50fd286b 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -19,6 +19,7 @@ #include "gui.h" #include "imgui_internal.h" +#include "../engine/macroInt.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" #include "guiConst.h" @@ -43,24 +44,91 @@ const char* fmParamShortNames[3][32]={ {"ALG", "FB", "FMS", "AMS", "A", "D", "D2", "R", "S", "TL", "RS", "ML", "DT", "DT2", "SSG", "AM", "DAM", "DVB", "EGT", "EGS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM", "EGS", "REV", "Fine", "FMS2", "AMS2"} }; -const char* opllInsNames[17]={ - "User", - "Violin", - "Guitar", - "Piano", - "Flute", - "Clarinet", - "Oboe", - "Trumpet", - "Organ", - "Horn", - "Synth", - "Harpsichord", - "Vibraphone", - "Synth Bass", - "Acoustic Bass", - "Electric Guitar", - "Drums" +const char* opllVariants[4]={ + "OPLL", + "YMF281", + "YM2423", + "VRC7" +}; + +const char* opllInsNames[4][17]={ + /* YM2413 */ { + "User", + "Violin", + "Guitar", + "Piano", + "Flute", + "Clarinet", + "Oboe", + "Trumpet", + "Organ", + "Horn", + "Synth", + "Harpsichord", + "Vibraphone", + "Synth Bass", + "Acoustic Bass", + "Electric Guitar", + "Drums" + }, + /* YMF281 */ { + "User", + "Electric String", + "Bow wow", + "Electric Guitar", + "Organ", + "Clarinet", + "Saxophone", + "Trumpet", + "Street Organ", + "Synth Brass", + "Electric Piano", + "Bass", + "Vibraphone", + "Chime", + "Tom Tom II", + "Noise", + "Drums" + }, + /* YM2423 */ { + "User", + "Strings", + "Guitar", + "Electric Guitar", + "Electric Piano", + "Flute", + "Marimba", + "Trumpet", + "Harmonica", + "Tuba", + "Synth Brass", + "Short Saw", + "Vibraphone", + "Electric Guitar 2", + "Synth Bass", + "Sitar", + "Drums" + }, + // stolen from FamiTracker + /* VRC7 */ { + "User", + "Bell", + "Guitar", + "Piano", + "Flute", + "Clarinet", + "Rattling Bell", + "Trumpet", + "Reed Organ", + "Soft Bell", + "Xylophone", + "Vibraphone", + "Brass", + "Bass Guitar", + "Synth", + "Chorus", + "Drums" + } }; const char* oplWaveforms[8]={ @@ -183,6 +251,10 @@ const char* suControlBits[5]={ "ring mod", "low pass", "high pass", "band pass", NULL }; +const char* es5506FilterModes[4]={ + "HP/K2, HP/K2", "HP/K2, LP/K1", "LP/K2, LP/K2", "LP/K2, LP/K1", +}; + const char* panBits[3]={ "right", "left", NULL }; @@ -191,6 +263,14 @@ const char* oneBit[2]={ "on", NULL }; +const char* es5506EnvelopeModes[3]={ + "k1 slowdown", "k2 slowdown", NULL +}; + +const char* es5506ControlModes[2]={ + "pause", NULL +}; + const int orderedOps[4]={ 0, 2, 1, 3 }; @@ -217,28 +297,71 @@ const char* dualWSEffects[9]={ "Phase Modulation" }; +const char* gbHWSeqCmdTypes[6]={ + "Envelope", + "Sweep", + "Wait", + "Wait for Release", + "Loop", + "Loop until Release" +}; + +// do not change these! +// anything other than a checkbox will look ugly! +// +// if you really need to, and have a good rationale (and by good I mean a VERY +// good one), please tell me and we'll sort it out. const char* macroAbsoluteMode="Fixed"; const char* macroRelativeMode="Relative"; const char* macroQSoundMode="QSound"; - const char* macroDummyMode="Bug"; -String macroHoverNote(int id, float val) { - if (val<-60 || val>=120) return "???"; - return fmt::sprintf("%d: %s",id,noteNames[(int)val+60]); +String macroHoverNote(int id, float val, void* u) { + int* macroVal=(int*)u; + if ((macroVal[id]&0xc0000000)==0x40000000 || (macroVal[id]&0xc0000000)==0x80000000) { + if (val<-60 || val>=120) return "???"; + return fmt::sprintf("%d: %s",id,noteNames[(int)val+60]); + } + return fmt::sprintf("%d: %d",id,(int)val); } -String macroHover(int id, float val) { +String macroHover(int id, float val, void* u) { return fmt::sprintf("%d: %d",id,val); } -String macroHoverLoop(int id, float val) { +String macroHoverLoop(int id, float val, void* u) { if (val>1) return "Release"; if (val>0) return "Loop"; return ""; } -String macroLFOWaves(int id, float val) { +String macroHoverBit30(int id, float val, void* u) { + if (val>0) return "Fixed"; + return "Relative"; +} + +String macroHoverES5506FilterMode(int id, float val, void* u) { + String mode="???"; + switch (((int)val)&3) { + case 0: + mode="HP/K2, HP/K2"; + break; + case 1: + mode="HP/K2, LP/K1"; + break; + case 2: + mode="LP/K2, LP/K2"; + break; + case 3: + mode="LP/K2, LP/K1"; + break; + default: + break; + } + return fmt::sprintf("%d: %s",id,mode); +} + +String macroLFOWaves(int id, float val, void* u) { switch (((int)val)&3) { case 0: return "Saw"; @@ -1078,10 +1201,22 @@ String genericGuide(float value) { return fmt::sprintf("%d",(int)value); } +inline int deBit30(const int val) { + if ((val&0xc0000000)==0x40000000 || (val&0xc0000000)==0x80000000) return val^0x40000000; + return val; +} + +inline bool enBit30(const int val) { + if ((val&0xc0000000)==0x40000000 || (val&0xc0000000)==0x80000000) return true; + return false; +} + void FurnaceGUI::drawMacros(std::vector& macros) { float asFloat[256]; int asInt[256]; float loopIndicator[256]; + float bit30Indicator[256]; + bool doHighlight[256]; int index=0; float reservedSpace=(settings.oldMacroVSlider)?(20.0f*dpiScale+ImGui::GetStyle().ItemSpacing.x):ImGui::GetStyle().ScrollbarSize; @@ -1100,14 +1235,14 @@ void FurnaceGUI::drawMacros(std::vector& macros) { } ImGui::TableNextColumn(); float availableWidth=ImGui::GetContentRegionAvail().x-reservedSpace; - int totalFit=MIN(128,availableWidth/MAX(1,macroPointSize*dpiScale)); - if (macroDragScroll>128-totalFit) { - macroDragScroll=128-totalFit; + int totalFit=MIN(255,availableWidth/MAX(1,macroPointSize*dpiScale)); + if (macroDragScroll>255-totalFit) { + macroDragScroll=255-totalFit; } ImGui::SetNextItemWidth(availableWidth); - if (CWSliderInt("##MacroScroll",¯oDragScroll,0,128-totalFit,"")) { + if (CWSliderInt("##MacroScroll",¯oDragScroll,0,255-totalFit,"")) { if (macroDragScroll<0) macroDragScroll=0; - if (macroDragScroll>128-totalFit) macroDragScroll=128-totalFit; + if (macroDragScroll>255-totalFit) macroDragScroll=255-totalFit; } // draw macros @@ -1124,9 +1259,36 @@ void FurnaceGUI::drawMacros(std::vector& macros) { } if (i.macro->open) { ImGui::SetNextItemWidth(lenAvail); - if (ImGui::InputScalar("##IMacroLen",ImGuiDataType_U8,&i.macro->len,&_ONE,&_THREE)) { MARK_MODIFIED - if (i.macro->len>128) i.macro->len=128; + int macroLen=i.macro->len; + if (ImGui::InputScalar("##IMacroLen",ImGuiDataType_U8,¯oLen,&_ONE,&_THREE)) { MARK_MODIFIED + if (macroLen<0) macroLen=0; + if (macroLen>255) macroLen=255; + i.macro->len=macroLen; } + if (ImGui::Button(ICON_FA_BAR_CHART "##IMacroType")) { + + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Coming soon!"); + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_ELLIPSIS_H "##IMacroSet"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Delay/Step Length"); + } + if (ImGui::BeginPopupContextItem("IMacroSetP",ImGuiPopupFlags_MouseButtonLeft)) { + if (ImGui::InputScalar("Step Length (ticks)##IMacroSpeed",ImGuiDataType_U8,&i.macro->speed,&_ONE,&_THREE)) { + if (i.macro->speed<1) i.macro->speed=1; + MARK_MODIFIED; + } + if (ImGui::InputScalar("Delay##IMacroDelay",ImGuiDataType_U8,&i.macro->delay,&_ONE,&_THREE)) { + MARK_MODIFIED; + } + ImGui::EndPopup(); + } + // do not change this! + // anything other than a checkbox will look ugly! + // if you really need more than two macro modes please tell me. if (i.modeName!=NULL) { bool modeVal=i.macro->mode; String modeName=fmt::sprintf("%s##IMacroMode",i.modeName); @@ -1139,17 +1301,19 @@ void FurnaceGUI::drawMacros(std::vector& macros) { // macro area ImGui::TableNextColumn(); for (int j=0; j<256; j++) { + bit30Indicator[j]=0; if (j+macroDragScroll>=i.macro->len) { asFloat[j]=0; asInt[j]=0; } else { - asFloat[j]=i.macro->val[j+macroDragScroll]; - asInt[j]=i.macro->val[j+macroDragScroll]+i.bitOffset; + asFloat[j]=deBit30(i.macro->val[j+macroDragScroll]); + asInt[j]=deBit30(i.macro->val[j+macroDragScroll])+i.bitOffset; + if (i.bit30) bit30Indicator[j]=enBit30(i.macro->val[j+macroDragScroll]); } if (j+macroDragScroll>=i.macro->len || (j+macroDragScroll>i.macro->rel && i.macro->looprel)) { loopIndicator[j]=0; } else { - loopIndicator[j]=((i.macro->loop!=-1 && (j+macroDragScroll)>=i.macro->loop))|((i.macro->rel!=-1 && (j+macroDragScroll)==i.macro->rel)<<1); + loopIndicator[j]=((i.macro->loop!=255 && (j+macroDragScroll)>=i.macro->loop))|((i.macro->rel!=255 && (j+macroDragScroll)==i.macro->rel)<<1); } } ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); @@ -1169,11 +1333,33 @@ void FurnaceGUI::drawMacros(std::vector& macros) { if (i.macro->vZoom>(i.max-i.min)) { i.macro->vZoom=i.max-i.min; } - + + memset(doHighlight,0,256*sizeof(bool)); + if (e->isRunning()) for (int j=0; jgetTotalChannelCount(); j++) { + DivChannelState* chanState=e->getChanState(j); + if (chanState==NULL) continue; + + if (chanState->keyOff) continue; + if (chanState->lastIns!=curIns) continue; + + DivMacroInt* macroInt=e->getMacroInt(j); + if (macroInt==NULL) continue; + + DivMacroStruct* macroStruct=macroInt->structByName(i.macro->name); + if (macroStruct==NULL) continue; + + if (macroStruct->lastPos>i.macro->len) continue; + if (macroStruct->lastPoslastPos>255) continue; + if (!macroStruct->actualHad) continue; + + doHighlight[macroStruct->lastPos-macroDragScroll]=true; + } + if (i.isBitfield) { - PlotBitfield("##IMacro",asInt,totalFit,0,i.bitfieldBits,i.max,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale))); + PlotBitfield("##IMacro",asInt,totalFit,0,i.bitfieldBits,i.max,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),doHighlight); } else { - PlotCustom("##IMacro",asFloat,totalFit,macroDragScroll,NULL,i.min+i.macro->vScroll,i.min+i.macro->vScroll+i.macro->vZoom,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),i.color,i.macro->len-macroDragScroll,i.hoverFunc,i.blockMode,i.macro->open?genericGuide:NULL); + PlotCustom("##IMacro",asFloat,totalFit,macroDragScroll,NULL,i.min+i.macro->vScroll,i.min+i.macro->vScroll+i.macro->vZoom,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),i.color,i.macro->len-macroDragScroll,i.hoverFunc,i.hoverFuncUser,i.blockMode,i.macro->open?genericGuide:NULL,doHighlight); } if (i.macro->open && (ImGui::IsItemClicked(ImGuiMouseButton_Left) || ImGui::IsItemClicked(ImGuiMouseButton_Right))) { macroDragStart=ImGui::GetItemRectMin(); @@ -1191,6 +1377,8 @@ void FurnaceGUI::drawMacros(std::vector& macros) { macroDragInitialValue=false; macroDragLen=totalFit; macroDragActive=true; + macroDragBit30=i.bit30; + macroDragSettingBit30=false; macroDragTarget=i.macro->val; macroDragChar=false; macroDragLineMode=(i.isBitfield)?false:ImGui::IsItemClicked(ImGuiMouseButton_Right); @@ -1258,6 +1446,27 @@ void FurnaceGUI::drawMacros(std::vector& macros) { } } + // bit 30 area + if (i.bit30) { + PlotCustom("##IMacroBit30",bit30Indicator,totalFit,macroDragScroll,NULL,0,1,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),i.color,i.macro->len-macroDragScroll,¯oHoverBit30); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + macroDragStart=ImGui::GetItemRectMin(); + macroDragAreaSize=ImVec2(availableWidth,12.0f*dpiScale); + macroDragInitialValueSet=false; + macroDragInitialValue=false; + macroDragLen=totalFit; + macroDragActive=true; + macroDragBit30=i.bit30; + macroDragSettingBit30=true; + macroDragTarget=i.macro->val; + macroDragChar=false; + macroDragLineMode=false; + macroDragLineInitial=ImVec2(0,0); + lastMacroDesc=i; + processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); + } + } + // loop area PlotCustom("##IMacroLoop",loopIndicator,totalFit,macroDragScroll,NULL,0,2,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),i.color,i.macro->len-macroDragScroll,¯oHoverLoop); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { @@ -1274,18 +1483,18 @@ void FurnaceGUI::drawMacros(std::vector& macros) { } if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { - i.macro->rel=-1; + i.macro->rel=255; } else { - i.macro->loop=-1; + i.macro->loop=255; } } ImGui::SetNextItemWidth(availableWidth); String& mmlStr=mmlString[index]; if (ImGui::InputText("##IMacroMML",&mmlStr)) { - decodeMMLStr(mmlStr,i.macro->val,i.macro->len,i.macro->loop,i.min,(i.isBitfield)?((1<<(i.isBitfield?i.max:0))-1):i.max,i.macro->rel); + decodeMMLStr(mmlStr,i.macro->val,i.macro->len,i.macro->loop,i.min,(i.isBitfield)?((1<<(i.isBitfield?i.max:0))-1):i.max,i.macro->rel,i.bit30); } if (!ImGui::IsItemActive()) { - encodeMMLStr(mmlStr,i.macro->val,i.macro->len,i.macro->loop,i.macro->rel); + encodeMMLStr(mmlStr,i.macro->val,i.macro->len,i.macro->loop,i.macro->rel,false,i.bit30); } } ImGui::PopStyleVar(); @@ -1297,9 +1506,9 @@ void FurnaceGUI::drawMacros(std::vector& macros) { ImGui::TableNextColumn(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(availableWidth); - if (CWSliderInt("##MacroScroll",¯oDragScroll,0,128-totalFit,"")) { + if (CWSliderInt("##MacroScroll",¯oDragScroll,0,255-totalFit,"")) { if (macroDragScroll<0) macroDragScroll=0; - if (macroDragScroll>128-totalFit) macroDragScroll=128-totalFit; + if (macroDragScroll>255-totalFit) macroDragScroll=255-totalFit; } ImGui::EndTable(); } @@ -1345,6 +1554,52 @@ void FurnaceGUI::drawMacros(std::vector& macros) { #define CENTER_VSLIDER \ ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5f*ImGui::GetContentRegionAvail().x-10.0f*dpiScale); +#define CENTER_TEXT_20(text) \ + ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(20.0f*dpiScale-ImGui::CalcTextSize(text).x)); + +#define OP_DRAG_POINT \ + if (ImGui::Button(ICON_FA_ARROWS)) { \ + } \ + if (ImGui::BeginDragDropSource()) { \ + opToMove=i; \ + ImGui::SetDragDropPayload("FUR_OP",NULL,0,ImGuiCond_Once); \ + ImGui::Button(ICON_FA_ARROWS "##SysDrag"); \ + ImGui::SameLine(); \ + if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { \ + ImGui::Text("(copying)"); \ + } else { \ + ImGui::Text("(swapping)"); \ + } \ + ImGui::EndDragDropSource(); \ + } else if (ImGui::IsItemHovered()) { \ + ImGui::SetTooltip("- drag to swap operator\n- shift-drag to copy operator"); \ + } \ + if (ImGui::BeginDragDropTarget()) { \ + const ImGuiPayload* dragItem=ImGui::AcceptDragDropPayload("FUR_OP"); \ + if (dragItem!=NULL) { \ + if (dragItem->IsDataType("FUR_OP")) { \ + if (opToMove!=i && opToMove>=0) { \ + int destOp=(opCount==4 && ins->type!=DIV_INS_OPL_DRUMS)?opOrder[i]:i; \ + int sourceOp=(opCount==4 && ins->type!=DIV_INS_OPL_DRUMS)?opOrder[opToMove]:opToMove; \ + if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { \ + e->lockEngine([ins,destOp,sourceOp]() { \ + ins->fm.op[destOp]=ins->fm.op[sourceOp]; \ + }); \ + } else { \ + e->lockEngine([ins,destOp,sourceOp]() { \ + DivInstrumentFM::Operator origOp=ins->fm.op[sourceOp]; \ + ins->fm.op[sourceOp]=ins->fm.op[destOp]; \ + ins->fm.op[destOp]=origOp; \ + }); \ + } \ + PARAMETER; \ + } \ + opToMove=-1; \ + } \ + } \ + ImGui::EndDragDropTarget(); \ + } + void FurnaceGUI::drawInsEdit() { if (nextWindow==GUI_WINDOW_INS_EDIT) { insEditOpen=true; @@ -1352,7 +1607,14 @@ void FurnaceGUI::drawInsEdit() { nextWindow=GUI_WINDOW_NOTHING; } if (!insEditOpen) return; - ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + if (mobileUI) { + patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f)); + patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f))); + ImGui::SetNextWindowPos(patWindowPos); + ImGui::SetNextWindowSize(patWindowSize); + } else { + ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + } if (ImGui::Begin("Instrument Editor",&insEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) { if (curIns<0 || curIns>=(int)e->song.ins.size()) { ImGui::Text("no instrument selected"); @@ -1400,6 +1662,12 @@ void FurnaceGUI::drawInsEdit() { if (ImGui::Button(ICON_FA_FLOPPY_O "##IESave")) { doAction(GUI_ACTION_INS_LIST_SAVE); } + if (ImGui::BeginPopupContextItem("InsSaveFormats",ImGuiMouseButton_Right)) { + if (ImGui::MenuItem("save as .dmp...")) { + doAction(GUI_ACTION_INS_LIST_SAVE_DMP); + } + ImGui::EndPopup(); + } ImGui::TableNextColumn(); ImGui::Text("Type"); @@ -1413,7 +1681,7 @@ void FurnaceGUI::drawInsEdit() { ins->type=(DivInstrumentType)insType; } */ - if (ImGui::BeginCombo("##Type",insTypes[insType])) { + if (ImGui::BeginCombo("##Type",insType==DIV_INS_N163?settings.c163Name.c_str():insTypes[insType])) { std::vector insTypeList; if (settings.displayAllInsTypes) { for (int i=0; insTypes[i]; i++) { @@ -1423,7 +1691,7 @@ void FurnaceGUI::drawInsEdit() { insTypeList=e->getPossibleInsTypes(); } for (DivInstrumentType i: insTypeList) { - if (ImGui::Selectable(insTypes[i],insType==i)) { + if (ImGui::Selectable(i==DIV_INS_N163?settings.c163Name.c_str():insTypes[i],insType==i)) { ins->type=i; // reset macro zoom @@ -1483,6 +1751,7 @@ void FurnaceGUI::drawInsEdit() { int opCount=4; if (ins->type==DIV_INS_OPLL) opCount=2; if (ins->type==DIV_INS_OPL) opCount=(ins->fm.ops==4)?4:2; + bool opsAreMutable=(ins->type==DIV_INS_FM); if (ImGui::BeginTabItem("FM")) { if (ImGui::BeginTable("fmDetails",3,ImGuiTableFlags_SizingStretchSame)) { @@ -1569,10 +1838,59 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); drawAlgorithm(0,FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,24.0*dpiScale)); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("##LLPreset",opllInsNames[ins->fm.opllPreset])) { - for (int i=0; i<17; i++) { - if (ImGui::Selectable(opllInsNames[i])) { - ins->fm.opllPreset=i; + + bool isPresent[4]; + int isPresentCount=0; + memset(isPresent,0,4*sizeof(bool)); + for (int i=0; isong.systemLen; i++) { + if (e->song.system[i]==DIV_SYSTEM_VRC7) { + isPresent[3]=true; + } else if (e->song.system[i]==DIV_SYSTEM_OPLL || e->song.system[i]==DIV_SYSTEM_OPLL_DRUMS) { + isPresent[(e->song.systemFlags[i]>>4)&3]=true; + } + } + if (!isPresent[0] && !isPresent[1] && !isPresent[2] && !isPresent[3]) { + isPresent[0]=true; + } + for (int i=0; i<4; i++) { + if (isPresent[i]) isPresentCount++; + } + int presentWhich=0; + for (int i=0; i<4; i++) { + if (isPresent[i]) { + presentWhich=i; + break; + } + } + + if (ImGui::BeginCombo("##LLPreset",opllInsNames[presentWhich][ins->fm.opllPreset])) { + if (isPresentCount>1) { + if (ImGui::BeginTable("LLPresetList",isPresentCount)) { + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + for (int i=0; i<4; i++) { + if (!isPresent[i]) continue; + ImGui::TableNextColumn(); + ImGui::Text("%s name",opllVariants[i]); + } + for (int i=0; i<17; i++) { + ImGui::TableNextRow(); + for (int j=0; j<4; j++) { + if (!isPresent[j]) continue; + ImGui::TableNextColumn(); + ImGui::PushID(j*17+i); + if (ImGui::Selectable(opllInsNames[j][i])) { + ins->fm.opllPreset=i; + } + ImGui::PopID(); + } + } + ImGui::EndTable(); + } + } else { + for (int i=0; i<17; i++) { + if (ImGui::Selectable(opllInsNames[presentWhich][i])) { + ins->fm.opllPreset=i; + } } } ImGui::EndCombo(); @@ -1788,17 +2106,31 @@ void FurnaceGUI::drawInsEdit() { if (i==0) sliderHeight=(ImGui::GetContentRegionAvail().y/opCount)-ImGui::GetStyle().ItemSpacing.y; ImGui::PushID(fmt::sprintf("op%d",i).c_str()); + String opNameLabel; if (ins->type==DIV_INS_OPL_DRUMS) { - ImGui::Text("%s",oplDrumNames[i]); + opNameLabel=fmt::sprintf("%s",oplDrumNames[i]); } else if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) { if (i==1) { - ImGui::Text("Kick"); + opNameLabel="Kick"; } else { - ImGui::Text("Env"); + opNameLabel="Env"; } } else { - ImGui::Text("OP%d",i+1); + opNameLabel=fmt::sprintf("OP%d",i+1); } + if (opsAreMutable) { + pushToggleColors(op.enable); + if (ImGui::Button(opNameLabel.c_str())) { + op.enable=!op.enable; + PARAMETER; + } + popToggleColors(); + } else { + ImGui::TextUnformatted(opNameLabel.c_str()); + } + + // drag point + OP_DRAG_POINT; int maxTl=127; if (ins->type==DIV_INS_OPLL) { @@ -1891,11 +2223,11 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { - int detune=(op.dt&7)-3; + int detune=(op.dt&7)-(settings.unsignedDetune?0:3); ImGui::TableNextColumn(); CENTER_VSLIDER; if (CWVSliderInt("##DT",ImVec2(20.0f*dpiScale,sliderHeight),&detune,-3,4)) { PARAMETER - op.dt=detune+3; + op.dt=detune+(settings.unsignedDetune?0:3); } ImGui::TableNextColumn(); @@ -2016,7 +2348,465 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndTable(); } - } else { + } else if (settings.fmLayout>=4 && settings.fmLayout<=6) { // alternate + int columns=2; + switch (settings.fmLayout) { + case 4: // 2x2 + columns=2; + break; + case 5: // 1x4 + columns=1; + break; + case 6: // 4x1 + columns=opCount; + break; + } + char tempID[1024]; + ImVec2 oldPadding=ImGui::GetStyle().CellPadding; + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(8.0f*dpiScale,4.0f*dpiScale)); + if (ImGui::BeginTable("KGE93BSIEO3NOWBDJZBA",columns,ImGuiTableFlags_SizingStretchSame|ImGuiTableFlags_BordersInner)) { + for (int i=0; ifm.op[(opCount==4 && ins->type!=DIV_INS_OPL_DRUMS)?opOrder[i]:i]; + if ((settings.fmLayout!=6 && ((i+1)&1)) || i==0 || settings.fmLayout==5) ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushID(fmt::sprintf("op%d",i).c_str()); + + // push colors + if (settings.separateFMColors) { + bool mod=true; + if (ins->type==DIV_INS_OPL_DRUMS) { + mod=false; + } else if (opCount==4) { + if (ins->type==DIV_INS_OPL) { + if (opIsOutputOPL[ins->fm.alg&3][i]) mod=false; + } else { + if (opIsOutput[ins->fm.alg&7][i]) mod=false; + } + } else { + if (i==1 || (ins->type==DIV_INS_OPL && (ins->fm.alg&1))) mod=false; + } + if (mod) { + pushAccentColors( + uiColors[GUI_COLOR_FM_PRIMARY_MOD], + uiColors[GUI_COLOR_FM_SECONDARY_MOD], + uiColors[GUI_COLOR_FM_BORDER_MOD], + uiColors[GUI_COLOR_FM_BORDER_SHADOW_MOD] + ); + } else { + pushAccentColors( + uiColors[GUI_COLOR_FM_PRIMARY_CAR], + uiColors[GUI_COLOR_FM_SECONDARY_CAR], + uiColors[GUI_COLOR_FM_BORDER_CAR], + uiColors[GUI_COLOR_FM_BORDER_SHADOW_CAR] + ); + } + } + + ImGui::Dummy(ImVec2(dpiScale,dpiScale)); + if (ins->type==DIV_INS_OPL_DRUMS) { + snprintf(tempID,1024,"%s",oplDrumNames[i]); + } else if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) { + if (i==1) { + snprintf(tempID,1024,"Envelope 2 (kick only)"); + } else { + snprintf(tempID,1024,"Envelope"); + } + } else { + snprintf(tempID,1024,"Operator %d",i+1); + } + float nextCursorPosX=ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(tempID).x-(opsAreMutable?(ImGui::GetStyle().FramePadding.x*2.0f):0.0f)); + OP_DRAG_POINT; + ImGui::SameLine(); + ImGui::SetCursorPosX(nextCursorPosX); + if (opsAreMutable) { + pushToggleColors(op.enable); + if (ImGui::Button(tempID)) { + op.enable=!op.enable; + PARAMETER; + } + popToggleColors(); + } else { + ImGui::TextUnformatted(tempID); + } + + float sliderHeight=200.0f*dpiScale; + float waveWidth=140.0*dpiScale; + float waveHeight=sliderHeight-ImGui::GetFrameHeightWithSpacing()*(ins->type==DIV_INS_OPLL?4.5f:5.5f); + + int maxTl=127; + if (ins->type==DIV_INS_OPLL) { + if (i==1) { + maxTl=15; + } else { + maxTl=63; + } + } + if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS) { + maxTl=63; + } + int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ)?31:15; + + bool ssgOn=op.ssgEnv&8; + bool ksrOn=op.ksr; + bool vibOn=op.vib; + bool egtOn=op.egt; + bool susOn=op.sus; // yawn + unsigned char ssgEnv=op.ssgEnv&7; + + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,oldPadding); + if (ImGui::BeginTable("opParams",4,ImGuiTableFlags_BordersInnerV)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,waveWidth); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + float textY=ImGui::GetCursorPosY(); + CENTER_TEXT_20(FM_SHORT_NAME(FM_AR)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_AR)); + ImGui::TableNextColumn(); + if (ins->type==DIV_INS_FM) { + ImGui::Text("SSG-EG"); + } else { + ImGui::Text("Waveform"); + } + ImGui::TableNextColumn(); + ImGui::Text("Envelope"); + ImGui::TableNextColumn(); + CENTER_TEXT(FM_SHORT_NAME(FM_TL)); + ImGui::Text("TL"); + + // A/D/S/R + ImGui::TableNextColumn(); + + op.ar&=maxArDr; + P(CWVSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); + + ImGui::SameLine(); + op.dr&=maxArDr; + float textX_DR=ImGui::GetCursorPosX(); + P(CWVSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); + + float textX_SL=0.0f; + if (settings.susPosition==0) { + ImGui::SameLine(); + op.sl&=15; + textX_SL=ImGui::GetCursorPosX(); + P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + } + + float textX_D2R=0.0f; + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + ImGui::SameLine(); + op.d2r&=31; + textX_D2R=ImGui::GetCursorPosX(); + P(CWVSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); + } + + ImGui::SameLine(); + op.rr&=15; + float textX_RR=ImGui::GetCursorPosX(); + P(CWVSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); + + if (settings.susPosition==1) { + ImGui::SameLine(); + op.sl&=15; + textX_SL=ImGui::GetCursorPosX(); + P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + } + + ImVec2 prevCurPos=ImGui::GetCursorPos(); + + // labels + ImGui::SetCursorPos(ImVec2(textX_DR,textY)); + CENTER_TEXT_20(FM_SHORT_NAME(FM_DR)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_DR)); + + ImGui::SetCursorPos(ImVec2(textX_SL,textY)); + CENTER_TEXT_20(FM_SHORT_NAME(FM_SL)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_SL)); + + ImGui::SetCursorPos(ImVec2(textX_RR,textY)); + CENTER_TEXT_20(FM_SHORT_NAME(FM_RR)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_RR)); + + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + ImGui::SetCursorPos(ImVec2(textX_D2R,textY)); + CENTER_TEXT_20(FM_SHORT_NAME(FM_D2R)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_D2R)); + } + + ImGui::SetCursorPos(prevCurPos); + + ImGui::TableNextColumn(); + switch (ins->type) { + case DIV_INS_FM: { + // SSG + ImGui::BeginDisabled(!ssgOn); + drawSSGEnv(op.ssgEnv&7,ImVec2(waveWidth,waveHeight)); + ImGui::EndDisabled(); + if (ImGui::Checkbox("##SSGOn",&ssgOn)) { PARAMETER + op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Only for OPN family chips"); + } + + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER + op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); + } + + // params + ImGui::Separator(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_MULT)); + P(CWSliderScalar("##MULT",ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN,tempID)); rightClickable + + int detune=(op.dt&7)-(settings.unsignedDetune?0:3); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_DT)); + if (CWSliderInt("##DT",&detune,-3,4,tempID)) { PARAMETER + op.dt=detune+(settings.unsignedDetune?0:3); + } rightClickable + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_DT2)); + P(CWSliderScalar("##DT2",ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE,tempID)); rightClickable + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Only on YM2151 (OPM)"); + } + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_RS)); + P(CWSliderScalar("##RS",ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE,tempID)); rightClickable + + break; + } + case DIV_INS_OPLL: + // waveform + drawWaveform(i==0?(ins->fm.ams&1):(ins->fm.fms&1),ins->type==DIV_INS_OPZ,ImVec2(waveWidth,waveHeight)); + + // params + ImGui::Separator(); + if (ImGui::BeginTable("FMParamsInner",2)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + bool amOn=op.am; + if (ImGui::Checkbox(FM_NAME(FM_AM),&amOn)) { PARAMETER + op.am=amOn; + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox(FM_NAME(FM_KSR),&ksrOn)) { PARAMETER + op.ksr=ksrOn; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Checkbox(FM_NAME(FM_VIB),&vibOn)) { PARAMETER + op.vib=vibOn; + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox(FM_NAME(FM_EGS),&ssgOn)) { PARAMETER + op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); + } + + ImGui::EndTable(); + } + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_MULT)); + P(CWSliderScalar("##MULT",ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN,tempID)); rightClickable + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_KSL)); + P(CWSliderScalar("##KSL",ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE,tempID)); rightClickable + + break; + case DIV_INS_OPL: + case DIV_INS_OPL_DRUMS: + // waveform + drawWaveform(op.ws&7,ins->type==DIV_INS_OPZ,ImVec2(waveWidth,waveHeight)); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(CWSliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,(ins->type==DIV_INS_OPZ)?opzWaveforms[op.ws&7]:(settings.oplStandardWaveNames?oplWaveformsStandard[op.ws&7]:oplWaveforms[op.ws&7]))); rightClickable + if ((ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS) && ImGui::IsItemHovered()) { + ImGui::SetTooltip("OPL2/3 only (last 4 waveforms are OPL3 only)"); + } + + // params + ImGui::Separator(); + if (ImGui::BeginTable("FMParamsInner",2)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + bool amOn=op.am; + if (ImGui::Checkbox(FM_NAME(FM_AM),&amOn)) { PARAMETER + op.am=amOn; + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox(FM_NAME(FM_KSR),&ksrOn)) { PARAMETER + op.ksr=ksrOn; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Checkbox(FM_NAME(FM_VIB),&vibOn)) { PARAMETER + op.vib=vibOn; + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox(FM_NAME(FM_SUS),&susOn)) { PARAMETER + op.sus=susOn; + } + + ImGui::EndTable(); + } + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_MULT)); + P(CWSliderScalar("##MULT",ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN,tempID)); rightClickable + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_KSL)); + P(CWSliderScalar("##KSL",ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE,tempID)); rightClickable + + break; + case DIV_INS_OPZ: { + // waveform + drawWaveform(op.ws&7,ins->type==DIV_INS_OPZ,ImVec2(waveWidth,waveHeight)); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(CWSliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,(ins->type==DIV_INS_OPZ)?opzWaveforms[op.ws&7]:(settings.oplStandardWaveNames?oplWaveformsStandard[op.ws&7]:oplWaveforms[op.ws&7]))); rightClickable + if ((ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS) && ImGui::IsItemHovered()) { + ImGui::SetTooltip("OPL2/3 only (last 4 waveforms are OPL3 only)"); + } + + // params + ImGui::Separator(); + if (egtOn) { + int block=op.dt; + int freqNum=(op.mult<<4)|(op.dvb&15); + ImGui::Text("Block"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImVec2 cursorAlign=ImGui::GetCursorPos(); + if (ImGui::InputInt("##Block",&block,1,1)) { + if (block<0) block=0; + if (block>7) block=7; + op.dt=block; + } + + ImGui::Text("Freq"); + ImGui::SameLine(); + ImGui::SetCursorPos(ImVec2(cursorAlign.x,ImGui::GetCursorPosY())); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##FreqNum",&freqNum,1,16)) { + if (freqNum<0) freqNum=0; + if (freqNum>255) freqNum=255; + op.mult=freqNum>>4; + op.dvb=freqNum&15; + } + } else { + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_MULT)); + P(CWSliderScalar("##MULT",ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN,tempID)); rightClickable + + int detune=(op.dt&7)-(settings.unsignedDetune?0:3); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_DT)); + if (CWSliderInt("##DT",&detune,-3,4,tempID)) { PARAMETER + op.dt=detune+(settings.unsignedDetune?0:3); + } rightClickable + } + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_DT2)); + P(CWSliderScalar("##DT2",ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE,tempID)); rightClickable + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Only on YM2151 (OPM)"); + } + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_RS)); + P(CWSliderScalar("##RS",ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE,tempID)); rightClickable + break; + } + default: + break; + } + + ImGui::TableNextColumn(); + float envHeight=sliderHeight;//-ImGui::GetStyle().ItemSpacing.y*2.0f; + if (ins->type==DIV_INS_OPZ) { + envHeight-=ImGui::GetFrameHeightWithSpacing()*2.0f; + } + drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,op.sus,op.ssgEnv&8,ins->fm.alg,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,envHeight),ins->type); + + if (ins->type==DIV_INS_OPZ) { + ImGui::Separator(); + if (ImGui::BeginTable("FMParamsInnerOPZ",2)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (!egtOn) { + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_FINE)); + P(CWSliderScalar("##FINE",ImGuiDataType_U8,&op.dvb,&_ZERO,&_FIFTEEN,tempID)); rightClickable + } + + ImGui::TableNextColumn(); + bool amOn=op.am; + if (ImGui::Checkbox(FM_NAME(FM_AM),&amOn)) { PARAMETER + op.am=amOn; + } + ImGui::SameLine(); + if (ImGui::Checkbox("Fixed",&egtOn)) { PARAMETER + op.egt=egtOn; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_EGSHIFT)); + P(CWSliderScalar("##EGShift",ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE,tempID)); rightClickable + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_REV)); + P(CWSliderScalar("##REV",ImGuiDataType_U8,&op.dam,&_ZERO,&_SEVEN,tempID)); rightClickable + + ImGui::TableNextColumn(); + + + ImGui::EndTable(); + } + } + + ImGui::TableNextColumn(); + op.tl&=maxTl; + P(CWVSliderScalar("##TL",ImVec2(ImGui::GetFrameHeight(),sliderHeight-(ins->type==DIV_INS_FM?(ImGui::GetFrameHeightWithSpacing()+ImGui::CalcTextSize(FM_SHORT_NAME(FM_AM)).y+ImGui::GetStyle().ItemSpacing.y):0.0f)),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); + + if (ins->type==DIV_INS_FM) { + CENTER_TEXT(FM_SHORT_NAME(FM_AM)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_AM)); + bool amOn=op.am; + if (ImGui::Checkbox("##AM",&amOn)) { PARAMETER + op.am=amOn; + } + } + + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + + if (settings.separateFMColors) { + popAccentColors(); + } + + ImGui::PopID(); + } + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + } else { // classic int columns=2; switch (settings.fmLayout) { case 1: // 2x2 @@ -2069,16 +2859,29 @@ void FurnaceGUI::drawInsEdit() { } ImGui::Dummy(ImVec2(dpiScale,dpiScale)); + String opNameLabel; + OP_DRAG_POINT; + ImGui::SameLine(); if (ins->type==DIV_INS_OPL_DRUMS) { - ImGui::Text("%s",oplDrumNames[i]); + opNameLabel=fmt::sprintf("%s",oplDrumNames[i]); } else if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) { if (i==1) { - ImGui::Text("Envelope 2 (kick only)"); + opNameLabel="Envelope 2 (kick only)"; } else { - ImGui::Text("Envelope"); + opNameLabel="Envelope"; } } else { - ImGui::Text("OP%d",i+1); + opNameLabel=fmt::sprintf("OP%d",i+1); + } + if (opsAreMutable) { + pushToggleColors(op.enable); + if (ImGui::Button(opNameLabel.c_str())) { + op.enable=!op.enable; + PARAMETER; + } + popToggleColors(); + } else { + ImGui::TextUnformatted(opNameLabel.c_str()); } ImGui::SameLine(); @@ -2217,12 +3020,12 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("%s",FM_NAME(FM_MULT)); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { - int detune=(op.dt&7)-3; + int detune=(op.dt&7)-(settings.unsignedDetune?0:3); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (CWSliderInt("##DT",&detune,-3,4)) { PARAMETER - op.dt=detune+3; + op.dt=detune+(settings.unsignedDetune?0:3); } rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_DT)); @@ -2312,7 +3115,14 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("PM Depth",&ins->std.ex2Macro,0,127,128,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("LFO Speed",&ins->std.ex3Macro,0,255,128,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("LFO Shape",&ins->std.waveMacro,0,3,48,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,macroLFOWaves)); + } + if (ins->type==DIV_INS_FM) { macroList.push_back(FurnaceGUIMacroDesc("OpMask",&ins->std.ex4Macro,0,4,128,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,fmOperatorBits)); + } else if (ins->type==DIV_INS_OPZ) { + macroList.push_back(FurnaceGUIMacroDesc("AM Depth 2",&ins->std.ex5Macro,0,127,128,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("PM Depth 2",&ins->std.ex6Macro,0,127,128,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("LFO2 Speed",&ins->std.ex7Macro,0,255,128,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("LFO2 Shape",&ins->std.ex8Macro,0,3,48,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,macroLFOWaves)); } drawMacros(macroList); ImGui::EndTabItem(); @@ -2391,52 +3201,281 @@ void FurnaceGUI::drawInsEdit() { } } if (ins->type==DIV_INS_GB) if (ImGui::BeginTabItem("Game Boy")) { - P(CWSliderScalar("Volume",ImGuiDataType_U8,&ins->gb.envVol,&_ZERO,&_FIFTEEN)); rightClickable - P(CWSliderScalar("Envelope Length",ImGuiDataType_U8,&ins->gb.envLen,&_ZERO,&_SEVEN)); rightClickable - P(CWSliderScalar("Sound Length",ImGuiDataType_U8,&ins->gb.soundLen,&_ZERO,&_SIXTY_FOUR,ins->gb.soundLen>63?"Infinity":"%d")); rightClickable - ImGui::Text("Envelope Direction:"); + P(ImGui::Checkbox("Use software envelope",&ins->gb.softEnv)); + P(ImGui::Checkbox("Initialize envelope on every note",&ins->gb.alwaysInit)); - bool goesUp=ins->gb.envDir; - ImGui::SameLine(); - if (ImGui::RadioButton("Up",goesUp)) { PARAMETER - goesUp=true; - ins->gb.envDir=goesUp; - } - ImGui::SameLine(); - if (ImGui::RadioButton("Down",!goesUp)) { PARAMETER - goesUp=false; - ins->gb.envDir=goesUp; + ImGui::BeginDisabled(ins->gb.softEnv); + if (ImGui::BeginTable("GBParams",2)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.6f); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.4f); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::BeginTable("GBParamsI",2)) { + ImGui::TableSetupColumn("ci0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("ci1",ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Volume"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(CWSliderScalar("##GBVolume",ImGuiDataType_U8,&ins->gb.envVol,&_ZERO,&_FIFTEEN)); rightClickable + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Length"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(CWSliderScalar("##GBEnvLen",ImGuiDataType_U8,&ins->gb.envLen,&_ZERO,&_SEVEN)); rightClickable + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Sound Length"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(CWSliderScalar("##GBSoundLen",ImGuiDataType_U8,&ins->gb.soundLen,&_ZERO,&_SIXTY_FOUR,ins->gb.soundLen>63?"Infinity":"%d")); rightClickable + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Direction"); + ImGui::TableNextColumn(); + bool goesUp=ins->gb.envDir; + if (ImGui::RadioButton("Up",goesUp)) { PARAMETER + goesUp=true; + ins->gb.envDir=goesUp; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Down",!goesUp)) { PARAMETER + goesUp=false; + ins->gb.envDir=goesUp; + } + + ImGui::EndTable(); + } + + ImGui::TableNextColumn(); + drawGBEnv(ins->gb.envVol,ins->gb.envLen,ins->gb.soundLen,ins->gb.envDir,ImVec2(ImGui::GetContentRegionAvail().x,100.0f*dpiScale)); + + ImGui::EndTable(); } - drawGBEnv(ins->gb.envVol,ins->gb.envLen,ins->gb.soundLen,ins->gb.envDir,ImVec2(ImGui::GetContentRegionAvail().x,100.0f*dpiScale)); + if (ImGui::BeginChild("HWSeq",ImGui::GetContentRegionAvail(),true,ImGuiWindowFlags_MenuBar)) { + ImGui::BeginMenuBar(); + ImGui::Text("Hardware Sequence"); + ImGui::EndMenuBar(); + + if (ins->gb.hwSeqLen>0) if (ImGui::BeginTable("HWSeqList",3)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); + int curFrame=0; + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Tick"); + ImGui::TableNextColumn(); + ImGui::Text("Command"); + ImGui::TableNextColumn(); + ImGui::Text("Move/Remove"); + for (int i=0; igb.hwSeqLen; i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%d (#%d)",curFrame,i); + ImGui::TableNextColumn(); + ImGui::PushID(i); + if (ins->gb.hwSeq[i].cmd>=DivInstrumentGB::DIV_GB_HWCMD_MAX) { + ins->gb.hwSeq[i].cmd=0; + } + int cmd=ins->gb.hwSeq[i].cmd; + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::Combo("##HWSeqCmd",&cmd,gbHWSeqCmdTypes,DivInstrumentGB::DIV_GB_HWCMD_MAX)) { + if (ins->gb.hwSeq[i].cmd!=cmd) { + ins->gb.hwSeq[i].cmd=cmd; + ins->gb.hwSeq[i].data=0; + } + } + bool somethingChanged=false; + switch (ins->gb.hwSeq[i].cmd) { + case DivInstrumentGB::DIV_GB_HWCMD_ENVELOPE: { + int hwsVol=(ins->gb.hwSeq[i].data&0xf0)>>4; + bool hwsDir=ins->gb.hwSeq[i].data&8; + int hwsLen=ins->gb.hwSeq[i].data&7; + int hwsSoundLen=ins->gb.hwSeq[i].data>>8; + + if (CWSliderInt("Volume",&hwsVol,0,15)) { + somethingChanged=true; + } + if (CWSliderInt("Env Length",&hwsLen,0,7)) { + somethingChanged=true; + } + if (CWSliderInt("Sound Length",&hwsSoundLen,0,64,hwsSoundLen>63?"Infinity":"%d")) { + somethingChanged=true; + } + if (ImGui::RadioButton("Up",hwsDir)) { PARAMETER + hwsDir=true; + somethingChanged=true; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Down",!hwsDir)) { PARAMETER + hwsDir=false; + somethingChanged=true; + } + + if (somethingChanged) { + ins->gb.hwSeq[i].data=(hwsLen&7)|(hwsDir?8:0)|(hwsVol<<4)|(hwsSoundLen<<8); + PARAMETER; + } + break; + } + case DivInstrumentGB::DIV_GB_HWCMD_SWEEP: { + int hwsShift=ins->gb.hwSeq[i].data&7; + int hwsSpeed=(ins->gb.hwSeq[i].data&0x70)>>4; + bool hwsDir=ins->gb.hwSeq[i].data&8; + + if (CWSliderInt("Shift",&hwsShift,0,7)) { + somethingChanged=true; + } + if (CWSliderInt("Speed",&hwsSpeed,0,7)) { + somethingChanged=true; + } + + if (ImGui::RadioButton("Up",!hwsDir)) { PARAMETER + hwsDir=false; + somethingChanged=true; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Down",hwsDir)) { PARAMETER + hwsDir=true; + somethingChanged=true; + } + + if (somethingChanged) { + ins->gb.hwSeq[i].data=(hwsShift&7)|(hwsDir?8:0)|(hwsSpeed<<4); + PARAMETER; + } + break; + } + case DivInstrumentGB::DIV_GB_HWCMD_WAIT: { + int len=ins->gb.hwSeq[i].data+1; + curFrame+=ins->gb.hwSeq[i].data+1; + + if (ImGui::InputInt("Ticks",&len)) { + if (len<1) len=1; + if (len>255) len=256; + somethingChanged=true; + } + + if (somethingChanged) { + ins->gb.hwSeq[i].data=len-1; + PARAMETER; + } + break; + } + case DivInstrumentGB::DIV_GB_HWCMD_WAIT_REL: + curFrame++; + break; + case DivInstrumentGB::DIV_GB_HWCMD_LOOP: + case DivInstrumentGB::DIV_GB_HWCMD_LOOP_REL: { + int pos=ins->gb.hwSeq[i].data; + + if (ImGui::InputInt("Position",&pos)) { + if (pos<0) pos=0; + if (pos>(ins->gb.hwSeqLen-1)) pos=(ins->gb.hwSeqLen-1); + somethingChanged=true; + } + + if (somethingChanged) { + ins->gb.hwSeq[i].data=pos; + PARAMETER; + } + break; + } + default: + break; + } + ImGui::PopID(); + ImGui::TableNextColumn(); + ImGui::PushID(i+512); + if (ImGui::Button(ICON_FA_CHEVRON_UP "##HWCmdUp")) { + if (i>0) { + e->lockEngine([ins,i]() { + ins->gb.hwSeq[i-1].cmd^=ins->gb.hwSeq[i].cmd; + ins->gb.hwSeq[i].cmd^=ins->gb.hwSeq[i-1].cmd; + ins->gb.hwSeq[i-1].cmd^=ins->gb.hwSeq[i].cmd; + + ins->gb.hwSeq[i-1].data^=ins->gb.hwSeq[i].data; + ins->gb.hwSeq[i].data^=ins->gb.hwSeq[i-1].data; + ins->gb.hwSeq[i-1].data^=ins->gb.hwSeq[i].data; + }); + } + MARK_MODIFIED; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_CHEVRON_DOWN "##HWCmdDown")) { + if (igb.hwSeqLen-1) { + e->lockEngine([ins,i]() { + ins->gb.hwSeq[i+1].cmd^=ins->gb.hwSeq[i].cmd; + ins->gb.hwSeq[i].cmd^=ins->gb.hwSeq[i+1].cmd; + ins->gb.hwSeq[i+1].cmd^=ins->gb.hwSeq[i].cmd; + + ins->gb.hwSeq[i+1].data^=ins->gb.hwSeq[i].data; + ins->gb.hwSeq[i].data^=ins->gb.hwSeq[i+1].data; + ins->gb.hwSeq[i+1].data^=ins->gb.hwSeq[i].data; + }); + } + MARK_MODIFIED; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_TIMES "##HWCmdDel")) { + for (int j=i; jgb.hwSeqLen-1; j++) { + ins->gb.hwSeq[j].cmd=ins->gb.hwSeq[j+1].cmd; + ins->gb.hwSeq[j].data=ins->gb.hwSeq[j+1].data; + } + ins->gb.hwSeqLen--; + } + ImGui::PopID(); + } + ImGui::EndTable(); + } + + if (ImGui::Button(ICON_FA_PLUS "##HWCmdAdd")) { + if (ins->gb.hwSeqLen<255) { + ins->gb.hwSeq[ins->gb.hwSeqLen].cmd=0; + ins->gb.hwSeq[ins->gb.hwSeqLen].data=0; + ins->gb.hwSeqLen++; + } + } + } + ImGui::EndChild(); + ImGui::EndDisabled(); ImGui::EndTabItem(); } if (ins->type==DIV_INS_C64) if (ImGui::BeginTabItem("C64")) { ImGui::Text("Waveform"); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.triOn)); + pushToggleColors(ins->c64.triOn); if (ImGui::Button("tri")) { PARAMETER ins->c64.triOn=!ins->c64.triOn; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.sawOn)); + pushToggleColors(ins->c64.sawOn); if (ImGui::Button("saw")) { PARAMETER ins->c64.sawOn=!ins->c64.sawOn; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.pulseOn)); + pushToggleColors(ins->c64.pulseOn); if (ImGui::Button("pulse")) { PARAMETER ins->c64.pulseOn=!ins->c64.pulseOn; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.noiseOn)); + pushToggleColors(ins->c64.noiseOn); if (ImGui::Button("noise")) { PARAMETER ins->c64.noiseOn=!ins->c64.noiseOn; } - ImGui::PopStyleColor(); + popToggleColors(); ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale); @@ -2498,29 +3537,29 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("Filter Mode"); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.lp)); + pushToggleColors(ins->c64.lp); if (ImGui::Button("low")) { PARAMETER ins->c64.lp=!ins->c64.lp; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.bp)); + pushToggleColors(ins->c64.bp); if (ImGui::Button("band")) { PARAMETER ins->c64.bp=!ins->c64.bp; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.hp)); + pushToggleColors(ins->c64.hp); if (ImGui::Button("high")) { PARAMETER ins->c64.hp=!ins->c64.hp; } - ImGui::PopStyleColor(); + popToggleColors(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.ch3off)); + pushToggleColors(ins->c64.ch3off); if (ImGui::Button("ch3off")) { PARAMETER ins->c64.ch3off=!ins->c64.ch3off; } - ImGui::PopStyleColor(); + popToggleColors(); P(ImGui::Checkbox("Volume Macro is Cutoff Macro",&ins->c64.volIsCutoff)); P(ImGui::Checkbox("Absolute Cutoff Macro",&ins->c64.filterIsAbs)); @@ -2528,13 +3567,20 @@ void FurnaceGUI::drawInsEdit() { P(ImGui::Checkbox("Don't test/gate before new note",&ins->c64.noTest)); ImGui::EndTabItem(); } - if (ins->type==DIV_INS_AMIGA) if (ImGui::BeginTabItem("Sample")) { + if (ins->type==DIV_INS_AMIGA || + ins->type==DIV_INS_SU || + ins->type==DIV_INS_SNES || + ins->type==DIV_INS_ES5506) if (ImGui::BeginTabItem((ins->type==DIV_INS_SU)?"Sound Unit":"Sample")) { String sName; if (ins->amiga.initSample<0 || ins->amiga.initSample>=e->song.sampleLen) { sName="none selected"; } else { sName=e->song.sample[ins->amiga.initSample]->name; } + if (ins->type==DIV_INS_SU) { + P(ImGui::Checkbox("Use sample",&ins->su.useSample)); + P(ImGui::Checkbox("Switch roles of frequency and phase reset timer",&ins->su.switchRoles)); + } if (ImGui::BeginCombo("Initial Sample",sName.c_str())) { String id; for (int i=0; isong.sampleLen; i++) { @@ -2545,23 +3591,26 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndCombo(); } - P(ImGui::Checkbox("Use wavetable (Amiga only)",&ins->amiga.useWave)); - if (ins->amiga.useWave) { - int len=ins->amiga.waveLen+1; - if (ImGui::InputInt("Width",&len,2,16)) { - if (len<2) len=2; - if (len>256) len=256; - ins->amiga.waveLen=(len&(~1))-1; - PARAMETER + if (ins->type==DIV_INS_AMIGA) { + P(ImGui::Checkbox("Use wavetable (Amiga only)",&ins->amiga.useWave)); + if (ins->amiga.useWave) { + int len=ins->amiga.waveLen+1; + if (ImGui::InputInt("Width",&len,2,16)) { + if (len<2) len=2; + if (len>256) len=256; + ins->amiga.waveLen=(len&(~1))-1; + PARAMETER + } } } ImGui::BeginDisabled(ins->amiga.useWave); P(ImGui::Checkbox("Use sample map (does not work yet!)",&ins->amiga.useNoteMap)); if (ins->amiga.useNoteMap) { - if (ImGui::BeginTable("NoteMap",3,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { + // TODO: frequency map? + if (ImGui::BeginTable("NoteMap",2,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); + //ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupScrollFreeze(0,1); @@ -2569,41 +3618,42 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::TableNextColumn(); ImGui::Text("Sample"); - ImGui::TableNextColumn(); - ImGui::Text("Frequency"); + /*ImGui::TableNextColumn(); + ImGui::Text("Frequency");*/ for (int i=0; i<120; i++) { + DivInstrumentAmiga::SampleMap& sampleMap=ins->amiga.noteMap[i]; ImGui::TableNextRow(); ImGui::PushID(fmt::sprintf("NM_%d",i).c_str()); ImGui::TableNextColumn(); ImGui::Text("%s",noteNames[60+i]); ImGui::TableNextColumn(); - if (ins->amiga.noteMap[i]<0 || ins->amiga.noteMap[i]>=e->song.sampleLen) { + if (sampleMap.map<0 || sampleMap.map>=e->song.sampleLen) { sName="-- empty --"; - ins->amiga.noteMap[i]=-1; + sampleMap.map=-1; } else { - sName=e->song.sample[ins->amiga.noteMap[i]]->name; + sName=e->song.sample[sampleMap.map]->name; } ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::BeginCombo("##SM",sName.c_str())) { String id; - if (ImGui::Selectable("-- empty --",ins->amiga.noteMap[i]==-1)) { PARAMETER - ins->amiga.noteMap[i]=-1; + if (ImGui::Selectable("-- empty --",sampleMap.map==-1)) { PARAMETER + sampleMap.map=-1; } for (int j=0; jsong.sampleLen; j++) { id=fmt::sprintf("%d: %s",j,e->song.sample[j]->name); - if (ImGui::Selectable(id.c_str(),ins->amiga.noteMap[i]==j)) { PARAMETER - ins->amiga.noteMap[i]=j; - if (ins->amiga.noteFreq[i]<=0) ins->amiga.noteFreq[i]=(int)((double)e->song.sample[j]->centerRate*pow(2.0,((double)i-48.0)/12.0)); + if (ImGui::Selectable(id.c_str(),sampleMap.map==j)) { PARAMETER + sampleMap.map=j; + if (sampleMap.freq<=0) sampleMap.freq=(int)((double)e->song.sample[j]->centerRate*pow(2.0,((double)i-48.0)/12.0)); } } ImGui::EndCombo(); } - ImGui::TableNextColumn(); + /*ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##SF",&ins->amiga.noteFreq[i],50,500)) { PARAMETER - if (ins->amiga.noteFreq[i]<0) ins->amiga.noteFreq[i]=0; - if (ins->amiga.noteFreq[i]>262144) ins->amiga.noteFreq[i]=262144; - } + if (ImGui::InputInt("##SF",&sampleMap.freq,50,500)) { PARAMETER + if (sampleMap.freq<0) sampleMap.freq=0; + if (sampleMap.freq>262144) sampleMap.freq=262144; + }*/ ImGui::PopID(); } ImGui::EndTable(); @@ -2612,7 +3662,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndDisabled(); ImGui::EndTabItem(); } - if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) { + if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem(settings.c163Name.c_str())) { if (ImGui::InputInt("Waveform##WAVE",&ins->n163.wave,1,10)) { PARAMETER if (ins->n163.wave<0) ins->n163.wave=0; if (ins->n163.wave>=e->song.waveLen) ins->n163.wave=e->song.waveLen-1; @@ -2657,7 +3707,7 @@ void FurnaceGUI::drawInsEdit() { modTable[i]=ins->fds.modTable[i]; } ImVec2 modTableSize=ImVec2(ImGui::GetContentRegionAvail().x,96.0f*dpiScale); - PlotCustom("ModTable",modTable,32,0,NULL,-4,3,modTableSize,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,true); + PlotCustom("ModTable",modTable,32,0,NULL,-4,3,modTableSize,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,NULL,true); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { macroDragStart=ImGui::GetItemRectMin(); macroDragAreaSize=modTableSize; @@ -2677,6 +3727,42 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndTabItem(); } + if (ins->type==DIV_INS_ES5506) if (ImGui::BeginTabItem("ES5506")) { + if (ImGui::BeginTable("ESParams",2,ImGuiTableFlags_SizingStretchSame)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.0); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); + // filter + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + P(CWSliderScalar("Filter 4,3 Mode",ImGuiDataType_U8,&ins->es5506.filter.mode,&_ZERO,&_THREE,es5506FilterModes[ins->es5506.filter.mode&3])); rightClickable + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + P(CWSliderScalar("Filter K1",ImGuiDataType_U16,&ins->es5506.filter.k1,&_ZERO,&_SIXTY_FIVE_THOUSAND_FIVE_HUNDRED_THIRTY_FIVE)); rightClickable + ImGui::TableNextColumn(); + P(CWSliderScalar("Filter K2",ImGuiDataType_U16,&ins->es5506.filter.k2,&_ZERO,&_SIXTY_FIVE_THOUSAND_FIVE_HUNDRED_THIRTY_FIVE)); rightClickable + // envelope + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + P(CWSliderScalar("Envelope count",ImGuiDataType_U16,&ins->es5506.envelope.ecount,&_ZERO,&_FIVE_HUNDRED_ELEVEN)); rightClickable + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + P(CWSliderScalar("Left Volume Ramp",ImGuiDataType_S8,&ins->es5506.envelope.lVRamp,&_MINUS_ONE_HUNDRED_TWENTY_EIGHT,&_ONE_HUNDRED_TWENTY_SEVEN)); rightClickable + ImGui::TableNextColumn(); + P(CWSliderScalar("Right Volume Ramp",ImGuiDataType_S8,&ins->es5506.envelope.rVRamp,&_MINUS_ONE_HUNDRED_TWENTY_EIGHT,&_ONE_HUNDRED_TWENTY_SEVEN)); rightClickable + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + P(CWSliderScalar("Filter K1 Ramp",ImGuiDataType_S8,&ins->es5506.envelope.k1Ramp,&_MINUS_ONE_HUNDRED_TWENTY_EIGHT,&_ONE_HUNDRED_TWENTY_SEVEN)); rightClickable + ImGui::TableNextColumn(); + P(CWSliderScalar("Filter K2 Ramp",ImGuiDataType_S8,&ins->es5506.envelope.k2Ramp,&_MINUS_ONE_HUNDRED_TWENTY_EIGHT,&_ONE_HUNDRED_TWENTY_SEVEN)); rightClickable + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Checkbox("K1 Ramp Slowdown",&ins->es5506.envelope.k1Slow); + ImGui::TableNextColumn(); + ImGui::Checkbox("K2 Ramp Slowdown",&ins->es5506.envelope.k2Slow); + ImGui::EndTable(); + } + ImGui::EndTabItem(); + } if (ins->type==DIV_INS_MULTIPCM) { if (ImGui::BeginTabItem("MultiPCM")) { String sName; @@ -2779,6 +3865,101 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndTabItem(); } } + if (ins->type==DIV_INS_SNES) if (ImGui::BeginTabItem("SNES")) { + P(ImGui::Checkbox("Use envelope",&ins->snes.useEnv)); + ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale); + if (ins->snes.useEnv) { + if (ImGui::BeginTable("SNESEnvParams",5,ImGuiTableFlags_NoHostExtendX)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + CENTER_TEXT("A"); + ImGui::TextUnformatted("A"); + ImGui::TableNextColumn(); + CENTER_TEXT("D"); + ImGui::TextUnformatted("D"); + ImGui::TableNextColumn(); + CENTER_TEXT("S"); + ImGui::TextUnformatted("S"); + ImGui::TableNextColumn(); + CENTER_TEXT("R"); + ImGui::TextUnformatted("R"); + ImGui::TableNextColumn(); + CENTER_TEXT("Envelope"); + ImGui::TextUnformatted("Envelope"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + P(CWVSliderScalar("##Attack",sliderSize,ImGuiDataType_U8,&ins->snes.a,&_ZERO,&_FIFTEEN)); + ImGui::TableNextColumn(); + P(CWVSliderScalar("##Decay",sliderSize,ImGuiDataType_U8,&ins->snes.d,&_ZERO,&_SEVEN)); + ImGui::TableNextColumn(); + P(CWVSliderScalar("##Sustain",sliderSize,ImGuiDataType_U8,&ins->snes.s,&_ZERO,&_SEVEN)); + ImGui::TableNextColumn(); + P(CWVSliderScalar("##Release",sliderSize,ImGuiDataType_U8,&ins->snes.r,&_ZERO,&_THIRTY_ONE)); + ImGui::TableNextColumn(); + drawFMEnv(0,ins->snes.a+1,1+ins->snes.d*2,ins->snes.r,ins->snes.r,(14-ins->snes.s*2),(ins->snes.r==0),0,0,7,16,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); + + ImGui::EndTable(); + } + } else { + if (ImGui::BeginTable("SNESGainParams",3,ImGuiTableFlags_NoHostExtendX)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + CENTER_TEXT("Gain Mode"); + ImGui::TextUnformatted("Gain Mode"); + ImGui::TableNextColumn(); + CENTER_TEXT("Gain"); + ImGui::TextUnformatted("Gain"); + ImGui::TableNextColumn(); + CENTER_TEXT("Envelope"); + ImGui::TextUnformatted("Envelope"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::RadioButton("Direct",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT)) { + ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_DIRECT; + PARAMETER; + } + if (ImGui::RadioButton("Decrease (linear)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DEC_LINEAR)) { + ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_DEC_LINEAR; + PARAMETER; + } + if (ImGui::RadioButton("Decrease (logarithmic)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DEC_LOG)) { + ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_DEC_LOG; + PARAMETER; + } + if (ImGui::RadioButton("Increase (linear)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_INC_LINEAR)) { + ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_INC_LINEAR; + PARAMETER; + } + if (ImGui::RadioButton("Increase (bent line)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_INC_INVLOG)) { + ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_INC_INVLOG; + PARAMETER; + } + + ImGui::TableNextColumn(); + unsigned char gainMax=(ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT)?127:31; + if (ins->snes.gain>gainMax) ins->snes.gain=gainMax; + P(CWVSliderScalar("##Gain",sliderSize,ImGuiDataType_U8,&ins->snes.gain,&_ZERO,&gainMax)); + + ImGui::TableNextColumn(); + ImGui::Text("Envelope goes here..."); + + ImGui::EndTable(); + } + } + ImGui::EndTabItem(); + } if (ins->type==DIV_INS_GB || (ins->type==DIV_INS_AMIGA && ins->amiga.useWave) || ins->type==DIV_INS_X1_010 || @@ -2787,6 +3968,7 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_SWAN || ins->type==DIV_INS_PCE || ins->type==DIV_INS_SCC || + ins->type==DIV_INS_SNES || ins->type==DIV_INS_NAMCO) { if (ImGui::BeginTabItem("Wavetable")) { if (ImGui::Checkbox("Enable synthesizer",&ins->ws.enabled)) { @@ -2952,17 +4134,21 @@ void FurnaceGUI::drawInsEdit() { if ((ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930)) { volMax=31; } - if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_VERA || ins->type==DIV_INS_VRC6_SAW) { + if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_VERA || ins->type==DIV_INS_VRC6_SAW) { volMax=63; } if (ins->type==DIV_INS_AMIGA) { volMax=64; } - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU) { + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU || ins->type==DIV_INS_OPZ) { volMax=127; } if (ins->type==DIV_INS_GB) { - volMax=0; + if (ins->gb.softEnv) { + volMax=15; + } else { + volMax=0; + } } if (ins->type==DIV_INS_PET || ins->type==DIV_INS_BEEPER) { volMax=1; @@ -2970,6 +4156,9 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_FDS) { volMax=32; } + if (ins->type==DIV_INS_ES5506) { + volMax=65535; + } const char* dutyLabel="Duty/Noise"; int dutyMin=0; @@ -3014,7 +4203,7 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Noise"; dutyMax=8; } - if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_VRC6_SAW || ins->type==DIV_INS_FDS || ins->type==DIV_INS_MULTIPCM) { + if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_VRC6_SAW || ins->type==DIV_INS_FDS || ins->type==DIV_INS_MULTIPCM) { dutyMax=0; } if (ins->type==DIV_INS_VERA) { @@ -3032,9 +4221,13 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_SU) { dutyMax=127; } + if (ins->type==DIV_INS_ES5506) { + dutyLabel="Filter Mode"; + dutyMax=3; + } const char* waveLabel="Waveform"; - int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VERA)?3:63; + int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VERA)?3:(MAX(1,e->song.waveLen-1)); bool bitMode=false; if (ins->type==DIV_INS_C64 || ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) { bitMode=true; @@ -3043,7 +4236,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_VIC || ins->type==DIV_INS_OPLL) waveMax=15; if (ins->type==DIV_INS_C64) waveMax=4; if (ins->type==DIV_INS_SAA1099) waveMax=2; - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) waveMax=0; + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPZ) waveMax=0; if (ins->type==DIV_INS_MIKEY) waveMax=0; if (ins->type==DIV_INS_MULTIPCM) waveMax=0; if (ins->type==DIV_INS_SU) waveMax=7; @@ -3071,7 +4264,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_X1_010) { dutyMax=0; ex1Max=7; - ex2Max=63; + ex2Max=255; ex2Bit=false; } if (ins->type==DIV_INS_N163) { @@ -3087,12 +4280,22 @@ void FurnaceGUI::drawInsEdit() { ex2Max=255; } if (ins->type==DIV_INS_SAA1099) ex1Max=8; + if (ins->type==DIV_INS_ES5506) { + ex1Max=65535; + ex2Max=65535; + } int panMin=0; int panMax=0; bool panSingle=false; bool panSingleNoBit=false; - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_GB || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_VERA) { + if (ins->type==DIV_INS_STD ||//Game Gear + ins->type==DIV_INS_FM || + ins->type==DIV_INS_OPL || + ins->type==DIV_INS_OPL_DRUMS || + ins->type==DIV_INS_GB || + ins->type==DIV_INS_OPZ || + ins->type==DIV_INS_VERA) { panMax=1; panSingle=true; } @@ -3116,14 +4319,19 @@ void FurnaceGUI::drawInsEdit() { panMax=127; panSingleNoBit=true; } + if (ins->type==DIV_INS_ES5506) { + panMax=65535; + } if (volMax>0) { macroList.push_back(FurnaceGUIMacroDesc(volumeLabel,&ins->std.volMacro,volMin,volMax,160,uiColors[GUI_COLOR_MACRO_VOLUME])); } - macroList.push_back(FurnaceGUIMacroDesc("Arpeggio",&ins->std.arpMacro,-120,120,160,uiColors[GUI_COLOR_MACRO_PITCH],true,macroAbsoluteMode,ins->std.arpMacro.mode?(¯oHoverNote):NULL)); + macroList.push_back(FurnaceGUIMacroDesc("Arpeggio",&ins->std.arpMacro,-120,120,160,uiColors[GUI_COLOR_MACRO_PITCH],true,NULL,macroHoverNote,false,NULL,0,true,ins->std.arpMacro.val)); if (dutyMax>0) { if (ins->type==DIV_INS_MIKEY) { macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,mikeyFeedbackBits)); + } else if (ins->type==DIV_INS_ES5506) { + macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,dutyMin,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,¯oHoverES5506FilterMode)); } else { macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,dutyMin,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER])); } @@ -3136,15 +4344,15 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("Panning",&ins->std.panLMacro,0,2,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,panBits)); } else { if (panSingleNoBit || (ins->type==DIV_INS_AMIGA && ins->std.panLMacro.mode)) { - macroList.push_back(FurnaceGUIMacroDesc("Panning",&ins->std.panLMacro,panMin,panMax,(31+panMax-panMin),uiColors[GUI_COLOR_MACRO_OTHER],false,(ins->type==DIV_INS_AMIGA)?macroQSoundMode:NULL)); + macroList.push_back(FurnaceGUIMacroDesc("Panning",&ins->std.panLMacro,panMin,panMax,CLAMP(31+panMax-panMin,32,160),uiColors[GUI_COLOR_MACRO_OTHER],false,(ins->type==DIV_INS_AMIGA)?macroQSoundMode:NULL)); } else { - macroList.push_back(FurnaceGUIMacroDesc("Panning (left)",&ins->std.panLMacro,panMin,panMax,(31+panMax-panMin),uiColors[GUI_COLOR_MACRO_OTHER],false,(ins->type==DIV_INS_AMIGA)?macroQSoundMode:NULL)); + macroList.push_back(FurnaceGUIMacroDesc("Panning (left)",&ins->std.panLMacro,panMin,panMax,CLAMP(31+panMax-panMin,32,160),uiColors[GUI_COLOR_MACRO_OTHER],false,(ins->type==DIV_INS_AMIGA)?macroQSoundMode:NULL)); } if (!panSingleNoBit) { if (ins->type==DIV_INS_AMIGA && ins->std.panLMacro.mode) { macroList.push_back(FurnaceGUIMacroDesc("Surround",&ins->std.panRMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); } else { - macroList.push_back(FurnaceGUIMacroDesc("Panning (right)",&ins->std.panRMacro,panMin,panMax,(31+panMax-panMin),uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("Panning (right)",&ins->std.panRMacro,panMin,panMax,CLAMP(31+panMax-panMin,32,160),uiColors[GUI_COLOR_MACRO_OTHER])); } } } @@ -3153,6 +4361,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_FM || ins->type==DIV_INS_STD || ins->type==DIV_INS_OPL || + ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_PCE || ins->type==DIV_INS_GB || @@ -3163,7 +4372,8 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_SWAN || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU || - ins->type==DIV_INS_MIKEY) { + ins->type==DIV_INS_MIKEY || + ins->type==DIV_INS_ES5506) { macroList.push_back(FurnaceGUIMacroDesc("Phase Reset",&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); } if (ex1Max>0) { @@ -3179,6 +4389,8 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("Mod Depth",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } else if (ins->type==DIV_INS_SU) { macroList.push_back(FurnaceGUIMacroDesc("Cutoff",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); + } else if (ins->type==DIV_INS_ES5506) { + macroList.push_back(FurnaceGUIMacroDesc("Filter K1",&ins->std.ex1Macro,((ins->std.ex1Macro.mode==1)?(-ex1Max):0),ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,macroRelativeMode)); } else { macroList.push_back(FurnaceGUIMacroDesc("Duty",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } @@ -3192,6 +4404,8 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("Mod Speed",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } else if (ins->type==DIV_INS_SU) { macroList.push_back(FurnaceGUIMacroDesc("Resonance",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); + } else if (ins->type==DIV_INS_ES5506) { + macroList.push_back(FurnaceGUIMacroDesc("Filter K2",&ins->std.ex2Macro,((ins->std.ex2Macro.mode==1)?(-ex2Max):0),ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,macroRelativeMode)); } else { macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex2Macro,0,ex2Max,ex2Bit?64:160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,ex2Bit,ayEnvBits)); } @@ -3220,6 +4434,16 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_SU) { macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.ex3Macro,0,4,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,suControlBits)); + macroList.push_back(FurnaceGUIMacroDesc("Phase Reset Timer",&ins->std.ex4Macro,0,65535,160,uiColors[GUI_COLOR_MACRO_OTHER])); // again reuse code from resonance macro but use ex4 instead + } + if (ins->type==DIV_INS_ES5506) { + macroList.push_back(FurnaceGUIMacroDesc("Envelope counter",&ins->std.ex3Macro,0,511,160,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("Envelope left volume ramp",&ins->std.ex4Macro,-128,127,160,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("Envelope right volume ramp",&ins->std.ex5Macro,-128,127,160,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("Envelope K1 ramp",&ins->std.ex6Macro,-128,127,160,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("Envelope K2 ramp",&ins->std.ex7Macro,-128,127,160,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("Envelope mode",&ins->std.ex8Macro,0,2,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,es5506EnvelopeModes)); + macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.algMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,es5506ControlModes)); } drawMacros(macroList); @@ -3259,8 +4483,8 @@ void FurnaceGUI::drawInsEdit() { ImGui::Separator(); if (ImGui::MenuItem("clear")) { lastMacroDesc.macro->len=0; - lastMacroDesc.macro->loop=-1; - lastMacroDesc.macro->rel=-1; + lastMacroDesc.macro->loop=255; + lastMacroDesc.macro->rel=255; for (int i=0; i<256; i++) { lastMacroDesc.macro->val[i]=0; } @@ -3289,15 +4513,15 @@ void FurnaceGUI::drawInsEdit() { lastMacroDesc.macro->val[i]=val; } - if (lastMacroDesc.macro->loop>=0 && lastMacroDesc.macro->looplen) { + if (lastMacroDesc.macro->looplen) { lastMacroDesc.macro->loop+=macroOffX; } else { - lastMacroDesc.macro->loop=-1; + lastMacroDesc.macro->loop=255; } if ((lastMacroDesc.macro->rel+macroOffX)>=0 && (lastMacroDesc.macro->rel+macroOffX)len) { lastMacroDesc.macro->rel+=macroOffX; } else { - lastMacroDesc.macro->rel=-1; + lastMacroDesc.macro->rel=255; } ImGui::CloseCurrentPopup(); @@ -3332,6 +4556,28 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndMenu(); } + if (ImGui::BeginMenu("randomize...")) { + if (macroRandMinlastMacroDesc.max) macroRandMin=lastMacroDesc.max; + if (macroRandMaxlastMacroDesc.max) macroRandMax=lastMacroDesc.max; + ImGui::InputInt("Min",¯oRandMin,1,10); + ImGui::InputInt("Max",¯oRandMax,1,10); + if (ImGui::Button("randomize")) { + for (int i=0; ilen; i++) { + int val=0; + if (macroRandMax<=macroRandMin) { + val=macroRandMin; + } else { + val=macroRandMin+(rand()%(macroRandMax-macroRandMin+1)); + } + lastMacroDesc.macro->val[i]=val; + } + + ImGui::CloseCurrentPopup(); + } + ImGui::EndMenu(); + } ImGui::EndPopup(); } diff --git a/src/gui/intConst.cpp b/src/gui/intConst.cpp index 8bee0f88..9a41486e 100644 --- a/src/gui/intConst.cpp +++ b/src/gui/intConst.cpp @@ -31,6 +31,9 @@ const int _SIXTY_FOUR=64; const int _ONE_HUNDRED=100; const int _ONE_HUNDRED_TWENTY_SEVEN=127; const int _TWO_HUNDRED_FIFTY_FIVE=255; +const int _FIVE_HUNDRED_ELEVEN=511; const int _TWO_THOUSAND_FORTY_SEVEN=2047; const int _FOUR_THOUSAND_NINETY_FIVE=4095; +const int _SIXTY_FIVE_THOUSAND_FIVE_HUNDRED_THIRTY_FIVE=65535; const int _MINUS_ONE_HUNDRED_TWENTY_SEVEN=-127; +const int _MINUS_ONE_HUNDRED_TWENTY_EIGHT=-128; diff --git a/src/gui/intConst.h b/src/gui/intConst.h index 98c6c34f..ff11a496 100644 --- a/src/gui/intConst.h +++ b/src/gui/intConst.h @@ -33,6 +33,9 @@ extern const int _SIXTY_FOUR; extern const int _ONE_HUNDRED; extern const int _ONE_HUNDRED_TWENTY_SEVEN; extern const int _TWO_HUNDRED_FIFTY_FIVE; +extern const int _FIVE_HUNDRED_ELEVEN; extern const int _TWO_THOUSAND_FORTY_SEVEN; extern const int _FOUR_THOUSAND_NINETY_FIVE; +extern const int _SIXTY_FIVE_THOUSAND_FIVE_HUNDRED_THIRTY_FIVE; extern const int _MINUS_ONE_HUNDRED_TWENTY_SEVEN; +extern const int _MINUS_ONE_HUNDRED_TWENTY_EIGHT; diff --git a/src/gui/newSong.cpp b/src/gui/newSong.cpp index 0dea772b..c819ffd3 100644 --- a/src/gui/newSong.cpp +++ b/src/gui/newSong.cpp @@ -18,7 +18,8 @@ */ #include "gui.h" -#include +#include "misc/cpp/imgui_stdlib.h" +#include void FurnaceGUI::drawNewSong() { bool accepted=false; @@ -32,39 +33,81 @@ void FurnaceGUI::drawNewSong() { avail.y-=ImGui::GetFrameHeightWithSpacing(); if (ImGui::BeginChild("sysPickerC",avail,false,ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) { - if (ImGui::BeginTable("sysPicker",2,ImGuiTableFlags_BordersInnerV)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0f); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputTextWithHint("##SysSearch","Search...",&newSongQuery)) { + String lowerCase=newSongQuery; + for (char& i: lowerCase) { + if (i>='A' && i<='Z') i+='a'-'A'; + } + auto lastItem=std::remove_if(lowerCase.begin(),lowerCase.end(),[](char c) { + return (c==' ' || c=='_' || c=='-'); + }); + lowerCase.erase(lastItem,lowerCase.end()); + newSongSearchResults.clear(); + for (FurnaceGUISysCategory& i: sysCategories) { + for (FurnaceGUISysDef& j: i.systems) { + String lowerCase1=j.name; + for (char& i: lowerCase1) { + if (i>='A' && i<='Z') i+='a'-'A'; + } + auto lastItem=std::remove_if(lowerCase1.begin(),lowerCase1.end(),[](char c) { + return (c==' ' || c=='_' || c=='-'); + }); + lowerCase1.erase(lastItem,lowerCase1.end()); + if (lowerCase1.find(lowerCase)!=String::npos) { + newSongSearchResults.push_back(j); + } + } + std::sort(newSongSearchResults.begin(),newSongSearchResults.end(),[](const FurnaceGUISysDef& a, const FurnaceGUISysDef& b) { + return strcmp(a.name,b.name)<0; + }); + auto lastItem=std::unique(newSongSearchResults.begin(),newSongSearchResults.end(),[](const FurnaceGUISysDef& a, const FurnaceGUISysDef& b) { + return strcmp(a.name,b.name)==0; + }); + newSongSearchResults.erase(lastItem,newSongSearchResults.end()); + } + } + if (ImGui::BeginTable("sysPicker",newSongQuery.empty()?2:1,ImGuiTableFlags_BordersInnerV)) { + if (newSongQuery.empty()) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0f); + } ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0f); - ImGui::TableNextRow(ImGuiTableRowFlags_Headers); - ImGui::TableNextColumn(); - ImGui::Text("Categories"); - ImGui::TableNextColumn(); - ImGui::Text("Systems"); + if (newSongQuery.empty()) { + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Categories"); + ImGui::TableNextColumn(); + ImGui::Text("Systems"); + } ImGui::TableNextRow(); // CATEGORIES - ImGui::TableNextColumn(); - int index=0; - for (FurnaceGUISysCategory& i: sysCategories) { - if (ImGui::Selectable(i.name,newSongCategory==index,ImGuiSelectableFlags_DontClosePopups)) { \ - newSongCategory=index; + if (newSongQuery.empty()) { + ImGui::TableNextColumn(); + int index=0; + for (FurnaceGUISysCategory& i: sysCategories) { + if (ImGui::Selectable(i.name,newSongCategory==index,ImGuiSelectableFlags_DontClosePopups)) { \ + newSongCategory=index; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s",i.description); + } + index++; } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("%s",i.description); - } - index++; } // SYSTEMS ImGui::TableNextColumn(); if (ImGui::BeginTable("Systems",1,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollY)) { - for (FurnaceGUISysDef& i: sysCategories[newSongCategory].systems) { + std::vector& category=(newSongQuery.empty())?(sysCategories[newSongCategory].systems):(newSongSearchResults); + for (FurnaceGUISysDef& i: category) { ImGui::TableNextRow(); ImGui::TableNextColumn(); if (ImGui::Selectable(i.name,false,ImGuiSelectableFlags_DontClosePopups)) { nextDesc=i.definition.data(); + nextDescName=i.name; accepted=true; } } @@ -97,7 +140,7 @@ void FurnaceGUI::drawNewSong() { } if (accepted) { - e->createNew(nextDesc); + e->createNew(nextDesc,nextDescName); undoHist.clear(); redoHist.clear(); curFileName=""; diff --git a/src/gui/patManager.cpp b/src/gui/patManager.cpp new file mode 100644 index 00000000..22d5b645 --- /dev/null +++ b/src/gui/patManager.cpp @@ -0,0 +1,113 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" +#include "misc/cpp/imgui_stdlib.h" +#include "IconsFontAwesome4.h" +#include + +void FurnaceGUI::drawPatManager() { + if (nextWindow==GUI_WINDOW_PAT_MANAGER) { + patManagerOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!patManagerOpen) return; + char id[1024]; + unsigned char isUsed[256]; + bool isNull[256]; + if (ImGui::Begin("Pattern Manager",&patManagerOpen,globalWinFlags)) { + ImGui::Text("Global Tasks"); + + if (ImGui::Button("De-duplicate patterns")) { + e->lockEngine([this]() { + e->curSubSong->optimizePatterns(); + }); + } + ImGui::SameLine(); + if (ImGui::Button("Re-arrange patterns")) { + e->lockEngine([this]() { + e->curSubSong->rearrangePatterns(); + }); + } + + if (ImGui::BeginTable("PatManTable",257,ImGuiTableFlags_ScrollX|ImGuiTableFlags_SizingFixedFit)) { + ImGui::PushFont(patFont); + + for (int i=0; igetTotalChannelCount(); i++) { + ImGui::TableNextRow(); + memset(isUsed,0,256); + memset(isNull,0,256*sizeof(bool)); + for (int j=0; jcurSubSong->ordersLen; j++) { + isUsed[e->curSubSong->orders.ord[i][j]]++; + } + for (int j=0; j<256; j++) { + isNull[j]=(e->curSubSong->pat[i].data[j]==NULL); + } + ImGui::TableNextColumn(); + ImGui::Text("%s",e->getChannelShortName(i)); + + ImGui::PushID(1000+i); + for (int k=0; k<256; k++) { + ImGui::TableNextColumn(); + + snprintf(id,1023,"%.2X",k); + if (isNull[k]) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PAT_MANAGER_NULL]); + } else if (isUsed[k]>=e->curSubSong->ordersLen) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PAT_MANAGER_COMBO_BREAKER]); + } else if (isUsed[k]>=0.7*(double)e->curSubSong->ordersLen) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PAT_MANAGER_EXTREMELY_OVERUSED]); + } else if (isUsed[k]>=0.4*(double)e->curSubSong->ordersLen) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PAT_MANAGER_OVERUSED]); + } else if (isUsed[k]) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PAT_MANAGER_USED]); + } else { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PAT_MANAGER_UNUSED]); + } + ImGui::Selectable(id,isUsed[k]); + if (ImGui::IsItemHovered()) { + ImGui::PushFont(mainFont); + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); + if (isNull[k]) { + ImGui::SetTooltip("Pattern %.2X\n- not allocated",k); + } else { + ImGui::SetTooltip("Pattern %.2X\n- use count: %d (%.0f%%)\n\nright-click to erase",k,isUsed[k],100.0*(double)isUsed[k]/(double)e->curSubSong->ordersLen); + } + ImGui::PopStyleColor(); + ImGui::PopFont(); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + e->lockEngine([this,i,k]() { + delete e->curSubSong->pat[i].data[k]; + e->curSubSong->pat[i].data[k]=NULL; + }); + } + ImGui::PopStyleColor(); + } + ImGui::PopID(); + } + ImGui::PopFont(); + + ImGui::EndTable(); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_PAT_MANAGER; + ImGui::End(); +} diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index fa00243c..4fbb9997 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -17,7 +17,6 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include #define _USE_MATH_DEFINES #include "gui.h" #include "../ta-log.h" @@ -31,9 +30,33 @@ inline float randRange(float min, float max) { return min+((float)rand()/(float)RAND_MAX)*(max-min); } +void _pushPartBlend(const ImDrawList* drawList, const ImDrawCmd* cmd) { + if (cmd!=NULL) { + if (cmd->UserCallbackData!=NULL) { + ((FurnaceGUI*)cmd->UserCallbackData)->pushPartBlend(); + } + } +} + +void _popPartBlend(const ImDrawList* drawList, const ImDrawCmd* cmd) { + if (cmd!=NULL) { + if (cmd->UserCallbackData!=NULL) { + ((FurnaceGUI*)cmd->UserCallbackData)->popPartBlend(); + } + } +} + +void FurnaceGUI::pushPartBlend() { + SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_ADD); +} + +void FurnaceGUI::popPartBlend() { + SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_BLEND); +} + // draw a pattern row inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache, bool inhibitSel) { - static char id[32]; + static char id[64]; bool selectedRow=(i>=sel1.y && i<=sel2.y && !inhibitSel); ImGui::TableNextRow(0,lineHeight); ImGui::TableNextColumn(); @@ -91,9 +114,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int ImGui::PushStyleColor(ImGuiCol_Text,rowIndexColor); if (settings.patRowsBase==1) { - snprintf(id,31," %.2X ##PR_%d",i,i); + snprintf(id,63," %.2X ##PR_%d",i,i); } else { - snprintf(id,31,"%3d ##PR_%d",i,i); + snprintf(id,63,"%3d ##PR_%d",i,i); } ImGui::Selectable(id,false,ImGuiSelectableFlags_NoPadWithHalfSpacing,fourChars); if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) { @@ -128,7 +151,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int bool cursorVol=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==2 && curWindowLast==GUI_WINDOW_PATTERN); // note - sprintf(id,"%s##PN_%d_%d",noteName(pat->data[i][0],pat->data[i][1]),i,j); + snprintf(id,63,"%.31s##PN_%d_%d",noteName(pat->data[i][0],pat->data[i][1]),i,j); if (pat->data[i][0]==0 && pat->data[i][1]==0) { ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor); } else { @@ -159,7 +182,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int // instrument if (pat->data[i][2]==-1) { ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor); - sprintf(id,"..##PI_%d_%d",i,j); + snprintf(id,63,"%.31s##PI_%d_%d",emptyLabel2,i,j); } else { if (pat->data[i][2]<0 || pat->data[i][2]>=e->song.insLen) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS_ERROR]); @@ -171,7 +194,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS]); } } - sprintf(id,"%.2X##PI_%d_%d",pat->data[i][2],i,j); + snprintf(id,63,"%.2X##PI_%d_%d",pat->data[i][2],i,j); } ImGui::SameLine(0.0f,0.0f); if (cursorIns) { @@ -198,13 +221,13 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int if (e->curSubSong->chanCollapse[j]<2) { // volume if (pat->data[i][3]==-1) { - sprintf(id,"..##PV_%d_%d",i,j); + snprintf(id,63,"%.31s##PV_%d_%d",emptyLabel2,i,j); ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor); } else { int volColor=(pat->data[i][3]*127)/chanVolMax; if (volColor>127) volColor=127; if (volColor<0) volColor=0; - sprintf(id,"%.2X##PV_%d_%d",pat->data[i][3],i,j); + snprintf(id,63,"%.2X##PV_%d_%d",pat->data[i][3],i,j); ImGui::PushStyleColor(ImGuiCol_Text,volColors[volColor]); } ImGui::SameLine(0.0f,0.0f); @@ -240,15 +263,15 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int // effect if (pat->data[i][index]==-1) { - sprintf(id,"..##PE%d_%d_%d",k,i,j); + snprintf(id,63,"%.31s##PE%d_%d_%d",emptyLabel2,k,i,j); ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor); } else { if (pat->data[i][index]>0xff) { - sprintf(id,"??##PE%d_%d_%d",k,i,j); + snprintf(id,63,"??##PE%d_%d_%d",k,i,j); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]); } else { const unsigned char data=pat->data[i][index]; - sprintf(id,"%.2X##PE%d_%d_%d",data,k,i,j); + snprintf(id,63,"%.2X##PE%d_%d_%d",data,k,i,j); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[fxColors[data]]); } } @@ -274,9 +297,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int // effect value if (pat->data[i][index+1]==-1) { - sprintf(id,"..##PF%d_%d_%d",k,i,j); + snprintf(id,63,"%.31s##PF%d_%d_%d",emptyLabel2,k,i,j); } else { - sprintf(id,"%.2X##PF%d_%d_%d",pat->data[i][index+1],k,i,j); + snprintf(id,63,"%.2X##PF%d_%d_%d",pat->data[i][index+1],k,i,j); } ImGui::SameLine(0.0f,0.0f); if (cursorEffectVal) { @@ -349,10 +372,17 @@ void FurnaceGUI::drawPattern() { sel2.xFine^=sel1.xFine; } ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0.0f,0.0f)); + if (mobileUI) { + patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f)); + patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f))); + ImGui::SetNextWindowPos(patWindowPos); + ImGui::SetNextWindowSize(patWindowSize); + } if (ImGui::Begin("Pattern",&patternOpen,globalWinFlags|(settings.avoidRaisingPattern?ImGuiWindowFlags_NoBringToFrontOnFocus:0))) { - //ImGui::SetWindowSize(ImVec2(scrW*dpiScale,scrH*dpiScale)); - patWindowPos=ImGui::GetWindowPos(); - patWindowSize=ImGui::GetWindowSize(); + if (!mobileUI) { + patWindowPos=ImGui::GetWindowPos(); + patWindowSize=ImGui::GetWindowSize(); + } //char id[32]; ImGui::PushFont(patFont); int ord=oldOrder; @@ -438,20 +468,44 @@ void FurnaceGUI::drawPattern() { snprintf(chanID,2048," %s##_CH%d",chName,i); } } + bool muted=e->isChannelMuted(i); - ImVec4 chanHead=muted?uiColors[GUI_COLOR_CHANNEL_MUTED]:uiColors[GUI_COLOR_CHANNEL_FM+e->getChannelType(i)]; + ImVec4 chanHead=muted?uiColors[GUI_COLOR_CHANNEL_MUTED]:channelColor(i); ImVec4 chanHeadActive=chanHead; ImVec4 chanHeadHover=chanHead; + if (e->keyHit[i]) { - keyHit[i]=0.2; - if (!muted) { - int note=e->getChanState(i)->note+60; - if (note>=0 && note<180) { - pianoKeyHit[note]=1.0; + if (settings.channelFeedbackStyle==1) { + keyHit[i]=0.2; + if (!muted) { + int note=e->getChanState(i)->note+60; + if (note>=0 && note<180) { + pianoKeyHit[note]=1.0; + } } } e->keyHit[i]=false; } + if (settings.channelFeedbackStyle==2 && e->isRunning()) { + float amount=((float)(e->getChanState(i)->volume>>8)/(float)e->getMaxVolumeChan(i)); + if (!e->getChanState(i)->keyOn) amount=0.0f; + keyHit[i]=amount*0.2f; + if (!muted) { + int note=e->getChanState(i)->note+60; + if (note>=0 && note<180) { + pianoKeyHit[note]=amount; + } + } + } else if (settings.channelFeedbackStyle==3 && e->isRunning()) { + bool active=e->getChanState(i)->keyOn; + keyHit[i]=active?0.2f:0.0f; + if (!muted) { + int note=e->getChanState(i)->note+60; + if (note>=0 && note<180) { + pianoKeyHit[note]=active?1.0f:0.0f; + } + } + } if (settings.guiColorsBase) { chanHead.x*=1.0-keyHit[i]; chanHead.y*=1.0-keyHit[i]; chanHead.z*=1.0-keyHit[i]; chanHeadActive.x*=0.5; chanHeadActive.y*=0.5; chanHeadActive.z*=0.5; @@ -466,12 +520,18 @@ void FurnaceGUI::drawPattern() { ImGui::PushStyleColor(ImGuiCol_Header,chanHead); ImGui::PushStyleColor(ImGuiCol_HeaderActive,chanHeadActive); ImGui::PushStyleColor(ImGuiCol_HeaderHovered,chanHeadHover); + ImGui::PushStyleColor(ImGuiCol_Text,ImGui::GetColorU32(channelTextColor(i))); ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(chanHead)); if (muted) ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_CHANNEL_MUTED]); + if (settings.channelFont==0) ImGui::PushFont(mainFont); + + // TODO: appearance ImGui::Selectable(chanID,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+1.0f*dpiScale)); + if (displayTooltip && ImGui::IsItemHovered()) { ImGui::SetTooltip("%s",e->getChannelName(i)); } + if (settings.channelFont==0) ImGui::PopFont(); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { if (settings.soloAction!=1 && soloTimeout>0 && soloChan==i) { e->toggleSolo(i); @@ -483,7 +543,7 @@ void FurnaceGUI::drawPattern() { } } if (muted) ImGui::PopStyleColor(); - ImGui::PopStyleColor(3); + ImGui::PopStyleColor(4); if (settings.soloAction!=2) if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { inhibitMenu=true; e->toggleSolo(i); @@ -678,6 +738,15 @@ void FurnaceGUI::drawPattern() { if (i.cmd==DIV_CMD_SAMPLE_BANK) continue; if (i.cmd==DIV_CMD_GET_VOLUME) continue; if (i.cmd==DIV_ALWAYS_SET_VOLUME) continue; + if (i.cmd==DIV_CMD_HINT_VOLUME || + i.cmd==DIV_CMD_HINT_PORTA || + i.cmd==DIV_CMD_HINT_LEGATO || + i.cmd==DIV_CMD_HINT_VOL_SLIDE || + i.cmd==DIV_CMD_HINT_ARPEGGIO || + i.cmd==DIV_CMD_HINT_PITCH || + i.cmd==DIV_CMD_HINT_VIBRATO || + i.cmd==DIV_CMD_HINT_VIBRATO_RANGE || + i.cmd==DIV_CMD_HINT_VIBRATO_SHAPE) continue; float width=patChanX[i.chan+1]-patChanX[i.chan]; float speedX=0.0f; @@ -692,11 +761,14 @@ void FurnaceGUI::drawPattern() { ImU32* color=noteGrad; switch (i.cmd) { - case DIV_CMD_NOTE_ON: + case DIV_CMD_NOTE_ON: { + float strength=CLAMP(i.value,0,119); partIcon=ICON_FA_ASTERISK; - life=96.0f; + life=80.0f+((i.value==DIV_NOTE_NULL)?0.0f:(strength*0.3f)); lifeSpeed=3.0f; + num=6+(strength/16); break; + } case DIV_CMD_LEGATO: partIcon=ICON_FA_COG; color=insGrad; @@ -794,7 +866,7 @@ void FurnaceGUI::drawPattern() { float frameTime=ImGui::GetIO().DeltaTime*60.0f; - // note slides + // note slides and vibrato ImVec2 arrowPoints[7]; if (e->isPlaying()) for (int i=0; icurSubSong->chanShow[i]) continue; @@ -849,11 +921,30 @@ void FurnaceGUI::drawPattern() { } } } + if (ch->vibratoDepth>0) { + ImVec4 col=uiColors[GUI_COLOR_PATTERN_EFFECT_PITCH]; + col.w*=0.2; + float width=patChanX[i+1]-patChanX[i]; + + particles.push_back(Particle( + pitchGrad, + ICON_FA_GLASS, + off.x+patChanX[i]+(width*0.5+0.5*sin(M_PI*(float)ch->vibratoPosGiant/64.0f)*width)-scrollX, + off.y+(ImGui::GetWindowHeight()*0.5f)+randRange(0,patFont->FontSize), + randRange(-4.0f,4.0f), + 2.0f*(3.0f+(rand()%5)+ch->vibratoRate), + 0.4f, + 1.0f, + 128.0f, + 4.0f + )); + } } // particle simulation ImDrawList* fdl=ImGui::GetForegroundDrawList(); if (!particles.empty()) WAKE_UP; + fdl->AddCallback(_pushPartBlend,this); for (size_t i=0; iAddCallback(_popPartBlend,this); } ImGui::PopStyleColor(3); diff --git a/src/gui/piano.cpp b/src/gui/piano.cpp index 0e0063bb..082cc65e 100644 --- a/src/gui/piano.cpp +++ b/src/gui/piano.cpp @@ -52,27 +52,28 @@ void FurnaceGUI::drawPiano() { nextWindow=GUI_WINDOW_NOTHING; } if (!pianoOpen) return; + if (mobileUI) { + ImGui::SetNextWindowPos(ImVec2(patWindowPos.x,patWindowPos.y+patWindowSize.y)); + ImGui::SetNextWindowSize(portrait?ImVec2(scrW*dpiScale,0.4*scrW*dpiScale):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),0.3*scrH*dpiScale)); + } if (ImGui::Begin("Piano",&pianoOpen,((pianoOptions)?0:ImGuiWindowFlags_NoTitleBar)|ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) { bool oldPianoKeyPressed[180]; memcpy(oldPianoKeyPressed,pianoKeyPressed,180*sizeof(bool)); memset(pianoKeyPressed,0,180*sizeof(bool)); - if (ImGui::BeginTable("PianoLayout",(pianoOptions?2:1)+((pianoInputPadMode==1 && cursor.xFine>0)?1:0),ImGuiTableFlags_BordersInnerV)) { + if (ImGui::BeginTable("PianoLayout",((pianoOptions && (!mobileUI || !portrait))?2:1),ImGuiTableFlags_BordersInnerV)) { int& off=(e->isPlaying() || pianoSharePosition)?pianoOffset:pianoOffsetEdit; int& oct=(e->isPlaying() || pianoSharePosition)?pianoOctaves:pianoOctavesEdit; bool view=(pianoView==2)?(!e->isPlaying()):pianoView; - if (pianoOptions) { + if (pianoOptions && (!mobileUI || !portrait)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); } - if (pianoInputPadMode==1 && cursor.xFine>0) { - ImGui::TableSetupColumn("c0s",ImGuiTableColumnFlags_WidthStretch,2.0f); - } ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,1.0f); ImGui::TableNextRow(); if (pianoOptions) { ImGui::TableNextColumn(); - float optionSizeY=ImGui::GetContentRegionAvail().y*0.5-ImGui::GetStyle().ItemSpacing.y; - ImVec2 optionSize=ImVec2(1.2f*optionSizeY,optionSizeY); + float optionSizeY=ImGui::GetContentRegionAvail().y*((mobileUI && portrait)?0.3:0.5)-ImGui::GetStyle().ItemSpacing.y; + ImVec2 optionSize=ImVec2((mobileUI && portrait)?((ImGui::GetContentRegionAvail().x-ImGui::GetStyle().ItemSpacing.x*5.0f)/6.0f):(1.2f*optionSizeY),optionSizeY); if (pianoOptionsSet) { if (ImGui::Button("OFF##PianoNOff",optionSize)) { if (edit) noteInput(0,100); @@ -122,6 +123,10 @@ void FurnaceGUI::drawPiano() { ImGui::EndPopup(); } + if (mobileUI && portrait) { + ImGui::SameLine(); + } + if (pianoOptionsSet) { if (ImGui::Button("REL##PianoNMRel",optionSize)) { if (edit) noteInput(0,102); @@ -148,6 +153,10 @@ void FurnaceGUI::drawPiano() { } } + if (mobileUI && portrait) { + ImGui::TableNextRow(); + } + ImGui::TableNextColumn(); if (pianoInputPadMode==1 && cursor.xFine>0) { ImVec2 buttonSize=ImGui::GetContentRegionAvail(); @@ -195,192 +204,192 @@ void FurnaceGUI::drawPiano() { ImGui::EndTable(); } - ImGui::TableNextColumn(); - } - ImGuiWindow* window=ImGui::GetCurrentWindow(); - ImVec2 size=ImGui::GetContentRegionAvail(); - ImDrawList* dl=ImGui::GetWindowDrawList(); + } else { + ImGuiWindow* window=ImGui::GetCurrentWindow(); + ImVec2 size=ImGui::GetContentRegionAvail(); + ImDrawList* dl=ImGui::GetWindowDrawList(); - ImVec2 minArea=window->DC.CursorPos; - ImVec2 maxArea=ImVec2( - minArea.x+size.x, - minArea.y+size.y - ); - ImRect rect=ImRect(minArea,maxArea); + ImVec2 minArea=window->DC.CursorPos; + ImVec2 maxArea=ImVec2( + minArea.x+size.x, + minArea.y+size.y + ); + ImRect rect=ImRect(minArea,maxArea); - // render piano - //ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y); - if (ImGui::ItemAdd(rect,ImGui::GetID("pianoDisplay"))) { - ImGui::ItemHoverable(rect,ImGui::GetID("pianoDisplay")); - if (view) { - int notes=oct*12; - // evaluate input - for (TouchPoint& i: activePoints) { - if (rect.Contains(ImVec2(i.x,i.y))) { - int note=(((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*notes)+12*off; - if (note<0) continue; - if (note>=180) continue; - pianoKeyPressed[note]=true; - } - } - - for (int i=0; i=180) continue; - float pkh=pianoKeyHit[note]; - ImVec4 color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM]; - if (pianoKeyPressed[note]) { - color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE]; - } else { - ImVec4 colorHit=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT]; - color.x+=(colorHit.x-color.x)*pkh; - color.y+=(colorHit.y-color.y)*pkh; - color.z+=(colorHit.z-color.z)*pkh; - color.w+=(colorHit.w-color.w)*pkh; - } - ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/notes,0.0f)); - ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/notes,1.0f)); - p1.x-=dpiScale; - dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color)); - if ((i%12)==0) { - String label=fmt::sprintf("%d",(note-60)/12); - ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f)); - ImVec2 labelSize=ImGui::CalcTextSize(label.c_str()); - pText.x-=labelSize.x*0.5f; - pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y; - dl->AddText(pText,0xff404040,label.c_str()); - } - } - } else { - int bottomNotes=7*oct; - // evaluate input - for (TouchPoint& i: activePoints) { - if (rect.Contains(ImVec2(i.x,i.y))) { - // top - int o=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*oct; - ImVec2 op0=ImLerp(rect.Min,rect.Max,ImVec2((float)o/oct,0.0f)); - ImVec2 op1=ImLerp(rect.Min,rect.Max,ImVec2((float)(o+1)/oct,1.0f)); - bool foundTopKey=false; - - for (int j=0; j<5; j++) { - int note=topKeyNotes[j]+12*(o+off); + // render piano + //ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID("pianoDisplay"))) { + ImGui::ItemHoverable(rect,ImGui::GetID("pianoDisplay")); + if (view) { + int notes=oct*12; + // evaluate input + for (TouchPoint& i: activePoints) { + if (rect.Contains(ImVec2(i.x,i.y))) { + int note=(((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*notes)+12*off; if (note<0) continue; if (note>=180) continue; - ImRect keyRect=ImRect( - ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f)), - ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f)) - ); - if (keyRect.Contains(ImVec2(i.x,i.y))) { - pianoKeyPressed[note]=true; - foundTopKey=true; - break; - } + pianoKeyPressed[note]=true; } - if (foundTopKey) continue; - - // bottom - int n=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*bottomNotes; - int note=bottomKeyNotes[n%7]+12*((n/7)+off); - if (note<0) continue; - if (note>=180) continue; - pianoKeyPressed[note]=true; - } - } - - for (int i=0; i=180) continue; - - float pkh=pianoKeyHit[note]; - ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM]; - if (pianoKeyPressed[note]) { - color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE]; - } else { - ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT]; - color.x+=(colorHit.x-color.x)*pkh; - color.y+=(colorHit.y-color.y)*pkh; - color.z+=(colorHit.z-color.z)*pkh; - color.w+=(colorHit.w-color.w)*pkh; } - ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/bottomNotes,0.0f)); - ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/bottomNotes,1.0f)); - p1.x-=dpiScale; - - dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color)); - if ((i%7)==0) { - String label=fmt::sprintf("%d",(note-60)/12); - ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f)); - ImVec2 labelSize=ImGui::CalcTextSize(label.c_str()); - pText.x-=labelSize.x*0.5f; - pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y; - dl->AddText(pText,0xff404040,label.c_str()); - } - } - - for (int i=0; i=180) continue; float pkh=pianoKeyHit[note]; - ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_TOP]; + ImVec4 color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM]; if (pianoKeyPressed[note]) { - color=uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE]; + color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE]; } else { - ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]; + ImVec4 colorHit=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT]; color.x+=(colorHit.x-color.x)*pkh; color.y+=(colorHit.y-color.y)*pkh; color.z+=(colorHit.z-color.z)*pkh; color.w+=(colorHit.w-color.w)*pkh; } - ImVec2 p0=ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f)); - ImVec2 p1=ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f)); - dl->AddRectFilled(p0,p1,ImGui::GetColorU32(uiColors[GUI_COLOR_PIANO_BACKGROUND])); - p0.x+=dpiScale; + ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/notes,0.0f)); + ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/notes,1.0f)); p1.x-=dpiScale; - p1.y-=dpiScale; dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color)); + if ((i%12)==0) { + String label=fmt::sprintf("%d",(note-60)/12); + ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f)); + ImVec2 labelSize=ImGui::CalcTextSize(label.c_str()); + pText.x-=labelSize.x*0.5f; + pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y; + dl->AddText(pText,0xff404040,label.c_str()); + } + } + } else { + int bottomNotes=7*oct; + // evaluate input + for (TouchPoint& i: activePoints) { + if (rect.Contains(ImVec2(i.x,i.y))) { + // top + int o=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*oct; + ImVec2 op0=ImLerp(rect.Min,rect.Max,ImVec2((float)o/oct,0.0f)); + ImVec2 op1=ImLerp(rect.Min,rect.Max,ImVec2((float)(o+1)/oct,1.0f)); + bool foundTopKey=false; + + for (int j=0; j<5; j++) { + int note=topKeyNotes[j]+12*(o+off); + if (note<0) continue; + if (note>=180) continue; + ImRect keyRect=ImRect( + ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f)), + ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f)) + ); + if (keyRect.Contains(ImVec2(i.x,i.y))) { + pianoKeyPressed[note]=true; + foundTopKey=true; + break; + } + } + if (foundTopKey) continue; + + // bottom + int n=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*bottomNotes; + int note=bottomKeyNotes[n%7]+12*((n/7)+off); + if (note<0) continue; + if (note>=180) continue; + pianoKeyPressed[note]=true; + } + } + + for (int i=0; i=180) continue; + + float pkh=pianoKeyHit[note]; + ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM]; + if (pianoKeyPressed[note]) { + color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE]; + } else { + ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT]; + color.x+=(colorHit.x-color.x)*pkh; + color.y+=(colorHit.y-color.y)*pkh; + color.z+=(colorHit.z-color.z)*pkh; + color.w+=(colorHit.w-color.w)*pkh; + } + + ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/bottomNotes,0.0f)); + ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/bottomNotes,1.0f)); + p1.x-=dpiScale; + + dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color)); + if ((i%7)==0) { + String label=fmt::sprintf("%d",(note-60)/12); + ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f)); + ImVec2 labelSize=ImGui::CalcTextSize(label.c_str()); + pText.x-=labelSize.x*0.5f; + pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y; + dl->AddText(pText,0xff404040,label.c_str()); + } + } + + for (int i=0; i=180) continue; + float pkh=pianoKeyHit[note]; + ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_TOP]; + if (pianoKeyPressed[note]) { + color=uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE]; + } else { + ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]; + color.x+=(colorHit.x-color.x)*pkh; + color.y+=(colorHit.y-color.y)*pkh; + color.z+=(colorHit.z-color.z)*pkh; + color.w+=(colorHit.w-color.w)*pkh; + } + ImVec2 p0=ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f)); + ImVec2 p1=ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f)); + dl->AddRectFilled(p0,p1,ImGui::GetColorU32(uiColors[GUI_COLOR_PIANO_BACKGROUND])); + p0.x+=dpiScale; + p1.x-=dpiScale; + p1.y-=dpiScale; + dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color)); + } + } + } + + const float reduction=ImGui::GetIO().DeltaTime*60.0f*0.12; + for (int i=0; i<180; i++) { + pianoKeyHit[i]-=reduction; + if (pianoKeyHit[i]<0) pianoKeyHit[i]=0; + } + } + + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + pianoOptions=!pianoOptions; + } + + // first check released keys + for (int i=0; i<180; i++) { + int note=i-60; + if (!pianoKeyPressed[i]) { + if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) { + e->synchronized([this,note]() { + e->autoNoteOff(-1,note); + }); } } } - - const float reduction=ImGui::GetIO().DeltaTime*60.0f*0.12; + // then pressed ones for (int i=0; i<180; i++) { - pianoKeyHit[i]-=reduction; - if (pianoKeyHit[i]<0) pianoKeyHit[i]=0; - } - } - - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - pianoOptions=!pianoOptions; - } - - // first check released keys - for (int i=0; i<180; i++) { - int note=i-60; - if (!pianoKeyPressed[i]) { - if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) { - e->synchronized([this,note]() { - e->autoNoteOff(-1,note); - }); - } - } - } - // then pressed ones - for (int i=0; i<180; i++) { - int note=i-60; - if (pianoKeyPressed[i]) { - if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) { - e->synchronized([this,note]() { - e->autoNoteOn(-1,curIns,note); - }); - if (edit) noteInput(note,0); + int note=i-60; + if (pianoKeyPressed[i]) { + if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) { + e->synchronized([this,note]() { + e->autoNoteOn(-1,curIns,note); + }); + if (edit) noteInput(note,0); + } } } } diff --git a/src/gui/plot_nolerp.cpp b/src/gui/plot_nolerp.cpp index 64ffc410..b75bda4f 100644 --- a/src/gui/plot_nolerp.cpp +++ b/src/gui/plot_nolerp.cpp @@ -183,7 +183,7 @@ void PlotNoLerp(const char* label, const float* values, int values_count, int va PlotNoLerpEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); } -int PlotBitfieldEx(const char* label, int (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char** overlay_text, int bits, ImVec2 frame_size) +int PlotBitfieldEx(const char* label, int (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char** overlay_text, int bits, ImVec2 frame_size, const bool* values_highlight, ImVec4 highlightColor) { ImGuiContext& g = *GImGui; ImGuiWindow* window = ImGui::GetCurrentWindow(); @@ -253,7 +253,11 @@ int PlotBitfieldEx(const char* label, int (*values_getter)(void* data, int idx), if (pos1.y <= pos0.y - 2.0f) pos1.y += 1.0f; if (v1&(1<DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base); + ImU32 rCol=(idx_hovered == v1_idx ? col_hovered : col_base); + if (values_highlight!=NULL) { + if (values_highlight[v1_idx]) rCol=ImGui::GetColorU32(highlightColor); + } + window->DrawList->AddRectFilled(pos0, pos1, rCol); } } tp0 = tp1; @@ -283,13 +287,13 @@ int PlotBitfieldEx(const char* label, int (*values_getter)(void* data, int idx), return idx_hovered; } -void PlotBitfield(const char* label, const int* values, int values_count, int values_offset, const char** overlay_text, int bits, ImVec2 graph_size, int stride) +void PlotBitfield(const char* label, const int* values, int values_count, int values_offset, const char** overlay_text, int bits, ImVec2 graph_size, int stride, const bool* values_highlight, ImVec4 highlightColor) { FurnacePlotIntArrayGetterData data(values, stride); - PlotBitfieldEx(label, &Plot_IntArrayGetter, (void*)&data, values_count, values_offset, overlay_text, bits, graph_size); + PlotBitfieldEx(label, &Plot_IntArrayGetter, (void*)&data, values_count, values_offset, overlay_text, bits, graph_size, values_highlight, highlightColor); } -int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_display_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float), bool blockMode, std::string (*guideFunc)(float)) +int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_display_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float,void*), void* hoverFuncUser, bool blockMode, std::string (*guideFunc)(float), const bool* values_highlight, ImVec4 highlightColor) { ImGuiContext& g = *GImGui; ImGuiWindow* window = ImGui::GetCurrentWindow(); @@ -355,7 +359,7 @@ int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_gett const float v0 = values_getter(data, (v_idx) % values_count); const float v1 = values_getter(data, (v_idx + 1) % values_count); if (hoverFunc) { - std::string hoverText=hoverFunc(v_idx+values_display_offset,v0); + std::string hoverText=hoverFunc(v_idx+values_display_offset,v0,hoverFuncUser); if (!hoverText.empty()) { ImGui::SetTooltip("%s",hoverText.c_str()); } @@ -413,7 +417,11 @@ int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_gett pos0.y-=(inner_bb.Max.y-inner_bb.Min.y)*inv_scale; //pos1.y+=1.0f; } - window->DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base); + ImU32 rCol=(idx_hovered == v1_idx ? col_hovered : col_base); + if (values_highlight!=NULL) { + if (values_highlight[v1_idx]) rCol=ImGui::GetColorU32(highlightColor); + } + window->DrawList->AddRectFilled(pos0, pos1, rCol); } t0 = t1; @@ -451,8 +459,8 @@ int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_gett return idx_hovered; } -void PlotCustom(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float), bool blockMode, std::string (*guideFunc)(float)) +void PlotCustom(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float,void*), void* hoverFuncUser, bool blockMode, std::string (*guideFunc)(float), const bool* values_highlight, ImVec4 highlightColor) { FurnacePlotArrayGetterData data(values, stride); - PlotCustomEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size, color, highlight, hoverFunc, blockMode, guideFunc); -} \ No newline at end of file + PlotCustomEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size, color, highlight, hoverFunc, hoverFuncUser, blockMode, guideFunc, values_highlight, highlightColor); +} diff --git a/src/gui/plot_nolerp.h b/src/gui/plot_nolerp.h index 5862dfad..48332b33 100644 --- a/src/gui/plot_nolerp.h +++ b/src/gui/plot_nolerp.h @@ -21,5 +21,5 @@ #include void PlotNoLerp(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float)); -void PlotBitfield(const char* label, const int* values, int values_count, int values_offset = 0, const char** overlay_text = NULL, int bits = 8, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float)); -void PlotCustom(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float), ImVec4 fgColor = ImVec4(1.0f,1.0f,1.0f,1.0f), int highlight = 0, std::string (*hoverFunc)(int,float) = NULL, bool blockMode=false, std::string (*guideFunc)(float) = NULL); \ No newline at end of file +void PlotBitfield(const char* label, const int* values, int values_count, int values_offset = 0, const char** overlay_text = NULL, int bits = 8, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float), const bool* values_highlight = NULL, ImVec4 highlightColor = ImVec4(1.0f,1.0f,1.0f,1.0f)); +void PlotCustom(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float), ImVec4 fgColor = ImVec4(1.0f,1.0f,1.0f,1.0f), int highlight = 0, std::string (*hoverFunc)(int,float,void*) = NULL, void* hoverFuncUser=NULL, bool blockMode=false, std::string (*guideFunc)(float) = NULL, const bool* values_highlight = NULL, ImVec4 highlightColor = ImVec4(1.0f,1.0f,1.0f,1.0f)); diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index b2c86440..7577dda0 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -56,13 +56,13 @@ void FurnaceGUI::initSystemPresets() { )); cat.systems.push_back(FurnaceGUISysDef( "Yamaha YM2608 (OPNA)", { - DIV_SYSTEM_PC98, 64, 0, 3, + DIV_SYSTEM_PC98, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( "Yamaha YM2608 (extended channel 3)", { - DIV_SYSTEM_PC98_EXT, 64, 0, 3, + DIV_SYSTEM_PC98_EXT, 64, 0, 0, 0 } )); @@ -79,7 +79,7 @@ void FurnaceGUI::initSystemPresets() { } )); cat.systems.push_back(FurnaceGUISysDef( - "Yamaha YM2610B (OPNB-B)", { + "Yamaha YM2610B (OPNB2)", { DIV_SYSTEM_YM2610B, 64, 0, 0, 0 } @@ -221,18 +221,67 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "TI SN76489A", { + DIV_SYSTEM_SMS, 64, 0, 0x40, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "TI SN76496", { + DIV_SYSTEM_SMS, 64, 0, 0x44, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NCR 8496", { + DIV_SYSTEM_SMS, 64, 0, 0x48, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Tandy PSSJ 3-voice sound", { + DIV_SYSTEM_SMS, 64, 0, 0x4c, + // 8 bit DAC + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Sega PSG (SN76489-like)", { DIV_SYSTEM_SMS, 64, 0, 0, 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega PSG (SN76489-like, Stereo)", { + DIV_SYSTEM_SMS, 64, 0, 0xc, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "TI SN94624", { + DIV_SYSTEM_SMS, 64, 0, 0x182, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "TI SN76494", { + DIV_SYSTEM_SMS, 64, 0, 0x186, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "AY-3-8910", { DIV_SYSTEM_AY8910, 64, 0, 0, 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "AY-3-8914", { + DIV_SYSTEM_AY8910, 64, 0, 48, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Yamaha YM2149(F)", { DIV_SYSTEM_AY8910, 64, 0, 16, @@ -366,7 +415,7 @@ void FurnaceGUI::initSystemPresets() { } )); cat.systems.push_back(FurnaceGUISysDef( - "Namco C163", { + "Namco 163", { DIV_SYSTEM_N163, 64, 0, 0, 0 } @@ -447,13 +496,13 @@ void FurnaceGUI::initSystemPresets() { 0 } )); - cat.systems.push_back(FurnaceGUISysDef( - "tildearrow Sound Unit", { - DIV_SYSTEM_SOUND_UNIT, 64, 0, 0, - 0 - } - )); } + cat.systems.push_back(FurnaceGUISysDef( + "tildearrow Sound Unit", { + DIV_SYSTEM_SOUND_UNIT, 64, 0, 0, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Game consoles","let's play some chiptune making games!"); @@ -521,6 +570,12 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Game Gear", { + DIV_SYSTEM_SMS, 64, 0, 0xc, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Game Boy", { DIV_SYSTEM_GB, 64, 0, 0, @@ -540,49 +595,49 @@ void FurnaceGUI::initSystemPresets() { } )); cat.systems.push_back(FurnaceGUISysDef( - "NES with Konami VRC6", { + "Famicom with Konami VRC6", { DIV_SYSTEM_NES, 64, 0, 0, DIV_SYSTEM_VRC6, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "NES with Konami VRC7", { + "Famicom with Konami VRC7", { DIV_SYSTEM_NES, 64, 0, 0, DIV_SYSTEM_VRC7, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "NES with MMC5", { + "Famicom with MMC5", { DIV_SYSTEM_NES, 64, 0, 0, DIV_SYSTEM_MMC5, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "NES with Sunsoft 5B", { + "Famicom with Sunsoft 5B", { DIV_SYSTEM_NES, 64, 0, 0, DIV_SYSTEM_AY8910, 64, 0, 32, 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "NES with Namco C163", { + "Famicom with Namco 163", { DIV_SYSTEM_NES, 64, 0, 0, DIV_SYSTEM_N163, 64, 0, 112, 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "NES with Family Noraebang", { + "Comboy with Family Noraebang", { DIV_SYSTEM_NES, 64, 0, 0, DIV_SYSTEM_OPLL, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "NES with Family Noraebang (drums mode)", { + "Comboy with Family Noraebang (drums mode)", { DIV_SYSTEM_NES, 64, 0, 0, DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, 0 @@ -615,13 +670,13 @@ void FurnaceGUI::initSystemPresets() { )); cat.systems.push_back(FurnaceGUISysDef( "Neo Geo AES", { - DIV_SYSTEM_YM2610_FULL, 64, 0, 0, + DIV_SYSTEM_YM2610_FULL, 64, 0, 1, 0 } )); cat.systems.push_back(FurnaceGUISysDef( "Neo Geo AES (extended channel 2)", { - DIV_SYSTEM_YM2610_FULL_EXT, 64, 0, 0, + DIV_SYSTEM_YM2610_FULL_EXT, 64, 0, 1, 0 } )); @@ -791,7 +846,7 @@ void FurnaceGUI::initSystemPresets() { cat.systems.push_back(FurnaceGUISysDef( "MSX + Playsoniq", { DIV_SYSTEM_AY8910, 64, 0, 16, - DIV_SYSTEM_SMS, 64, 0, 0, + DIV_SYSTEM_SMS, 64, 0, 0, // Sega VDP DIV_SYSTEM_C64_8580, 64, 0, 0, DIV_SYSTEM_SCC_PLUS, 64, 0, 0, 0 @@ -812,26 +867,145 @@ void FurnaceGUI::initSystemPresets() { } )); cat.systems.push_back(FurnaceGUISysDef( - "NEC PC-98 (with PC-9801-26K)", { - DIV_SYSTEM_OPN, 64, 0, 3, + "MSX + Neotron", { + DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_YM2610_FULL, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "NEC PC-98 (with PC-9801-26K; extended channel 3)", { - DIV_SYSTEM_OPN_EXT, 64, 0, 3, + "MSX + Neotron (extended channel 2)", { + DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_YM2610_FULL_EXT, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "NEC PC-98 (with PC-9801-86)", { - DIV_SYSTEM_PC98, 64, 0, 3, + "MSX + Neotron (with YM2610B)", { + DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_YM2610B, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "NEC PC-98 (with PC-9801-86; extended channel 3)", { - DIV_SYSTEM_PC98_EXT, 64, 0, 3, + "MSX + Neotron (with YM2610B; extended channel 3)", { + DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_YM2610B_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + SIMPL", { + DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_PCM_DAC, 64, 0, 55929|(7<<16), // variable rate, Mono DAC + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with PC-9801-26/K)", { + DIV_SYSTEM_OPN, 64, 0, 4, // 3.9936MHz but some compatible card has 4MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with PC-9801-26/K; extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 4, // 3.9936MHz but some compatible card has 4MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Orchestra)", { + DIV_SYSTEM_OPN, 64, 0, 4, + DIV_SYSTEM_OPL2, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Orchestra; extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 4, + DIV_SYSTEM_OPL2, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Orchestra in drums mode)", { + DIV_SYSTEM_OPN, 64, 0, 4, + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Orchestra in drums mode; extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 4, + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Orchestra V)", { + DIV_SYSTEM_OPN, 64, 0, 4, + DIV_SYSTEM_Y8950, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Orchestra V; extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 4, + DIV_SYSTEM_Y8950, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Orchestra V in drums mode)", { + DIV_SYSTEM_OPN, 64, 0, 4, + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Orchestra V in drums mode; extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 4, + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with PC-9801-86)", { // -73 also has OPNA + DIV_SYSTEM_PC98, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with PC-9801-86; extended channel 3)", { // -73 also has OPNA + DIV_SYSTEM_PC98_EXT, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Blaster 16 for PC-9800 w/PC-9801-26/K compatible)", { + DIV_SYSTEM_OPN, 64, 0, 2, // 4MHz + DIV_SYSTEM_OPL3, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Blaster 16 for PC-9800 w/PC-9801-26/K compatible; extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 2, // 4MHz + DIV_SYSTEM_OPL3, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Blaster 16 for PC-9800 w/PC-9801-26/K compatible in drums mode)", { + DIV_SYSTEM_OPN, 64, 0, 2, // 4MHz + DIV_SYSTEM_OPL3_DRUMS, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with Sound Blaster 16 for PC-9800 w/PC-9801-26/K compatible in drums mode; extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 2, // 4MHz + DIV_SYSTEM_OPL3_DRUMS, 64, 0, 2, 0 } )); @@ -851,8 +1025,76 @@ void FurnaceGUI::initSystemPresets() { cat.systems.push_back(FurnaceGUISysDef( "ZX Spectrum (128K) with TurboSound FM", { DIV_SYSTEM_AY8910, 64, 0, 1, - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_OPN, 64, 0, 0, + DIV_SYSTEM_OPN, 64, 0, 1, + DIV_SYSTEM_OPN, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K) with TurboSound FM (extended channel 3 on first OPN)", { + DIV_SYSTEM_AY8910, 64, 0, 1, + DIV_SYSTEM_OPN_EXT, 64, 0, 1, + DIV_SYSTEM_OPN, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K) with TurboSound FM (extended channel 3 on second OPN)", { + DIV_SYSTEM_AY8910, 64, 0, 1, + DIV_SYSTEM_OPN, 64, 0, 1, + DIV_SYSTEM_OPN_EXT, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K) with TurboSound FM (extended channel 3 on both OPNs)", { + DIV_SYSTEM_AY8910, 64, 0, 1, + DIV_SYSTEM_OPN_EXT, 64, 0, 1, + DIV_SYSTEM_OPN_EXT, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K) with TurboSound FM + SAA", { + DIV_SYSTEM_AY8910, 64, 0, 1, + DIV_SYSTEM_OPN, 64, 0, 1, + DIV_SYSTEM_OPN, 64, 0, 1, + DIV_SYSTEM_SAA1099, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K) with TurboSound FM + SAA (extended channel 3 on first OPN)", { + DIV_SYSTEM_AY8910, 64, 0, 1, + DIV_SYSTEM_OPN_EXT, 64, 0, 1, + DIV_SYSTEM_OPN, 64, 0, 1, + DIV_SYSTEM_SAA1099, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K) with TurboSound FM + SAA (extended channel 3 on second OPN)", { + DIV_SYSTEM_AY8910, 64, 0, 1, + DIV_SYSTEM_OPN, 64, 0, 1, + DIV_SYSTEM_OPN_EXT, 64, 0, 1, + DIV_SYSTEM_SAA1099, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K) with TurboSound FM + SAA (extended channel 3 on both OPNs)", { + DIV_SYSTEM_AY8910, 64, 0, 1, + DIV_SYSTEM_OPN_EXT, 64, 0, 1, + DIV_SYSTEM_OPN_EXT, 64, 0, 1, + DIV_SYSTEM_SAA1099, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K) with TurboSound", { + DIV_SYSTEM_AY8910, 64, 0, 1, + DIV_SYSTEM_AY8910, 64, 0, 1, // or YM2149 + DIV_SYSTEM_AY8910, 64, 0, 1, // or YM2149 0 } )); @@ -870,7 +1112,7 @@ void FurnaceGUI::initSystemPresets() { )); cat.systems.push_back(FurnaceGUISysDef( "BBC Micro", { - DIV_SYSTEM_SMS, 64, 0, 6, + DIV_SYSTEM_SMS, 64, 0, 0x42, // SN76489A 4MHz 0 } )); @@ -880,6 +1122,20 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "IBM PCjr", { + // it can be enable sound output at once + DIV_SYSTEM_SMS, 64, 0, 0x44, // SN76496 + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Tandy 1000", { + DIV_SYSTEM_SMS, 64, 0, 0x44, // NCR 8496 or SN76496 or Tandy PSSJ(with 8 bit DAC) + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "PC + Covox Sound Master", { DIV_SYSTEM_AY8930, 64, 0, 3, @@ -1002,7 +1258,7 @@ void FurnaceGUI::initSystemPresets() { )); cat.systems.push_back(FurnaceGUISysDef( "FM Towns", { - DIV_SYSTEM_YM2612, 64, 0, 2, + DIV_SYSTEM_YM2612, 64, 0, 2, // YM3438 DIV_SYSTEM_RF5C68, 64, 0, 0, 0 } @@ -1014,186 +1270,715 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "TI-99/4A", { + DIV_SYSTEM_SMS, 64, 0, 0x182, // SN94624 447KHz + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Arcade systems","INSERT COIN"); cat.systems.push_back(FurnaceGUISysDef( "Bally Midway MCR", { - DIV_SYSTEM_AY8910, 64, 0, 0, - DIV_SYSTEM_AY8910, 64, 0, 0, + // SSIO sound board + DIV_SYSTEM_AY8910, 64, 0, 3, // 2MHz + DIV_SYSTEM_AY8910, 64, 0, 3, // 2MHz + // additional sound boards, mostly software controlled DAC 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "Gyruss", { + "Williams/Midway Y/T unit w/ADPCM sound board", { + // ADPCM sound board + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_PCM_DAC, 64, 0, 15624|(7<<16), // variable via OPM timer? + DIV_SYSTEM_MSM6295, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Konami Gyruss", { DIV_SYSTEM_AY8910, 64, 0, 0, DIV_SYSTEM_AY8910, 64, 0, 0, DIV_SYSTEM_AY8910, 64, 0, 0, DIV_SYSTEM_AY8910, 64, 0, 0, DIV_SYSTEM_AY8910, 64, 0, 0, + // additional discrete sound logics + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Konami Bubble System", { + DIV_SYSTEM_AY8910, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 0, + DIV_SYSTEM_BUBSYS_WSG, 64, 0, 0, + // VLM5030 exists but not used for music at all + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Konami Battlantis", { + DIV_SYSTEM_OPL2, 64, 0, 3, // 3MHz + DIV_SYSTEM_OPL2, 64, 0, 3, // "" + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Konami Battlantis (drums mode on first OPL2)", { + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 3, // 3MHz + DIV_SYSTEM_OPL2, 64, 0, 3, // "" + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Konami Battlantis (drums mode on second OPL2)", { + DIV_SYSTEM_OPL2, 64, 0, 3, // 3MHz + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 3, // "" + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Konami Battlantis (drums mode on both OPL2s)", { + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 3, // 3MHz + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 3, // "" + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Konami Hexion", { + DIV_SYSTEM_SCC, 64, 0, 2, // 1.5MHz (3MHz input) + DIV_SYSTEM_MSM6295, 64, 0, 1, 0 } )); cat.systems.push_back(FurnaceGUISysDef( "Sega Kyugo", { - DIV_SYSTEM_AY8910, 64, 0, 4, - DIV_SYSTEM_AY8910, 64, 0, 4, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Sega OutRun/X Board", { - DIV_SYSTEM_YM2151, 64, 0, 2, - DIV_SYSTEM_SEGAPCM, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Capcom CPS-1", { - DIV_SYSTEM_YM2151, 64, 0, 2, - DIV_SYSTEM_MSM6295, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Jaleco Mega System 1", { - DIV_SYSTEM_YM2151, 64, 0, 2, - DIV_SYSTEM_MSM6295, 64, 0, 0, - DIV_SYSTEM_MSM6295, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "NMK 16-bit Arcade", { - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_MSM6295, 64, 0, 2, - DIV_SYSTEM_MSM6295, 64, 0, 2, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Data East Arcade", { - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_OPL2, 64, 0, 0, - DIV_SYSTEM_MSM6295, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Kaneko Toybox System", { - DIV_SYSTEM_AY8910, 64, 0, 1, - DIV_SYSTEM_AY8910, 64, 0, 1, - DIV_SYSTEM_MSM6295, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Tecmo Arcade", { - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_MSM6295, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Seibu Kaihatsu Arcade", { - DIV_SYSTEM_OPL2, 64, 0, 0, - DIV_SYSTEM_MSM6295, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Data East Arcade (Dark Seal)", { - DIV_SYSTEM_YM2151, 64, 0, 2, - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_MSM6295, 64, 0, 0, - DIV_SYSTEM_MSM6295, 64, 0, 8, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Sunsoft Arcade", { - DIV_SYSTEM_YM2612, 64, 0, 4, - DIV_SYSTEM_MSM6295, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Atari Arcade (Rampart)", { - DIV_SYSTEM_OPLL, 64, 0, 0, - DIV_SYSTEM_MSM6295, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Data East Deco 156", { - DIV_SYSTEM_MSM6295, 64, 0, 0, - DIV_SYSTEM_MSM6295, 64, 0, 8, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "SNK Triple Z80 (Chopper)", { //or Namco? - DIV_SYSTEM_Y8950, 64, 0, 0, - DIV_SYSTEM_OPL2, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Sega System 18", { - DIV_SYSTEM_YM2612, 64, 0, 2, - DIV_SYSTEM_YM2612, 64, 0, 2, - DIV_SYSTEM_RF5C68, 64, 0, 1, + DIV_SYSTEM_AY8910, 64, 0, 14, + DIV_SYSTEM_AY8910, 64, 0, 14, 0 } )); cat.systems.push_back(FurnaceGUISysDef( "Sega System 1", { - DIV_SYSTEM_SMS, 64, 0, 2, - DIV_SYSTEM_SMS, 64, 0, 3, + DIV_SYSTEM_SMS, 64, 0, 0x42, // SN76489A 4MHz + DIV_SYSTEM_SMS, 64, 0, 0x0141, // SN76489A 2MHz 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "Sega System 32", { - DIV_SYSTEM_YM2612, 64, 0, 4, - DIV_SYSTEM_YM2612, 64, 0, 4, - DIV_SYSTEM_RF5C68, 64, 0, 2, + "Sega System E", { + DIV_SYSTEM_SMS, 64, 0, 0, + DIV_SYSTEM_SMS, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "Sega Hang-On", { - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_SEGAPCM, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "SNK Alpha-68K", { - DIV_SYSTEM_OPN, 64, 0, 0, + "Sega System E (with FM expansion)", { + DIV_SYSTEM_SMS, 64, 0, 0, + DIV_SYSTEM_SMS, 64, 0, 0, DIV_SYSTEM_OPLL, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "Data East Karnov", { - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_OPL, 64, 0, 0, + "Sega System E (with FM expansion in drums mode)", { + DIV_SYSTEM_SMS, 64, 0, 0, + DIV_SYSTEM_SMS, 64, 0, 0, + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "Capcom Arcade", { - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_OPN, 64, 0, 0, + "Sega Hang-On", { + DIV_SYSTEM_OPN, 64, 0, 2, // 4MHz + DIV_SYSTEM_SEGAPCM, 64, 0, 0, // discrete logics, 62.5KHz output rate + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Hang-On (extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 2, // 4MHz + DIV_SYSTEM_SEGAPCM, 64, 0, 0, // discrete logics, 62.5KHz output rate + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega OutRun/X Board", { + DIV_SYSTEM_YM2151, 64, 0, 2, // 4MHz + DIV_SYSTEM_SEGAPCM, 64, 0, 0, // ASIC, 31.25KHz output rate + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega System 24", { + DIV_SYSTEM_YM2151, 64, 0, 2, // 4MHz + DIV_SYSTEM_PCM_DAC, 64, 0, 61499|(7<<16), // software controlled, variable rate via configurable timers + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega System 18", { + DIV_SYSTEM_YM2612, 64, 0, 2, // discrete 8MHz YM3438 + DIV_SYSTEM_YM2612, 64, 0, 2, // ^^ + DIV_SYSTEM_RF5C68, 64, 0, 1, // 10MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega System 18 (extended channel 3 on first OPN2C)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 2, // discrete 8MHz YM3438 + DIV_SYSTEM_YM2612, 64, 0, 2, // ^^ + DIV_SYSTEM_RF5C68, 64, 0, 1, // 10MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega System 18 (extended channel 3 on second OPN2C)", { + DIV_SYSTEM_YM2612, 64, 0, 2, // discrete 8MHz YM3438 + DIV_SYSTEM_YM2612_EXT, 64, 0, 2, // ^^ + DIV_SYSTEM_RF5C68, 64, 0, 1, // 10MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega System 18 (extended channel 3 on both OPN2Cs)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 2, // discrete 8MHz YM3438 + DIV_SYSTEM_YM2612_EXT, 64, 0, 2, // ^^ + DIV_SYSTEM_RF5C68, 64, 0, 1, // 10MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega System 32", { + DIV_SYSTEM_YM2612, 64, 0, 4, // discrete 8.05MHz YM3438 + DIV_SYSTEM_YM2612, 64, 0, 4, // ^^ + DIV_SYSTEM_RF5C68, 64, 0, 2, // 12.5MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega System 32 (extended channel 3 on first OPN2C)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 4, // discrete 8.05MHz YM3438 + DIV_SYSTEM_YM2612, 64, 0, 4, // ^^ + DIV_SYSTEM_RF5C68, 64, 0, 2, // 12.5MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega System 32 (extended channel 3 on second OPN2C)", { + DIV_SYSTEM_YM2612, 64, 0, 4, // discrete 8.05MHz YM3438 + DIV_SYSTEM_YM2612_EXT, 64, 0, 4, // ^^ + DIV_SYSTEM_RF5C68, 64, 0, 2, // 12.5MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega System 32 (extended channel 3 on both OPN2Cs)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 4, // discrete 8.05MHz YM3438 + DIV_SYSTEM_YM2612_EXT, 64, 0, 4, // ^^ + DIV_SYSTEM_RF5C68, 64, 0, 2, // 12.5MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom Exed Eyes", { + DIV_SYSTEM_AY8910, 64, 0, 4, // 1.5MHz + DIV_SYSTEM_SMS, 64, 0, 0x0104, // SN76489 3MHz + DIV_SYSTEM_SMS, 64, 0, 0x0104, // SN76489 3MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom Arcade", { // 1943, Side arms, etc + DIV_SYSTEM_OPN, 64, 0, 5, // 4 or 1.5MHz; various per games + DIV_SYSTEM_OPN, 64, 0, 5, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom Arcade (extended channel 3 on first OPN)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 5, + DIV_SYSTEM_OPN, 64, 0, 5, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom Arcade (extended channel 3 on second OPN)", { + DIV_SYSTEM_OPN, 64, 0, 5, + DIV_SYSTEM_OPN_EXT, 64, 0, 5, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom Arcade (extended channel 3 on both OPNs)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 5, + DIV_SYSTEM_OPN_EXT, 64, 0, 5, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom CPS-1", { + DIV_SYSTEM_YM2151, 64, 0, 0, // 3.58MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom CPS-2 (QSound)", { + DIV_SYSTEM_QSOUND, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Jaleco Ginga NinkyouDen", { + DIV_SYSTEM_AY8910, 64, 0, 16, // 1.79MHz + DIV_SYSTEM_Y8950, 64, 0, 0, // 3.58MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Jaleco Ginga NinkyouDen (drums mode)", { + DIV_SYSTEM_AY8910, 64, 0, 16, // 1.79MHz + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 0, // 3.58MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Jaleco Mega System 1", { + DIV_SYSTEM_YM2151, 64, 0, 1, // 3.5MHz (7MHz / 2) + DIV_SYSTEM_MSM6295, 64, 0, 2, // 4MHz + DIV_SYSTEM_MSM6295, 64, 0, 2, // 4MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NMK 16-bit Arcade", { + DIV_SYSTEM_OPN, 64, 0, 5, // 1.5MHz; optional + DIV_SYSTEM_MSM6295, 64, 0, 130, // 4MHz + DIV_SYSTEM_MSM6295, 64, 0, 130, // ^^ + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NMK 16-bit Arcade (extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 5, // 1.5MHz; optional + DIV_SYSTEM_MSM6295, 64, 0, 130, // 4MHz + DIV_SYSTEM_MSM6295, 64, 0, 130, // ^^ + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Kaneko DJ Boy", { + DIV_SYSTEM_OPN, 64, 0, 3, // 3MHz + DIV_SYSTEM_MSM6295, 64, -127, 12, // 1.5MHz, Left output + DIV_SYSTEM_MSM6295, 64, 127, 12, // 1.5MHz, Right output + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Kaneko DJ Boy (extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 3, // 3MHz + DIV_SYSTEM_MSM6295, 64, -127, 12, // 1.5MHz, Left output + DIV_SYSTEM_MSM6295, 64, 127, 12, // 1.5MHz, Right output + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Kaneko Air Buster", { + DIV_SYSTEM_OPN, 64, 0, 3, // 3MHz + DIV_SYSTEM_MSM6295, 64, 0, 141, // 3MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Kaneko Air Buster (extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 3, // 3MHz + DIV_SYSTEM_MSM6295, 64, 0, 141, // 3MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Kaneko Toybox System", { + DIV_SYSTEM_AY8910, 64, 0, 19, // YM2149 2MHz + DIV_SYSTEM_AY8910, 64, 0, 19, // ^^ + DIV_SYSTEM_MSM6295, 64, 0, 8, // 2MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Kaneko Jackie Chan", { + DIV_SYSTEM_YMZ280B, 64, 0, 3, // 16MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Super Kaneko Nova System", { + DIV_SYSTEM_YMZ280B, 64, 0, 4, // 16.67MHz (33.33MHz / 2) + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Tecmo Ninja Gaiden", { // Ninja Gaiden, Raiga, etc + DIV_SYSTEM_OPN, 64, 0, 2, // 4MHz + DIV_SYSTEM_OPN, 64, 0, 2, // 4MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Tecmo Ninja Gaiden (extended channel 3 on first OPN)", { // Ninja Gaiden, Raiga, etc + DIV_SYSTEM_OPN_EXT, 64, 0, 2, // 4MHz + DIV_SYSTEM_OPN, 64, 0, 2, // 4MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Tecmo Ninja Gaiden (extended channel 3 on second OPN)", { // Ninja Gaiden, Raiga, etc + DIV_SYSTEM_OPN, 64, 0, 2, // 4MHz + DIV_SYSTEM_OPN_EXT, 64, 0, 2, // 4MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Tecmo Ninja Gaiden (extended channel 3 on both OPNs)", { // Ninja Gaiden, Raiga, etc + DIV_SYSTEM_OPN_EXT, 64, 0, 2, // 4MHz + DIV_SYSTEM_OPN_EXT, 64, 0, 2, // 4MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Tecmo System", { + DIV_SYSTEM_OPL3, 64, 0, 0, + DIV_SYSTEM_YMZ280B, 64, 0, 0, + DIV_SYSTEM_MSM6295, 64, 0, 8, // 2MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Tecmo System (drums mode)", { + DIV_SYSTEM_OPL3_DRUMS, 64, 0, 0, + DIV_SYSTEM_YMZ280B, 64, 0, 0, + DIV_SYSTEM_MSM6295, 64, 0, 8, // 2MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Seibu Kaihatsu Raiden", { // Raiden, Seibu cup soccer, Zero team, etc + DIV_SYSTEM_OPL2, 64, 0, 0, // YM3812 3.58MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1 or 1.023MHz (28.636363MHz / 28); various per games + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Seibu Kaihatsu Raiden (drums mode)", { // Raiden, Seibu cup soccer, Zero team, etc + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 0, // YM3812 3.58MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1 or 1.023MHz (28.636363MHz / 28); various per games + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sunsoft Shanghai 3", { + DIV_SYSTEM_AY8910, 64, 0, 20, // YM2149 1.5MHz + DIV_SYSTEM_MSM6295, 64, 0, 1, // 1.056MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sunsoft Arcade", { + DIV_SYSTEM_YM2612, 64, 0, 2, // discrete YM3438 8MHz + DIV_SYSTEM_MSM6295, 64, 0, 1, // 1.056MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sunsoft Arcade (extended channel 3)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 2, // discrete YM3438 8MHz + DIV_SYSTEM_MSM6295, 64, 0, 1, // 1.056MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Atari Klax", { + DIV_SYSTEM_MSM6295, 64, 0, 7, // 0.895MHz (3.579545MHz / 4) + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Atari Rampart", { + DIV_SYSTEM_OPLL, 64, 0, 0, // 3.579545MHz + DIV_SYSTEM_MSM6295, 64, 0, 14, // 1.193MHz (3.579545MHz / 3) + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Atari Rampart (drums mode)", { + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, // 3.579545MHz + DIV_SYSTEM_MSM6295, 64, 0, 14, // 1.193MHz (3.579545MHz / 3) + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Atari JSA IIIs", { + DIV_SYSTEM_YM2151, 64, 0, 0, // 3.579545MHz + DIV_SYSTEM_MSM6295, 64, -127, 14, // 1.193MHz (3.579545MHz / 3), Left output + DIV_SYSTEM_MSM6295, 64, 127, 14, // 1.193MHz (3.579545MHz / 3), Right output + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Karnov", { + DIV_SYSTEM_OPN, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_OPL, 64, 0, 3, // 3MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Karnov (extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_OPL, 64, 0, 3, // 3MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Karnov (drums mode)", { + DIV_SYSTEM_OPN, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_OPL_DRUMS, 64, 0, 3, // 3MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Karnov (extended channel 3; drums mode)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_OPL_DRUMS, 64, 0, 3, // 3MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Arcade", { // Bad dudes, Robocop, etc + DIV_SYSTEM_OPN, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_OPL2, 64, 0, 3, // 3MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1 to 1.056MHz; various per games or optional + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Arcade (extended channel 3)", { // Bad dudes, Robocop, etc + DIV_SYSTEM_OPN_EXT, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_OPL2, 64, 0, 3, // 3MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1 to 1.056MHz; various per games or optional + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Arcade (drums mode)", { // Bad dudes, Robocop, etc + DIV_SYSTEM_OPN, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 3, // 3MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1 to 1.056MHz; various per games or optional + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Arcade (extended channel 3; drums mode)", { // Bad dudes, Robocop, etc + DIV_SYSTEM_OPN_EXT, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 3, // 3MHz + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1 to 1.056MHz; various per games or optional 0 } )); cat.systems.push_back(FurnaceGUISysDef( "Data East PCX", { - DIV_SYSTEM_OPN, 64, 0, 0, - DIV_SYSTEM_PCE, 64, 0, 2, + DIV_SYSTEM_OPN, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_PCE, 64, 0, 0, + // software controlled MSM5205 + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East PCX (extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 5, // 1.5MHz + DIV_SYSTEM_PCE, 64, 0, 0, + // software controlled MSM5205 + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Dark Seal", { // Dark Seal, Crude Buster, Vapor Trail, etc + DIV_SYSTEM_YM2151, 64, 0, 0, // 3.580MHz (32.22MHz / 9) + DIV_SYSTEM_OPN, 64, 0, 2, // 4.0275MHz (32.22MHz / 8); optional + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1.007MHz (32.22MHz / 32) + DIV_SYSTEM_MSM6295, 64, 0, 8, // 2.014MHz (32.22MHz / 16); optional + // HuC6280 is for control them, internal sound isn't used + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Dark Seal (extended channel 3)", { // Dark Seal, Crude Buster, Vapor Trail, etc + DIV_SYSTEM_YM2151, 64, 0, 0, // 3.580MHz (32.22MHz / 9) + DIV_SYSTEM_OPN_EXT, 64, 0, 2, // 4.0275MHz (32.22MHz / 8); optional + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1.007MHz (32.22MHz / 32) + DIV_SYSTEM_MSM6295, 64, 0, 8, // 2.014MHz (32.22MHz / 16); optional + // HuC6280 is for control them, internal sound isn't used + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East Deco 156", { + DIV_SYSTEM_MSM6295, 64, 0, 0, // 1 or 1.007MHz (32.22MHz / 32); various per games + DIV_SYSTEM_MSM6295, 64, 0, 8, // 1 or 2 or 2.014MHz (32.22MHz / 16); various per games + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Data East MLC", { + DIV_SYSTEM_YMZ280B, 64, 0, 5, // 14MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Ikari Warriors", { + DIV_SYSTEM_OPL, 64, 0, 2, + DIV_SYSTEM_OPL, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Ikari Warriors (drums mode on first OPL)", { + DIV_SYSTEM_OPL_DRUMS, 64, 0, 2, + DIV_SYSTEM_OPL, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Ikari Warriors (drums mode on second OPL)", { + DIV_SYSTEM_OPL, 64, 0, 2, + DIV_SYSTEM_OPL_DRUMS, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Ikari Warriors (drums mode on both OPLs)", { + DIV_SYSTEM_OPL_DRUMS, 64, 0, 2, + DIV_SYSTEM_OPL_DRUMS, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Triple Z80", { + DIV_SYSTEM_Y8950, 64, 0, 2, + DIV_SYSTEM_OPL, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Triple Z80 (drums mode on Y8950)", { + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 2, + DIV_SYSTEM_OPL, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Triple Z80 (drums mode on OPL)", { + DIV_SYSTEM_Y8950, 64, 0, 2, + DIV_SYSTEM_OPL_DRUMS, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Triple Z80 (drums mode on Y8950 and OPL)", { + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 2, + DIV_SYSTEM_OPL_DRUMS, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Chopper I", { + DIV_SYSTEM_Y8950, 64, 0, 2, + DIV_SYSTEM_OPL2, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Chopper I (drums mode on Y8950)", { + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 2, + DIV_SYSTEM_OPL2, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Chopper I (drums mode on OPL2)", { + DIV_SYSTEM_Y8950, 64, 0, 2, + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Chopper I (drums mode on Y8950 and OPL2)", { + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 2, + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Touchdown Fever", { + DIV_SYSTEM_OPL, 64, 0, 2, + DIV_SYSTEM_Y8950, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Touchdown Fever (drums mode on OPL)", { + DIV_SYSTEM_OPL_DRUMS, 64, 0, 2, + DIV_SYSTEM_Y8950, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Touchdown Fever (drums mode on Y8950)", { + DIV_SYSTEM_OPL, 64, 0, 2, + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNK Touchdown Fever (drums mode on OPL and Y8950)", { + DIV_SYSTEM_OPL_DRUMS, 64, 0, 2, + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Alpha denshi Alpha-68K", { + DIV_SYSTEM_OPN, 64, 0, 3, // 3MHz + DIV_SYSTEM_OPLL, 64, 0, 0, // 3.58MHz + DIV_SYSTEM_PCM_DAC, 64, 0, 7613|(7<<16), // software controlled 8 bit DAC + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Alpha denshi Alpha-68K (extended channel 3)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 3, // 3MHz + DIV_SYSTEM_OPLL, 64, 0, 0, // 3.58MHz + DIV_SYSTEM_PCM_DAC, 64, 0, 7613|(7<<16), // software controlled 8 bit DAC + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Alpha denshi Alpha-68K (drums mode)", { + DIV_SYSTEM_OPN, 64, 0, 3, // 3MHz + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, // 3.58MHz + DIV_SYSTEM_PCM_DAC, 64, 0, 7613|(7<<16), // software controlled 8 bit DAC + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Alpha denshi Alpha-68K (extended channel 3; drums mode)", { + DIV_SYSTEM_OPN_EXT, 64, 0, 3, // 3MHz + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, // 3.58MHz + DIV_SYSTEM_PCM_DAC, 64, 0, 7613|(7<<16), // software controlled 8 bit DAC 0 } )); @@ -1210,39 +1995,55 @@ void FurnaceGUI::initSystemPresets() { } )); cat.systems.push_back(FurnaceGUISysDef( - "Capcom Exed Eyes", { - DIV_SYSTEM_AY8910, 64, 0, 0, - DIV_SYSTEM_SMS, 64, 0, 0, - DIV_SYSTEM_SMS, 64, 0, 0, + "Nichibutsu Mag Max", { + DIV_SYSTEM_AY8910, 64, 0, 13, + DIV_SYSTEM_AY8910, 64, 0, 13, + DIV_SYSTEM_AY8910, 64, 0, 13, 0 } - )); + )); cat.systems.push_back(FurnaceGUISysDef( - "Nichibutsu Arcade", { - DIV_SYSTEM_AY8910, 64, 0, 0, - DIV_SYSTEM_AY8910, 64, 0, 0, - DIV_SYSTEM_AY8910, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Namco (3-channel WSG)", { + "Namco (3-channel WSG)", { // Pac-Man, Galaga, Xevious, etc DIV_SYSTEM_NAMCO, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "Namco (8-channel WSG)", { + "Namco Mappy", { // Mappy, Super Pac-Man, Libble Rabble, etc DIV_SYSTEM_NAMCO_15XX, 64, 0, 0, 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "Namco System 1", { + "Namco Pac-Land", { // Pac-Land, Baraduke, Sky kid, etc DIV_SYSTEM_NAMCO_CUS30, 64, 0, 0, 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Namco System 86", { // without expansion board case; Hopping Mappy, etc + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_NAMCO_CUS30, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Namco Thunder Ceptor", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_NAMCO_CUS30, 64, 0, 0, + DIV_SYSTEM_PCM_DAC, 64, 0, 7999|(7<<16), // M65C02 software driven, correct sample rate? + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Namco System 1", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_NAMCO_CUS30, 64, 0, 0, + DIV_SYSTEM_PCM_DAC, 64, 0, 5999|(7<<16), // sample rate verified from https://github.com/mamedev/mame/blob/master/src/devices/sound/n63701x.cpp + DIV_SYSTEM_PCM_DAC, 64, 0, 5999|(7<<16), // "" + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Taito Arcade", { DIV_SYSTEM_YM2610B, 64, 0, 0, @@ -1255,12 +2056,6 @@ void FurnaceGUI::initSystemPresets() { 0 } )); - cat.systems.push_back(FurnaceGUISysDef( - "Capcom CPS-2 (QSound)", { - DIV_SYSTEM_QSOUND, 64, 0, 0, - 0 - } - )); cat.systems.push_back(FurnaceGUISysDef( "Seta 1", { DIV_SYSTEM_X1_010, 64, 0, 0, @@ -1274,6 +2069,13 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta 1 + FM addon (extended channel 3)", { + DIV_SYSTEM_X1_010, 64, 0, 0, + DIV_SYSTEM_YM2612_EXT, 64, 0, 2, // Discrete YM3438 + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Seta 2", { DIV_SYSTEM_X1_010, 64, 0, 1, @@ -1287,18 +2089,74 @@ void FurnaceGUI::initSystemPresets() { } )); cat.systems.push_back(FurnaceGUISysDef( - "SNK Triple Z80", { - DIV_SYSTEM_Y8950, 64, 0, 0, - DIV_SYSTEM_OPL, 64, 0, 0, + "Coreland Cyber Tank", { + DIV_SYSTEM_Y8950, 64, -127, 0, // 3.58MHz, Left output + DIV_SYSTEM_Y8950, 64, 127, 0, // 3.58MHz, Right output 0 } )); cat.systems.push_back(FurnaceGUISysDef( - "Konami Bubble System", { - DIV_SYSTEM_AY8910, 64, 0, 0, - DIV_SYSTEM_AY8910, 64, 0, 0, - DIV_SYSTEM_BUBSYS_WSG, 64, 0, 0, - // VLM5030 exists but not used for music at all + "Coreland Cyber Tank (drums mode)", { + DIV_SYSTEM_Y8950, 64, -127, 0, // 3.58MHz, Left output + DIV_SYSTEM_Y8950, 64, 127, 0, // 3.58MHz, Right output + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ICE Skimaxx", { + DIV_SYSTEM_MSM6295, 64, -127, 130, // 4MHz, Left output + DIV_SYSTEM_MSM6295, 64, 127, 130, // 4MHz, Right output + DIV_SYSTEM_MSM6295, 64, -127, 8, // 2MHz, Left output + DIV_SYSTEM_MSM6295, 64, 127, 8, // 2MHz, Right output + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Toaplan 1", { + DIV_SYSTEM_OPL2, 64, 0, 5, // 3.5MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Toaplan 1 (drums mode)", { + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 5, // 3.5MHz + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Dynax/Nakanihon 3rd generation hardware", { + DIV_SYSTEM_AY8910, 64, 0, 0, // AY or YM, optional - 1.79MHz or 3.58MHz; various per game + DIV_SYSTEM_OPLL, 64, 0, 0, + DIV_SYSTEM_MSM6295, 64, 0, 6, // 1.023MHz mostly + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Dynax/Nakanihon 3rd generation hardware (drums mode)", { + DIV_SYSTEM_AY8910, 64, 0, 0, // AY or YM, optional - 1.79MHz or 3.58MHz; various per game + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, + DIV_SYSTEM_MSM6295, 64, 0, 6, // 1.023MHz mostly + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Dynax/Nakanihon Real Break", { + DIV_SYSTEM_OPLL, 64, 0, 0, + DIV_SYSTEM_YMZ280B, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Dynax/Nakanihon Real Break (drums mode)", { + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, + DIV_SYSTEM_YMZ280B, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Irem M72", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_PCM_DAC, 64, 0, 7811|(7<<16), 0 } )); @@ -1351,7 +2209,7 @@ void FurnaceGUI::initSystemPresets() { } )); cat.systems.push_back(FurnaceGUISysDef( - "NES with Konami VRC7", { + "Famicom with Konami VRC7", { DIV_SYSTEM_NES, 64, 0, 0, DIV_SYSTEM_VRC7, 64, 0, 0, 0 diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 19fd7596..51411c43 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -35,13 +35,19 @@ void FurnaceGUI::drawSampleEdit() { nextWindow=GUI_WINDOW_NOTHING; } if (!sampleEditOpen) return; + if (mobileUI) { + patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f)); + patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f))); + ImGui::SetNextWindowPos(patWindowPos); + ImGui::SetNextWindowSize(patWindowSize); + } if (ImGui::Begin("Sample Editor",&sampleEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) { if (curSample<0 || curSample>=(int)e->song.sample.size()) { ImGui::Text("no sample selected"); } else { DivSample* sample=e->song.sample[curSample]; String sampleType="Invalid"; - if (sample->depth<17) { + if (sample->depthdepth]!=NULL) { sampleType=sampleDepths[sample->depth]; } @@ -61,11 +67,11 @@ void FurnaceGUI::drawSampleEdit() { ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::BeginCombo("##SampleType",sampleType.c_str())) { - for (int i=0; i<17; i++) { + for (int i=0; iprepareUndo(true); - sample->depth=i; + sample->depth=(DivSampleDepth)i; e->renderSamplesP(); updateSampleTex=true; MARK_MODIFIED; @@ -93,22 +99,43 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::TableNextColumn(); - bool doLoop=(sample->loopStart>=0); + bool doLoop=(sample->isLoopable()); if (ImGui::Checkbox("Loop",&doLoop)) { MARK_MODIFIED if (doLoop) { sample->loopStart=0; + sample->loopEnd=sample->samples; } else { sample->loopStart=-1; + sample->loopEnd=sample->samples; } updateSampleTex=true; } if (doLoop) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Loop Start"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { MARK_MODIFIED - if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { + if (ImGui::InputInt("##LoopStartPosition",&sample->loopStart,1,10)) { MARK_MODIFIED + if (sample->loopStart<0) { sample->loopStart=0; } + if (sample->loopStart>sample->loopEnd) { + sample->loopStart=sample->loopEnd; + } + updateSampleTex=true; + } + ImGui::TableNextColumn(); + ImGui::Text("Loop End"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##LoopEndPosition",&sample->loopEnd,1,10)) { MARK_MODIFIED + if (sample->loopEndloopStart) { + sample->loopEnd=sample->loopStart; + } + if (sample->loopEnd>=(int)sample->samples) { + sample->loopEnd=sample->samples; + } updateSampleTex=true; } } @@ -123,22 +150,22 @@ void FurnaceGUI::drawSampleEdit() { */ ImGui::Separator(); - ImGui::BeginDisabled(sample->depth!=8 && sample->depth!=16); + ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode)); + pushToggleColors(!sampleDragMode); if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { sampleDragMode=false; } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Select"); } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(sampleDragMode)); + pushToggleColors(sampleDragMode); if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) { sampleDragMode=true; } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Draw"); } @@ -275,14 +302,14 @@ void FurnaceGUI::drawSampleEdit() { SAMPLE_OP_BEGIN; float vol=amplifyVol/100.0f; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[i]*vol; if (val<-32768) val=-32768; if (val>32767) val=32767; sample->data16[i]=val; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; idata8[i]*vol; if (val<-128) val=-128; @@ -335,7 +362,7 @@ void FurnaceGUI::drawSampleEdit() { if (silenceSize<0) silenceSize=0; if (silenceSize>16777215) silenceSize=16777215; } - if (ImGui::Button("Resize")) { + if (ImGui::Button("Go")) { int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?sample->samples:sampleSelStart; sample->prepareUndo(true); e->lockEngine([this,sample,pos]() { @@ -466,7 +493,7 @@ void FurnaceGUI::drawSampleEdit() { double power=(sampleFilterCutStart>sampleFilterCutEnd)?0.5:2.0; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; irate))*M_PI); @@ -482,7 +509,7 @@ void FurnaceGUI::drawSampleEdit() { if (val>32767) val=32767; sample->data16[i]=val; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; irate))*M_PI); @@ -512,14 +539,14 @@ void FurnaceGUI::drawSampleEdit() { ImGui::SameLine(); ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); ImGui::SameLine(); - if (ImGui::Button(ICON_FA_VOLUME_UP "##PreviewSample")) { + if (ImGui::Button(ICON_FA_PLAY "##PreviewSample")) { e->previewSample(curSample); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Preview sample"); } ImGui::SameLine(); - if (ImGui::Button(ICON_FA_VOLUME_OFF "##StopSample")) { + if (ImGui::Button(ICON_FA_STOP "##StopSample")) { e->stopSamplePreview(); } if (ImGui::IsItemHovered()) { @@ -574,11 +601,11 @@ void FurnaceGUI::drawSampleEdit() { ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::BeginCombo("##SampleType",sampleType.c_str())) { - for (int i=0; i<17; i++) { + for (int i=0; iprepareUndo(true); - sample->depth=i; + sample->depth=(DivSampleDepth)i; e->renderSamplesP(); updateSampleTex=true; MARK_MODIFIED; @@ -607,22 +634,43 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::TableNextColumn(); - bool doLoop=(sample->loopStart>=0); + bool doLoop=(sample->isLoopable()); if (ImGui::Checkbox("Loop",&doLoop)) { MARK_MODIFIED if (doLoop) { sample->loopStart=0; + sample->loopEnd=sample->samples; } else { sample->loopStart=-1; + sample->loopEnd=sample->samples; } updateSampleTex=true; } if (doLoop) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Loop Start"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { MARK_MODIFIED - if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { + if (ImGui::InputInt("##LoopStartPosition",&sample->loopStart,1,10)) { MARK_MODIFIED + if (sample->loopStart<0) { sample->loopStart=0; } + if (sample->loopStart>sample->loopEnd) { + sample->loopStart=sample->loopEnd; + } + updateSampleTex=true; + } + ImGui::TableNextColumn(); + ImGui::Text("Loop End"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##LoopEndPosition",&sample->loopEnd,1,10)) { MARK_MODIFIED + if (sample->loopEndloopStart) { + sample->loopEnd=sample->loopStart; + } + if (sample->loopEnd>=(int)sample->samples) { + sample->loopEnd=sample->samples; + } updateSampleTex=true; } } @@ -637,22 +685,22 @@ void FurnaceGUI::drawSampleEdit() { */ ImGui::Separator(); - ImGui::BeginDisabled(sample->depth!=8 && sample->depth!=16); + ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode)); + pushToggleColors(!sampleDragMode); if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { sampleDragMode=false; } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Select"); } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(sampleDragMode)); + pushToggleColors(sampleDragMode); if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) { sampleDragMode=true; } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Draw"); } @@ -765,16 +813,16 @@ void FurnaceGUI::drawSampleEdit() { float highP=sampleFilterH*100.0f; float resP=sampleFilterRes*100.0f; ImGui::Text("Cutoff:"); - if (ImGui::SliderFloat("From",&sampleFilterCutStart,0.0f,sample->rate*0.5,"%.0fHz")) { + if (CWSliderFloat("From",&sampleFilterCutStart,0.0f,sample->rate*0.5,"%.0fHz")) { if (sampleFilterCutStart<0.0) sampleFilterCutStart=0.0; if (sampleFilterCutStart>sample->rate*0.5) sampleFilterCutStart=sample->rate*0.5; } - if (ImGui::SliderFloat("To",&sampleFilterCutEnd,0.0f,sample->rate*0.5,"%.0fHz")) { + if (CWSliderFloat("To",&sampleFilterCutEnd,0.0f,sample->rate*0.5,"%.0fHz")) { if (sampleFilterCutEnd<0.0) sampleFilterCutEnd=0.0; if (sampleFilterCutEnd>sample->rate*0.5) sampleFilterCutEnd=sample->rate*0.5; } ImGui::Separator(); - if (ImGui::SliderFloat("Resonance",&resP,0.0f,99.0f,"%.1f%%")) { + if (CWSliderFloat("Resonance",&resP,0.0f,99.0f,"%.1f%%")) { sampleFilterRes=resP/100.0f; if (sampleFilterRes<0.0f) sampleFilterRes=0.0f; if (sampleFilterRes>0.99f) sampleFilterRes=0.99f; @@ -793,17 +841,17 @@ void FurnaceGUI::drawSampleEdit() { sampleFilterPower=3; } ImGui::Separator(); - if (ImGui::SliderFloat("Low-pass",&lowP,0.0f,100.0f,"%.1f%%")) { + if (CWSliderFloat("Low-pass",&lowP,0.0f,100.0f,"%.1f%%")) { sampleFilterL=lowP/100.0f; if (sampleFilterL<0.0f) sampleFilterL=0.0f; if (sampleFilterL>1.0f) sampleFilterL=1.0f; } - if (ImGui::SliderFloat("Band-pass",&bandP,0.0f,100.0f,"%.1f%%")) { + if (CWSliderFloat("Band-pass",&bandP,0.0f,100.0f,"%.1f%%")) { sampleFilterB=bandP/100.0f; if (sampleFilterB<0.0f) sampleFilterB=0.0f; if (sampleFilterB>1.0f) sampleFilterB=1.0f; } - if (ImGui::SliderFloat("High-pass",&highP,0.0f,100.0f,"%.1f%%")) { + if (CWSliderFloat("High-pass",&highP,0.0f,100.0f,"%.1f%%")) { sampleFilterH=highP/100.0f; if (sampleFilterH<0.0f) sampleFilterH=0.0f; if (sampleFilterH>1.0f) sampleFilterH=1.0f; @@ -820,7 +868,7 @@ void FurnaceGUI::drawSampleEdit() { double power=(sampleFilterCutStart>sampleFilterCutEnd)?0.5:2.0; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; irate))*M_PI); @@ -836,7 +884,7 @@ void FurnaceGUI::drawSampleEdit() { if (val>32767) val=32767; sample->data16[i]=val; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; irate))*M_PI); @@ -890,14 +938,14 @@ void FurnaceGUI::drawSampleEdit() { SAMPLE_OP_BEGIN; float vol=amplifyVol/100.0f; - if (sample->depth==16) { + if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[i]*vol; if (val<-32768) val=-32768; if (val>32767) val=32767; sample->data16[i]=val; } - } else if (sample->depth==8) { + } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; idata8[i]*vol; if (val<-128) val=-128; @@ -991,7 +1039,7 @@ void FurnaceGUI::drawSampleEdit() { if (silenceSize<0) silenceSize=0; if (silenceSize>16777215) silenceSize=16777215; } - if (ImGui::Button("Resize")) { + if (ImGui::Button("Go")) { int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?sample->samples:sampleSelStart; sample->prepareUndo(true); e->lockEngine([this,sample,pos]() { @@ -1138,7 +1186,7 @@ void FurnaceGUI::drawSampleEdit() { for (int i=0; iloopStart>=0 && sample->loopStart<(int)sample->samples && scaledPos>=sample->loopStart) { + if (sample->isLoopable() && (scaledPos>=sample->loopStart && scaledPos<=sample->loopEnd)) { data[i*availX+j]=bgColorLoop; } else { data[i*availX+j]=bgColor; @@ -1158,7 +1206,7 @@ void FurnaceGUI::drawSampleEdit() { if (xCoarse>=sample->samples) break; int y1, y2; int totalAdvance=0; - if (sample->depth==8) { + if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { y1=((unsigned char)sample->data8[xCoarse]^0x80)*availY/256; } else { y1=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; @@ -1171,7 +1219,7 @@ void FurnaceGUI::drawSampleEdit() { totalAdvance+=xAdvanceCoarse; if (xCoarse>=sample->samples) break; do { - if (sample->depth==8) { + if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { y2=((unsigned char)sample->data8[xCoarse]^0x80)*availY/256; } else { y2=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; @@ -1209,11 +1257,11 @@ void FurnaceGUI::drawSampleEdit() { sampleSelStart=0; sampleSelEnd=sample->samples; } else { - if (sample->samples>0 && (sample->depth==16 || sample->depth==8)) { + if (sample->samples>0 && (sample->depth==DIV_SAMPLE_DEPTH_16BIT || sample->depth==DIV_SAMPLE_DEPTH_8BIT)) { sampleDragStart=rectMin; sampleDragAreaSize=rectSize; - sampleDrag16=(sample->depth==16); - sampleDragTarget=(sample->depth==16)?((void*)sample->data16):((void*)sample->data8); + sampleDrag16=(sample->depth==DIV_SAMPLE_DEPTH_16BIT); + sampleDragTarget=(sample->depth==DIV_SAMPLE_DEPTH_16BIT)?((void*)sample->data16):((void*)sample->data8); sampleDragLen=sample->samples; sampleDragActive=true; sampleSelStart=-1; @@ -1251,6 +1299,9 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::MenuItem("set loop to selection",BIND_FOR(GUI_ACTION_SAMPLE_SET_LOOP))) { doAction(GUI_ACTION_SAMPLE_SET_LOOP); } + if (ImGui::MenuItem("create wavetable from selection",BIND_FOR(GUI_ACTION_SAMPLE_CREATE_WAVE))) { + doAction(GUI_ACTION_SAMPLE_CREATE_WAVE); + } ImGui::EndPopup(); } @@ -1312,7 +1363,7 @@ void FurnaceGUI::drawSampleEdit() { posX=samplePos+pos.x*sampleZoom; if (posX>(int)sample->samples) posX=-1; } - posY=(0.5-pos.y/rectSize.y)*((sample->depth==8)?255:32767); + posY=(0.5-pos.y/rectSize.y)*((sample->depth==DIV_SAMPLE_DEPTH_8BIT)?255:32767); if (posX>=0) { statusBar+=fmt::sprintf(" | (%d, %d)",posX,posY); } @@ -1362,7 +1413,7 @@ void FurnaceGUI::drawSampleEdit() { } } - if (sample->depth!=8 && sample->depth!=16) { + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) { statusBar="Non-8/16-bit samples cannot be edited without prior conversion."; } diff --git a/src/gui/sampleUtil.h b/src/gui/sampleUtil.h index 981a5604..187f5ebf 100644 --- a/src/gui/sampleUtil.h +++ b/src/gui/sampleUtil.h @@ -20,6 +20,10 @@ #define SAMPLE_OP_BEGIN \ unsigned int start=0; \ unsigned int end=sample->samples; \ + if (sampleSelStart<0) sampleSelStart=0; \ + if (sampleSelStart>(int)sample->samples) sampleSelStart=sample->samples; \ + if (sampleSelEnd<0) sampleSelEnd=0; \ + if (sampleSelEnd>(int)sample->samples) sampleSelEnd=sample->samples; \ if (sampleSelStart!=-1 && sampleSelEnd!=-1 && sampleSelStart!=sampleSelEnd) { \ start=sampleSelStart; \ end=sampleSelEnd; \ diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index ea1d9274..c771f6a1 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -21,6 +21,7 @@ #include "fonts.h" #include "../ta-log.h" #include "../fileutils.h" +#include "../utfutils.h" #include "util.h" #include "guiConst.h" #include "intConst.h" @@ -39,6 +40,13 @@ #define POWER_SAVE_DEFAULT 0 #endif +#ifdef __HAIKU__ +// NFD doesn't support Haiku +#define SYS_FILE_DIALOG_DEFAULT 0 +#else +#define SYS_FILE_DIALOG_DEFAULT 1 +#endif + const char* mainFonts[]={ "IBM Plex Sans", "Liberation Sans", @@ -89,6 +97,11 @@ const char* nesCores[]={ "NSFplay" }; +const char* c64Cores[]={ + "reSID", + "reSIDfp" +}; + const char* pcspkrOutMethods[]={ "evdev SND_TONE", "KIOCSOUND on /dev/tty1", @@ -251,10 +264,10 @@ void FurnaceGUI::drawSettings() { } ImGui::Separator(); - - ImGui::Text("Initial system/chips:"); + + ImGui::Text("Initial system:"); ImGui::SameLine(); - if (ImGui::Button("Current systems")) { + if (ImGui::Button("Current system")) { settings.initialSys.clear(); for (int i=0; isong.systemLen; i++) { settings.initialSys.push_back(e->song.system[i]); @@ -262,6 +275,7 @@ void FurnaceGUI::drawSettings() { settings.initialSys.push_back(e->song.systemPan[i]); settings.initialSys.push_back(e->song.systemFlags[i]); } + settings.initialSysName=e->song.systemName; } ImGui::SameLine(); if (ImGui::Button("Randomize")) { @@ -282,6 +296,31 @@ void FurnaceGUI::drawSettings() { settings.initialSys.push_back(0); settings.initialSys.push_back(0); } + // randomize system name + std::vector wordPool[6]; + for (size_t i=0; igetSystemName((DivSystem)settings.initialSys[i*4]); + String nameWord; + sName+=" "; + for (char& i: sName) { + if (i==' ') { + if (nameWord!="") { + wordPool[wpPos++].push_back(nameWord); + if (wpPos>=6) break; + nameWord=""; + } + } else { + nameWord+=i; + } + } + } + settings.initialSysName=""; + for (int i=0; i<6; i++) { + if (wordPool[i].empty()) continue; + settings.initialSysName+=wordPool[i][rand()%wordPool[i].size()]; + settings.initialSysName+=" "; + } } ImGui::SameLine(); if (ImGui::Button("Reset to defaults")) { @@ -294,7 +333,14 @@ void FurnaceGUI::drawSettings() { settings.initialSys.push_back(32); settings.initialSys.push_back(0); settings.initialSys.push_back(0); + settings.initialSysName="Sega Genesis/Mega Drive"; } + + ImGui::Text("Name"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputText("##InitSysName",&settings.initialSysName); + for (size_t i=0; igetAudioDescWant(); TAAudioDesc& audioGot=e->getAudioDescGot(); @@ -627,7 +710,7 @@ void FurnaceGUI::drawSettings() { } if (hasToReloadMidi) { - midiMap.read(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); + midiMap.read(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); midiMap.compile(); } @@ -902,6 +985,10 @@ void FurnaceGUI::drawSettings() { ImGui::SameLine(); ImGui::Combo("##FDSCore",&settings.fdsCore,nesCores,2); + ImGui::Text("SID core"); + ImGui::SameLine(); + ImGui::Combo("##C64Core",&settings.c64Core,c64Cores,2); + ImGui::Separator(); ImGui::Text("PC Speaker strategy"); @@ -1015,6 +1102,48 @@ void FurnaceGUI::drawSettings() { ); } + bool loadChineseTraditionalB=settings.loadChineseTraditional; + if (ImGui::Checkbox("Display Chinese (Traditional) characters",&loadChineseTraditionalB)) { + settings.loadChineseTraditional=loadChineseTraditionalB; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "Only toggle this option if you have enough graphics memory.\n" + "This is a temporary solution until dynamic font atlas is implemented in Dear ImGui.\n\n" + "請在確保你有足夠的顯存后再啟動此設定\n" + "這是一個在ImGui實現動態字體加載之前的臨時解決方案" + ); + } + + bool loadKoreanB=settings.loadKorean; + if (ImGui::Checkbox("Display Korean characters",&loadKoreanB)) { + settings.loadKorean=loadKoreanB; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "Only toggle this option if you have enough graphics memory.\n" + "This is a temporary solution until dynamic font atlas is implemented in Dear ImGui.\n\n" + "그래픽 메모리가 충분한 경우에만 이 옵션을 선택하십시오.\n" + "이 옵션은 Dear ImGui에 동적 글꼴 아틀라스가 구현될 때까지 임시 솔루션입니다." + ); + } + + ImGui::Separator(); + + if (ImGui::InputInt("Number of recent files",&settings.maxRecentFile)) { + if (settings.maxRecentFile<0) settings.maxRecentFile=0; + if (settings.maxRecentFile>30) settings.maxRecentFile=30; + } + + ImGui::Separator(); + + ImGui::Text("Pattern view labels:"); + ImGui::InputTextWithHint("Note off (3-char)","OFF",&settings.noteOffLabel); + ImGui::InputTextWithHint("Note release (3-char)","===",&settings.noteRelLabel); + ImGui::InputTextWithHint("Macro release (3-char)","REL",&settings.macroRelLabel); + ImGui::InputTextWithHint("Empty field (3-char)","...",&settings.emptyLabel); + ImGui::InputTextWithHint("Empty field (2-char)","..",&settings.emptyLabel2); + ImGui::Separator(); ImGui::Text("Orders row number format:"); @@ -1117,6 +1246,15 @@ void FurnaceGUI::drawSettings() { if (ImGui::RadioButton("Compact (4x1)##fml3",settings.fmLayout==3)) { settings.fmLayout=3; } + if (ImGui::RadioButton("Alternate (2x2)##fml4",settings.fmLayout==4)) { + settings.fmLayout=4; + } + if (ImGui::RadioButton("Alternate (1x4)##fml5",settings.fmLayout==5)) { + settings.fmLayout=5; + } + if (ImGui::RadioButton("Alternate (4x1)##fml5",settings.fmLayout==6)) { + settings.fmLayout=6; + } ImGui::Text("Position of Sustain in FM editor:"); if (ImGui::RadioButton("Between Decay and Sustain Rate##susp0",settings.susPosition==0)) { @@ -1128,6 +1266,94 @@ void FurnaceGUI::drawSettings() { ImGui::Separator(); + ImGui::Text("Namco 163 chip name"); + ImGui::SameLine(); + ImGui::InputTextWithHint("##C163Name",DIV_C163_DEFAULT_NAME,&settings.c163Name); + + ImGui::Separator(); + + ImGui::Text("Channel colors:"); + if (ImGui::RadioButton("Single##CHC0",settings.channelColors==0)) { + settings.channelColors=0; + } + if (ImGui::RadioButton("Channel type##CHC1",settings.channelColors==1)) { + settings.channelColors=1; + } + if (ImGui::RadioButton("Instrument type##CHC2",settings.channelColors==2)) { + settings.channelColors=2; + } + + ImGui::Text("Channel name colors:"); + if (ImGui::RadioButton("Single##CTC0",settings.channelTextColors==0)) { + settings.channelTextColors=0; + } + if (ImGui::RadioButton("Channel type##CTC1",settings.channelTextColors==1)) { + settings.channelTextColors=1; + } + if (ImGui::RadioButton("Instrument type##CTC2",settings.channelTextColors==2)) { + settings.channelTextColors=2; + } + + ImGui::Text("Channel style:"); + if (ImGui::RadioButton("Classic##CHS0",settings.channelStyle==0)) { + settings.channelStyle=0; + } + if (ImGui::RadioButton("Line##CHS1",settings.channelStyle==1)) { + settings.channelStyle=1; + } + if (ImGui::RadioButton("Round##CHS2",settings.channelStyle==2)) { + settings.channelStyle=2; + } + if (ImGui::RadioButton("Split button##CHS3",settings.channelStyle==3)) { + settings.channelStyle=3; + } + if (ImGui::RadioButton("Square border##CH42",settings.channelStyle==4)) { + settings.channelStyle=4; + } + if (ImGui::RadioButton("Round border##CHS5",settings.channelStyle==5)) { + settings.channelStyle=5; + } + + ImGui::Text("Channel volume bar:"); + if (ImGui::RadioButton("None##CHV0",settings.channelVolStyle==0)) { + settings.channelVolStyle=0; + } + if (ImGui::RadioButton("Simple##CHV1",settings.channelVolStyle==1)) { + settings.channelVolStyle=1; + } + if (ImGui::RadioButton("Stereo##CHV2",settings.channelVolStyle==2)) { + settings.channelVolStyle=2; + } + if (ImGui::RadioButton("Real##CHV3",settings.channelVolStyle==3)) { + settings.channelVolStyle=3; + } + + ImGui::Text("Channel feedback style:"); + + if (ImGui::RadioButton("Off##CHF0",settings.channelFeedbackStyle==0)) { + settings.channelFeedbackStyle=0; + } + if (ImGui::RadioButton("Note##CHF1",settings.channelFeedbackStyle==1)) { + settings.channelFeedbackStyle=1; + } + if (ImGui::RadioButton("Volume##CHF2",settings.channelFeedbackStyle==2)) { + settings.channelFeedbackStyle=2; + } + if (ImGui::RadioButton("Active##CHF3",settings.channelFeedbackStyle==3)) { + settings.channelFeedbackStyle=3; + } + + ImGui::Text("Channel font:"); + + if (ImGui::RadioButton("Regular##CHFont0",settings.channelFont==0)) { + settings.channelFont=0; + } + if (ImGui::RadioButton("Monospace##CHFont1",settings.channelFont==1)) { + settings.channelFont=1; + } + + ImGui::Separator(); + bool insEditColorizeB=settings.insEditColorize; if (ImGui::Checkbox("Colorize instrument editor using instrument type",&insEditColorizeB)) { settings.insEditColorize=insEditColorizeB; @@ -1153,11 +1379,6 @@ void FurnaceGUI::drawSettings() { } ImGui::EndDisabled(); - bool chipNamesB=settings.chipNames; - if (ImGui::Checkbox("Use chip names instead of system names",&chipNamesB)) { - settings.chipNames=chipNamesB; - } - bool oplStandardWaveNamesB=settings.oplStandardWaveNames; if (ImGui::Checkbox("Use standard OPL waveform names",&oplStandardWaveNamesB)) { settings.oplStandardWaveNames=oplStandardWaveNamesB; @@ -1177,7 +1398,12 @@ void FurnaceGUI::drawSettings() { if (ImGui::Checkbox("Use German notation",&germanNotationB)) { settings.germanNotation=germanNotationB; } - + + bool unsignedDetuneB=settings.unsignedDetune; + if (ImGui::Checkbox("Unsigned FM detune values",&unsignedDetuneB)) { + settings.unsignedDetune=unsignedDetuneB; + } + // sorry. temporarily disabled until ImGui has a way to add separators in tables arbitrarily. /*bool sysSeparatorsB=settings.sysSeparators; if (ImGui::Checkbox("Add separators between systems in Orders",&sysSeparatorsB)) { @@ -1386,12 +1612,12 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_FM_SECONDARY_MOD,"Mod. accent (secondary)"); UI_COLOR_CONFIG(GUI_COLOR_FM_BORDER_MOD,"Mod. border"); UI_COLOR_CONFIG(GUI_COLOR_FM_BORDER_SHADOW_MOD,"Mod. border shadow"); - + UI_COLOR_CONFIG(GUI_COLOR_FM_PRIMARY_CAR,"Car. accent (primary"); UI_COLOR_CONFIG(GUI_COLOR_FM_SECONDARY_CAR,"Car. accent (secondary)"); UI_COLOR_CONFIG(GUI_COLOR_FM_BORDER_CAR,"Car. border"); UI_COLOR_CONFIG(GUI_COLOR_FM_BORDER_SHADOW_CAR,"Car. border shadow"); - + ImGui::TreePop(); } if (ImGui::TreeNode("Macro Editor")) { @@ -1420,7 +1646,11 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_OPL,"FM (OPL)"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_FDS,"FDS"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_VBOY,"Virtual Boy"); - UI_COLOR_CONFIG(GUI_COLOR_INSTR_N163,"Namco 163"); + // special case + String c163Label=fmt::sprintf("%s##CC_GUI_COLOR_INSTR_N163",settings.c163Name); + if (ImGui::ColorEdit4(c163Label.c_str(),(float*)&uiColors[GUI_COLOR_INSTR_N163])) { + applyUISettings(false); + } UI_COLOR_CONFIG(GUI_COLOR_INSTR_SCC,"Konami SCC"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_OPZ,"FM (OPZ)"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_POKEY,"POKEY"); @@ -1438,6 +1668,8 @@ void FurnaceGUI::drawSettings() { ImGui::TreePop(); } if (ImGui::TreeNode("Channel")) { + UI_COLOR_CONFIG(GUI_COLOR_CHANNEL_BG,"Single color (background)"); + UI_COLOR_CONFIG(GUI_COLOR_CHANNEL_FG,"Single color (text)"); UI_COLOR_CONFIG(GUI_COLOR_CHANNEL_FM,"FM"); UI_COLOR_CONFIG(GUI_COLOR_CHANNEL_PULSE,"Pulse"); UI_COLOR_CONFIG(GUI_COLOR_CHANNEL_NOISE,"Noise"); @@ -1479,12 +1711,21 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SONG,"Song effect"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_TIME,"Time effect"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SPEED,"Speed effect"); - UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,"Primary system effect"); - UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY,"Secondary system effect"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,"Primary specific effect"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY,"Secondary specific effect"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_MISC,"Miscellaneous"); UI_COLOR_CONFIG(GUI_COLOR_EE_VALUE,"External command output"); ImGui::TreePop(); } + if (ImGui::TreeNode("Pattern Manager")) { + UI_COLOR_CONFIG(GUI_COLOR_PAT_MANAGER_NULL,"Unallocated"); + UI_COLOR_CONFIG(GUI_COLOR_PAT_MANAGER_UNUSED,"Unused"); + UI_COLOR_CONFIG(GUI_COLOR_PAT_MANAGER_USED,"Used"); + UI_COLOR_CONFIG(GUI_COLOR_PAT_MANAGER_OVERUSED,"Overused"); + UI_COLOR_CONFIG(GUI_COLOR_PAT_MANAGER_EXTREMELY_OVERUSED,"Really overused"); + UI_COLOR_CONFIG(GUI_COLOR_PAT_MANAGER_COMBO_BREAKER,"Combo Breaker"); + ImGui::TreePop(); + } if (ImGui::TreeNode("Piano")) { UI_COLOR_CONFIG(GUI_COLOR_PIANO_BACKGROUND,"Background"); UI_COLOR_CONFIG(GUI_COLOR_PIANO_KEY_TOP,"Upper key"); @@ -1577,6 +1818,7 @@ void FurnaceGUI::drawSettings() { UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_DEBUG); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_OSCILLOSCOPE); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_CHAN_OSC); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_EFFECT_LIST); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_VOL_METER); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_STATS); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_COMPAT_FLAGS); @@ -1623,7 +1865,7 @@ void FurnaceGUI::drawSettings() { ImGui::Text("%s",SDL_GetScancodeName((SDL_Scancode)i.scan)); ImGui::TableNextColumn(); if (i.val==102) { - snprintf(id,4095,"Envelope release##SNType_%d",i.scan); + snprintf(id,4095,"Macro release##SNType_%d",i.scan); if (ImGui::Button(id)) { noteKeys[i.scan]=0; } @@ -1736,7 +1978,7 @@ void FurnaceGUI::drawSettings() { UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COLLAPSE_ROWS); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_EXPAND_ROWS); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_LATCH); - + // TODO: collapse/expand pattern and song KEYBIND_CONFIG_END; @@ -1939,6 +2181,7 @@ void FurnaceGUI::syncSettings() { settings.audioDevice=e->getConfString("audioDevice",""); settings.midiInDevice=e->getConfString("midiInDevice",""); settings.midiOutDevice=e->getConfString("midiOutDevice",""); + settings.c163Name=e->getConfString("c163Name",DIV_C163_DEFAULT_NAME); settings.audioQuality=e->getConfInt("audioQuality",0); settings.audioBufSize=e->getConfInt("audioBufSize",1024); settings.audioRate=e->getConfInt("audioRate",44100); @@ -1947,6 +2190,7 @@ void FurnaceGUI::syncSettings() { settings.snCore=e->getConfInt("snCore",0); settings.nesCore=e->getConfInt("nesCore",0); settings.fdsCore=e->getConfInt("fdsCore",0); + settings.c64Core=e->getConfInt("c64Core",1); settings.pcSpeakerOutMethod=e->getConfInt("pcSpeakerOutMethod",0); settings.yrw801Path=e->getConfString("yrw801Path",""); settings.tg100Path=e->getConfString("tg100Path",""); @@ -1983,12 +2227,14 @@ void FurnaceGUI::syncSettings() { settings.insFocusesPattern=e->getConfInt("insFocusesPattern",1); settings.stepOnInsert=e->getConfInt("stepOnInsert",0); settings.unifiedDataView=e->getConfInt("unifiedDataView",0); - settings.sysFileDialog=e->getConfInt("sysFileDialog",1); + settings.sysFileDialog=e->getConfInt("sysFileDialog",SYS_FILE_DIALOG_DEFAULT); settings.roundedWindows=e->getConfInt("roundedWindows",1); settings.roundedButtons=e->getConfInt("roundedButtons",1); settings.roundedMenus=e->getConfInt("roundedMenus",0); settings.loadJapanese=e->getConfInt("loadJapanese",0); settings.loadChinese=e->getConfInt("loadChinese",0); + settings.loadChineseTraditional=e->getConfInt("loadChineseTraditional",0); + settings.loadKorean=e->getConfInt("loadKorean",0); settings.fmLayout=e->getConfInt("fmLayout",0); settings.sampleLayout=e->getConfInt("sampleLayout",0); settings.waveLayout=e->getConfInt("waveLayout",0); @@ -2028,6 +2274,25 @@ void FurnaceGUI::syncSettings() { settings.effectValCellSpacing=e->getConfInt("effectValCellSpacing",0); settings.doubleClickColumn=e->getConfInt("doubleClickColumn",1); settings.blankIns=e->getConfInt("blankIns",0); + settings.dragMovesSelection=e->getConfInt("dragMovesSelection",2); + settings.unsignedDetune=e->getConfInt("unsignedDetune",0); + settings.noThreadedInput=e->getConfInt("noThreadedInput",0); + settings.saveWindowPos=e->getConfInt("saveWindowPos",1); + settings.initialSysName=e->getConfString("initialSysName",""); + settings.clampSamples=e->getConfInt("clampSamples",0); + settings.noteOffLabel=e->getConfString("noteOffLabel","OFF"); + settings.noteRelLabel=e->getConfString("noteRelLabel","==="); + settings.macroRelLabel=e->getConfString("macroRelLabel","REL"); + settings.emptyLabel=e->getConfString("emptyLabel","..."); + settings.emptyLabel2=e->getConfString("emptyLabel2",".."); + settings.saveUnusedPatterns=e->getConfInt("saveUnusedPatterns",0); + settings.channelColors=e->getConfInt("channelColors",1); + settings.channelTextColors=e->getConfInt("channelTextColors",0); + settings.channelStyle=e->getConfInt("channelStyle",0); + settings.channelVolStyle=e->getConfInt("channelVolStyle",0); + settings.channelFeedbackStyle=e->getConfInt("channelFeedbackStyle",1); + settings.channelFont=e->getConfInt("channelFont",1); + settings.maxRecentFile=e->getConfInt("maxRecentFile",10); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2041,6 +2306,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.snCore,0,1); clampSetting(settings.nesCore,0,1); clampSetting(settings.fdsCore,0,1); + clampSetting(settings.c64Core,0,1); clampSetting(settings.pcSpeakerOutMethod,0,4); clampSetting(settings.mainFont,0,6); clampSetting(settings.patFont,0,6); @@ -2077,7 +2343,9 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.roundedMenus,0,1); clampSetting(settings.loadJapanese,0,1); clampSetting(settings.loadChinese,0,1); - clampSetting(settings.fmLayout,0,3); + clampSetting(settings.loadChineseTraditional,0,1); + clampSetting(settings.loadKorean,0,1); + clampSetting(settings.fmLayout,0,6); clampSetting(settings.susPosition,0,1); clampSetting(settings.effectCursorDir,0,2); clampSetting(settings.cursorPastePos,0,1); @@ -2112,6 +2380,19 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.effectValCellSpacing,0,32); clampSetting(settings.doubleClickColumn,0,1); clampSetting(settings.blankIns,0,1); + clampSetting(settings.dragMovesSelection,0,2); + clampSetting(settings.unsignedDetune,0,1); + clampSetting(settings.noThreadedInput,0,1); + clampSetting(settings.saveWindowPos,0,1); + clampSetting(settings.clampSamples,0,1); + clampSetting(settings.saveUnusedPatterns,0,1); + clampSetting(settings.channelColors,0,2); + clampSetting(settings.channelTextColors,0,2); + clampSetting(settings.channelStyle,0,5); + clampSetting(settings.channelVolStyle,0,3); + clampSetting(settings.channelFeedbackStyle,0,3); + clampSetting(settings.channelFont,0,1); + clampSetting(settings.maxRecentFile,0,30); settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys","")); if (settings.initialSys.size()<4) { @@ -2136,7 +2417,7 @@ void FurnaceGUI::syncSettings() { parseKeybinds(); - midiMap.read(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); + midiMap.read(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); midiMap.compile(); e->setMidiDirect(midiMap.directChannel); @@ -2144,7 +2425,7 @@ void FurnaceGUI::syncSettings() { } void FurnaceGUI::commitSettings() { - bool sampleROMsChanged = settings.yrw801Path!=e->getConfString("yrw801Path","") || + bool sampleROMsChanged=settings.yrw801Path!=e->getConfString("yrw801Path","") || settings.tg100Path!=e->getConfString("tg100Path","") || settings.mu5Path!=e->getConfString("mu5Path",""); @@ -2155,6 +2436,7 @@ void FurnaceGUI::commitSettings() { e->setConf("audioDevice",settings.audioDevice); e->setConf("midiInDevice",settings.midiInDevice); e->setConf("midiOutDevice",settings.midiOutDevice); + e->setConf("c163Name",settings.c163Name); e->setConf("audioQuality",settings.audioQuality); e->setConf("audioBufSize",settings.audioBufSize); e->setConf("audioRate",settings.audioRate); @@ -2163,6 +2445,7 @@ void FurnaceGUI::commitSettings() { e->setConf("snCore",settings.snCore); e->setConf("nesCore",settings.nesCore); e->setConf("fdsCore",settings.fdsCore); + e->setConf("c64Core",settings.c64Core); e->setConf("pcSpeakerOutMethod",settings.pcSpeakerOutMethod); e->setConf("yrw801Path",settings.yrw801Path); e->setConf("tg100Path",settings.tg100Path); @@ -2205,6 +2488,8 @@ void FurnaceGUI::commitSettings() { e->setConf("roundedMenus",settings.roundedMenus); e->setConf("loadJapanese",settings.loadJapanese); e->setConf("loadChinese",settings.loadChinese); + e->setConf("loadChineseTraditional",settings.loadChineseTraditional); + e->setConf("loadKorean",settings.loadKorean); e->setConf("fmLayout",settings.fmLayout); e->setConf("sampleLayout",settings.sampleLayout); e->setConf("waveLayout",settings.waveLayout); @@ -2234,6 +2519,7 @@ void FurnaceGUI::commitSettings() { e->setConf("moveWindowTitle",settings.moveWindowTitle); e->setConf("hiddenSystems",settings.hiddenSystems); e->setConf("initialSys",e->encodeSysDesc(settings.initialSys)); + e->setConf("initialSysName",settings.initialSysName); e->setConf("horizontalDataView",settings.horizontalDataView); e->setConf("noMultiSystem",settings.noMultiSystem); e->setConf("oldMacroVSlider",settings.oldMacroVSlider); @@ -2245,6 +2531,24 @@ void FurnaceGUI::commitSettings() { e->setConf("effectValCellSpacing",settings.effectValCellSpacing); e->setConf("doubleClickColumn",settings.doubleClickColumn); e->setConf("blankIns",settings.blankIns); + e->setConf("dragMovesSelection",settings.dragMovesSelection); + e->setConf("unsignedDetune",settings.unsignedDetune); + e->setConf("noThreadedInput",settings.noThreadedInput); + e->setConf("saveWindowPos",settings.saveWindowPos); + e->setConf("clampSamples",settings.clampSamples); + e->setConf("noteOffLabel",settings.noteOffLabel); + e->setConf("noteRelLabel",settings.noteRelLabel); + e->setConf("macroRelLabel",settings.macroRelLabel); + e->setConf("emptyLabel",settings.emptyLabel); + e->setConf("emptyLabel2",settings.emptyLabel2); + e->setConf("saveUnusedPatterns",settings.saveUnusedPatterns); + e->setConf("channelColors",settings.channelColors); + e->setConf("channelTextColors",settings.channelTextColors); + e->setConf("channelStyle",settings.channelStyle); + e->setConf("channelVolStyle",settings.channelVolStyle); + e->setConf("channelFeedbackStyle",settings.channelFeedbackStyle); + e->setConf("channelFont",settings.channelFont); + e->setConf("maxRecentFile",settings.maxRecentFile); // colors for (int i=0; isaveConf(); + while (!recentFile.empty() && (int)recentFile.size()>settings.maxRecentFile) { + recentFile.pop_back(); + } + if (sampleROMsChanged) { if (e->loadSampleROMs()) { showError(e->getLastError()); @@ -2657,6 +2965,20 @@ void FurnaceGUI::popAccentColors() { #define SYSTEM_PAT_FONT_PATH_3 "/usr/share/fonts/ubuntu/UbuntuMono-R.ttf" #endif +void setupLabel(const char* lStr, char* label, int len) { + memset(label,0,32); + for (int i=0, p=0; i=0.5f) dpiScale=settings.dpiScale; // colors @@ -2819,6 +3147,12 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { if (settings.loadChinese) { range.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesChineseSimplifiedCommon()); } + if (settings.loadChineseTraditional) { + range.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesChineseFull()); + } + if (settings.loadKorean) { + range.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesKorean()); + } // I'm terribly sorry range.UsedChars[0x80>>5]=0; @@ -2885,7 +3219,7 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { if ((iconFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(iconFont_compressed_data,iconFont_compressed_size,e->getConfInt("iconSize",16)*dpiScale,&fc,fontRangeIcon))==NULL) { logE("could not load icon font!"); } - + if (settings.mainFontSize==settings.patFontSize && settings.patFont<5 && builtinFontM[settings.patFont]==builtinFont[settings.mainFont]) { logD("using main font for pat font."); patFont=mainFont; @@ -2919,7 +3253,7 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { } } } - + if ((bigFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_plexSans_compressed_data,font_plexSans_compressed_size,40*dpiScale))==NULL) { logE("could not load big UI font!"); } diff --git a/src/gui/songInfo.cpp b/src/gui/songInfo.cpp index e90043b7..44982aba 100644 --- a/src/gui/songInfo.cpp +++ b/src/gui/songInfo.cpp @@ -64,6 +64,25 @@ void FurnaceGUI::drawSongInfo() { if (ImGui::InputText("##Author",&e->song.author)) { MARK_MODIFIED; } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Album"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputText("##Category",&e->song.category)) { + MARK_MODIFIED; + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("System"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputText("##SystemName",&e->song.systemName)) { + MARK_MODIFIED; + updateWindowTitle(); + } + ImGui::EndTable(); } @@ -176,7 +195,7 @@ void FurnaceGUI::drawSongInfo() { float setHz=tempoView?e->curSubSong->hz*2.5:e->curSubSong->hz; if (ImGui::InputFloat("##Rate",&setHz,1.0f,1.0f,"%g")) { MARK_MODIFIED if (tempoView) setHz/=2.5; - if (setHz<10) setHz=10; + if (setHz<1) setHz=1; if (setHz>999) setHz=999; e->setSongRate(setHz,setHz<52); } diff --git a/src/engine/platform/ym2610shared.h b/src/gui/spoiler.cpp similarity index 56% rename from src/engine/platform/ym2610shared.h rename to src/gui/spoiler.cpp index 8d8847c2..dc64b14c 100644 --- a/src/engine/platform/ym2610shared.h +++ b/src/gui/spoiler.cpp @@ -17,29 +17,21 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -static unsigned short opOffs[4]={ - 0x00, 0x04, 0x08, 0x0c -}; -static bool isOutput[8][4]={ - // 1 3 2 4 - {false,false,false,true}, - {false,false,false,true}, - {false,false,false,true}, - {false,false,false,true}, - {false,false,true ,true}, - {false,true ,true ,true}, - {false,true ,true ,true}, - {true ,true ,true ,true}, -}; -static unsigned char dtTable[8]={ - 7,6,5,0,1,2,3,4 -}; +#include "gui.h" +#include "imgui.h" -static int orderedOps[4]={ - 0,2,1,3 -}; - -#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} -#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } - -#define CHIP_FREQBASE 9440540 +void FurnaceGUI::drawSpoiler() { + if (nextWindow==GUI_WINDOW_SPOILER) { + spoilerOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!spoilerOpen) return; + if (ImGui::Begin("Spoiler",&spoilerOpen,globalWinFlags|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::PushFont(bigFont); + ImGui::Text("SPOILER"); + ImGui::PopFont(); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SPOILER; + ImGui::End(); +} diff --git a/src/gui/subSongs.cpp b/src/gui/subSongs.cpp index b13b8420..c56bf640 100644 --- a/src/gui/subSongs.cpp +++ b/src/gui/subSongs.cpp @@ -2,6 +2,7 @@ #include "imgui.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" +#include "intConst.h" void FurnaceGUI::drawSubSongs() { if (nextWindow==GUI_WINDOW_SUBSONGS) { @@ -11,7 +12,7 @@ void FurnaceGUI::drawSubSongs() { } if (!subSongsOpen) return; ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); - if (ImGui::Begin("Subsongs",&subSongsOpen,ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar|globalWinFlags)) { + if (ImGui::Begin("Subsongs",&subSongsOpen,globalWinFlags)) { char id[1024]; ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-ImGui::GetFrameHeightWithSpacing()*2.0f-ImGui::GetStyle().ItemSpacing.x); if (e->curSubSong->name.empty()) { diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 9ab85da2..b18a03d5 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -28,19 +28,19 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool case DIV_SYSTEM_YM2612_EXT: case DIV_SYSTEM_YM2612_FRAC: case DIV_SYSTEM_YM2612_FRAC_EXT: { - if (ImGui::RadioButton("NTSC (7.67MHz)",(flags&7)==0)) { + if (ImGui::RadioButton("NTSC (7.67MHz)",(flags&(~0x80000000))==0)) { copyOfFlags=(flags&0x80000000)|0; } - if (ImGui::RadioButton("PAL (7.61MHz)",(flags&7)==1)) { + if (ImGui::RadioButton("PAL (7.61MHz)",(flags&(~0x80000000))==1)) { copyOfFlags=(flags&0x80000000)|1; } - if (ImGui::RadioButton("FM Towns (8MHz)",(flags&7)==2)) { + if (ImGui::RadioButton("FM Towns (8MHz)",(flags&(~0x80000000))==2)) { copyOfFlags=(flags&0x80000000)|2; } - if (ImGui::RadioButton("AtGames Genesis (6.13MHz)",(flags&7)==3)) { + if (ImGui::RadioButton("AtGames Genesis (6.13MHz)",(flags&(~0x80000000))==3)) { copyOfFlags=(flags&0x80000000)|3; } - if (ImGui::RadioButton("Sega System 32 (8.05MHz)",(flags&7)==4)) { + if (ImGui::RadioButton("Sega System 32 (8.05MHz)",(flags&(~0x80000000))==4)) { copyOfFlags=(flags&0x80000000)|4; } bool ladder=flags&0x80000000; @@ -51,43 +51,159 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } case DIV_SYSTEM_SMS: { ImGui::Text("Clock rate:"); - if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&3)==0)) { - copyOfFlags=(flags&(~3))|0; - + if (ImGui::RadioButton("3.58MHz (NTSC)",(flags&0xff03)==0x0000)) { + copyOfFlags=(flags&(~0xff03))|0x0000; } - if (ImGui::RadioButton("PAL (3.55MHz)",(flags&3)==1)) { - copyOfFlags=(flags&(~3))|1; - + if (ImGui::RadioButton("3.55MHz (PAL)",(flags&0xff03)==0x0001)) { + copyOfFlags=(flags&(~0xff03))|0x0001; } - if (ImGui::RadioButton("BBC Micro (4MHz)",(flags&3)==2)) { - copyOfFlags=(flags&(~3))|2; - + if (ImGui::RadioButton("4MHz (BBC Micro)",(flags&0xff03)==0x0002)) { + copyOfFlags=(flags&(~0xff03))|0x0002; } - if (ImGui::RadioButton("Half NTSC (1.79MHz)",(flags&3)==3)) { - copyOfFlags=(flags&(~3))|3; - + if (ImGui::RadioButton("1.79MHz (Half NTSC)",(flags&0xff03)==0x0003)) { + copyOfFlags=(flags&(~0xff03))|0x0003; + } + if (ImGui::RadioButton("3MHz (Exed Exes)",(flags&0xff03)==0x0100)) { + copyOfFlags=(flags&(~0xff03))|0x0100; + } + if (ImGui::RadioButton("2MHz (Sega System 1)",(flags&0xff03)==0x0101)) { + copyOfFlags=(flags&(~0xff03))|0x0101; + } + if (ImGui::RadioButton("447KHz (TI-99/4A)",(flags&0xff03)==0x0102)) { + copyOfFlags=(flags&(~0xff03))|0x0102; } ImGui::Text("Chip type:"); - if (ImGui::RadioButton("Sega VDP/Master System",((flags>>2)&3)==0)) { - copyOfFlags=(flags&(~12))|0; - + if (ImGui::RadioButton("Sega VDP/Master System",(flags&0xcc)==0x00)) { + copyOfFlags=(flags&(~0xcc))|0x00; } - if (ImGui::RadioButton("TI SN76489",((flags>>2)&3)==1)) { - copyOfFlags=(flags&(~12))|4; - + if (ImGui::RadioButton("TI SN76489",(flags&0xcc)==0x04)) { + copyOfFlags=(flags&(~0xcc))|0x04; } - if (ImGui::RadioButton("TI SN76489 with Atari-like short noise",((flags>>2)&3)==2)) { - copyOfFlags=(flags&(~12))|8; - + if (ImGui::RadioButton("TI SN76489 with Atari-like short noise",(flags&0xcc)==0x08)) { + copyOfFlags=(flags&(~0xcc))|0x08; + } + if (ImGui::RadioButton("Game Gear",(flags&0xcc)==0x0c)) { + copyOfFlags=(flags&(~0xcc))|0x0c; + } + if (ImGui::RadioButton("TI SN76489A",(flags&0xcc)==0x40)) { + copyOfFlags=(flags&(~0xcc))|0x40; + } + if (ImGui::RadioButton("TI SN76496",(flags&0xcc)==0x44)) { + copyOfFlags=(flags&(~0xcc))|0x44; + } + if (ImGui::RadioButton("NCR 8496",(flags&0xcc)==0x48)) { + copyOfFlags=(flags&(~0xcc))|0x48; + } + if (ImGui::RadioButton("Tandy PSSJ 3-voice sound",(flags&0xcc)==0x4c)) { + copyOfFlags=(flags&(~0xcc))|0x4c; + } + if (ImGui::RadioButton("TI SN94624",(flags&0xcc)==0x80)) { + copyOfFlags=(flags&(~0xcc))|0x80; + } + if (ImGui::RadioButton("TI SN76494",(flags&0xcc)==0x84)) { + copyOfFlags=(flags&(~0xcc))|0x84; } - /*if (ImGui::RadioButton("Game Gear",(flags>>2)==3)) { - copyOfFlags=(flags&3)|12); - }*/ - bool noPhaseReset=flags&16; if (ImGui::Checkbox("Disable noise period change phase reset",&noPhaseReset)) { copyOfFlags=(flags&(~16))|(noPhaseReset<<4); - + } + break; + } + case DIV_SYSTEM_PCE: { + sysPal=flags&1; + if (ImGui::Checkbox("Pseudo-PAL",&sysPal)) { + copyOfFlags=(flags&(~1))|(unsigned int)sysPal; + } + bool antiClick=flags&8; + if (ImGui::Checkbox("Disable anti-click",&antiClick)) { + copyOfFlags=(flags&(~8))|(antiClick<<3); + } + ImGui::Text("Chip revision:"); + if (ImGui::RadioButton("HuC6280 (original)",(flags&4)==0)) { + copyOfFlags=(flags&(~4))|0; + } + if (ImGui::RadioButton("HuC6280A (SuperGrafx)",(flags&4)==4)) { + copyOfFlags=(flags&(~4))|4; + } + break; + } + case DIV_SYSTEM_SOUND_UNIT: { + ImGui::Text("CPU rate:"); + if (ImGui::RadioButton("6.18MHz (NTSC)",(flags&3)==0)) { + copyOfFlags=(flags&(~3))|0; + } + if (ImGui::RadioButton("5.95MHz (PAL)",(flags&3)==1)) { + copyOfFlags=(flags&(~3))|1; + } + ImGui::Text("Sample memory:"); + if (ImGui::RadioButton("8K (rev A/B/E)",(flags&16)==0)) { + copyOfFlags=(flags&(~16))|0; + } + if (ImGui::RadioButton("64K (rev D/F)",(flags&16)==16)) { + copyOfFlags=(flags&(~16))|16; + } + ImGui::Text("DAC resolution"); + if (ImGui::RadioButton("16-bit (rev A/B/D/F)",(flags&32)==0)) { + copyOfFlags=(flags&(~32))|0; + } + if (ImGui::RadioButton("1-bit PDM (rev C/E)",(flags&32)==32)) { + copyOfFlags=(flags&(~32))|32; + } + bool echo=flags&4; + if (ImGui::Checkbox("Enable echo",&echo)) { + copyOfFlags=(flags&(~4))|(echo<<2); + } + bool flipEcho=flags&8; + if (ImGui::Checkbox("Swap echo channels",&flipEcho)) { + copyOfFlags=(flags&(~8))|(flipEcho<<3); + } + ImGui::Text("Echo delay:"); + int echoBufSize=(flags&0x3f00)>>8; + if (CWSliderInt("##EchoBufSize",&echoBufSize,0,63)) { + if (echoBufSize<0) echoBufSize=0; + if (echoBufSize>63) echoBufSize=63; + copyOfFlags=(flags&~0x3f00)|(echoBufSize<<8); + } rightClickable + ImGui::Text("Echo resolution:"); + int echoResolution=(flags&0xf00000)>>20; + if (CWSliderInt("##EchoResolution",&echoResolution,0,15)) { + if (echoResolution<0) echoResolution=0; + if (echoResolution>15) echoResolution=15; + copyOfFlags=(flags&(~0xf00000))|(echoResolution<<20); + } rightClickable + ImGui::Text("Echo feedback:"); + int echoFeedback=(flags&0xf0000)>>16; + if (CWSliderInt("##EchoFeedback",&echoFeedback,0,15)) { + if (echoFeedback<0) echoFeedback=0; + if (echoFeedback>15) echoFeedback=15; + copyOfFlags=(flags&(~0xf0000))|(echoFeedback<<16); + } rightClickable + ImGui::Text("Echo volume:"); + int echoVolume=(signed char)((flags&0xff000000)>>24); + if (CWSliderInt("##EchoVolume",&echoVolume,-128,127)) { + if (echoVolume<-128) echoVolume=-128; + if (echoVolume>127) echoVolume=127; + copyOfFlags=(flags&(~0xff000000))|(((unsigned char)echoVolume)<<24); + } rightClickable + break; + } + case DIV_SYSTEM_GB: { + bool antiClick=flags&8; + if (ImGui::Checkbox("Disable anti-click",&antiClick)) { + copyOfFlags=(flags&(~8))|(antiClick<<3); + } + ImGui::Text("Chip revision:"); + if (ImGui::RadioButton("Original (DMG)",(flags&7)==0)) { + copyOfFlags=(flags&(~7))|0; + } + if (ImGui::RadioButton("Game Boy Color (rev C)",(flags&7)==1)) { + copyOfFlags=(flags&(~7))|1; + } + if (ImGui::RadioButton("Game Boy Color (rev E)",(flags&7)==2)) { + copyOfFlags=(flags&(~7))|2; + } + if (ImGui::RadioButton("Game Boy Advance",(flags&7)==3)) { + copyOfFlags=(flags&(~7))|3; } break; } @@ -97,37 +213,29 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool ImGui::Text("Clock rate:"); if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&15)==0)) { copyOfFlags=(flags&(~15))|0; - } if (ImGui::RadioButton("PAL (3.55MHz)",(flags&15)==1)) { copyOfFlags=(flags&(~15))|1; - } if (ImGui::RadioButton("BBC Micro (4MHz)",(flags&15)==2)) { copyOfFlags=(flags&(~15))|2; - } if (ImGui::RadioButton("Half NTSC (1.79MHz)",(flags&15)==3)) { copyOfFlags=(flags&(~15))|3; - } if (type!=DIV_SYSTEM_VRC7) { ImGui::Text("Patch set:"); if (ImGui::RadioButton("Yamaha YM2413",((flags>>4)&15)==0)) { copyOfFlags=(flags&(~0xf0))|0; - } if (ImGui::RadioButton("Yamaha YMF281",((flags>>4)&15)==1)) { copyOfFlags=(flags&(~0xf0))|0x10; - } if (ImGui::RadioButton("Yamaha YM2423",((flags>>4)&15)==2)) { copyOfFlags=(flags&(~0xf0))|0x20; - } if (ImGui::RadioButton("Konami VRC7",((flags>>4)&15)==3)) { copyOfFlags=(flags&(~0xf0))|0x30; - } } break; @@ -135,15 +243,12 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool case DIV_SYSTEM_YM2151: if (ImGui::RadioButton("NTSC/X16 (3.58MHz)",flags==0)) { copyOfFlags=0; - } if (ImGui::RadioButton("PAL (3.55MHz)",flags==1)) { copyOfFlags=1; - } if (ImGui::RadioButton("X1/X68000 (4MHz)",flags==2)) { copyOfFlags=2; - } break; case DIV_SYSTEM_NES: @@ -152,30 +257,37 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool case DIV_SYSTEM_MMC5: if (ImGui::RadioButton("NTSC (1.79MHz)",flags==0)) { copyOfFlags=0; - } if (ImGui::RadioButton("PAL (1.67MHz)",flags==1)) { copyOfFlags=1; - } if (ImGui::RadioButton("Dendy (1.77MHz)",flags==2)) { copyOfFlags=2; - } break; case DIV_SYSTEM_C64_8580: case DIV_SYSTEM_C64_6581: if (ImGui::RadioButton("NTSC (1.02MHz)",flags==0)) { copyOfFlags=0; - } if (ImGui::RadioButton("PAL (0.99MHz)",flags==1)) { copyOfFlags=1; - } if (ImGui::RadioButton("SSI 2001 (0.89MHz)",flags==2)) { copyOfFlags=2; - + } + break; + case DIV_SYSTEM_YM2610: + case DIV_SYSTEM_YM2610_EXT: + case DIV_SYSTEM_YM2610_FULL: + case DIV_SYSTEM_YM2610_FULL_EXT: + case DIV_SYSTEM_YM2610B: + case DIV_SYSTEM_YM2610B_EXT: + if (ImGui::RadioButton("8MHz (Neo Geo MVS)",(flags&0xff)==0)) { + copyOfFlags=(flags&(~0xff))|0; + } + if (ImGui::RadioButton("8.06MHz (Neo Geo AES)",(flags&0xff)==1)) { + copyOfFlags=(flags&(~0xff))|1; } break; case DIV_SYSTEM_AY8910: @@ -183,87 +295,74 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool ImGui::Text("Clock rate:"); if (ImGui::RadioButton("1.79MHz (ZX Spectrum NTSC/MSX)",(flags&15)==0)) { copyOfFlags=(flags&(~15))|0; - } if (ImGui::RadioButton("1.77MHz (ZX Spectrum)",(flags&15)==1)) { copyOfFlags=(flags&(~15))|1; - } if (ImGui::RadioButton("1.75MHz (ZX Spectrum)",(flags&15)==2)) { copyOfFlags=(flags&(~15))|2; - } if (ImGui::RadioButton("2MHz (Atari ST/Sharp X1)",(flags&15)==3)) { copyOfFlags=(flags&(~15))|3; - } if (ImGui::RadioButton("1.5MHz (Vectrex)",(flags&15)==4)) { copyOfFlags=(flags&(~15))|4; - } if (ImGui::RadioButton("1MHz (Amstrad CPC)",(flags&15)==5)) { copyOfFlags=(flags&(~15))|5; - } if (ImGui::RadioButton("0.89MHz (Pre-divided Sunsoft 5B)",(flags&15)==6)) { copyOfFlags=(flags&(~15))|6; - } if (ImGui::RadioButton("1.67MHz (?)",(flags&15)==7)) { copyOfFlags=(flags&(~15))|7; - } if (ImGui::RadioButton("0.83MHz (Pre-divided Sunsoft 5B on PAL)",(flags&15)==8)) { copyOfFlags=(flags&(~15))|8; - } if (ImGui::RadioButton("1.10MHz (Gamate/VIC-20 PAL)",(flags&15)==9)) { copyOfFlags=(flags&(~15))|9; - } if (ImGui::RadioButton("2^21Hz (Game Boy)",(flags&15)==10)) { copyOfFlags=(flags&(~15))|10; - } if (ImGui::RadioButton("3.58MHz (Darky)",(flags&15)==11)) { copyOfFlags=(flags&(~15))|11; - } if (ImGui::RadioButton("3.6MHz (Darky)",(flags&15)==12)) { copyOfFlags=(flags&(~15))|12; - + } + if (ImGui::RadioButton("1.25MHz (Mag Max)",(flags&15)==13)) { + copyOfFlags=(flags&(~15))|13; + } + if (ImGui::RadioButton("1.536MHz (Kyugo)",(flags&15)==14)) { + copyOfFlags=(flags&(~15))|14; } if (type==DIV_SYSTEM_AY8910) { ImGui::Text("Chip type:"); if (ImGui::RadioButton("AY-3-8910",(flags&0x30)==0)) { copyOfFlags=(flags&(~0x30))|0; - } if (ImGui::RadioButton("YM2149(F)",(flags&0x30)==16)) { copyOfFlags=(flags&(~0x30))|16; - } if (ImGui::RadioButton("Sunsoft 5B",(flags&0x30)==32)) { copyOfFlags=(flags&(~0x30))|32; - } if (ImGui::RadioButton("AY-3-8914",(flags&0x30)==48)) { copyOfFlags=(flags&(~0x30))|48; - } } bool stereo=flags&0x40; ImGui::BeginDisabled((type==DIV_SYSTEM_AY8910) && ((flags&0x30)==32)); if (ImGui::Checkbox("Stereo##_AY_STEREO",&stereo)) { copyOfFlags=(flags&(~0x40))|(stereo?0x40:0); - } ImGui::EndDisabled(); bool clockSel=flags&0x80; ImGui::BeginDisabled((type==DIV_SYSTEM_AY8910) && ((flags&0x30)!=16)); if (ImGui::Checkbox("Half Clock divider##_AY_CLKSEL",&clockSel)) { copyOfFlags=(flags&(~0x80))|(clockSel?0x80:0); - } ImGui::EndDisabled(); break; @@ -303,6 +402,24 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } break; } + case DIV_SYSTEM_TIA: { + ImGui::Text("Mixing mode:"); + if (ImGui::RadioButton("Mono",(flags&6)==0)) { + copyOfFlags=(flags&(~6)); + } + if (ImGui::RadioButton("Mono (no distortion)",(flags&6)==2)) { + copyOfFlags=(flags&(~6))|2; + } + if (ImGui::RadioButton("Stereo",(flags&6)==4)) { + copyOfFlags=(flags&(~6))|4; + } + + sysPal=flags&1; + if (ImGui::Checkbox("PAL",&sysPal)) { + copyOfFlags=(flags&(~1))|(unsigned int)sysPal; + } + break; + } case DIV_SYSTEM_PCSPKR: { ImGui::Text("Speaker type:"); if (ImGui::RadioButton("Unfiltered",(flags&3)==0)) { @@ -375,18 +492,57 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } break; } - case DIV_SYSTEM_OPN: { - if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&3)==0)) { - copyOfFlags=(flags&0x80000000)|0; + case DIV_SYSTEM_OPN: + case DIV_SYSTEM_OPN_EXT: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("3.58MHz (NTSC)",(flags&31)==0)) { + copyOfFlags=(flags&(~31))|0; } - if (ImGui::RadioButton("PAL (3.54MHz)",(flags&3)==1)) { - copyOfFlags=(flags&0x80000000)|1; + if (ImGui::RadioButton("3.54MHz (PAL)",(flags&31)==1)) { + copyOfFlags=(flags&(~31))|1; } - if (ImGui::RadioButton("Arcade (4MHz)",(flags&3)==2)) { - copyOfFlags=(flags&0x80000000)|2; + if (ImGui::RadioButton("4MHz",(flags&31)==2)) { + copyOfFlags=(flags&(~31))|2; } - if (ImGui::RadioButton("PC-9801-26K? TODO: CONFIRM (3MHz)",(flags&3)==3)) { - copyOfFlags=(flags&0x80000000)|3; + if (ImGui::RadioButton("3MHz",(flags&31)==3)) { + copyOfFlags=(flags&(~31))|3; + } + if (ImGui::RadioButton("3.9936MHz (PC-88/PC-98)",(flags&31)==4)) { + copyOfFlags=(flags&(~31))|4; + } + if (ImGui::RadioButton("1.5MHz",(flags&31)==5)) { + copyOfFlags=(flags&(~31))|5; + } + ImGui::Text("Output rate:"); + if (ImGui::RadioButton("FM: clock / 72, SSG: clock / 16",(flags&96)==0)) { + copyOfFlags=(flags&(~96))|0; + } + if (ImGui::RadioButton("FM: clock / 36, SSG: clock / 8",(flags&96)==32)) { + copyOfFlags=(flags&(~96))|32; + } + if (ImGui::RadioButton("FM: clock / 24, SSG: clock / 4",(flags&96)==64)) { + copyOfFlags=(flags&(~96))|64; + } + break; + } + case DIV_SYSTEM_PC98: + case DIV_SYSTEM_PC98_EXT: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("8MHz (Arcade)",(flags&31)==0)) { + copyOfFlags=(flags&(~31))|0; + } + if (ImGui::RadioButton("7.987MHz (PC-88/PC-98)",(flags&31)==1)) { + copyOfFlags=(flags&(~31))|1; + } + ImGui::Text("Output rate:"); + if (ImGui::RadioButton("FM: clock / 144, SSG: clock / 32",(flags&96)==0)) { + copyOfFlags=(flags&(~96))|0; + } + if (ImGui::RadioButton("FM: clock / 72, SSG: clock / 16",(flags&96)==32)) { + copyOfFlags=(flags&(~96))|32; + } + if (ImGui::RadioButton("FM: clock / 48, SSG: clock / 8",(flags&96)==64)) { + copyOfFlags=(flags&(~96))|64; } break; } @@ -394,24 +550,19 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool ImGui::Text("Clock rate:"); if (ImGui::RadioButton("8MHz (FM Towns)",(flags&15)==0)) { copyOfFlags=(flags&(~15))|0; - } if (ImGui::RadioButton("10MHz (Sega System 18)",(flags&15)==1)) { copyOfFlags=(flags&(~15))|1; - } if (ImGui::RadioButton("12.5MHz (Sega CD/System 32)",(flags&15)==2)) { copyOfFlags=(flags&(~15))|2; - } ImGui::Text("Chip type:"); if (ImGui::RadioButton("RF5C68 (10-bit output)",((flags>>4)&15)==0)) { copyOfFlags=(flags&(~240))|0; - } if (ImGui::RadioButton("RF5C164 (16-bit output)",((flags>>4)&15)==1)) { copyOfFlags=(flags&(~240))|16; - } break; } @@ -433,63 +584,176 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } case DIV_SYSTEM_MSM6295: { ImGui::Text("Clock rate:"); - if (ImGui::RadioButton("1MHz",flags==0)) { - copyOfFlags=0; + if (ImGui::RadioButton("1MHz",(flags&127)==0)) { + copyOfFlags=(flags&(~127))|0; } - if (ImGui::RadioButton("1.056MHz",flags==1)) { - copyOfFlags=1; + if (ImGui::RadioButton("1.056MHz",(flags&127)==1)) { + copyOfFlags=(flags&(~127))|1; } - if (ImGui::RadioButton("4MHz",flags==2)) { - copyOfFlags=2; + if (ImGui::RadioButton("4MHz",(flags&127)==2)) { + copyOfFlags=(flags&(~127))|2; } - if (ImGui::RadioButton("4.224MHz",flags==3)) { - copyOfFlags=3; + if (ImGui::RadioButton("4.224MHz",(flags&127)==3)) { + copyOfFlags=(flags&(~127))|3; } - if (ImGui::RadioButton("3.58MHz",flags==4)) { - copyOfFlags=4; + if (ImGui::RadioButton("3.58MHz",(flags&127)==4)) { + copyOfFlags=(flags&(~127))|4; } - if (ImGui::RadioButton("1.79MHz",flags==5)) { - copyOfFlags=5; + if (ImGui::RadioButton("1.79MHz",(flags&127)==5)) { + copyOfFlags=(flags&(~127))|5; } - if (ImGui::RadioButton("1.02MHz",flags==6)) { - copyOfFlags=6; + if (ImGui::RadioButton("1.02MHz",(flags&127)==6)) { + copyOfFlags=(flags&(~127))|6; } - if (ImGui::RadioButton("0.89MHz",flags==7)) { - copyOfFlags=7; + if (ImGui::RadioButton("0.89MHz",(flags&127)==7)) { + copyOfFlags=(flags&(~127))|7; } - if (ImGui::RadioButton("2MHz",flags==8)) { - copyOfFlags=8; + if (ImGui::RadioButton("2MHz",(flags&127)==8)) { + copyOfFlags=(flags&(~127))|8; } - if (ImGui::RadioButton("2.112MHz",flags==9)) { - copyOfFlags=9; + if (ImGui::RadioButton("2.112MHz",(flags&127)==9)) { + copyOfFlags=(flags&(~127))|9; } - if (ImGui::RadioButton("0.875MHz",flags==10)) { - copyOfFlags=10; + if (ImGui::RadioButton("0.875MHz",(flags&127)==10)) { + copyOfFlags=(flags&(~127))|10; } - if (ImGui::RadioButton("0.9375MHz",flags==11)) { - copyOfFlags=11; + if (ImGui::RadioButton("0.9375MHz",(flags&127)==11)) { + copyOfFlags=(flags&(~127))|11; } - if (ImGui::RadioButton("1.5MHz",flags==12)) { - copyOfFlags=12; + if (ImGui::RadioButton("1.5MHz",(flags&127)==12)) { + copyOfFlags=(flags&(~127))|12; + } + if (ImGui::RadioButton("3MHz",(flags&127)==13)) { + copyOfFlags=(flags&(~127))|13; + } + if (ImGui::RadioButton("1.193MHz (Atari)",(flags&127)==14)) { + copyOfFlags=(flags&(~127))|14; + } + ImGui::Text("Output rate:"); + if (ImGui::RadioButton("clock / 132",(flags&128)==0)) { + copyOfFlags=(flags&(~128))|0; + } + if (ImGui::RadioButton("clock / 165",(flags&128)==128)) { + copyOfFlags=(flags&(~128))|128; + } + break; + } + case DIV_SYSTEM_SCC: + case DIV_SYSTEM_SCC_PLUS: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("1.79MHz (NTSC/MSX)",(flags&127)==0)) { + copyOfFlags=(flags&(~127))|0; + } + if (ImGui::RadioButton("1.77MHz (PAL)",(flags&127)==1)) { + copyOfFlags=(flags&(~127))|1; + } + if (ImGui::RadioButton("1.5MHz (Arcade)",(flags&127)==2)) { + copyOfFlags=(flags&(~127))|2; + } + if (ImGui::RadioButton("2MHz",(flags&127)==3)) { + copyOfFlags=(flags&(~127))|3; + } + break; + } + case DIV_SYSTEM_OPL: + case DIV_SYSTEM_OPL_DRUMS: + case DIV_SYSTEM_OPL2: + case DIV_SYSTEM_OPL2_DRUMS: + case DIV_SYSTEM_Y8950: + case DIV_SYSTEM_Y8950_DRUMS: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("3.58MHz (NTSC)",(flags&255)==0)) { + copyOfFlags=(flags&(~255))|0; + } + if (ImGui::RadioButton("3.54MHz (PAL)",(flags&255)==1)) { + copyOfFlags=(flags&(~255))|1; + } + if (ImGui::RadioButton("4MHz",(flags&255)==2)) { + copyOfFlags=(flags&(~255))|2; + } + if (ImGui::RadioButton("3MHz",(flags&255)==3)) { + copyOfFlags=(flags&(~255))|3; + } + if (ImGui::RadioButton("3.9936MHz (PC-88/PC-98)",(flags&255)==4)) { + copyOfFlags=(flags&(~255))|4; + } + if (ImGui::RadioButton("3.5MHz",(flags&255)==5)) { + copyOfFlags=(flags&(~255))|5; + } + break; + } + case DIV_SYSTEM_OPL3: + case DIV_SYSTEM_OPL3_DRUMS: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("14.32MHz (NTSC)",(flags&255)==0)) { + copyOfFlags=(flags&(~255))|0; + } + if (ImGui::RadioButton("14.19MHz (PAL)",(flags&255)==1)) { + copyOfFlags=(flags&(~255))|1; + } + if (ImGui::RadioButton("14MHz",(flags&255)==2)) { + copyOfFlags=(flags&(~255))|2; + } + if (ImGui::RadioButton("16MHz",(flags&255)==3)) { + copyOfFlags=(flags&(~255))|3; + } + if (ImGui::RadioButton("15MHz",(flags&255)==4)) { + copyOfFlags=(flags&(~255))|4; + } + break; + } + case DIV_SYSTEM_YMZ280B: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("16.9344MHz",(flags&255)==0)) { + copyOfFlags=(flags&(~255))|0; + } + if (ImGui::RadioButton("14.32MHz (NTSC)",(flags&255)==1)) { + copyOfFlags=(flags&(~255))|1; + } + if (ImGui::RadioButton("14.19MHz (PAL)",(flags&255)==2)) { + copyOfFlags=(flags&(~255))|2; + } + if (ImGui::RadioButton("16MHz",(flags&255)==3)) { + copyOfFlags=(flags&(~255))|3; + } + if (ImGui::RadioButton("16.67MHz",(flags&255)==4)) { + copyOfFlags=(flags&(~255))|4; + } + if (ImGui::RadioButton("14MHz",(flags&255)==5)) { + copyOfFlags=(flags&(~255))|5; + } + break; + } + case DIV_SYSTEM_PCM_DAC: { + // default to 44100Hz 16-bit stereo + if (!flags) copyOfFlags=flags=0x1f0000|44099; + int sampRate=(flags&65535)+1; + int bitDepth=((flags>>16)&15)+1; + bool stereo=(flags>>20)&1; + ImGui::Text("Output rate:"); + if (CWSliderInt("##SampRate",&sampRate,1000,65536)) { + if (sampRate<1000) sampRate=1000; + if (sampRate>65536) sampRate=65536; + copyOfFlags=(flags&(~65535))|(sampRate-1); + } rightClickable + ImGui::Text("Output bit depth:"); + if (CWSliderInt("##BitDepth",&bitDepth,1,16)) { + if (bitDepth<1) bitDepth=1; + if (bitDepth>16) bitDepth=16; + copyOfFlags=(flags&(~(15<<16)))|((bitDepth-1)<<16); + } rightClickable + if (ImGui::Checkbox("Stereo",&stereo)) { + copyOfFlags=(flags&(~(1<<20)))|(stereo<<20); } break; } - case DIV_SYSTEM_GB: case DIV_SYSTEM_SWAN: case DIV_SYSTEM_VERA: case DIV_SYSTEM_BUBSYS_WSG: - case DIV_SYSTEM_YM2610: - case DIV_SYSTEM_YM2610_EXT: - case DIV_SYSTEM_YM2610_FULL: - case DIV_SYSTEM_YM2610_FULL_EXT: - case DIV_SYSTEM_YM2610B: - case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_YMU759: case DIV_SYSTEM_PET: - case DIV_SYSTEM_SCC: - case DIV_SYSTEM_SCC_PLUS: - case DIV_SYSTEM_YMZ280B: case DIV_SYSTEM_SNES: + case DIV_SYSTEM_T6W28: ImGui::Text("nothing to configure"); break; default: diff --git a/src/gui/sysEx.cpp b/src/gui/sysEx.cpp index 5b2b6657..f8849b56 100644 --- a/src/gui/sysEx.cpp +++ b/src/gui/sysEx.cpp @@ -1,6 +1,17 @@ #include "gui.h" #include "../ta-log.h" +// table taken from https://nornand.hatenablog.com/entry/2020/11/21/201911 +// Yamaha why didn't you just use 0-127 as it should be? +const unsigned char tlTable[100]={ + 127, 122, 118, 114, 110, 107, 104, 102, 100, 98, 96, 94, 92, 90, 88, 86, 85, 84, 82, 81, + // desde aquí la tabla consiste de valores que bajan de 1 en 1 + 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, + 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, + 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, + 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 +}; + bool FurnaceGUI::parseSysEx(unsigned char* data, size_t len) { SafeReader reader(data,len); @@ -137,7 +148,7 @@ bool FurnaceGUI::parseSysEx(unsigned char* data, size_t len) { op.sl=15-reader.readC(); reader.readC(); // LS - ignore op.am=(reader.readC()&0x40)?1:0; - op.tl=3+((99-reader.readC())*124)/99; + op.tl=tlTable[reader.readC()%100]; unsigned char freq=reader.readC(); logV("OP%d freq: %d",i,freq); op.mult=freq>>2; diff --git a/src/gui/sysManager.cpp b/src/gui/sysManager.cpp new file mode 100644 index 00000000..98f939d0 --- /dev/null +++ b/src/gui/sysManager.cpp @@ -0,0 +1,124 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" +#include "misc/cpp/imgui_stdlib.h" +#include "IconsFontAwesome4.h" +#include +#include + +void FurnaceGUI::drawSysManager() { + if (nextWindow==GUI_WINDOW_SYS_MANAGER) { + sysManagerOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!sysManagerOpen) return; + if (ImGui::Begin("Chip Manager",&sysManagerOpen,globalWinFlags)) { + ImGui::Checkbox("Preserve channel order",&preserveChanPos); + if (ImGui::BeginTable("SystemList",3)) { + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + ImGui::Text("Name"); + ImGui::TableNextColumn(); + ImGui::Text("Actions"); + for (unsigned char i=0; isong.systemLen; i++) { + ImGui::PushID(i); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Button(ICON_FA_ARROWS)) { + } + if (ImGui::BeginDragDropSource()) { + sysToMove=i; + ImGui::SetDragDropPayload("FUR_SYS",NULL,0,ImGuiCond_Once); + ImGui::Button(ICON_FA_ARROWS "##SysDrag"); + ImGui::EndDragDropSource(); + } else if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("(drag to swap chips)"); + } + if (ImGui::BeginDragDropTarget()) { + const ImGuiPayload* dragItem=ImGui::AcceptDragDropPayload("FUR_SYS"); + if (dragItem!=NULL) { + if (dragItem->IsDataType("FUR_SYS")) { + if (sysToMove!=i && sysToMove>=0) { + e->swapSystem(sysToMove,i,preserveChanPos); + } + sysToMove=-1; + } + } + ImGui::EndDragDropTarget(); + } + ImGui::TableNextColumn(); + if (ImGui::TreeNode(fmt::sprintf("%d. %s##_SYSM%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { + drawSysConf(i,e->song.system[i],e->song.systemFlags[i],true); + ImGui::TreePop(); + } + ImGui::TableNextColumn(); + ImGui::Button(ICON_FA_CHEVRON_DOWN "##SysChange"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Change"); + } + if (ImGui::BeginPopupContextItem("SysPickerC",ImGuiPopupFlags_MouseButtonLeft)) { + DivSystem picked=systemPicker(); + if (picked!=DIV_SYSTEM_NULL) { + e->changeSystem(i,picked,preserveChanPos); + updateWindowTitle(); + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + ImGui::SameLine(); + ImGui::BeginDisabled(e->song.systemLen<=1); + if (ImGui::Button(ICON_FA_TIMES "##SysRemove")) { + sysToDelete=i; + showWarning("Are you sure you want to remove this chip?",GUI_WARN_SYSTEM_DEL); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Remove"); + } + ImGui::EndDisabled(); + ImGui::PopID(); + } + if (e->song.systemLen<32) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + ImGui::Button(ICON_FA_PLUS "##SysAdd"); + if (ImGui::BeginPopupContextItem("SysPickerA",ImGuiPopupFlags_MouseButtonLeft)) { + DivSystem picked=systemPicker(); + if (picked!=DIV_SYSTEM_NULL) { + if (!e->addSystem(picked)) { + showError("cannot add chip! ("+e->getLastError()+")"); + } + updateWindowTitle(); + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } + ImGui::EndTable(); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SYS_MANAGER; + ImGui::End(); +} diff --git a/src/gui/sysPicker.cpp b/src/gui/sysPicker.cpp new file mode 100644 index 00000000..f59e547a --- /dev/null +++ b/src/gui/sysPicker.cpp @@ -0,0 +1,97 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" +#include "misc/cpp/imgui_stdlib.h" +#include "IconsFontAwesome4.h" +#include "guiConst.h" +#include + +DivSystem FurnaceGUI::systemPicker() { + DivSystem ret=DIV_SYSTEM_NULL; + DivSystem hoveredSys=DIV_SYSTEM_NULL; + bool reissueSearch=false; + if (curSysSection==NULL) { + curSysSection=availableSystems; + } + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputTextWithHint("##SysSearch","Search...",&sysSearchQuery)) reissueSearch=true; + if (ImGui::BeginTabBar("SysCats")) { + for (int i=0; chipCategories[i]; i++) { + if (ImGui::BeginTabItem(chipCategoryNames[i])) { + if (ImGui::IsItemActive()) { + reissueSearch=true; + } + curSysSection=chipCategories[i]; + ImGui::EndTabItem(); + } + } + ImGui::EndTabBar(); + } + if (reissueSearch) { + String lowerCase=sysSearchQuery; + for (char& i: lowerCase) { + if (i>='A' && i<='Z') i+='a'-'A'; + } + sysSearchResults.clear(); + for (int j=0; curSysSection[j]; j++) { + String lowerCase1=e->getSystemName((DivSystem)curSysSection[j]); + for (char& i: lowerCase1) { + if (i>='A' && i<='Z') i+='a'-'A'; + } + if (lowerCase1.find(lowerCase)!=String::npos) { + sysSearchResults.push_back((DivSystem)curSysSection[j]); + } + } + } + if (ImGui::BeginTable("SysList",1,ImGuiTableFlags_ScrollY,ImVec2(500.0f*dpiScale,200.0*dpiScale))) { + if (sysSearchQuery.empty()) { + // display chip list + for (int j=0; curSysSection[j]; j++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(e->getSystemName((DivSystem)curSysSection[j]),false,0,ImVec2(500.0f*dpiScale,0.0f))) ret=(DivSystem)curSysSection[j]; + if (ImGui::IsItemHovered()) { + hoveredSys=(DivSystem)curSysSection[j]; + } + } + } else { + // display search results + for (DivSystem i: sysSearchResults) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(e->getSystemName(i),false,0,ImVec2(500.0f*dpiScale,0.0f))) ret=i; + if (ImGui::IsItemHovered()) { + hoveredSys=i; + } + } + } + ImGui::EndTable(); + } + ImGui::Separator(); + if (ImGui::BeginChild("SysDesc",ImVec2(0.0f,150.0f*dpiScale),false,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) { + if (hoveredSys!=DIV_SYSTEM_NULL) { + const DivSysDef* sysDef=e->getSystemDef(hoveredSys); + ImGui::TextWrapped("%s",sysDef->description); + } + } + ImGui::EndChild(); + return ret; +} \ No newline at end of file diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 4ff2182e..87f5ad38 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -17,12 +17,146 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#define _USE_MATH_DEFINES #include "gui.h" #include "plot_nolerp.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" +#include #include +const char* waveGenBaseShapes[4]={ + "Sine", + "Triangle", + "Saw", + "Pulse" +}; + +const float multFactors[17]={ + M_PI, + 2*M_PI, + 4*M_PI, + 6*M_PI, + 8*M_PI, + 10*M_PI, + 12*M_PI, + 14*M_PI, + 16*M_PI, + 18*M_PI, + 20*M_PI, + 22*M_PI, + 24*M_PI, + 26*M_PI, + 28*M_PI, + 30*M_PI, + 32*M_PI, +}; + +void FurnaceGUI::doGenerateWave() { + float finalResult[256]; + if (curWave<0 || curWave>=(int)e->song.wave.size()) return; + + DivWavetable* wave=e->song.wave[curWave]; + memset(finalResult,0,sizeof(float)*256); + + if (wave->len<2) return; + + if (waveGenFM) { + float s0fb0=0; + float s0fb1=0; + float s1fb0=0; + float s1fb1=0; + float s2fb0=0; + float s2fb1=0; + float s3fb0=0; + float s3fb1=0; + for (int i=0; ilen; i++) { + float pos=(float)i/(float)wave->len; + float s0=sin((pos+(waveGenFB[0]?((s0fb0+s0fb1)*pow(2.0f,waveGenFB[0]-8)):0.0f))*multFactors[waveGenMult[0]])*waveGenTL[0]; + s0fb0=s0fb1; + s0fb1=s0; + + float s1=sin((pos+(waveGenFB[1]?((s1fb0+s1fb1)*pow(2.0f,waveGenFB[1]-8)):0.0f)+(waveGenFMCon1[0]?s0:0.0f))*multFactors[waveGenMult[1]])*waveGenTL[1]; + s1fb0=s1fb1; + s1fb1=s1; + + float s2=sin((pos+(waveGenFB[2]?((s2fb0+s2fb1)*pow(2.0f,waveGenFB[2]-8)):0.0f)+(waveGenFMCon1[1]?s0:0.0f)+(waveGenFMCon2[0]?s1:0.0f))*multFactors[waveGenMult[2]])*waveGenTL[2]; + s2fb0=s2fb1; + s2fb1=s2; + + float s3=sin((pos+(waveGenFB[3]?((s3fb0+s3fb1)*pow(2.0f,waveGenFB[3]-8)):0.0f)+(waveGenFMCon1[2]?s0:0.0f)+(waveGenFMCon2[1]?s1:0.0f)+(waveGenFMCon3[0]?s2:0.0f))*multFactors[waveGenMult[3]])*waveGenTL[3]; + s3fb0=s3fb1; + s3fb1=s3; + + if (waveGenFMCon1[3]) finalResult[i]+=s0; + if (waveGenFMCon2[2]) finalResult[i]+=s1; + if (waveGenFMCon3[1]) finalResult[i]+=s2; + finalResult[i]+=s3; + } + } else { + switch (waveGenBaseShape) { + case 0: // sine + for (int i=0; ilen; i++) { + for (int j=0; j<16; j++) { + float pos=fmod((waveGenPhase[j]*wave->len)+(i*(j+1)),wave->len); + float partial=sin((0.5+pos)*2.0*M_PI/(double)wave->len); + partial=pow(partial,waveGenPower); + partial*=waveGenAmp[j]; + finalResult[i]+=partial; + } + } + break; + case 1: // triangle + for (int i=0; ilen; i++) { + for (int j=0; j<16; j++) { + float pos=fmod((waveGenPhase[j]*wave->len)+(i*(j+1)),wave->len); + float partial=4.0*(0.5-fabs(0.5-(pos/(double)(wave->len-1))))-1.0; + partial=pow(partial,waveGenPower); + partial*=waveGenAmp[j]; + finalResult[i]+=partial; + } + } + break; + case 2: // saw + for (int i=0; ilen; i++) { + for (int j=0; j<16; j++) { + float pos=fmod((waveGenPhase[j]*wave->len)+(i*(j+1)),wave->len); + float partial=((2*pos)/(double)(wave->len-1))-1.0; + partial=pow(partial,waveGenPower); + partial*=waveGenAmp[j]; + finalResult[i]+=partial; + } + } + break; + case 3: // pulse + for (int i=0; ilen; i++) { + for (int j=0; j<16; j++) { + float pos=fmod((waveGenPhase[j]*wave->len)+(i*(j+1)),wave->len); + float partial=(pos>=(waveGenDuty*wave->len))?1:-1; + partial=pow(partial,waveGenPower); + partial*=waveGenAmp[j]; + finalResult[i]+=partial; + } + } + break; + } + } + + for (int i=waveGenInvertPoint*wave->len; ilen; i++) { + finalResult[i]=-finalResult[i]; + } + + for (int i=0; ilen; i++) { + finalResult[i]=(1.0+finalResult[i])*0.5; + if (finalResult[i]<0.0f) finalResult[i]=0.0f; + if (finalResult[i]>1.0f) finalResult[i]=1.0f; + wave->data[i]=round(finalResult[i]*wave->max); + } +} + +#define CENTER_TEXT(text) \ + ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(text).x)); + void FurnaceGUI::drawWaveEdit() { if (nextWindow==GUI_WINDOW_WAVE_EDIT) { waveEditOpen=true; @@ -31,36 +165,65 @@ void FurnaceGUI::drawWaveEdit() { } if (!waveEditOpen) return; float wavePreview[257]; - ImGui::SetNextWindowSizeConstraints(ImVec2(300.0f*dpiScale,300.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + if (mobileUI) { + patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f)); + patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f))); + ImGui::SetNextWindowPos(patWindowPos); + ImGui::SetNextWindowSize(patWindowSize); + } else { + ImGui::SetNextWindowSizeConstraints(ImVec2(300.0f*dpiScale,300.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + } if (ImGui::Begin("Wavetable Editor",&waveEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) { if (curWave<0 || curWave>=(int)e->song.wave.size()) { ImGui::Text("no wavetable selected"); } else { - ImGui::SetNextItemWidth(80.0f*dpiScale); - if (ImGui::InputInt("##CurWave",&curWave,1,1)) { - if (curWave<0) curWave=0; - if (curWave>=(int)e->song.wave.size()) curWave=e->song.wave.size()-1; - } - ImGui::SameLine(); - // TODO: load replace - if (ImGui::Button(ICON_FA_FOLDER_OPEN "##WELoad")) { - doAction(GUI_ACTION_WAVE_LIST_OPEN); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FLOPPY_O "##WESave")) { - doAction(GUI_ACTION_WAVE_LIST_SAVE); - } - ImGui::SameLine(); - DivWavetable* wave=e->song.wave[curWave]; - - if (!settings.waveLayout){ - ImGui::Text("Width"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine and WonderSwan\n- 64 on FDS\n- 128 on X1-010\nany other widths will be scaled during playback."); + + if (ImGui::BeginTable("WEProps",2)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(80.0f*dpiScale); + if (ImGui::InputInt("##CurWave",&curWave,1,1)) { + if (curWave<0) curWave=0; + if (curWave>=(int)e->song.wave.size()) curWave=e->song.wave.size()-1; } ImGui::SameLine(); - ImGui::SetNextItemWidth(128.0f*dpiScale); + if (ImGui::Button(ICON_FA_FOLDER_OPEN "##WELoad")) { + doAction(GUI_ACTION_WAVE_LIST_OPEN_REPLACE); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FLOPPY_O "##WESave")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE); + } + if (ImGui::BeginPopupContextItem("WaveSaveFormats",ImGuiMouseButton_Right)) { + if (ImGui::MenuItem("save as .dmw...")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_DMW); + } + if (ImGui::MenuItem("save raw...")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE_RAW); + } + ImGui::EndPopup(); + } + ImGui::SameLine(); + + if (ImGui::RadioButton("Steps",waveEditStyle==0)) { + waveEditStyle=0; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Lines",waveEditStyle==1)) { + waveEditStyle=1; + } + + ImGui::TableNextColumn(); + ImGui::Text("Width"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine, SCC, Konami Bubble System, Namco WSG and WonderSwan\n- 64 on FDS\n- 128 on X1-010\nany other widths will be scaled during playback."); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(96.0f*dpiScale); if (ImGui::InputInt("##_WTW",&wave->len,1,2)) { if (wave->len>256) wave->len=256; if (wave->len<1) wave->len=1; @@ -71,18 +234,505 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SameLine(); ImGui::Text("Height"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, X1-010 Envelope shape and N163\n- 31 for PC Engine\n- 63 for FDS\n- 255 for X1-010\nany other heights will be scaled during playback."); + ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, Namco WSG, Konami Bubble System, X1-010 Envelope shape and N163\n- 31 for PC Engine\n- 63 for FDS\n- 255 for X1-010 and SCC\nany other heights will be scaled during playback."); } ImGui::SameLine(); - ImGui::SetNextItemWidth(128.0f*dpiScale); + ImGui::SetNextItemWidth(96.0f*dpiScale); if (ImGui::InputInt("##_WTH",&wave->max,1,2)) { if (wave->max>255) wave->max=255; if (wave->max<1) wave->max=1; e->notifyWaveChange(curWave); MARK_MODIFIED; } + + ImGui::SameLine(); + if (ImGui::Button(waveGenVisible?(ICON_FA_CHEVRON_RIGHT "##WEWaveGen"):(ICON_FA_CHEVRON_LEFT "##WEWaveGen"))) { + waveGenVisible=!waveGenVisible; + } + + ImGui::EndTable(); } - ImGui::SameLine(); + + for (int i=0; ilen; i++) { + if (wave->data[i]>wave->max) wave->data[i]=wave->max; + wavePreview[i]=wave->data[i]; + if (waveSigned && !waveHex) { + wavePreview[i]-=(int)((wave->max+1)/2); + } + } + if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; + + if (ImGui::BeginTable("WEWaveSection",waveGenVisible?2:1)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch); + if (waveGenVisible) ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,250.0f*dpiScale); + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); + + ImVec2 contentRegion=ImGui::GetContentRegionAvail(); // wavetable graph size determined here + contentRegion.y-=ImGui::GetFrameHeightWithSpacing()+ImGui::GetStyle().WindowPadding.y; + if (waveEditStyle) { + PlotNoLerp("##Waveform",wavePreview,wave->len+1,0,NULL,(waveSigned && !waveHex)?(-(int)((wave->max+1)/2)):0,(waveSigned && !waveHex)?((int)(wave->max/2)):wave->max,contentRegion); + } else { + PlotCustom("##Waveform",wavePreview,wave->len,0,NULL,(waveSigned && !waveHex)?(-(int)((wave->max+1)/2)):0,(waveSigned && !waveHex)?((int)(wave->max/2)):wave->max,contentRegion,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,NULL,true); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + waveDragStart=ImGui::GetItemRectMin(); + waveDragAreaSize=contentRegion; + waveDragMin=0; + waveDragMax=wave->max; + waveDragLen=wave->len; + waveDragActive=true; + waveDragTarget=wave->data; + processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); + e->notifyWaveChange(curWave); + modified=true; + } + ImGui::PopStyleVar(); + + if (waveGenVisible) { + ImGui::TableNextColumn(); + + if (ImGui::BeginTabBar("WaveGenOpt")) { + if (ImGui::BeginTabItem("Shapes")) { + waveGenFM=false; + + if (waveGenBaseShape<0) waveGenBaseShape=0; + if (waveGenBaseShape>3) waveGenBaseShape=3; + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderInt("##WGShape",&waveGenBaseShape,0,3,waveGenBaseShapes[waveGenBaseShape])) { + if (waveGenBaseShape<0) waveGenBaseShape=0; + if (waveGenBaseShape>3) waveGenBaseShape=3; + doGenerateWave(); + } + + if (ImGui::BeginTable("WGShapeProps",2)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Duty"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderFloat("##WGDuty",&waveGenDuty,0.0f,1.0f)) { + doGenerateWave(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Exponent"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderInt("##WGExp",&waveGenPower,1,8)) { + doGenerateWave(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("XOR Point"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderFloat("##WGXOR",&waveGenInvertPoint,0.0f,1.0f)) { + doGenerateWave(); + } + + ImGui::EndTable(); + } + + if (ImGui::TreeNode("Amplitude/Phase")) { + if (ImGui::BeginTable("WGShapeProps",3)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.6f); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.4f); + + for (int i=0; i<16; i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%d",i+1); + ImGui::TableNextColumn(); + ImGui::PushID(140+i); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderFloat("##WGAmp",&waveGenAmp[i],-1.0f,1.0f)) { + doGenerateWave(); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) { + waveGenAmp[i]=0.0f; + doGenerateWave(); + } + ImGui::PopID(); + ImGui::TableNextColumn(); + ImGui::PushID(140+i); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderFloat("##WGPhase",&waveGenPhase[i],0.0f,1.0f)) { + doGenerateWave(); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) { + waveGenPhase[i]=0.0f; + doGenerateWave(); + } + ImGui::PopID(); + } + + ImGui::EndTable(); + } + ImGui::TreePop(); + } + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("FM")) { + waveGenFM=true; + + if (ImGui::BeginTable("WGFMProps",4)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,ImGui::CalcTextSize("Op").x); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.5); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.25); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.25); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Op"); + ImGui::TableNextColumn(); + ImGui::Text("Level"); + ImGui::TableNextColumn(); + ImGui::Text("Mult"); + ImGui::TableNextColumn(); + ImGui::Text("FB"); + + for (int i=0; i<4; i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%d",i+1); + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::PushID(i); + if (CWSliderFloat("##WGTL",&waveGenTL[i],0.0f,1.0f)) { + doGenerateWave(); + } + ImGui::PopID(); + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::PushID(i); + if (CWSliderInt("##WGMULT",&waveGenMult[i],1,16)) { + doGenerateWave(); + } + ImGui::PopID(); + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::PushID(i); + if (CWSliderInt("##WGFB",&waveGenFB[i],0,7)) { + doGenerateWave(); + } + ImGui::PopID(); + } + + ImGui::EndTable(); + } + + CENTER_TEXT("Connection Diagram"); + ImGui::Text("Connection Diagram"); + + if (ImGui::BeginTable("WGFMCon",5)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text(">>"); + ImGui::TableNextColumn(); + ImGui::Text("2"); + ImGui::TableNextColumn(); + ImGui::Text("3"); + ImGui::TableNextColumn(); + ImGui::Text("4"); + ImGui::TableNextColumn(); + ImGui::Text("Out"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("1"); + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##Con12",&waveGenFMCon1[0])) { + doGenerateWave(); + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##Con13",&waveGenFMCon1[1])) { + doGenerateWave(); + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##Con14",&waveGenFMCon1[2])) { + doGenerateWave(); + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##Con1O",&waveGenFMCon1[3])) { + doGenerateWave(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("2"); + ImGui::TableNextColumn(); + // blank + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##Con23",&waveGenFMCon2[0])) { + doGenerateWave(); + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##Con24",&waveGenFMCon2[1])) { + doGenerateWave(); + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##Con2O",&waveGenFMCon2[2])) { + doGenerateWave(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("3"); + ImGui::TableNextColumn(); + // blank + ImGui::TableNextColumn(); + // blank + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##Con34",&waveGenFMCon3[0])) { + doGenerateWave(); + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##Con3O",&waveGenFMCon3[1])) { + doGenerateWave(); + } + + ImGui::EndTable(); + } + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("WaveTools")) { + if (ImGui::BeginTable("WGParamItems",2)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##WGScaleX",&waveGenScaleX,1,16)) { + if (waveGenScaleX<2) waveGenScaleX=2; + if (waveGenScaleX>256) waveGenScaleX=256; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Scale X")) { + if (waveGenScaleX>0 && wave->len!=waveGenScaleX) e->lockEngine([this,wave]() { + int origData[256]; + memcpy(origData,wave->data,wave->len*sizeof(int)); + for (int i=0; idata[i]=origData[i*wave->len/waveGenScaleX]; + } + wave->len=waveGenScaleX; + MARK_MODIFIED; + }); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##WGScaleY",&waveGenScaleY,1,16)) { + if (waveGenScaleY<2) waveGenScaleY=2; + if (waveGenScaleY>256) waveGenScaleY=256; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Scale Y")) { + if (waveGenScaleY>0 && wave->max!=waveGenScaleY) e->lockEngine([this,wave]() { + for (int i=0; ilen; i++) { + wave->data[i]=(wave->data[i]*(waveGenScaleY+1))/(wave->max+1); + } + wave->max=waveGenScaleY; + MARK_MODIFIED; + }); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##WGOffsetX",&waveGenOffsetX,1,16)) { + if (waveGenOffsetX<-wave->len+1) waveGenOffsetX=-wave->len+1; + if (waveGenOffsetX>wave->len-1) waveGenOffsetX=wave->len-1; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Offset X")) { + if (waveGenOffsetX!=0 && wave->len>0) e->lockEngine([this,wave]() { + int origData[256]; + memcpy(origData,wave->data,wave->len*sizeof(int)); + int realOff=-waveGenOffsetX; + while (realOff<0) realOff+=wave->len; + + for (int i=0; ilen; i++) { + wave->data[i]=origData[(i+realOff)%wave->len]; + } + MARK_MODIFIED; + }); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##WGOffsetY",&waveGenOffsetY,1,16)) { + if (waveGenOffsetY<-wave->max) waveGenOffsetY=-wave->max; + if (waveGenOffsetY>wave->max) waveGenOffsetY=wave->max; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Offset Y")) { + if (waveGenOffsetY!=0) e->lockEngine([this,wave]() { + for (int i=0; ilen; i++) { + wave->data[i]=CLAMP(wave->data[i]+waveGenOffsetY,0,wave->max); + } + MARK_MODIFIED; + }); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##WGSmooth",&waveGenSmooth,1,4)) { + if (waveGenSmooth>wave->len) waveGenSmooth=wave->len; + if (waveGenSmooth<1) waveGenSmooth=1; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Smooth")) { + if (waveGenSmooth>0) e->lockEngine([this,wave]() { + int origData[256]; + memcpy(origData,wave->data,wave->len*sizeof(int)); + for (int i=0; ilen; i++) { + int dataSum=0; + for (int j=i; jlen; + dataSum+=origData[pos%wave->len]; + } + dataSum/=waveGenSmooth+1; + wave->data[i]=dataSum; + } + MARK_MODIFIED; + }); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + float amp=waveGenAmplify*100.0f; + if (ImGui::InputFloat("##WGAmplify",&,1.0f,10.0f)) { + waveGenAmplify=amp/100.0f; + if (waveGenAmplify<0.0f) waveGenAmplify=0.0f; + if (waveGenAmplify>100.0f) waveGenAmplify=100.0f; + } + ImGui::TableNextColumn(); + if (ImGui::Button("Amplify")) { + if (waveGenAmplify!=1.0f) e->lockEngine([this,wave]() { + for (int i=0; ilen; i++) { + wave->data[i]=CLAMP(round((float)(wave->data[i]-(int)( /* Clang can you stop complaining */ (int)(wave->max+1)/(int)2))*waveGenAmplify),(int)(-((wave->max+1)/2)),(int)(wave->max/2))+(int)((wave->max+1)/2); + } + MARK_MODIFIED; + }); + } + + ImGui::EndTable(); + } + + ImVec2 buttonSize=ImGui::GetContentRegionAvail(); + buttonSize.y=0.0f; + ImVec2 buttonSizeHalf=buttonSize; + buttonSizeHalf.x-=ImGui::GetStyle().ItemSpacing.x; + buttonSizeHalf.x*=0.5; + + if (ImGui::Button("Normalize",buttonSize)) { + e->lockEngine([this,wave]() { + // find lowest point + int lowest=wave->max; + for (int i=0; ilen; i++) { + if (wave->data[i]data[i]; + } + + // find highest point + int highest=0; + for (int i=0; ilen; i++) { + if (wave->data[i]>highest) highest=wave->data[i]; + } + + // abort if lowest and highest points are equal + if (lowest==highest) return; + + // abort if lowest and highest points already span the entire height + if (lowest==wave->max && highest==0) return; + + // apply offset + for (int i=0; ilen; i++) { + wave->data[i]-=lowest; + } + highest-=lowest; + + // scale + for (int i=0; ilen; i++) { + wave->data[i]=(wave->data[i]*wave->max)/highest; + } + MARK_MODIFIED; + }); + } + if (ImGui::Button("Invert",buttonSize)) { + e->lockEngine([this,wave]() { + for (int i=0; ilen; i++) { + wave->data[i]=wave->max-wave->data[i]; + } + MARK_MODIFIED; + }); + } + + if (ImGui::Button("Half",buttonSizeHalf)) { + int origData[256]; + memcpy(origData,wave->data,wave->len*sizeof(int)); + + for (int i=0; ilen; i++) { + wave->data[i]=origData[i>>1]; + } + MARK_MODIFIED; + } + ImGui::SameLine(); + if (ImGui::Button("Double",buttonSizeHalf)) { + int origData[256]; + memcpy(origData,wave->data,wave->len*sizeof(int)); + + for (int i=0; ilen; i++) { + wave->data[i]=origData[(i*2)%wave->len]; + } + MARK_MODIFIED; + } + + if (ImGui::Button("Convert Signed/Unsigned",buttonSize)) { + if (wave->max>0) e->lockEngine([this,wave]() { + for (int i=0; ilen; i++) { + if (wave->data[i]>(wave->max/2)) { + wave->data[i]-=(wave->max+1)/2; + } else { + wave->data[i]+=(wave->max+1)/2; + } + } + MARK_MODIFIED; + }); + } + if (ImGui::Button("Randomize",buttonSize)) { + if (wave->max>0) e->lockEngine([this,wave]() { + for (int i=0; ilen; i++) { + wave->data[i]=rand()%wave->max; + } + MARK_MODIFIED; + }); + } + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + } + ImGui::EndTable(); + } + if (ImGui::RadioButton("Dec",!waveHex)) { waveHex=false; } @@ -90,75 +740,35 @@ void FurnaceGUI::drawWaveEdit() { if (ImGui::RadioButton("Hex",waveHex)) { waveHex=true; } - - if (settings.waveLayout){ - if (ImGui::BeginTable("WaveProps",2,ImGuiTableFlags_SizingStretchSame)) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Width"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine and WonderSwan\n- 64 on FDS\n- 128 on X1-010\nany other widths will be scaled during playback."); - } - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##_WTW",&wave->len,1,2)) { - if (wave->len>256) wave->len=256; - if (wave->len<1) wave->len=1; - e->notifyWaveChange(curWave); - if (wavePreviewOn) e->previewWave(curWave,wavePreviewNote); - MARK_MODIFIED; - } - ImGui::TableNextColumn(); - ImGui::SameLine(); - ImGui::Text("Height"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, X1-010 Envelope shape and N163\n- 31 for PC Engine\n- 63 for FDS\n- 255 for X1-010\nany other heights will be scaled during playback."); - } - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##_WTH",&wave->max,1,2)) { - if (wave->max>255) wave->max=255; - if (wave->max<1) wave->max=1; - e->notifyWaveChange(curWave); - MARK_MODIFIED; - } - ImGui::EndTable(); - } + ImGui::SameLine(); + if (!waveHex) if (ImGui::Button(waveSigned?"±##WaveSign":"+##WaveSign",ImVec2(ImGui::GetFrameHeight(),ImGui::GetFrameHeight()))) { + waveSigned=!waveSigned; } - - for (int i=0; ilen; i++) { - if (wave->data[i]>wave->max) wave->data[i]=wave->max; - wavePreview[i]=wave->data[i]; + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Signed/Unsigned"); } - if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; + ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); // wavetable text input size found here if (ImGui::InputText("##MMLWave",&mmlStringW)) { - decodeMMLStrW(mmlStringW,wave->data,wave->len,wave->max,waveHex); + int actualData[256]; + decodeMMLStrW(mmlStringW,actualData,wave->len,(waveSigned && !waveHex)?(-((wave->max+1)/2)):0,(waveSigned && !waveHex)?(wave->max/2):wave->max,waveHex); + if (waveSigned && !waveHex) { + for (int i=0; ilen; i++) { + actualData[i]+=(wave->max+1)/2; + } + } + memcpy(wave->data,actualData,wave->len*sizeof(int)); } if (!ImGui::IsItemActive()) { - encodeMMLStr(mmlStringW,wave->data,wave->len,-1,-1,waveHex); + int actualData[256]; + memcpy(actualData,wave->data,256*sizeof(int)); + if (waveSigned && !waveHex) { + for (int i=0; ilen; i++) { + actualData[i]-=(wave->max+1)/2; + } + } + encodeMMLStr(mmlStringW,actualData,wave->len,-1,-1,waveHex); } - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); - - ImVec2 contentRegion=ImGui::GetContentRegionAvail(); // wavetable graph size determined here - if (ImGui::GetContentRegionAvail().y > (ImGui::GetContentRegionAvail().x / 2.0f)) { - contentRegion=ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetContentRegionAvail().x / 2.0f); - } - PlotNoLerp("##Waveform",wavePreview,wave->len+1,0,NULL,0,wave->max,contentRegion); - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - waveDragStart=ImGui::GetItemRectMin(); - waveDragAreaSize=contentRegion; - waveDragMin=0; - waveDragMax=wave->max; - waveDragLen=wave->len; - waveDragActive=true; - waveDragTarget=wave->data; - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); - e->notifyWaveChange(curWave); - modified=true; - } - ImGui::PopStyleVar(); } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_EDIT; diff --git a/src/log.cpp b/src/log.cpp index 89602b42..ef2750a2 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -19,7 +19,11 @@ #include "ta-log.h" +#ifdef IS_MOBILE +int logLevel=LOGLEVEL_TRACE; +#else int logLevel=LOGLEVEL_INFO; +#endif std::atomic logPosition; diff --git a/src/main.cpp b/src/main.cpp index 8c90c7c8..a1856092 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,7 +20,7 @@ #include #include #include -#ifdef HAVE_GUI +#ifdef HAVE_SDL2 #include "SDL_events.h" #endif #include "ta-log.h" @@ -36,6 +36,8 @@ #include #endif +#include "cli/cli.h" + #ifdef HAVE_GUI #include "gui/gui.h" #endif @@ -46,9 +48,13 @@ DivEngine e; FurnaceGUI g; #endif +FurnaceCLI cli; + String outName; String vgmOutName; +String cmdOutName; int loops=1; +int benchMode=0; DivAudioExportModes outMode=DIV_EXPORT_MODE_ONE; #ifdef HAVE_GUI @@ -58,6 +64,7 @@ bool consoleMode=true; #endif bool displayEngineFailError=false; +bool cmdOutBinary=false; std::vector params; @@ -109,6 +116,11 @@ TAParamResult pConsole(String val) { return TA_PARAM_SUCCESS; } +TAParamResult pBinary(String val) { + cmdOutBinary=true; + return TA_PARAM_SUCCESS; +} + TAParamResult pLogLevel(String val) { if (val=="trace") { logLevel=LOGLEVEL_TRACE; @@ -140,19 +152,38 @@ TAParamResult pVersion(String) { printf("- libsndfile by Erik de Castro Lopo and rest of libsndfile team (LGPLv2.1)\n"); printf("- SDL2 by Sam Lantinga (zlib license)\n"); printf("- zlib by Jean-loup Gailly and Mark Adler (zlib license)\n"); + printf("- RtMidi by Gary P. Scavone (RtMidi license)\n"); + printf("- backward-cpp by Google (MIT)\n"); printf("- Dear ImGui by Omar Cornut (MIT)\n"); + printf("- Portable File Dialogs by Sam Hocevar (WTFPL)\n"); + printf("- Native File Dialog (modified version) by Frogtoss Games (zlib license)\n"); + printf("- FFTW by Matteo Frigo and Steven G. Johnson (GPLv2)\n"); printf("- Nuked-OPM by Nuke.YKT (LGPLv2.1)\n"); printf("- Nuked-OPN2 by Nuke.YKT (LGPLv2.1)\n"); + printf("- Nuked-OPL3 by Nuke.YKT (LGPLv2.1)\n"); + printf("- Nuked-OPLL by Nuke.YKT (GPLv2)\n"); + printf("- Nuked-PSG (modified version) by Nuke.YKT (GPLv2)\n"); printf("- ymfm by Aaron Giles (BSD 3-clause)\n"); + printf("- adpcm by superctr (public domain)\n"); printf("- MAME SN76496 emulation core by Nicola Salmoria (BSD 3-clause)\n"); printf("- MAME AY-3-8910 emulation core by Couriersud (BSD 3-clause)\n"); printf("- MAME SAA1099 emulation core by Juergen Buchmueller and Manuel Abadia (BSD 3-clause)\n"); - printf("- SAASound (BSD 3-clause)\n"); + printf("- MAME Namco WSG by Nicola Salmoria and Aaron Giles (BSD 3-clause)\n"); + printf("- MAME RF5C68 core by Olivier Galibert and Aaron Giles (BSD 3-clause)\n"); + printf("- MAME MSM6258 core by Barry Rodewald (BSD 3-clause)\n"); + printf("- MAME YMZ280B core by Aaron Giles (BSD 3-clause)\n"); + printf("- QSound core by superctr (BSD 3-clause)\n"); + printf("- VICE VIC-20 by Rami Rasanen and viznut (GPLv2)\n"); + printf("- VERA core by Frank van den Hoef (BSD 2-clause)\n"); + printf("- SAASound by Dave Hooper and Simon Owen (BSD 3-clause)\n"); printf("- SameBoy by Lior Halphon (MIT)\n"); - printf("- Mednafen PCE by Mednafen Team (GPLv2)\n"); + printf("- Mednafen PCE and WonderSwan by Mednafen Team (GPLv2)\n"); printf("- puNES by FHorse (GPLv2)\n"); + printf("- NSFPlay by Brad Smith and Brezza (unknown open-source license)\n"); printf("- reSID by Dag Lem (GPLv2)\n"); + printf("- reSIDfp by Dag Lem, Antti Lankila and Leandro Nini (GPLv2)\n"); printf("- Stella by Stella Team (GPLv2)\n"); + printf("- vgsound_emu (first version) by cam900 (BSD 3-clause)\n"); return TA_PARAM_QUIT; } @@ -202,6 +233,19 @@ TAParamResult pOutMode(String val) { return TA_PARAM_SUCCESS; } +TAParamResult pBenchmark(String val) { + if (val=="render") { + benchMode=1; + } else if (val=="seek") { + benchMode=2; + } else { + logE("invalid value for benchmark! valid values are: render and seek."); + return TA_PARAM_ERROR; + } + e.setAudio(DIV_AUDIO_DUMMY); + return TA_PARAM_SUCCESS; +} + TAParamResult pOutput(String val) { outName=val; e.setAudio(DIV_AUDIO_DUMMY); @@ -214,6 +258,12 @@ TAParamResult pVGMOut(String val) { return TA_PARAM_SUCCESS; } +TAParamResult pCmdOut(String val) { + cmdOutName=val; + e.setAudio(DIV_AUDIO_DUMMY); + return TA_PARAM_SUCCESS; +} + bool needsValue(String param) { for (size_t i=0; i","output audio to file")); params.push_back(TAParam("O","vgmout",true,pVGMOut,"","output .vgm data")); + params.push_back(TAParam("C","cmdout",true,pCmdOut,"","output command stream")); + params.push_back(TAParam("b","binary",false,pBinary,"","set command stream output format to binary")); params.push_back(TAParam("L","loglevel",true,pLogLevel,"debug|info|warning|error","set the log level (info by default)")); params.push_back(TAParam("v","view",true,pView,"pattern|commands|nothing","set visualization (pattern by default)")); params.push_back(TAParam("c","console",false,pConsole,"","enable console mode")); @@ -236,10 +288,23 @@ void initParams() { params.push_back(TAParam("l","loops",true,pLoops,"","set number of loops (-1 means loop forever)")); params.push_back(TAParam("o","outmode",true,pOutMode,"one|persys|perchan","set file output mode")); + params.push_back(TAParam("B","benchmark",true,pBenchmark,"render|seek","run performance test")); + params.push_back(TAParam("V","version",false,pVersion,"","view information about Furnace.")); params.push_back(TAParam("W","warranty",false,pWarranty,"","view warranty disclaimer.")); } +#ifdef _WIN32 +void reportError(String what) { + logE("%s",what); + MessageBox(NULL,what.c_str(),"Furnace",MB_OK|MB_ICONERROR); +} +#else +void reportError(String what) { + logE("%s",what); +} +#endif + // TODO: CoInitializeEx on Windows? // TODO: add crash log int main(int argc, char** argv) { @@ -250,7 +315,7 @@ int main(int argc, char** argv) { logE("CoInitializeEx failed!"); } #endif -#if !(defined(__APPLE__) || defined(_WIN32) || defined(ANDROID)) +#if !(defined(__APPLE__) || defined(_WIN32) || defined(ANDROID) || defined(__HAIKU__)) // workaround for Wayland HiDPI issue if (getenv("SDL_VIDEODRIVER")==NULL) { setenv("SDL_VIDEODRIVER","x11",1); @@ -258,6 +323,7 @@ int main(int argc, char** argv) { #endif outName=""; vgmOutName=""; + cmdOutName=""; initParams(); @@ -280,7 +346,7 @@ int main(int argc, char** argv) { val=argv[i+1]; i++; } else { - logE("incomplete param %s.",arg.c_str()); + reportError(fmt::sprintf("incomplete param %s.",arg.c_str())); return 1; } } @@ -334,57 +400,83 @@ int main(int argc, char** argv) { logI("loading module..."); FILE* f=ps_fopen(fileName.c_str(),"rb"); if (f==NULL) { - perror("error"); + reportError(fmt::sprintf("couldn't open file! (%s)",strerror(errno))); return 1; } if (fseek(f,0,SEEK_END)<0) { - perror("size error"); + reportError(fmt::sprintf("couldn't open file! (couldn't get file size: %s)",strerror(errno))); fclose(f); return 1; } ssize_t len=ftell(f); if (len==(SIZE_MAX>>1)) { - perror("could not get file length"); + reportError(fmt::sprintf("couldn't open file! (couldn't get file length: %s)",strerror(errno))); fclose(f); return 1; } if (len<1) { if (len==0) { - printf("that file is empty!\n"); + reportError("that file is empty!"); } else { - perror("tell error"); + reportError(fmt::sprintf("couldn't open file! (tell error: %s)",strerror(errno))); } fclose(f); return 1; } unsigned char* file=new unsigned char[len]; if (fseek(f,0,SEEK_SET)<0) { - perror("size error"); + reportError(fmt::sprintf("couldn't open file! (size error: %s)",strerror(errno))); fclose(f); delete[] file; return 1; } if (fread(file,1,(size_t)len,f)!=(size_t)len) { - perror("read error"); + reportError(fmt::sprintf("couldn't open file! (read error: %s)",strerror(errno))); fclose(f); delete[] file; return 1; } fclose(f); if (!e.load(file,(size_t)len)) { - logE("could not open file!"); + reportError(fmt::sprintf("could not open file! (%s)",e.getLastError())); return 1; } } if (!e.init()) { - logE("could not initialize engine!"); if (consoleMode) { + reportError("could not initialize engine!"); return 1; } else { + logE("could not initialize engine!"); displayEngineFailError=true; } } - if (outName!="" || vgmOutName!="") { + if (benchMode) { + logI("starting benchmark!"); + if (benchMode==2) { + e.benchmarkSeek(); + } else { + e.benchmarkPlayback(); + } + return 0; + } + if (outName!="" || vgmOutName!="" || cmdOutName!="") { + if (cmdOutName!="") { + SafeWriter* w=e.saveCommand(cmdOutBinary); + if (w!=NULL) { + FILE* f=fopen(cmdOutName.c_str(),"wb"); + if (f!=NULL) { + fwrite(w->getFinalBuf(),1,w->size(),f); + fclose(f); + } else { + reportError(fmt::sprintf("could not open file! (%s)",e.getLastError())); + } + w->finish(); + delete w; + } else { + reportError("could not write command stream!"); + } + } if (vgmOutName!="") { SafeWriter* w=e.saveVGM(); if (w!=NULL) { @@ -393,12 +485,12 @@ int main(int argc, char** argv) { fwrite(w->getFinalBuf(),1,w->size(),f); fclose(f); } else { - logE("could not open file! %s",strerror(errno)); + reportError(fmt::sprintf("could not open file! (%s)",e.getLastError())); } w->finish(); delete w; } else { - logE("could not write VGM!"); + reportError("could not write VGM!"); } } if (outName!="") { @@ -410,30 +502,47 @@ int main(int argc, char** argv) { } if (consoleMode) { + bool cliSuccess=false; + cli.bindEngine(&e); + if (!cli.init()) { + reportError("error while starting CLI!"); + } else { + cliSuccess=true; + } logI("playing..."); e.play(); -#ifdef HAVE_GUI - SDL_Event ev; - while (true) { - SDL_WaitEvent(&ev); - if (ev.type==SDL_QUIT) break; - } - e.quit(); - return 0; + if (cliSuccess) { + cli.loop(); + cli.finish(); + e.quit(); + return 0; + } else { +#ifdef HAVE_SDL2 + SDL_Event ev; + while (true) { + SDL_WaitEvent(&ev); + if (ev.type==SDL_QUIT) break; + } + e.quit(); + return 0; #else - while (true) { + while (true) { #ifdef _WIN32 - Sleep(500); + Sleep(500); #else - usleep(500000); + usleep(500000); +#endif + } #endif } -#endif } #ifdef HAVE_GUI g.bindEngine(&e); - if (!g.init()) return 1; + if (!g.init()) { + reportError("error while starting GUI!"); + return 1; + } if (displayEngineFailError) { logE("displaying engine fail error."); diff --git a/src/utfutils.cpp b/src/utfutils.cpp index c9952898..4c727777 100644 --- a/src/utfutils.cpp +++ b/src/utfutils.cpp @@ -19,7 +19,7 @@ #include "utfutils.h" -int decodeUTF8(const unsigned char* data, char& len) { +int decodeUTF8(const unsigned char* data, signed char& len) { int ret=0xfffd; if (data[0]<0x80) { ret=data[0]; @@ -66,7 +66,7 @@ int decodeUTF8(const unsigned char* data, char& len) { size_t utf8len(const char* s) { size_t p=0; size_t r=0; - char cl; + signed char cl; while (s[p]!=0) { r++; decodeUTF8((const unsigned char*)&s[p],cl); @@ -76,7 +76,7 @@ size_t utf8len(const char* s) { } char utf8csize(const unsigned char* c) { - char ret; + signed char ret; decodeUTF8(c,ret); return ret; } @@ -84,7 +84,7 @@ char utf8csize(const unsigned char* c) { WString utf8To16(const char* s) { WString ret; int ch, p; - char chs; + signed char chs; p=0; while (s[p]!=0) { ch=decodeUTF8((const unsigned char*)&s[p],chs); diff --git a/src/utfutils.h b/src/utfutils.h index 087913a4..76c89470 100644 --- a/src/utfutils.h +++ b/src/utfutils.h @@ -21,6 +21,8 @@ #define _UTFUTILS_H #include "ta-utils.h" +int decodeUTF8(const unsigned char* data, signed char& len); + size_t utf8len(const char* s); size_t utf8clen(const char* s); size_t utf8pos(const char* s, size_t inpos); diff --git a/test/assert_delta.c b/test/assert_delta.c new file mode 100644 index 00000000..6dad6992 --- /dev/null +++ b/test/assert_delta.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include + +#define BUF_SIZE 8192 + +// usage: assert_delta file +// return values: +// - 0: pass (file is silence) +// - 1: fail (noise found) +// - 2: command line error +// - 3: file open error +int main(int argc, char** argv) { + if (argc<2) return 2; + + SF_INFO si; + memset(&si,0,sizeof(SF_INFO)); + SNDFILE* sf=sf_open(argv[1],SFM_READ,&si); + if (sf==NULL) { + fprintf(stderr,"open: %s\n",sf_strerror(NULL)); + return 3; + } + + if (si.channels<1) { + fprintf(stderr,"invalid channel count\n"); + return 3; + } + + float* buf=malloc(BUF_SIZE*si.channels*sizeof(float)); + + sf_count_t totalRead=0; + size_t seekPos=0; + while ((totalRead=sf_readf_float(sf,buf,BUF_SIZE))!=0) { + for (int i=0; i