diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7e060924f..ec8ed890b 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 566a3c3fc..25bd6d0e0 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ linuxbuild/ test/songs/ test/delta/ test/result/ +test/assert_delta android/.gradle/ android/app/build/ android/app/.cxx/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 15815fa6b..43c14562d 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 "") @@ -77,6 +97,7 @@ else() endif() list(APPEND DEPENDENCIES_INCLUDE_DIRS "extern/SAASound/include") +list(APPEND DEPENDENCIES_INCLUDE_DIRS "extern/vgsound_emu-modified") find_package(Threads REQUIRED) list(APPEND DEPENDENCIES_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) @@ -221,6 +242,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. @@ -294,6 +320,31 @@ extern/SAASound/src/SAANoise.cpp extern/SAASound/src/SAASndC.cpp extern/SAASound/src/SAASound.cpp +extern/vgsound_emu-modified/vgsound_emu/src/core/vox/vox.cpp + +extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x.cpp +extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x_alu.cpp +extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x_filter.cpp +extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5504.cpp +extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5505.cpp +extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5506.cpp + +extern/vgsound_emu-modified/vgsound_emu/src/k005289/k005289.cpp + +extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.cpp + +extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.cpp + +extern/vgsound_emu-modified/vgsound_emu/src/msm6295/msm6295.cpp + +extern/vgsound_emu-modified/vgsound_emu/src/n163/n163.cpp + +extern/vgsound_emu-modified/vgsound_emu/src/scc/scc.cpp + +extern/vgsound_emu-modified/vgsound_emu/src/vrcvi/vrcvi.cpp + +extern/vgsound_emu-modified/vgsound_emu/src/x1_010/x1_010.cpp + extern/adpcm/bs_codec.c extern/adpcm/oki_codec.c extern/adpcm/yma_codec.c @@ -343,7 +394,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 @@ -355,41 +425,26 @@ src/engine/platform/sound/lynx/Mikey.cpp src/engine/platform/sound/qsound.c -src/engine/platform/sound/x1_010/x1_010.cpp - src/engine/platform/sound/swan.cpp src/engine/platform/sound/su.cpp -src/engine/platform/sound/k005289/k005289.cpp - -src/engine/platform/sound/n163/n163.cpp - src/engine/platform/sound/vic20sound.c -src/engine/platform/sound/vrcvi/vrcvi.cpp - -src/engine/platform/sound/es550x/es550x.cpp -src/engine/platform/sound/es550x/es550x_alu.cpp -src/engine/platform/sound/es550x/es550x_filter.cpp -src/engine/platform/sound/es550x/es5504.cpp -src/engine/platform/sound/es550x/es5505.cpp -src/engine/platform/sound/es550x/es5506.cpp - -src/engine/platform/sound/scc/scc.cpp - src/engine/platform/sound/ymz280b.cpp src/engine/platform/sound/rf5c68.cpp src/engine/platform/sound/oki/okim6258.cpp -src/engine/platform/sound/oki/msm6295.cpp + +src/engine/platform/sound/snes/SPC_DSP.cpp src/engine/platform/oplAInterface.cpp src/engine/platform/ym2608Interface.cpp src/engine/platform/ym2610Interface.cpp src/engine/blip_buf.c +src/engine/brrUtils.c src/engine/safeReader.cpp src/engine/safeWriter.cpp src/engine/config.cpp @@ -408,6 +463,8 @@ src/engine/sysDef.cpp src/engine/wavetable.cpp src/engine/waveSynth.cpp src/engine/vgmOps.cpp +src/engine/zsmOps.cpp +src/engine/zsm.cpp src/engine/platform/abstract.cpp src/engine/platform/genesis.cpp src/engine/platform/genesisext.cpp @@ -456,6 +513,8 @@ src/engine/platform/scc.cpp 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 ) @@ -469,6 +528,10 @@ if (WIN32) 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 @@ -516,6 +579,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 @@ -529,6 +593,8 @@ 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 @@ -551,14 +617,17 @@ endif() if (NOT WIN32 AND NOT APPLE) list(APPEND GUI_SOURCES src/gui/icon.c) - include(CheckIncludeFile) - 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) - list(APPEND DEPENDENCIES_DEFINES HAVE_SYS_IO) - message(STATUS "PC speaker output: outb()") + 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) @@ -570,13 +639,24 @@ if (NOT WIN32 AND NOT APPLE) 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") @@ -675,24 +755,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 97efdf8cd..853020f94 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 b75db0d05..7efe2c2a0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ the biggest multi-system chiptune tracker ever made! [downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [FAQ](#frequently-asked-questions) -*** +--- ## downloads check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage). @@ -61,6 +61,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a - 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 - modern/fantasy: @@ -73,7 +74,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a - loads .dmf modules from all versions (beta 1 to 1.1.3) - saves .dmf modules - both modern and legacy - Furnace doubles as a module downgrader - - loads .dmp instruments and .dmw wavetables as well + - loads/saves .dmp instruments and .dmw wavetables as well - clean-room design (guesswork and ABX tests only, no decompilation involved) - bug/quirk implementation for increased playback accuracy through compatibility flags - VGM export @@ -102,7 +103,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a - built-in visualizer in pattern view - open-source under GPLv2 or later. -*** +--- # quick references - **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z). @@ -118,7 +119,7 @@ some people have provided packages for Unix/Unix-like distributions. here's a li - **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608. - **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari. -*** +--- # developer info [![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml) @@ -202,6 +203,8 @@ Available options: | `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` | ## console usage @@ -225,7 +228,7 @@ this will play a compatible file and enable the commands view. **note that these commands only actually work in Linux environments. on other command lines, such as Windows' Command Prompt, or MacOS Terminal, it may not work correctly.** -*** +--- # frequently asked questions > woah! 50 sound chips?! I can't believe it! @@ -271,7 +274,7 @@ the DefleMask format has several limitations. save in Furnace song format instea right click on the channel name. -*** +--- # footnotes copyright (C) 2021-2022 tildearrow and contributors. diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 85118b445..000000000 --- a/TODO.md +++ /dev/null @@ -1,15 +0,0 @@ -# to-do for ES5506 - -- envelope shape -- reversed playing flag in instrument/macro/commands -- transwave synthesizer (like ensoniq synths - 12 bit command and macro) - -# to-do for 0.6pre1.5-0.6pre2 - -- rewrite the system name detection function anyway - - this involves the addition of a new "system" field in the song (which solves the problem) - - songs made in older versions will go through old system name detection for compatibility -- Game Boy envelope macro/sequence -- volume commands should work on Game Boy -- ability to customize `OFF`, `===` and `REL` -- stereo separation control for AY diff --git a/android/app/build.gradle b/android/app/build.gradle index cb4b3c59d..92a79e4d6 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 5f7a06efc..b77cf32e1 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/BONUS. Sonic 2 Boss.fur b/demos/BONUS. Sonic 2 Boss.fur new file mode 100644 index 000000000..8f5263f79 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 000000000..dfd2a0425 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 000000000..339e7238f 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 000000000..b75fbf9cf Binary files /dev/null and b/demos/ChaosTune.fur differ diff --git a/demos/Egyptian_Rule.fur b/demos/Egyptian_Rule.fur new file mode 100644 index 000000000..1715906ec Binary files /dev/null and b/demos/Egyptian_Rule.fur differ diff --git a/demos/FEDMS.fur b/demos/FEDMS.fur new file mode 100644 index 000000000..4c521dada Binary files /dev/null and b/demos/FEDMS.fur differ diff --git a/demos/GEN Equinox Intro.fur b/demos/GEN Equinox Intro.fur new file mode 100644 index 000000000..04a5bb051 Binary files /dev/null and b/demos/GEN Equinox Intro.fur differ diff --git a/demos/Melody_of_Certain_Feelings.fur b/demos/Melody_of_Certain_Feelings.fur deleted file mode 100644 index 6ccf4e13c..000000000 Binary files a/demos/Melody_of_Certain_Feelings.fur and /dev/null differ diff --git a/demos/MetalSlug_BaseCamp_SMS_TIA.fur b/demos/MetalSlug_BaseCamp_SMS_TIA.fur new file mode 100644 index 000000000..dd2fa6af3 Binary files /dev/null and b/demos/MetalSlug_BaseCamp_SMS_TIA.fur differ diff --git a/demos/Moon.fur b/demos/Moon.fur new file mode 100644 index 000000000..366de0452 Binary files /dev/null and b/demos/Moon.fur differ diff --git a/demos/Phoenix_cover.fur b/demos/Phoenix_cover.fur new file mode 100644 index 000000000..1a031443a Binary files /dev/null and b/demos/Phoenix_cover.fur differ diff --git a/demos/README.md b/demos/README.md index ea3ef883e..d07738e58 100644 --- a/demos/README.md +++ b/demos/README.md @@ -11,4 +11,6 @@ contact me or send a pull request if you want your song to be added to this coll - Nintendo covers are frowned upon - big label music covers also are discouraged +tildearrow also accepts demo songs in the .dmf format as well as the .fur format. + thank you for contributing! 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 000000000..be28182fc 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 000000000..bbe6a3c77 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 000000000..105b64b11 Binary files /dev/null and b/demos/The Cheetahmen.fur differ diff --git a/demos/The_only_thing_the_Metallix_fear_is_you_Forte....fur b/demos/The_only_thing_the_Metallix_fear_is_you_Forte....fur deleted file mode 100644 index 123381d8d..000000000 Binary files a/demos/The_only_thing_the_Metallix_fear_is_you_Forte....fur and /dev/null differ diff --git a/demos/UNATCOPCM.fur b/demos/UNATCOPCM.fur deleted file mode 100644 index 0a5560782..000000000 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 000000000..2bf8a7af7 Binary files /dev/null and b/demos/Waterworld_-_Map.fur differ diff --git a/demos/You're_Doing_Well!_GB.fur b/demos/You're_Doing_Well!_GB.fur new file mode 100644 index 000000000..01cfff9d2 Binary files /dev/null and b/demos/You're_Doing_Well!_GB.fur differ diff --git a/demos/ecolove.fur b/demos/ecolove.fur deleted file mode 100644 index 744b0c9f6..000000000 Binary files a/demos/ecolove.fur and /dev/null differ diff --git a/demos/government funding breakcore-ish remix.fur b/demos/government funding breakcore-ish remix.fur new file mode 100644 index 000000000..7acbabc49 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 000000000..1cfabac19 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 000000000..af2e952f6 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 000000000..77a3fed21 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 000000000..84a6ac5b3 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 000000000..43531b8bc 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 000000000..f32a1f26c Binary files /dev/null and b/demos/massive_x_opz.fur differ diff --git a/demos/neon_night_riders_TFMX.fur b/demos/neon_night_riders_TFMX.fur deleted file mode 100644 index a15f44892..000000000 Binary files a/demos/neon_night_riders_TFMX.fur and /dev/null differ diff --git a/demos/pseudogba_pwaa_godot.fur b/demos/pseudogba_pwaa_godot.fur new file mode 100644 index 000000000..5bb97a2f8 Binary files /dev/null and b/demos/pseudogba_pwaa_godot.fur differ diff --git a/demos/rule2.fur b/demos/rule2.fur new file mode 100644 index 000000000..aa6375916 Binary files /dev/null and b/demos/rule2.fur differ diff --git a/demos/wolf3d.fur b/demos/wolf3d.fur deleted file mode 100644 index a99731ff1..000000000 Binary files a/demos/wolf3d.fur and /dev/null differ diff --git a/extern/Nuked-OPN2 b/extern/Nuked-OPN2 index 64704a443..b0e9de0f8 160000 --- a/extern/Nuked-OPN2 +++ b/extern/Nuked-OPN2 @@ -1 +1 @@ -Subproject commit 64704a443f8f6c1906ba26297092ea70fa1d45d7 +Subproject commit b0e9de0f816943ad3820ddfefa0fff276d659250 diff --git a/extern/backward/backward.hpp b/extern/backward/backward.hpp index 5edda65ad..04032a4dc 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 776ad3738..63ae3b879 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__) @@ -1547,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; diff --git a/extern/imgui_patched/imgui.cpp b/extern/imgui_patched/imgui.cpp index 8b013816f..d52945d4e 100644 --- a/extern/imgui_patched/imgui.cpp +++ b/extern/imgui_patched/imgui.cpp @@ -1818,7 +1818,7 @@ ImGuiID ImHashStr(const char* data_p, size_t data_size, ImU32 seed) ImFileHandle ImFileOpen(const char* filename, const char* mode) { -#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(__CYGWIN__) && !defined(__GNUC__) +#if defined(_WIN32) // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames. // Previously we used ImTextCountCharsFromUtf8/ImTextStrFromUtf8 here but we now need to support ImWchar16 and ImWchar32! const int filename_wsize = ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0); diff --git a/extern/imgui_patched/imgui.h b/extern/imgui_patched/imgui.h index 7249c5402..3d600ea2b 100644 --- a/extern/imgui_patched/imgui.h +++ b/extern/imgui_patched/imgui.h @@ -1042,7 +1042,7 @@ enum ImGuiInputTextFlags_ ImGuiInputTextFlags_AlwaysOverwrite = 1 << 13, // Overwrite mode ImGuiInputTextFlags_ReadOnly = 1 << 14, // Read-only mode ImGuiInputTextFlags_Password = 1 << 15, // Password mode, display all characters as '*' - ImGuiInputTextFlags_NoUndoRedo = 1 << 16, // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). + ImGuiInputTextFlags_UndoRedo = 1 << 16, // Enable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). ImGuiInputTextFlags_CharsScientific = 1 << 17, // Allow 0123456789.+-*/eE (Scientific notation input) ImGuiInputTextFlags_CallbackResize = 1 << 18, // Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. Notify when the string wants to be resized (for string types which hold a cache of their Size). You will be provided a new BufSize in the callback and NEED to honor it. (see misc/cpp/imgui_stdlib.h for an example of using this) ImGuiInputTextFlags_CallbackEdit = 1 << 19 // Callback on any edit (note that InputText() already returns true on edit, the callback is useful mainly to manipulate the underlying buffer while focus is active) @@ -1220,6 +1220,8 @@ enum ImGuiTableFlags_ // Sorting ImGuiTableFlags_SortMulti = 1 << 26, // Hold shift when clicking headers to sort on multiple column. TableGetSortSpecs() may return specs where (SpecsCount > 1). ImGuiTableFlags_SortTristate = 1 << 27, // Allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0). + // tildearrow + ImGuiTableFlags_NoBordersInFrozenArea = 1 << 28, // Disable vertical borders in frozen area. // [Internal] Combinations and masks ImGuiTableFlags_SizingMask_ = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_SizingFixedSame | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_SizingStretchSame diff --git a/extern/imgui_patched/imgui_tables.cpp b/extern/imgui_patched/imgui_tables.cpp index 5a21a0b13..46bf0b132 100644 --- a/extern/imgui_patched/imgui_tables.cpp +++ b/extern/imgui_patched/imgui_tables.cpp @@ -2532,7 +2532,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) // Draw inner border and resizing feedback ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); const float border_size = TABLE_BORDER_SIZE; - const float draw_y1 = table->InnerRect.Min.y; + const float draw_y1 = table->InnerRect.Min.y + ((table->Flags & ImGuiTableFlags_NoBordersInFrozenArea)?table_instance->LastFirstRowHeight:0.0f); const float draw_y2_body = table->InnerRect.Max.y; const float draw_y2_head = table->IsUsingHeaders ? ImMin(table->InnerRect.Max.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table_instance->LastFirstRowHeight) : draw_y1; if (table->Flags & ImGuiTableFlags_BordersInnerV) diff --git a/extern/imgui_patched/imgui_widgets.cpp b/extern/imgui_patched/imgui_widgets.cpp index 93dfd5343..4579400d4 100644 --- a/extern/imgui_patched/imgui_widgets.cpp +++ b/extern/imgui_patched/imgui_widgets.cpp @@ -3713,7 +3713,12 @@ static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const Im { const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0; const int text_len = obj->CurLenW; - IM_ASSERT(pos <= text_len); + if (pos > text_len) { + printf("failing STB_TEXTEDIT_INSERTCHARS assertion! oh man...\n"); + obj->Edited = true; // ??? + obj->ClearText(); + return false; + } const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len); if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA)) @@ -3776,8 +3781,11 @@ static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* st state->cursor = text_len; state->has_preferred_x = 0; return; + } else { + state->cursor = 0; + printf("STB_TEXTEDIT_INSERTCHARS fail!\n"); } - IM_ASSERT(0); // Failed to insert character, normally shouldn't happen because of how we currently use stb_textedit_replace() + //IM_ASSERT(0); // Failed to insert character, normally shouldn't happen because of how we currently use stb_textedit_replace() } } // namespace ImStb @@ -3962,7 +3970,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0; const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0; const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; - const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0; + const bool is_undoable = (flags & ImGuiInputTextFlags_UndoRedo) != 0; const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0; if (is_resizable) IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag! diff --git a/extern/imgui_patched/imstb_textedit.h b/extern/imgui_patched/imstb_textedit.h index 75a159dac..d93642305 100644 --- a/extern/imgui_patched/imstb_textedit.h +++ b/extern/imgui_patched/imstb_textedit.h @@ -717,6 +717,9 @@ static int stb_textedit_paste_internal(STB_TEXTEDIT_STRING *str, STB_TexteditSta state->cursor += len; state->has_preferred_x = 0; return 1; + } else { + printf("stb_textedit_paste_internal failed.\n"); + state->cursor=0; } // note: paste failure will leave deleted selection, may be restored with an undo (see https://github.com/nothings/stb/issues/734 for details) return 0; @@ -746,6 +749,9 @@ retry: if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { ++state->cursor; state->has_preferred_x = 0; + } else { + printf("key failed: first section.\n"); + state->cursor=0; } } else { stb_textedit_delete_selection(str,state); // implicitly clamps @@ -753,6 +759,9 @@ retry: stb_text_makeundo_insert(state, state->cursor, 1); ++state->cursor; state->has_preferred_x = 0; + } else { + printf("key failed: second section.\n"); + state->cursor=0; } } } @@ -1275,14 +1284,22 @@ static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length); } + bool steFailed=false; + // check type of recorded action: if (u.insert_length) { // easy case: was a deletion, so we need to insert n characters - STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); + if (!STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length)) { + printf("undo u.insert_length failed\n"); + state->cursor=0; + steFailed=true; + } s->undo_char_point -= u.insert_length; } - state->cursor = u.where + u.insert_length; + if (!steFailed) { + state->cursor = u.where + u.insert_length; + } s->undo_point--; s->redo_point--; @@ -1327,13 +1344,21 @@ static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length); } + bool steFailed=false; + if (r.insert_length) { // easy case: need to insert n characters - STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); + if (!STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length)) { + printf("redo insert char failed\n"); + state->cursor=0; + steFailed=true; + } s->redo_char_point += r.insert_length; } - state->cursor = r.where + r.insert_length; + if (!steFailed) { + state->cursor = r.where + r.insert_length; + } s->undo_point++; s->redo_point++; diff --git a/extern/nfd-modified/src/nfd_win.cpp b/extern/nfd-modified/src/nfd_win.cpp index 9273bb1e7..b4fa5a5fa 100644 --- a/extern/nfd-modified/src/nfd_win.cpp +++ b/extern/nfd-modified/src/nfd_win.cpp @@ -368,7 +368,7 @@ static nfdresult_t AllocPathSet( IShellItemArray *shellItems, nfdpathset_t *path static nfdresult_t SetDefaultPath( IFileDialog *dialog, const char *defaultPath ) { - if ( !defaultPath || strlen(defaultPath) == 0 ) + if ( !defaultPath || strlen(defaultPath) == 0 || strcmp(defaultPath,"\\")==0 ) return NFD_OKAY; wchar_t *defaultPathW = {0}; diff --git a/extern/opm/opm.c b/extern/opm/opm.c index 79f6414cb..aafce483d 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/vgsound_emu-modified/.clang-format b/extern/vgsound_emu-modified/.clang-format new file mode 100644 index 000000000..edce098df --- /dev/null +++ b/extern/vgsound_emu-modified/.clang-format @@ -0,0 +1,154 @@ +# +# License: Zlib +# see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details +# +# Copyright holder(s): cam900 +# Clang Format setting for vgsound_emu +# +--- +BasedOnStyle: Microsoft +UseCRLF: true +IndentWidth: 4 +ColumnLimit: 0 +--- +Language: Proto +DisableFormat: true +--- +Language: TableGen +DisableFormat: true +--- +Language: TextProto +DisableFormat: true +--- +Language: Cpp +TabWidth: 4 +UseTab: Always +AccessModifierOffset: 4 +ColumnLimit: 100 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: Right +AlignConsecutiveAssignments: + Enabled: true + AcrossEmptyLines: true + AcrossComments: false + AlignCompound: true + PadOperators: true +AlignConsecutiveMacros: + Enabled: true + AcrossEmptyLines: true + AcrossComments: false + AlignCompound: true + PadOperators: true +AlignConsecutiveBitFields: + Enabled: true + AcrossEmptyLines: true + AcrossComments: false + AlignCompound: true + PadOperators: true +AlignEscapedNewlines: Right +AlignOperands: AlignAfterOperator +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: true +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BitFieldColonSpacing: Both +BreakBeforeBraces: Allman +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: false +CompactNamespaces: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: true +EmptyLineAfterAccessModifier: Leave +EmptyLineBeforeAccessModifier: Always +FixNamespaceComments: true +IncludeBlocks: Regroup +IndentAccessModifiers: true +IndentCaseBlocks: true +IndentCaseLabels: true +IndentGotoLabels: true +IndentPPDirectives: AfterHash +IndentRequiresClause: true +IndentRequires: true +IndentWrappedFunctionNames: false +InsertBraces: true +KeepEmptyLinesAtTheStartOfBlocks: true +LambdaBodyIndentation: Signature +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: All +PPIndentWidth: 1 +PackConstructorInitializers: Never +PointerAlignment: Right +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: Microsoft +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RequiresClausePosition: OwnLine +SeparateDefinitionBlocks: Always +ShortNamespaceLines: 0 +SortIncludes: CaseSensitive +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceAroundPointerQualifiers: After +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: Custom +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: false + AfterFunctionDeclarationName: false + AfterFunctionDefinitionName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: Never +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +StatementAttributeLikeMacros: [] diff --git a/extern/vgsound_emu-modified/.gitignore b/extern/vgsound_emu-modified/.gitignore new file mode 100644 index 000000000..6a46037be --- /dev/null +++ b/extern/vgsound_emu-modified/.gitignore @@ -0,0 +1,18 @@ +.vs/* +.vscode/* +node_modules/* +package.json +package-lock.json + +build/* +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps diff --git a/extern/vgsound_emu-modified/CHANGELOG.md b/extern/vgsound_emu-modified/CHANGELOG.md new file mode 100644 index 000000000..bf1e66771 --- /dev/null +++ b/extern/vgsound_emu-modified/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelogs + +## Important changes + +### V 2.1.0 (2022-09-08) + +Move source folder into vgsound_emu folder +CMake support +Move each readmes into README.md each folders + +## Details + +See [here](https://gitlab.com/cam900/vgsound_emu/-/commits/main). + +### Previous changelogs + +See [here](https://github.com/cam900/vgsound_emu/commits/main). diff --git a/extern/vgsound_emu-modified/CMakeLists.txt b/extern/vgsound_emu-modified/CMakeLists.txt new file mode 100644 index 000000000..17e14291c --- /dev/null +++ b/extern/vgsound_emu-modified/CMakeLists.txt @@ -0,0 +1,161 @@ +# +# License: Zlib +# see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details +# +# Copyright holder(s): cam900 +# CMake for vgsound_emu +# + +cmake_minimum_required(VERSION 3.0) +project(vgsound_emu + VERSION 2.1.0 + LANGUAGES CXX) + +option(VGSOUND_EMU_ES5504 "Use ES5504 core" ON) +option(VGSOUND_EMU_ES5505 "Use ES5505 core" ON) +option(VGSOUND_EMU_ES5506 "Use ES5506 core" ON) +option(VGSOUND_EMU_K005289 "Use K005289 core" ON) +option(VGSOUND_EMU_K007232 "Use K007232 core" ON) +option(VGSOUND_EMU_K053260 "Use K053260 core" ON) +option(VGSOUND_EMU_MSM6295 "Use MSM6295 core" ON) +option(VGSOUND_EMU_NAMCO_163 "Use Namco 163 core" ON) +option(VGSOUND_EMU_SCC "Use SCC core" ON) +option(VGSOUND_EMU_VRCVI "Use VRC VI core" ON) +option(VGSOUND_EMU_X1_010 "Use X1-010 core" ON) + +message(STATUS "Host: ${CMAKE_HOST_SYSTEM_NAME}, ${CMAKE_HOST_SYSTEM_PROCESSOR}") +message(STATUS "Target: ${CMAKE_SYSTEM_NAME}, ${CMAKE_SYSTEM_PROCESSOR}") +message(STATUS "Compiler: ${CMAKE_CXX_COMPILER_ID}") +message(STATUS "CMake version: ${CMAKE_VERSION}") +message(STATUS "Generator: ${CMAKE_GENERATOR}") +message(STATUS "Extra generator: ${CMAKE_EXTRA_GENERATOR}") +message(STATUS "Make program: ${CMAKE_MAKE_PROGRAM}") + +set(CORE_SOURCE "") +set(EMU_SOURCE "") + +# Core functions +list(APPEND CORE_SOURCE + vgsound_emu/src/core/util.hpp +) + +# Dialogic ADPCM +if(VGSOUND_EMU_MSM6295) + list(APPEND CORE_SOURCE + vgsound_emu/src/core/vox/vox.cpp + vgsound_emu/src/core/vox/vox.hpp + ) + message(STATUS "Using Dialogic ADPCM core") +endif() + +# ES5504, ES5505, ES5506 +if(VGSOUND_EMU_ES5504 OR VGSOUND_EMU_ES5505 OR VGSOUND_EMU_ES5506) + list(APPEND EMU_SOURCE + vgsound_emu/src/es550x/es550x.hpp + + vgsound_emu/src/es550x/es550x.cpp + vgsound_emu/src/es550x/es550x_alu.cpp + vgsound_emu/src/es550x/es550x_filter.cpp + ) + + if(VGSOUND_EMU_ES5504) + list(APPEND EMU_SOURCE + vgsound_emu/src/es550x/es5504.hpp + vgsound_emu/src/es550x/es5504.cpp + ) + message(STATUS "Using ES5504 core") + endif() + + if(VGSOUND_EMU_ES5505) + list(APPEND EMU_SOURCE + vgsound_emu/src/es550x/es5505.hpp + vgsound_emu/src/es550x/es5505.cpp + ) + message(STATUS "Using ES5505 core") + endif() + + if(VGSOUND_EMU_ES5506) + list(APPEND EMU_SOURCE + vgsound_emu/src/es550x/es5506.hpp + vgsound_emu/src/es550x/es5506.cpp + ) + message(STATUS "Using ES5506 core") + endif() +endif() + +# K005289 +if(VGSOUND_EMU_K005289) + list(APPEND EMU_SOURCE + vgsound_emu/src/k005289/k005289.hpp + vgsound_emu/src/k005289/k005289.cpp + ) + message(STATUS "Using K005289 core") +endif() + +# K007232 +if(VGSOUND_EMU_K007232) + list(APPEND EMU_SOURCE + vgsound_emu/src/k007232/k007232.hpp + vgsound_emu/src/k007232/k007232.cpp + ) + message(STATUS "Using K007232 core") +endif() + +# K053260 +if(VGSOUND_EMU_K053260) + list(APPEND EMU_SOURCE + vgsound_emu/src/k053260/k053260.hpp + vgsound_emu/src/k053260/k053260.cpp + ) + message(STATUS "Using K053260 core") +endif() + +# MSM6295 +if(VGSOUND_EMU_MSM6295) + list(APPEND EMU_SOURCE + vgsound_emu/src/msm6295/msm6295.hpp + vgsound_emu/src/msm6295/msm6295.cpp + ) + message(STATUS "Using MSM6295 core") +endif() + +# Namco 163 +if(VGSOUND_EMU_NAMCO_163) + list(APPEND EMU_SOURCE + vgsound_emu/src/n163/n163.hpp + vgsound_emu/src/n163/n163.cpp + ) + message(STATUS "Using Namco 163 core") +endif() + +# SCC +if(VGSOUND_EMU_SCC) + list(APPEND EMU_SOURCE + vgsound_emu/src/scc/scc.hpp + vgsound_emu/src/scc/scc.cpp + ) + message(STATUS "Using SCC core") +endif() + +# VRC VI +if(VGSOUND_EMU_VRCVI) + list(APPEND EMU_SOURCE + vgsound_emu/src/vrcvi/vrcvi.hpp + vgsound_emu/src/vrcvi/vrcvi.cpp + ) + message(STATUS "Using VRC VI core") +endif() + +# X1-010 +if(VGSOUND_EMU_X1_010) + list(APPEND EMU_SOURCE + vgsound_emu/src/x1_010/x1_010.hpp + vgsound_emu/src/x1_010/x1_010.cpp + ) + message(STATUS "Using X1-010 core") +endif() + +add_library(vgsound_emu STATIC ${CORE_SOURCE} ${EMU_SOURCE}) +target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + +# target_compile_options(vgsound_emu PRIVATE -Wall -Werror) \ No newline at end of file diff --git a/extern/vgsound_emu-modified/LICENSE b/extern/vgsound_emu-modified/LICENSE new file mode 100644 index 000000000..928144684 --- /dev/null +++ b/extern/vgsound_emu-modified/LICENSE @@ -0,0 +1,19 @@ +zlib License + +(C) 2022-present cam900 and contributors + +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/vgsound_emu-modified/MODIFIED.md b/extern/vgsound_emu-modified/MODIFIED.md new file mode 100644 index 000000000..00a7e727d --- /dev/null +++ b/extern/vgsound_emu-modified/MODIFIED.md @@ -0,0 +1,11 @@ +# modification disclaimer + +this is a modified version of vgsound_emu emulation core library tailored for Furnace. + +it should not and shall NOT be mistaken for the original, authentic or actual version and revision of the library. + +you can get original software from [here](https://gitlab.com/cam900/vgsound_emu/). + +## Modifier + +- [cam900](https://gitlab.com/cam900) \ No newline at end of file diff --git a/extern/vgsound_emu-modified/README.md b/extern/vgsound_emu-modified/README.md new file mode 100644 index 000000000..b24e2888b --- /dev/null +++ b/extern/vgsound_emu-modified/README.md @@ -0,0 +1,131 @@ +# vgsound_emu V2 (modified) + +This is a library of video game sound chip emulation cores. useful for emulators, chiptune trackers, or players. + +This is a modified version of vgsound_emu, tailored for Furnace. + +## Important + +License is now changed to zlib license in vgsound_emu V2, now you must notify your all modifications. + +but [vgsound_emu V1 (pre-V2)](https://gitlab.com/cam900/vgsound_emu/-/tree/V1) is still exists, and it's still distributed under [BSD-3-Clause license](https://spdx.org/licenses/BSD-3-Clause.html).([details](https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE)) + +## V2 revision changes + +formatting codes with clang-format, Encapsulation for Maintenance, Fix GCC 12, Change license to zlib license for notify modifications in derived works from this cores. + +## Changelog + +See [here](https://gitlab.com/cam900/vgsound_emu/-/blob/main/CHANGELOG.md). + +## License + +This software is distributed under [zlib License](https://spdx.org/licenses/Zlib.html), unlike [vgsound_emu V1](https://gitlab.com/cam900/vgsound_emu/-/tree/V1)([standard BSD-3-Clause license](https://spdx.org/licenses/BSD-3-Clause.html)([details](https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE))). +You must notify your modifications at all files you have modified! + +See [here](https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE) for details. + +## Folders + +- vgsound_emu: base folder + - src: source codes for emulation cores + - core: core files used in most of emulation cores + - vox: Dialogic ADPCM core + - es550x: Ensoniq ES5504, ES5505, ES5506 PCM sound chip families, 25/32 voices with 16/4 stereo/6 stereo output channels + - k005289: Konami K005289, 2 timers + - k007232: Konami K007232, 2 PCM channels + - k053260: Konami K053260, 4 PCM or ADPCM channels with CPU to CPU communication feature + - msm6295: OKI MSM6295, 4 ADPCM channels + - n163: Namco 163, NES Mapper with up to 8 Wavetable channels + - scc: Konami SCC, MSX Mappers with 5 Wavetable channels + - vrcvi: Konami VRC VI, NES Mapper with 2 Pulse channels and 1 Sawtooth channel + - x1_010: Seta/Allumer X1-010, 16 Wavetable/PCM channels + - template: Template for sound emulation core + +## Build instruction + +### dependencies + +- CMake +- git (for source repository management) +- MSVC or GCC or Clang (for compile) + +### Clone repository + +type the following on a terminal/console/shell/whatever: + +``` +git clone https://gitlab.com/cam900/vgsound_emu.git +cd vgsound_emu +``` + +### Compile + +#### MSVC + +type the following on a developer tools command prompt: + +``` +mkdir build +cd build +cmake .. +msbuild ALL_BUILD.vcxproj +``` + +#### MinGW, GCC, Clang with MakeFile + +type the following on a terminal/console/shell/whatever: + +``` +mkdir build +cd build +cmake .. +make +``` + +### CMake options + +To add an CMake option from the command line: ```cmake -D= ..``` +You can add multiple option with CMake. + +#### Available options + +| Options | Available Value | Default | Descriptions | +| :-: | :-: | :-: | :-: | +| VGSOUND_EMU_ES5504 | ON/OFF | ON | Use ES5504 core | +| VGSOUND_EMU_ES5505 | ON/OFF | ON | Use ES5505 core | +| VGSOUND_EMU_ES5506 | ON/OFF | ON | Use ES5506 core | +| VGSOUND_EMU_K005289 | ON/OFF | ON | Use K005289 core | +| VGSOUND_EMU_K007232 | ON/OFF | ON | Use K007232 core | +| VGSOUND_EMU_K053260 | ON/OFF | ON | Use K053260 core | +| VGSOUND_EMU_MSM6295 | ON/OFF | ON | Use MSM6295 core | +| VGSOUND_EMU_NAMCO_163 | ON/OFF | ON | Use Namco 163 core | +| VGSOUND_EMU_SCC | ON/OFF | ON | Use SCC core | +| VGSOUND_EMU_VRCVI | ON/OFF | ON | Use VRC VI core | +| VGSOUND_EMU_X1_010 | ON/OFF | ON | Use X1-010 core | + +### Link at another project + +Copy this repository as submodule first, type the following on a terminal/console/shell/whatever: + +``` +git submodule add https://gitlab.com/cam900/vgsound_emu.git +``` + +Then, Add following options at your CMakeLists.txt file. + +example: + +``` +add_subdirectory( [EXCLUDE_FROM_ALL]) +... +target_include_directories( SYSTEM PRIVATE ) +target_link_libraries( PRIVATE vgsound_emu) +``` + +## Contributors + +- [cam900](https://gitlab.com/cam900) +- [Natt Akuma](https://github.com/akumanatt) +- [James Alan Nguyen](https://github.com/djtuBIG-MaliceX) +- [Laurens Holst](https://github.com/Grauw) diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/core/util.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/core/util.hpp new file mode 100644 index 000000000..a6a9d6a29 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/core/util.hpp @@ -0,0 +1,263 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Various core utilities for vgsound_emu +*/ + +#ifndef _VGSOUND_EMU_SRC_CORE_UTIL_HPP +#define _VGSOUND_EMU_SRC_CORE_UTIL_HPP + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace vgsound_emu +{ + typedef unsigned char u8; + typedef unsigned short u16; + typedef unsigned int u32; + typedef unsigned long long u64; + typedef signed char s8; + typedef signed short s16; + typedef signed int s32; + typedef signed long long s64; + typedef float f32; + typedef double f64; + + class vgsound_emu_core + { + public: + // constructors + vgsound_emu_core(std::string tag) + : m_tag(tag) + { + } + + // getters + std::string tag() { return m_tag; } + + protected: + const f64 PI = 3.1415926535897932384626433832795; + + // std::clamp is only for C++17 or later; I use my own code + template + T clamp(T in, T min, T max) + { +#if defined(_HAS_CXX17) && _HAS_CXX17 + // just use std::clamp if C++17 or above + return std::clamp(in, min, max); +#else + // otherwise, use my own implementation of std::clamp + return std::min(std::max(in, min), max); +#endif + } + + // get bitfield, bitfield(input, position, len) + template + T bitfield(T in, u8 pos, u8 len = 1) + { + return (in >> pos) & (len ? (T(1 << len) - 1) : 1); + } + + // get sign extended value, sign_ext(input, len) + template + T sign_ext(T in, u8 len) + { + len = std::max(0, (8 * sizeof(T)) - len); + return T(T(in) << len) >> len; + } + + // convert attenuation decibel value to gain + inline f32 dB_to_gain(f32 attenuation) { return std::pow(10.0f, attenuation / 20.0f); } + + private: + std::string m_tag = ""; // core tags + }; + + class vgsound_emu_mem_intf : public vgsound_emu_core + { + public: + // constructor + vgsound_emu_mem_intf() + : vgsound_emu_core("mem_intf") + { + } + + virtual u8 read_byte(u32 address) { return 0; } + + virtual u16 read_word(u32 address) { return 0; } + + virtual u32 read_dword(u32 address) { return 0; } + + virtual u64 read_qword(u32 address) { return 0; } + + virtual void write_byte(u32 address, u8 data) {} + + virtual void write_word(u32 address, u16 data) {} + + virtual void write_dword(u32 address, u32 data) {} + + virtual void write_qword(u32 address, u64 data) {} + }; + + template + class clock_pulse_t : public vgsound_emu_core + { + private: + const T m_init_width = 1; + + class edge_t : public vgsound_emu_core + { + private: + const u8 m_init_edge = 1; + + public: + edge_t(u8 init_edge = 0) + : vgsound_emu_core("clock_pulse_edge") + , m_init_edge(init_edge) + , m_current(init_edge ^ 1) + , m_previous(init_edge) + , m_rising(0) + , m_falling(0) + , m_changed(0) + { + set(init_edge); + } + + // internal states + void reset() + { + m_previous = m_init_edge; + m_current = m_init_edge ^ 1; + set(m_init_edge); + } + + void tick(bool toggle) + { + u8 current = m_current; + if (toggle) + { + current ^= 1; + } + set(current); + } + + void set(u8 edge) + { + edge &= 1; + m_rising = m_falling = m_changed = 0; + if (m_current != edge) + { + m_changed = 1; + if (m_current && (!edge)) + { + m_falling = 1; + } + else if ((!m_current) && edge) + { + m_rising = 1; + } + m_current = edge; + } + m_previous = m_current; + } + + // getters + inline bool current() { return m_current; } + + inline bool rising() { return m_rising; } + + inline bool falling() { return m_falling; } + + inline bool changed() { return m_changed; } + + private: + u8 m_current : 1; // current edge + u8 m_previous : 1; // previous edge + u8 m_rising : 1; // rising edge + u8 m_falling : 1; // falling edge + u8 m_changed : 1; // changed flag + }; + + public: + clock_pulse_t(T init_width, u8 init_edge = 0) + : vgsound_emu_core("clock_pulse") + , m_init_width(init_width) + , m_edge(edge_t(init_edge & 1)) + , m_width(init_width) + , m_width_latch(init_width) + , m_counter(init_width) + , m_cycle(0) + { + } + + void reset(T init) + { + m_edge.reset(); + m_width = m_width_latch = m_counter = init; + m_cycle = 0; + } + + inline void reset() { reset(m_init_width); } + + bool tick(T width = 0) + { + bool carry = ((--m_counter) <= 0); + if (carry) + { + if (!width) + { + m_width = m_width_latch; + } + else + { + m_width = width; // reset width + } + m_counter = m_width; + m_cycle = 0; + } + else + { + m_cycle++; + } + + m_edge.tick(carry); + return carry; + } + + inline void set_width(T width) { m_width = width; } + + inline void set_width_latch(T width) { m_width_latch = width; } + + // Accessors + inline bool current_edge() { return m_edge.current(); } + + inline bool rising_edge() { return m_edge.rising(); } + + inline bool falling_edge() { return m_edge.falling(); } + + // getters + edge_t &edge() { return m_edge; } + + inline T cycle() { return m_cycle; } + + private: + edge_t m_edge; + T m_width = 1; // clock pulse width + T m_width_latch = 1; // clock pulse width latch + T m_counter = 1; // clock counter + T m_cycle = 0; // clock cycle + }; +}; // namespace vgsound_emu + +using namespace vgsound_emu; + +#endif diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/core/vox/vox.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/core/vox/vox.cpp new file mode 100644 index 000000000..ec16ca2b0 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/core/vox/vox.cpp @@ -0,0 +1,75 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Dialogic ADPCM core +*/ + +#include "vox.hpp" + +// reset decoder +void vox_core::vox_decoder_t::decoder_state_t::reset() +{ + m_index = 0; + m_step = 16; +} + +// copy from source +void vox_core::vox_decoder_t::decoder_state_t::copy_state(decoder_state_t &src) +{ + m_index = src.index(); + m_step = src.step(); +} + +// decode single nibble +void vox_core::vox_decoder_t::decoder_state_t::decode(u8 nibble) +{ + const u8 delta = bitfield(nibble, 0, 3); + const s16 ss = m_vox.m_step_table[m_index]; // ss(n) + + // d(n) = (ss(n) * B2) + ((ss(n) / 2) * B1) + ((ss(n) / 4) * B0) + // + (ss(n) / 8) + s16 d = ss >> 3; + if (bitfield(delta, 2)) + { + d += ss; + } + if (bitfield(delta, 1)) + { + d += (ss >> 1); + } + if (bitfield(delta, 0)) + { + d += (ss >> 2); + } + + // if (B3 = 1) then d(n) = d(n) * (-1) X(n) = X(n-1) * d(n) + if (bitfield(nibble, 3)) + { + m_step -= d; + } + else + { + m_step += d; + } + + if (m_wraparound) // wraparound (MSM5205) + { + if (m_step < -2048) + { + m_step &= 0x7ff; + } + else if (m_step > 2047) + { + m_step |= ~0x7ff; + } + } + else + { + m_step = clamp(m_step, -2048, 2047); + } + + // adjust step index + m_index = clamp(m_index + m_vox.m_index_table[delta], 0, 48); +} diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/core/vox/vox.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/core/vox/vox.hpp new file mode 100644 index 000000000..231f9b638 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/core/vox/vox.hpp @@ -0,0 +1,109 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Dialogic ADPCM core +*/ + +#ifndef _VGSOUND_EMU_SRC_CORE_VOX_VOX_HPP +#define _VGSOUND_EMU_SRC_CORE_VOX_VOX_HPP + +#pragma once + +#include "../util.hpp" + +class vox_core : public vgsound_emu_core +{ + protected: + class vox_decoder_t : public vgsound_emu_core + { + private: + class decoder_state_t : public vgsound_emu_core + { + public: + decoder_state_t(vox_core &vox, bool wraparound) + : vgsound_emu_core("vox_decoder_state") + , m_wraparound(wraparound) + , m_vox(vox) + , m_index(0) + , m_step(16) + { + } + + // internal states + void reset(); + void decode(u8 nibble); + + // getters + s8 index() { return m_index; } + + s32 step() { return m_step; } + + void copy_state(decoder_state_t &src); + + private: + const bool m_wraparound = false; // wraparound or clamp? + + vox_core &m_vox; + s8 m_index = 0; + s32 m_step = 16; + }; + + public: + vox_decoder_t(vox_core &vox, bool wraparound) + : vgsound_emu_core("vox_decoder") + , m_curr(vox, wraparound) + , m_loop(vox, wraparound) + , m_loop_saved(false) + { + } + + virtual void reset() + { + m_curr.reset(); + m_loop.reset(); + m_loop_saved = false; + } + + void save() + { + if (!m_loop_saved) + { + m_loop.copy_state(m_curr); + m_loop_saved = true; + } + } + + void restore() + { + if (m_loop_saved) + { + m_curr.copy_state(m_loop); + } + } + + void decode(u8 nibble) { m_curr.decode(nibble); } + + s32 step() { return m_curr.step(); } + + private: + decoder_state_t m_curr; + decoder_state_t m_loop; + bool m_loop_saved = false; + }; + + const s8 m_index_table[8] = {-1, -1, -1, -1, 2, 4, 6, 8}; + const s32 m_step_table[49] = { + 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, + 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, + 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552}; + + public: + vox_core(std::string tag) + : vgsound_emu_core(tag) + { + } +}; + +#endif diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/README.md b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/README.md new file mode 100644 index 000000000..6f410e7d4 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/README.md @@ -0,0 +1,88 @@ +# Ensoniq ES5504 DOCII, ES5505 OTIS, ES5506 OTTO + +## Summary + +- 32 voice of PCMs + - per-voice features: + - 4 pole, 4 mode filter (2 filter coefficient, 12 bit per them) + - max 16 output, 12 bit volume (ES5504) + - max 4 stereo output channels, 8 bit logarithmic volume per left/right output (ES5505) + - max 6 stereo output channels, 12 bit logarithmic volume per left/right output (ES5506) + - Hardware envelope for volume and filter (ES5506) + - 16 bit linear PCM + - 8 bit compressed PCM (ES5506) + - total accessible memory: 1 MWord (2MByte) per bank (ES5504, ES5505) or 2 MWord (4MByte) per bank (ES5506) + - CA flag is also usable for bank + - 1 bit BS flag for bank - 2 bank total (ES5505) + - 2 bit BS flag for bank - 4 bank total (ES5506) + +## Source code + +- es550x.hpp: Base header + - es550x.cpp: Emulation core for common shared features + - es550x_alu.cpp: Emulation core for shared ALU function + - es550x_filter.cpp: Emulation core for shared filter function + - es5504.hpp: ES5504 header + - es5504.cpp: Emulation core for ES5504 specific features + - es5505.hpp: ES5505 header + - es5505.cpp: Emulation core for ES5505 specific features + - es5506.hpp: ES5506 header + - es5506.cpp: Emulation core for ES5506 specific features + +## Description + +After ES5503 DOC's appeared, Ensoniq announces ES5504 DOC II, ES5505 OTIS, ES5506 OTTO. + +These are not just PCM chip; but with built-in 4 pole filters and variable voice limits. + +It can be trades higher sample rate and finer frequency and Tons of voices, or vice-versa. + +These are mainly used with their synthesizers, musical stuffs. It's also mainly paired with ES5510 ESP/ES5511 ESP2 for post processing. ES5506 can be paired with itself, It's called Dual chip configuration and Both chips are can be shares same memory spaces. + +ES5505 was also mainly used on Taito's early- to late-90s arcade hardware for their PCM sample based sound system, paired with ES5510 ESP for post processing. It's configuration is borrowed from Ensoniq's 32 Voice synths powered by these chips. It's difference is external logic to adds per-voice bankswitching looks like what Konami doing on K007232. + +Atari Panther was will be use ES5505, but finally canceled. + +Ensoniq's ISA Sound Card for PC, Soundscape used ES5506, "Elite" model has optional daughterboard with ES5510 for digital effects. + +## Related chips + +- ES5530 "OPUS" variant is 2-in-one chip with built-in ES5506 and Sequoia. + +- ES5540 "OTTOFX" variant is ES5506 and ES5510 merged in single package. + +- ES5548 "OTTO48" variant is used at late-90s ensoniq synths and musical instruments, 2 ES5506s are merged in single package, or with 48 voices in chip? + +## Chip difference + +### ES5504 to ES5505 + +- Total voice amount is expanded to 32, rather than 25. + +- ADC and DAC is completely redesigned. + +- it's has now voice-independent 10 bit and Sony/Burr-Brown format DAC. + +- Output channel and Volume is changed to 16 mono to 4 stereo, 12 bit Analog to 8 bit Stereo digital, also Floating point-ish format and independent per left and right output. + +- Channel 3 is can be Input/Output. + +- Channel output is can be accessible at host for test purpose. + +- Max sample memory is expanded to 2MWords (1MWords * 2 Banks) + +### ES5505 to ES5506 + +- Frequency is more finer now: 11 bit fraction rather than 9 bit. + +- Output channel and Volume is changed to 4 stereo to 6 stereo, 8 bit to 16 bit, but only 12 bit is used for calculation; 4 LSB is used for envelope ramping. + +- Transwave flag is added - its helpful for transwave process, with interrupt per voices. Hardware envelope is added - K1, K2, Volume value is can be modified in run-time. also K1, K2 is expanded to 16 bit for finer envelope ramping. + +- Filter calculation resolution is expanded to 18 bit. + +- All channels are output, Serial output is now partially programmable. + +- Max sample memory is expanded to 8MWords (2MWords * 4 Banks) + +- Register format between these chips are incompatible. diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5504.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5504.cpp new file mode 100644 index 000000000..b31a13b20 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5504.cpp @@ -0,0 +1,459 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Ensoniq ES5504 emulation core +*/ + +#include "es5504.hpp" + +// Internal functions +void es5504_core::tick() +{ + m_voice_update = false; + m_voice_end = false; + // /CAS, E + if (m_clkin.falling_edge()) // falling edge triggers /CAS, E clock + { + // /CAS + if (m_cas.tick()) + { + // /CAS high, E low: get sample address + if (m_cas.falling_edge()) + { + // /CAS low, E low: fetch sample + if (!m_e.current_edge()) + { + m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch); + } + } + } + // E + if (m_clkin.falling_edge()) // falling edge triggers E clock + { + if (m_e.tick()) + { + m_intf.e_pin(m_e.current_edge()); + if (m_e.rising_edge()) // Host access + { + m_host_intf.update_strobe(); + voice_tick(); + } + if (m_e.falling_edge()) // Voice memory + { + m_host_intf.clear_host_access(); + m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch); + } + } + if (m_e.current_edge()) // Host interface + { + if (m_host_intf.host_access()) + { + if (m_host_intf.rw() && (m_e.cycle() == 2)) // Read + { + m_hd = read(m_ha); + m_host_intf.clear_host_access(); + } + else if ((!m_host_intf.rw()) && (m_e.cycle() == 2)) + { // Write + write(m_ha, m_hd); + } + } + } + else if (!m_e.current_edge()) + { + if (m_e.cycle() == 2) + { + // reset host access state + m_hd = 0; + m_host_intf.clear_strobe(); + } + } + } + } +} + +// less cycle accurate, but less CPU heavy routine +void es5504_core::tick_perf() +{ + m_voice_update = false; + m_voice_end = false; + // update + // falling edge + m_e.edge().set(false); + m_intf.e_pin(false); + m_host_intf.clear_host_access(); + m_host_intf.clear_strobe(); + m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch); + voice_tick(); + // rising edge + m_e.edge().set(true); + m_intf.e_pin(true); + m_host_intf.update_strobe(); + // falling edge + m_e.edge().set(false); + m_intf.e_pin(false); + m_host_intf.clear_host_access(); + m_host_intf.clear_strobe(); + m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch); + voice_tick(); + // rising edge + m_e.edge().set(true); + m_intf.e_pin(true); + m_host_intf.update_strobe(); +} + +void es5504_core::voice_tick() +{ + // Voice updates every 2 E clock cycle (= 1 CHSTRB cycle or 4 BCLK clock cycle) + m_voice_update = bitfield(m_voice_fetch++, 0); + if (m_voice_update) + { + // Update voice + m_voice[m_voice_cycle].tick(m_voice_cycle); + + // Refresh output (Multiplexed analog output) + m_out[m_voice[m_voice_cycle].cr().ca()] = m_voice[m_voice_cycle].out(); + + if ((++m_voice_cycle) > std::min(24, m_active)) // ~ 25 voices + { + m_voice_end = true; + m_voice_cycle = 0; + } + + m_voice_fetch = 0; + } +} + +void es5504_core::voice_t::fetch(u8 voice, u8 cycle) +{ + m_alu.set_sample( + cycle, + m_host.m_intf.read_sample(voice, + bitfield(m_cr.ca(), 0, 3), + bitfield(m_alu.get_accum_integer() + cycle, 0, m_alu.m_integer))); +} + +void es5504_core::voice_t::tick(u8 voice) +{ + m_out = 0; + + // Filter execute + m_filter.tick(m_alu.interpolation()); + + if (m_alu.busy()) + { + // Send to output + m_out = ((sign_ext(m_filter.o4_1(), 16) >> 3) * m_volume) >> + 12; // Analog multiplied in real chip, 13/12 bit ladder DAC + + // ALU execute + if (m_alu.tick()) + { + m_alu.loop_exec(); + } + + // ADC check + adc_exec(); + } + + // Update IRQ + m_alu.irq_exec(m_host.m_intf, m_host.m_irqv, voice); +} + +// ADC; Correct? +void es5504_core::voice_t::adc_exec() +{ + if (m_cr.adc()) + { + m_host.m_adc = m_host.m_intf.adc_r() & ~0x7; + } +} + +void es5504_core::reset() +{ + es550x_shared_core::reset(); + for (auto &elem : m_voice) + { + elem.reset(); + } + + m_adc = 0; + std::fill(m_out.begin(), m_out.end(), 0); +} + +void es5504_core::voice_t::reset() +{ + es550x_shared_core::es550x_voice_t::reset(); + m_volume = 0; + m_out = 0; +} + +// Accessors +u16 es5504_core::host_r(u8 address) +{ + if (!m_host_intf.host_access()) + { + m_ha = address; + if (m_e.rising_edge()) + { // update directly + m_hd = read(m_ha, true); + } + else + { + m_host_intf.set_strobe(true); + } + } + return m_hd; +} + +void es5504_core::host_w(u8 address, u16 data) +{ + if (!m_host_intf.host_access()) + { + m_ha = address; + m_hd = data; + if (m_e.rising_edge()) + { // update directly + write(m_ha, m_hd, true); + } + else + { + m_host_intf.set_strobe(false); + } + } +} + +u16 es5504_core::read(u8 address, bool cpu_access) { return regs_r(m_page, address, cpu_access); } + +void es5504_core::write(u8 address, u16 data, bool cpu_access) +{ + regs_w(m_page, address, data, cpu_access); +} + +u16 es5504_core::regs_r(u8 page, u8 address, bool cpu_access) +{ + u16 ret = 0xffff; + address = bitfield(address, 0, 4); // 4 bit address for CPU access + + if (address >= 12) // Global registers + { + switch (address) + { + case 12: // A/D (A to D Convert/Test) + ret = (ret & ~0xfffb) | (m_adc & 0xfffb); + break; + case 13: // ACT (Number of voices) + ret = (ret & ~0x1f) | bitfield(m_active, 0, 5); + break; + case 14: // IRQV (Interrupting voice vector) + ret = (ret & ~0x9f) | m_irqv.get(); + if (cpu_access) + { + m_irqv.clear(); + if (bool(bitfield(ret, 7)) != m_irqv.irqb()) + { + m_voice[m_irqv.voice()].alu().irq_update(m_intf, m_irqv); + } + } + break; + case 15: // PAGE (Page select register) + ret = (ret & ~0x3f) | bitfield(m_page, 0, 6); + break; + } + } + else // Voice specific registers + { + const u8 voice = bitfield(page, 0, 5); // Voice select + if (voice < 25) + { + voice_t &v = m_voice[voice]; + if (bitfield(page, 5)) // Page 32 - 56 + { + switch (address) + { + case 1: // O4(n-1) (Filter 4 Temp Register) + ret = v.filter().o4_1(); + break; + case 2: // O3(n-2) (Filter 3 Temp Register #2) + ret = v.filter().o3_2(); + break; + case 3: // O3(n-1) (Filter 3 Temp Register #1) + ret = v.filter().o3_1(); + break; + case 4: // O2(n-2) (Filter 2 Temp Register #2) + ret = v.filter().o2_2(); + break; + case 5: // O2(n-1) (Filter 2 Temp Register #1) + ret = v.filter().o2_1(); + break; + case 6: // O1(n-1) (Filter 1 Temp Register) + ret = v.filter().o1_1(); + break; + } + } + else // Page 0 - 24 + { + switch (address) + { + case 0: // CR (Control Register) + ret = (ret & ~0xff) | (v.alu().stop() ? 0x01 : 0x00) | + (v.cr().adc() ? 0x04 : 0x00) | (v.alu().lpe() ? 0x08 : 0x00) | + (v.alu().ble() ? 0x10 : 0x00) | (v.alu().irqe() ? 0x20 : 0x00) | + (v.alu().dir() ? 0x40 : 0x00) | (v.alu().irq() ? 0x80 : 0x00); + break; + case 1: // FC (Frequency Control) + ret = (ret & ~0xfffe) | (v.alu().fc() << 1); + break; + case 2: // STRT-H (Loop Start Register High) + ret = (ret & ~0x1fff) | bitfield(v.alu().start(), 16, 13); + break; + case 3: // STRT-L (Loop Start Register Low) + ret = (ret & ~0xffe0) | (v.alu().start() & 0xffe0); + break; + case 4: // END-H (Loop End Register High) + ret = (ret & ~0x1fff) | bitfield(v.alu().end(), 16, 13); + break; + case 5: // END-L (Loop End Register Low) + ret = (ret & ~0xffe0) | (v.alu().end() & 0xffe0); + break; + case 6: // K2 (Filter Cutoff Coefficient #2) + ret = (ret & ~0xfff0) | (v.filter().k2() & 0xfff0); + break; + case 7: // K1 (Filter Cutoff Coefficient #1) + ret = (ret & ~0xfff0) | (v.filter().k1() & 0xfff0); + break; + case 8: // Volume + ret = (ret & ~0xfff0) | ((v.volume() << 4) & 0xfff0); + break; + case 9: // CA (Filter Config, Channel Assign) + ret = (ret & ~0x3f) | bitfield(v.cr().ca(), 0, 4) | + (bitfield(v.filter().lp(), 0, 2) << 4); + break; + case 10: // ACCH (Accumulator High) + ret = (ret & ~0x1fff) | bitfield(v.alu().accum(), 16, 13); + break; + case 11: // ACCL (Accumulator Low) + ret = bitfield(v.alu().accum(), 0, 16); + break; + } + } + } + } + + return ret; +} + +void es5504_core::regs_w(u8 page, u8 address, u16 data, bool cpu_access) +{ + address = bitfield(address, 0, 4); // 4 bit address for CPU access + + if (address >= 12) // Global registers + { + switch (address) + { + case 12: // A/D (A to D Convert/Test) + if (bitfield(m_adc, 0)) // Writam_ble ADC + { + m_adc = (m_adc & 7) | (data & ~7); + if (cpu_access) + { + m_intf.adc_w(m_adc & ~7); + } + } + m_adc = (m_adc & ~3) | (data & 3); + break; + case 13: // ACT (Number of voices) + m_active = std::min(24, bitfield(data, 0, 5)); + break; + case 14: // IRQV (Interrupting voice vector) + // Read only + break; + case 15: // PAGE (Page select register) + m_page = bitfield(data, 0, 6); + break; + } + } + else // Voice specific registers + { + const u8 voice = bitfield(page, 0, 5); // Voice select + if (voice < 25) + { + voice_t &v = m_voice[voice]; + if (bitfield(page, 5)) // Page 32 - 56 + { + switch (address) + { + case 1: // O4(n-1) (Filter 4 Temp Register) + v.filter().set_o4_1(sign_ext(data, 16)); + break; + case 2: // O3(n-2) (Filter 3 Temp Register #2) + v.filter().set_o3_2(sign_ext(data, 16)); + break; + case 3: // O3(n-1) (Filter 3 Temp Register #1) + v.filter().set_o3_1(sign_ext(data, 16)); + break; + case 4: // O2(n-2) (Filter 2 Temp Register #2) + v.filter().set_o2_2(sign_ext(data, 16)); + break; + case 5: // O2(n-1) (Filter 2 Temp Register #1) + v.filter().set_o2_1(sign_ext(data, 16)); + break; + case 6: // O1(n-1) (Filter 1 Temp Register) + v.filter().set_o1_1(sign_ext(data, 16)); + break; + } + } + else // Page 0 - 24 + { + switch (address) + { + case 0: // CR (Control Register) + v.alu().set_stop(bitfield(data, 0, 2)); + v.cr().set_adc(bitfield(data, 2)); + v.alu().set_lpe(bitfield(data, 3)); + v.alu().set_ble(bitfield(data, 4)); + v.alu().set_irqe(bitfield(data, 5)); + v.alu().set_dir(bitfield(data, 6)); + v.alu().set_irq(bitfield(data, 7)); + break; + case 1: // FC (Frequency Control) + v.alu().set_fc(bitfield(data, 1, 15)); + break; + case 2: // STRT-H (Loop Start Register High) + v.alu().set_start(bitfield(data, 0, 13) << 16, 0x1fff0000); + break; + case 3: // STRT-L (Loop Start Register Low) + v.alu().set_start(data & 0xffe0, 0xffe0); + break; + case 4: // END-H (Loop End Register High) + v.alu().set_end(bitfield(data, 0, 13) << 16, 0x1fff0000); + break; + case 5: // END-L (Loop End Register Low) + v.alu().set_end(data & 0xffe0, 0xffe0); + break; + case 6: // K2 (Filter Cutoff Coefficient #2) + v.filter().set_k2(data & 0xfff0); + break; + case 7: // K1 (Filter Cutoff Coefficient #1) + v.filter().set_k1(data & 0xfff0); + break; + case 8: // Volume + v.set_volume(bitfield(data, 4, 12)); + break; + case 9: // CA (Filter Config, Channel Assign) + v.cr().set_ca(bitfield(data, 0, 4)); + v.filter().set_lp(bitfield(data, 4, 2)); + break; + case 10: // ACCH (Accumulator High) + v.alu().set_accum(bitfield(data, 0, 13) << 16, 0x1fff0000); + break; + case 11: // ACCL (Accumulator Low) + v.alu().set_accum(data, 0xffff); + break; + } + } + } + } +} diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5504.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5504.hpp new file mode 100644 index 000000000..d37ef36da --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5504.hpp @@ -0,0 +1,117 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Ensoniq ES5504 emulation core +*/ + +#ifndef _VGSOUND_EMU_SRC_ES5504_HPP +#define _VGSOUND_EMU_SRC_ES5504_HPP + +#pragma once + +#include "es550x.hpp" + +// ES5504 specific +class es5504_core : public es550x_shared_core +{ + private: + // es5504 voice classes + class voice_t : public es550x_voice_t + { + public: + // constructor + voice_t(es5504_core &host) + : es550x_voice_t("es5504_voice", 20, 9, false) + , m_host(host) + , m_volume(0) + , m_out(0) + { + } + + // internal state + virtual void reset() override; + virtual void fetch(u8 voice, u8 cycle) override; + virtual void tick(u8 voice) override; + + // setters + inline void set_volume(u16 volume) { m_volume = volume; } + + // getters + inline u16 volume() { return m_volume; } + + inline s32 out() { return m_out; } + + private: + void adc_exec(); + + // registers + es5504_core &m_host; + u16 m_volume = 0; // 12 bit Volume + s32 m_out = 0; // channel outputs + }; + + public: + // constructor + es5504_core(es550x_intf &intf) + : es550x_shared_core("es5504", 25, intf) + , m_voice{*this, *this, *this, *this, *this, *this, *this, *this, *this, + *this, *this, *this, *this, *this, *this, *this, *this, *this, + *this, *this, *this, *this, *this, *this, *this} + , m_adc(0) + , m_out{0} + { + } + + // host interface + u16 host_r(u8 address); + void host_w(u8 address, u16 data); + + // internal state + virtual void reset() override; + virtual void tick() override; + + // less cycle accurate, but also less cpu heavy update routine + void tick_perf(); + + // 16 analog output channels + inline s32 out(u8 ch) { return m_out[ch & 0xf]; } + + //----------------------------------------------------------------- + // + // for preview/debug purpose only, not for serious emulators + // + //----------------------------------------------------------------- + + // bypass chips host interface for debug purpose only + u16 read(u8 address, bool cpu_access = false); + void write(u8 address, u16 data, bool cpu_access = false); + + u16 regs_r(u8 page, u8 address, bool cpu_access = false); + void regs_w(u8 page, u8 address, u16 data, bool cpu_access = false); + + u16 regs_r(u8 page, u8 address) + { + u8 prev = m_page; + m_page = page; + u16 ret = read(address, false); + m_page = prev; + return ret; + } + + // per-voice outputs + inline s32 voice_out(u8 voice) { return (voice < 25) ? m_voice[voice].out() : 0; } + + protected: + virtual inline u8 max_voices() override { return 25; } + + virtual void voice_tick() override; + + private: + std::array m_voice; // 25 voices + u16 m_adc = 0; // ADC register + std::array m_out = {0}; // 16 channel outputs +}; + +#endif diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5505.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5505.cpp new file mode 100644 index 000000000..6172a8665 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5505.cpp @@ -0,0 +1,651 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Ensoniq ES5505 emulation core +*/ + +#include "es5505.hpp" + +// Internal functions +void es5505_core::tick() +{ + m_voice_update = false; + m_voice_end = false; + // CLKIN + if (m_clkin.tick()) + { + // SERBCLK + if (m_clkin.edge().changed()) // BCLK is freely running clock + { + if (m_bclk.tick()) + { + m_intf.bclk(m_bclk.current_edge()); + // Serial output + if (m_bclk.falling_edge()) + { + // SERLRCLK + if (m_lrclk.tick()) + { + m_intf.lrclk(m_lrclk.current_edge()); + } + } + // SERWCLK + if (m_lrclk.edge().changed()) + { + m_wclk = 0; + } + if (m_bclk.falling_edge()) + { + if (m_wclk == ((m_sermode.sony_bb()) ? 1 : 0)) + { + if (m_lrclk.current_edge()) + { + for (int i = 0; i < 4; i++) + { + // copy output + m_output[i].copy_output(m_output_temp[i]); + // clamp to 16 bit (upper 5 bits are overflow + // guard bits) + m_output_latch[i].clamp16(m_ch[i]); + m_output_temp[i].reset(); + // set signed + if (m_output_latch[i].left() < 0) + { + m_output_temp[i].set_left(-1); + } + if (m_output_latch[i].right() < 0) + { + m_output_temp[i].set_right(-1); + } + } + } + m_wclk_lr = m_lrclk.current_edge(); + m_output_bit = 16; + } + s8 output_bit = --m_output_bit; + if (m_output_bit >= 0) + { + for (int i = 0; i < 4; i++) + { + if (m_wclk_lr) + { // Right output + m_output_temp[i].serial_in( + m_wclk_lr, + bitfield(m_output_latch[i].right(), output_bit)); + } + else + { // Left output + m_output_temp[i].serial_in( + m_wclk_lr, + bitfield(m_output_latch[i].left(), output_bit)); + } + } + } + m_wclk++; + } + } + } + // /CAS, E + if (m_clkin.falling_edge()) // falling edge triggers /CAS, E clock + { + // /CAS + if (m_cas.tick()) + { + // /CAS high, E low: get sample address + if (m_cas.falling_edge()) + { + // /CAS low, E low: fetch sample + if (!m_e.current_edge()) + { + m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch); + } + } + } + // E + if (m_e.tick()) + { + m_intf.e_pin(m_e.current_edge()); + if (m_e.rising_edge()) // Host access + { + m_host_intf.update_strobe(); + voice_tick(); + } + else if (m_e.falling_edge()) // Voice memory + { + m_host_intf.clear_host_access(); + m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch); + } + if (m_e.current_edge()) // Host interface + { + if (m_host_intf.host_access()) + { + if (m_host_intf.rw() && (m_e.cycle() == 2)) // Read + { + m_hd = read(m_ha); + m_host_intf.clear_host_access(); + } + else if ((!m_host_intf.rw()) && (m_e.cycle() == 2)) + { // Write + write(m_ha, m_hd); + } + } + } + else if (!m_e.current_edge()) + { + if (m_e.cycle() == 2) + { + // reset host access state + m_hd = 0; + m_host_intf.clear_strobe(); + } + } + } + } + } +} + +// less cycle accurate, but less CPU heavy routine +void es5505_core::tick_perf() +{ + m_voice_update = false; + m_voice_end = false; + // output + for (int c = 0; c < 4; c++) + { + m_output[c].clamp16(m_ch[c]); + } + + // update + // falling edge + m_e.edge().set(false); + m_intf.e_pin(false); + m_host_intf.clear_host_access(); + m_host_intf.clear_strobe(); + m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch); + voice_tick(); + // rising edge + m_e.edge().set(true); + m_intf.e_pin(true); + m_host_intf.update_strobe(); + // falling edge + m_e.edge().set(false); + m_intf.e_pin(false); + m_host_intf.clear_host_access(); + m_host_intf.clear_strobe(); + m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch); + voice_tick(); + // rising edge + m_e.edge().set(true); + m_intf.e_pin(true); + m_host_intf.update_strobe(); +} + +void es5505_core::voice_tick() +{ + // Voice updates every 2 E clock cycle (or 4 BCLK clock cycle) + m_voice_update = bitfield(m_voice_fetch++, 0); + if (m_voice_update) + { + // Update voice + m_voice[m_voice_cycle].tick(m_voice_cycle); + + // Refresh output + if ((++m_voice_cycle) > clamp(m_active, 7, 31)) // 8 ~ 32 voices + { + m_voice_end = true; + m_voice_cycle = 0; + for (auto &elem : m_ch) + { + elem.reset(); + } + + for (auto &elem : m_voice) + { + m_ch[bitfield(elem.cr().ca(), 0, 2)] += elem.ch(); + elem.ch().reset(); + } + } + m_voice_fetch = 0; + } +} + +void es5505_core::voice_t::fetch(u8 voice, u8 cycle) +{ + m_alu.set_sample( + cycle, + m_host.m_intf.read_sample(voice, + bitfield(m_cr.bs(), 0), + bitfield(m_alu.get_accum_integer() + cycle, 0, m_alu.m_integer))); +} + +void es5505_core::voice_t::tick(u8 voice) +{ + m_ch.reset(); + + // Filter execute + m_filter.tick(m_alu.interpolation()); + + if (m_alu.busy()) + { + // Send to output + m_ch.set_left(volume_calc(m_lvol, sign_ext(m_filter.o4_1(), 16))); + m_ch.set_right(volume_calc(m_rvol, sign_ext(m_filter.o4_1(), 16))); + + // ALU execute + if (m_alu.tick()) + { + m_alu.loop_exec(); + } + } + + // Update IRQ + m_alu.irq_exec(m_host.m_intf, m_host.m_irqv, voice); +} + +// volume calculation +s32 es5505_core::voice_t::volume_calc(u8 volume, s32 in) +{ + u8 exponent = bitfield(volume, 4, 4); + u8 mantissa = bitfield(volume, 0, 4); + return exponent ? (in * s32(0x10 | mantissa)) >> (19 - exponent) : 0; +} + +void es5505_core::reset() +{ + es550x_shared_core::reset(); + for (auto &elem : m_voice) + { + elem.reset(); + } + + m_sermode.reset(); + m_bclk.reset(); + m_lrclk.reset(); + m_wclk = 0; + m_wclk_lr = false; + m_output_bit = 0; + for (auto &elem : m_ch) + { + elem.reset(); + } + for (auto &elem : m_output) + { + elem.reset(); + } + for (auto &elem : m_output_temp) + { + elem.reset(); + } + for (auto &elem : m_output_latch) + { + elem.reset(); + } +} + +void es5505_core::voice_t::reset() +{ + es550x_shared_core::es550x_voice_t::reset(); + m_lvol = 0; + m_rvol = 0; + m_ch.reset(); +} + +// Accessors +u16 es5505_core::host_r(u8 address) +{ + if (!m_host_intf.host_access()) + { + m_ha = address; + if (m_e.rising_edge()) + { // update directly + m_hd = read(m_ha, true); + } + else + { + m_host_intf.set_strobe(true); + } + } + return m_hd; +} + +void es5505_core::host_w(u8 address, u16 data) +{ + if (!m_host_intf.host_access()) + { + m_ha = address; + m_hd = data; + if (m_e.rising_edge()) + { // update directly + write(m_ha, m_hd); + } + else + { + m_host_intf.set_strobe(false); + } + } +} + +u16 es5505_core::read(u8 address, bool cpu_access) { return regs_r(m_page, address, cpu_access); } + +void es5505_core::write(u8 address, u16 data) +{ + regs_w(m_page, address, data); +} + +u16 es5505_core::regs_r(u8 page, u8 address, bool cpu_access) +{ + u16 ret = 0xffff; + address = bitfield(address, 0, 4); // 4 bit address for CPU access + + if (address >= 13) // Global registers + { + switch (address) + { + case 13: // ACT (Number of voices) + ret = (ret & ~0x1f) | bitfield(m_active, 0, 5); + break; + case 14: // IRQV (Interrupting voice vector) + ret = (ret & ~0x9f) | m_irqv.get(); + if (cpu_access) + { + m_irqv.clear(); + if (bool(bitfield(ret, 7)) != m_irqv.irqb()) + { + m_voice[m_irqv.voice()].alu().irq_update(m_intf, m_irqv); + } + } + break; + case 15: // PAGE (Page select register) + ret = (ret & ~0x7f) | bitfield(m_page, 0, 7); + break; + } + } + else + { + if (bitfield(page, 6)) // Channel registers + { + switch (address) + { + case 0: // CH0L (Channel 0 Left) + case 2: // CH1L (Channel 1 Left) + case 4: // CH2L (Channel 2 Left) + if (!cpu_access) + { // CPU can't read here + ret = m_ch[bitfield(address, 0, 2)].left(); + } + break; + case 1: // CH0R (Channel 0 Right) + case 3: // CH1R (Channel 1 Right) + case 5: // CH2R (Channel 2 Right) + if (!cpu_access) + { // CPU can't read here + ret = m_ch[bitfield(address, 0, 2)].right(); + } + break; + case 6: // CH3L (Channel 3 Left) + if ((!cpu_access) || m_sermode.adc()) + { + ret = m_ch[3].left(); + } + break; + case 7: // CH3R (Channel 3 Right) + if ((!cpu_access) || m_sermode.adc()) + { + ret = m_ch[3].right(); + } + break; + case 8: // SERMODE (Serial Mode) + ret = (ret & ~0xf807) | (m_sermode.adc() ? 0x01 : 0x00) | + (m_sermode.test() ? 0x02 : 0x00) | (m_sermode.sony_bb() ? 0x04 : 0x00) | + (bitfield(m_sermode.msb(), 0, 5) << 11); + break; + case 9: // PAR (Port A/D Register) + ret = (ret & ~0x3f) | (m_intf.adc_r() & ~0x3f); + break; + } + } + else // Voice specific registers + { + const u8 voice = bitfield(page, 0, 5); // Voice select + voice_t &v = m_voice[voice]; + if (bitfield(page, 5)) // Page 32 - 63 + { + switch (address) + { + case 1: // O4(n-1) (Filter 4 Temp Register) + ret = v.filter().o4_1(); + break; + case 2: // O3(n-2) (Filter 3 Temp Register #2) + ret = v.filter().o3_2(); + break; + case 3: // O3(n-1) (Filter 3 Temp Register #1) + ret = v.filter().o3_1(); + break; + case 4: // O2(n-2) (Filter 2 Temp Register #2) + ret = v.filter().o2_2(); + break; + case 5: // O2(n-1) (Filter 2 Temp Register #1) + ret = v.filter().o2_1(); + break; + case 6: // O1(n-1) (Filter 1 Temp Register) + ret = v.filter().o1_1(); + break; + } + } + else // Page 0 - 31 + { + switch (address) + { + case 0: // CR (Control Register) + ret = (ret & ~0xfff) | (v.alu().stop() << 0) | + (bitfield(v.cr().bs(), 0) ? 0x04 : 0x00) | + (v.alu().lpe() ? 0x08 : 0x00) | (v.alu().ble() ? 0x10 : 0x00) | + (v.alu().irqe() ? 0x20 : 0x00) | (v.alu().dir() ? 0x40 : 0x00) | + (v.alu().irq() ? 0x80 : 0x00) | (bitfield(v.cr().ca(), 0, 2) << 8) | + (bitfield(v.filter().lp(), 0, 2) << 10); + break; + case 1: // FC (Frequency Control) + ret = (ret & ~0xfffe) | (bitfield(v.alu().fc(), 0, 15) << 1); + break; + case 2: // STRT-H (Loop Start Register High) + ret = (ret & ~0x1fff) | bitfield(v.alu().start(), 16, 13); + break; + case 3: // STRT-L (Loop Start Register Low) + ret = (ret & ~0xffe0) | (v.alu().start() & 0xffe0); + break; + case 4: // END-H (Loop End Register High) + ret = (ret & ~0x1fff) | bitfield(v.alu().end(), 16, 13); + break; + case 5: // END-L (Loop End Register Low) + ret = (ret & ~0xffe0) | (v.alu().end() & 0xffe0); + break; + case 6: // K2 (Filter Cutoff Coefficient #2) + ret = (ret & ~0xfff0) | (v.filter().k2() & 0xfff0); + break; + case 7: // K1 (Filter Cutoff Coefficient #1) + ret = (ret & ~0xfff0) | (v.filter().k1() & 0xfff0); + break; + case 8: // LVOL (Left Volume) + ret = (ret & ~0xff00) | ((v.lvol() << 8) & 0xff00); + break; + case 9: // RVOL (Right Volume) + ret = (ret & ~0xff00) | ((v.rvol() << 8) & 0xff00); + break; + case 10: // ACCH (Accumulator High) + ret = (ret & ~0x1fff) | bitfield(v.alu().accum(), 16, 13); + break; + case 11: // ACCL (Accumulator Low) + ret = bitfield(v.alu().accum(), 0, 16); + break; + } + } + } + } + + return ret; +} + +void es5505_core::regs_w(u8 page, u8 address, u16 data) +{ + address = bitfield(address, 0, 4); // 4 bit address for CPU access + + if (address >= 12) // Global registers + { + switch (address) + { + case 13: // ACT (Number of voices) + m_active = std::max(7, bitfield(data, 0, 5)); + break; + case 14: // IRQV (Interrupting voice vector) + // Read only + break; + case 15: // PAGE (Page select register) + m_page = bitfield(data, 0, 7); + break; + } + } + else // Voice specific registers + { + if (bitfield(page, 6)) // Channel registers + { + switch (address) + { + case 0: // CH0L (Channel 0 Left) + if (m_sermode.test()) + { + m_ch[0].set_left(data); + } + break; + case 1: // CH0R (Channel 0 Right) + if (m_sermode.test()) + { + m_ch[0].set_right(data); + } + break; + case 2: // CH1L (Channel 1 Left) + if (m_sermode.test()) + { + m_ch[1].set_left(data); + } + break; + case 3: // CH1R (Channel 1 Right) + if (m_sermode.test()) + { + m_ch[1].set_right(data); + } + break; + case 4: // CH2L (Channel 2 Left) + if (m_sermode.test()) + { + m_ch[2].set_left(data); + } + break; + case 5: // CH2R (Channel 2 Right) + if (m_sermode.test()) + { + m_ch[2].set_right(data); + } + break; + case 6: // CH3L (Channel 3 Left) + if (m_sermode.test()) + { + m_ch[3].set_left(data); + } + break; + case 7: // CH3R (Channel 3 Right) + if (m_sermode.test()) + { + m_ch[3].set_right(data); + } + break; + case 8: // SERMODE (Serial Mode) + m_sermode.write(data); + break; + case 9: // PAR (Port A/D Register) + // Read only + break; + } + } + else // Voice specific registers + { + const u8 voice = bitfield(page, 0, 5); // Voice select + voice_t &v = m_voice[voice]; + if (bitfield(page, 5)) // Page 32 - 56 + { + switch (address) + { + case 1: // O4(n-1) (Filter 4 Temp Register) + v.filter().set_o4_1(sign_ext(data, 16)); + break; + case 2: // O3(n-2) (Filter 3 Temp Register #2) + v.filter().set_o3_2(sign_ext(data, 16)); + break; + case 3: // O3(n-1) (Filter 3 Temp Register #1) + v.filter().set_o3_1(sign_ext(data, 16)); + break; + case 4: // O2(n-2) (Filter 2 Temp Register #2) + v.filter().set_o2_2(sign_ext(data, 16)); + break; + case 5: // O2(n-1) (Filter 2 Temp Register #1) + v.filter().set_o2_1(sign_ext(data, 16)); + break; + case 6: // O1(n-1) (Filter 1 Temp Register) + v.filter().set_o1_1(sign_ext(data, 16)); + break; + } + } + else // Page 0 - 24 + { + switch (address) + { + case 0: // CR (Control Register) + v.alu().set_stop(bitfield(data, 0, 2)); + v.cr().set_bs(bitfield(data, 2)); + v.alu().set_lpe(bitfield(data, 3)); + v.alu().set_ble(bitfield(data, 4)); + v.alu().set_irqe(bitfield(data, 5)); + v.alu().set_dir(bitfield(data, 6)); + v.alu().set_irq(bitfield(data, 7)); + v.cr().set_ca(bitfield(data, 8, 2)); + v.filter().set_lp(bitfield(data, 10, 2)); + break; + case 1: // FC (Frequency Control) + v.alu().set_fc(bitfield(data, 1, 15)); + break; + case 2: // STRT-H (Loop Start Register High) + v.alu().set_start(bitfield(data, 0, 13) << 16, 0x1fff0000); + break; + case 3: // STRT-L (Loop Start Register Low) + v.alu().set_start(data & 0xffe0, 0xffe0); + break; + case 4: // END-H (Loop End Register High) + v.alu().set_end(bitfield(data, 0, 13) << 16, 0x1fff0000); + break; + case 5: // END-L (Loop End Register Low) + v.alu().set_end(data & 0xffe0, 0xffe0); + break; + case 6: // K2 (Filter Cutoff Coefficient #2) + v.filter().set_k2(data & 0xfff0); + break; + case 7: // K1 (Filter Cutoff Coefficient #1) + v.filter().set_k1(data & 0xfff0); + break; + case 8: // LVOL (Left Volume) + v.set_lvol(bitfield(data, 8, 8)); + break; + case 9: // RVOL (Right Volume) + v.set_rvol(bitfield(data, 8, 8)); + break; + case 10: // ACCH (Accumulator High) + v.alu().set_accum(bitfield(data, 0, 13) << 16, 0x1fff0000); + break; + case 11: // ACCL (Accumulator Low) + v.alu().set_accum(data, 0xffff); + break; + } + } + } + } +} diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5505.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5505.hpp new file mode 100644 index 000000000..52a3b5f36 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5505.hpp @@ -0,0 +1,298 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Ensoniq ES5504 emulation core +*/ + +#ifndef _VGSOUND_EMU_SRC_ES5505_HPP +#define _VGSOUND_EMU_SRC_ES5505_HPP + +#pragma once + +#include "es550x.hpp" + +// ES5505 specific +class es5505_core : public es550x_shared_core +{ + private: + class output_t : public vgsound_emu_core + { + public: + output_t(s32 left = 0, s32 right = 0) + : vgsound_emu_core("es5505_output") + , m_left(left) + , m_right(right) + { + } + + void reset() + { + m_left = 0; + m_right = 0; + } + + inline void copy_output(output_t &src) + { + m_left = src.left(); + m_right = src.right(); + } + + inline s32 clamp16(s32 in) { return clamp(in, -0x8000, 0x7fff); } + + inline void clamp16(output_t &src) + { + m_left = clamp16(src.left()); + m_right = clamp16(src.right()); + } + + inline void clamp16() + { + m_left = clamp16(m_left); + m_right = clamp16(m_right); + } + + // setters + inline void set_left(s32 left) { m_left = left; } + + inline void set_right(s32 right) { m_right = right; } + + inline void serial_in(bool ch, u8 in) + { + if (ch) // Right output + { + m_right = (m_right << 1) | (in ? 1 : 0); + } + else // Left output + { + m_left = (m_left << 1) | (in ? 1 : 0); + } + } + + // getters + inline s32 left() { return m_left; } + + inline s32 right() { return m_right; } + + output_t &operator+=(output_t &src) + { + m_left = clamp16(m_left + src.left()); + m_right = clamp16(m_right + src.right()); + return *this; + } + + output_t &operator>>(s32 shift) + { + m_left >>= shift; + m_right >>= shift; + return *this; + } + + private: + s32 m_left = 0; + s32 m_right = 0; + }; + + // es5505 voice classes + class voice_t : public es550x_voice_t + { + public: + // constructor + voice_t(es5505_core &host) + : es550x_voice_t("es5505_voice", 20, 9, false) + , m_host(host) + , m_lvol(0) + , m_rvol(0) + , m_ch(output_t()) + { + } + + // internal state + virtual void reset() override; + virtual void fetch(u8 voice, u8 cycle) override; + virtual void tick(u8 voice) override; + + // setters + inline void set_lvol(u8 lvol) { m_lvol = lvol; } + + inline void set_rvol(u8 rvol) { m_rvol = rvol; } + + // getters + inline u8 lvol() { return m_lvol; } + + inline u8 rvol() { return m_rvol; } + + output_t &ch() { return m_ch; } + + private: + s32 volume_calc(u8 volume, s32 in); + + // registers + es5505_core &m_host; + u8 m_lvol = 0; // Left volume + u8 m_rvol = 0; // Right volume + output_t m_ch; // channel output + }; + + class sermode_t : public vgsound_emu_core + { + public: + sermode_t() + : vgsound_emu_core("es5505_sermode") + , m_adc(0) + , m_test(0) + , m_sony_bb(0) + , m_msb(0) + { + } + + void reset() + { + m_adc = 0; + m_test = 0; + m_sony_bb = 0; + m_msb = 0; + } + + // setters + void write(u16 data) + { + m_adc = (data >> 0) & 1; + m_test = (data >> 1) & 1; + m_sony_bb = (data >> 2) & 1; + m_msb = (data >> 11) & 0x1f; + } + + void set_adc(bool adc) { m_adc = adc ? 1 : 0; } + + void set_test(bool test) { m_test = test ? 1 : 0; } + + void set_sony_bb(bool sony_bb) { m_sony_bb = sony_bb ? 1 : 0; } + + void set_msb(u8 msb) { m_msb = msb & 0x1f; } + + // getters + bool adc() { return m_adc; } + + bool test() { return m_test; } + + bool sony_bb() { return m_sony_bb; } + + u8 msb() { return m_msb; } + + private: + u8 m_adc : 1; // A/D + u8 m_test : 1; // Test + u8 m_sony_bb : 1; // Sony/BB format serial output + u8 m_msb : 5; // Serial output MSB + }; + + public: + // constructor + es5505_core(es550x_intf &intf) + : es550x_shared_core("es5505", 32, intf) + , m_voice{*this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this, + *this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this, + *this, *this, *this, *this, *this, *this, *this, *this, *this, *this} + , m_sermode(sermode_t()) + , m_bclk(clock_pulse_t(4, 0)) + , m_lrclk(clock_pulse_t(16, 1)) + , m_wclk(0) + , m_wclk_lr(false) + , m_output_bit(0) + , m_ch{output_t()} + , m_output{output_t()} + , m_output_temp{output_t()} + , m_output_latch{output_t()} + { + } + + // host interface + u16 host_r(u8 address); + void host_w(u8 address, u16 data); + + // internal state + virtual void reset() override; + virtual void tick() override; + + // less cycle accurate, but also less cpu heavy update routine + void tick_perf(); + + // clock outputs + inline bool bclk() { return m_bclk.current_edge(); } + + inline bool bclk_rising_edge() { return m_bclk.rising_edge(); } + + inline bool bclk_falling_edge() { return m_bclk.falling_edge(); } + + // Input mode for Channel 3 + inline void lin(s32 in) + { + if (m_sermode.adc()) + { + m_ch[3].set_left(in); + } + } + + inline void rin(s32 in) + { + if (m_sermode.adc()) + { + m_ch[3].set_right(in); + } + } + + // 4 stereo output channels + inline s32 lout(u8 ch) { return m_ch[ch & 0x3].left(); } + + inline s32 rout(u8 ch) { return m_ch[ch & 0x3].right(); } + + //----------------------------------------------------------------- + // + // for preview/debug purpose only, not for serious emulators + // + //----------------------------------------------------------------- + + // bypass chips host interface for debug purpose only + u16 read(u8 address, bool cpu_access = false); + void write(u8 address, u16 data); + + u16 regs_r(u8 page, u8 address, bool cpu_access = false); + void regs_w(u8 page, u8 address, u16 data); + + u16 regs_r(u8 page, u8 address) + { + u8 prev = m_page; + m_page = page; + u16 ret = read(address, false); + m_page = prev; + return ret; + } + + // per-voice outputs + inline s32 voice_lout(u8 voice) { return (voice < 32) ? m_voice[voice].ch().left() : 0; } + + inline s32 voice_rout(u8 voice) { return (voice < 32) ? m_voice[voice].ch().right() : 0; } + + protected: + virtual inline u8 max_voices() override { return 32; } + + virtual void voice_tick() override; + + private: + std::array m_voice; // 32 voices + // Serial related stuffs + sermode_t m_sermode; // Serial mode register + clock_pulse_t m_bclk; // BCLK clock (CLKIN / 4), freely running clock + clock_pulse_t m_lrclk; // LRCLK + s16 m_wclk = 0; // WCLK + bool m_wclk_lr = false; // WCLK, L/R output select + s8 m_output_bit = 0; // Bit position in output + std::array m_ch; // 4 stereo output channels + std::array m_output; // Serial outputs + std::array m_output_temp; // temporary signal for serial output + std::array m_output_latch; // output latch +}; + +#endif diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5506.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5506.cpp new file mode 100644 index 000000000..83e43cac9 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5506.cpp @@ -0,0 +1,870 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Ensoniq ES5506 emulation core +*/ + +#include "es5506.hpp" + +// Internal functions +void es5506_core::tick() +{ + m_voice_update = false; + m_voice_end = false; + // CLKIN + if (m_clkin.tick()) + { + // BCLK + if (m_clkin.edge().changed() && (!m_mode.bclk_en())) // BCLK is freely running clock + { + if (m_bclk.tick()) + { + m_intf.bclk(m_bclk.current_edge()); + // Serial output + if (!m_mode.lrclk_en()) + { + if (m_bclk.falling_edge()) + { + // LRCLK + if (m_lrclk.tick()) + { + m_intf.lrclk(m_lrclk.current_edge()); + if (m_lrclk.rising_edge()) + { + m_w_st_curr = m_w_st; + m_w_end_curr = m_w_end; + } + if (m_lrclk.falling_edge()) + { // update width + m_lrclk.set_width_latch(m_lr_end); + } + } + } + } + // WCLK + if (!m_mode.wclk_en()) + { + if (!m_mode.lrclk_en()) + { + if (m_lrclk.edge().changed()) + { + m_wclk = 0; + } + } + if (m_bclk.falling_edge()) + { + if (m_wclk == m_w_st_curr) + { + m_intf.wclk(true); + if (m_lrclk.current_edge()) + { + for (int i = 0; i < 6; i++) + { + // copy output + m_output[i].copy_output(m_output_temp[i]); + // clamp to 20 bit (upper 3 bits are + // overflow guard bits) + m_output_latch[i].clamp20(m_ch[i]); + m_output_temp[i].reset(); + m_output_latch[i].clamp20(); + // set signed + if (m_output_latch[i].left() < 0) + { + m_output_temp[i].set_left(-1); + } + if (m_output_latch[i].right() < 0) + { + m_output_temp[i].set_right(-1); + } + } + } + m_wclk_lr = m_lrclk.current_edge(); + m_output_bit = 20; + } + if (m_wclk < m_w_end_curr) + { + s8 output_bit = --m_output_bit; + if (m_output_bit >= 0) + { + for (int i = 0; i < 6; i++) + { + if (m_wclk_lr) + { + // Right output + m_output_temp[i].serial_in( + m_wclk_lr, + bitfield(m_output_latch[i].right(), output_bit)); + } + else + { + // Left output + m_output_temp[i].serial_in( + m_wclk_lr, + bitfield(m_output_latch[i].left(), output_bit)); + } + } + } + } + if (m_wclk == m_w_end_curr) + { + m_intf.wclk(false); + } + + m_wclk++; + } + } + } + } + // /CAS, E + if (m_clkin.falling_edge()) // falling edge triggers /CAS, E clock + { + // /CAS + if (m_cas.tick()) + { + // single OTTO master mode, /CAS high, E low: get sample address + // single OTTO early mode, /CAS falling, E high: get sample + // address + if (m_cas.falling_edge()) + { + if (!m_e.current_edge()) + { + // single OTTO master mode, /CAS low, E low: fetch + // sample + if (m_mode.master()) + { + m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch); + } + } + else if (m_e.current_edge()) + { + // dual OTTO slave mode, /CAS low, E high: fetch sample + if (m_mode.dual() && (!m_mode.master())) + { // Dual OTTO, slave mode + m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch); + } + } + } + } + // E + if (m_e.tick()) + { + m_intf.e_pin(m_e.current_edge()); + if (m_e.rising_edge()) + { + m_host_intf.update_strobe(); + } + else if (m_e.falling_edge()) + { + m_host_intf.clear_host_access(); + voice_tick(); + } + if (m_e.current_edge()) // Host interface + { + if (m_host_intf.host_access()) + { + if (m_host_intf.rw() && (m_e.cycle() == 0)) // Read + { + m_hd = read(m_ha); + m_host_intf.clear_host_access(); + } + else if ((!m_host_intf.rw()) && (m_e.cycle() == 2)) + { // Write + write(m_ha, m_hd); + } + } + } + else if (!m_e.current_edge()) + { + if (m_e.cycle() == 2) + { + // reset host access state + m_hd = 0; + m_host_intf.clear_strobe(); + } + } + } + } + } +} + +// less cycle accurate, but less CPU heavy routine +void es5506_core::tick_perf() +{ + m_voice_update = false; + m_voice_end = false; + // output + if (((!m_mode.lrclk_en()) && (!m_mode.bclk_en()) && (!m_mode.wclk_en())) && (m_w_st < m_w_end)) + { + const int output_bits = 20 - (m_w_end - m_w_st); + if (output_bits < 20) + { + for (int c = 0; c < 6; c++) + { + m_output[c].clamp20(m_ch[c] >> output_bits); + } + } + } + else + { + for (int c = 0; c < 6; c++) + { + m_output[c].reset(); + } + } + + // update + // falling edge + m_e.edge().set(false); + m_intf.e_pin(false); + m_host_intf.clear_host_access(); + m_host_intf.clear_strobe(); + m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch); + voice_tick(); + // rising edge + m_e.edge().set(true); + m_intf.e_pin(true); + m_host_intf.update_strobe(); + // falling edge + m_e.edge().set(false); + m_intf.e_pin(false); + m_host_intf.clear_host_access(); + m_host_intf.clear_strobe(); + m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch); + voice_tick(); + // rising edge + m_e.edge().set(true); + m_intf.e_pin(true); + m_host_intf.update_strobe(); +} + +void es5506_core::voice_tick() +{ + // Voice updates every 2 E clock cycle (or 4 BCLK clock cycle) + m_voice_update = bitfield(m_voice_fetch++, 0); + if (m_voice_update) + { + // Update voice + m_voice[m_voice_cycle].tick(m_voice_cycle); + + // Refresh output + if ((++m_voice_cycle) > clamp(m_active, 4, 31)) // 5 ~ 32 voices + { + m_voice_end = true; + m_voice_cycle = 0; + for (output_t &elem : m_ch) + { + elem.reset(); + } + + for (voice_t &elem : m_voice) + { + const u8 ca = bitfield(elem.cr().ca(), 0, 3); + if (ca < 6) + { + m_ch[ca] += elem.ch(); + } + elem.ch().reset(); + } + } + m_voice_fetch = 0; + } +} + +void es5506_core::voice_t::fetch(u8 voice, u8 cycle) +{ + m_alu.set_sample( + cycle, + m_host.m_intf.read_sample(voice, + m_cr.bs(), + bitfield(m_alu.get_accum_integer() + cycle, 0, m_alu.m_integer))); + if (m_cr.cmpd()) + { // Decompress (Upper 8 bit is used for compressed format) + m_alu.set_sample(cycle, decompress(bitfield(m_alu.sample(cycle), 8, 8))); + } +} + +void es5506_core::voice_t::tick(u8 voice) +{ + m_ch.reset(); + + // Filter execute + m_filter.tick(m_alu.interpolation()); + + if (m_alu.busy()) + { + // Send to output + m_ch.set_left(volume_calc(m_lvol, sign_ext(m_filter.o4_1(), 16))); + m_ch.set_right(volume_calc(m_rvol, sign_ext(m_filter.o4_1(), 16))); + + // ALU execute + if (m_alu.tick()) + { + m_alu.loop_exec(); + } + } + // Envelope + if (m_ecount != 0) + { + // Left and Right volume + if (bitfield(m_lvramp, 0, 8) != 0) + { + m_lvol = clamp(m_lvol + sign_ext(bitfield(m_lvramp, 0, 8), 8), 0, 0xffff); + } + if (bitfield(m_rvramp, 0, 8) != 0) + { + m_rvol = clamp(m_rvol + sign_ext(bitfield(m_rvramp, 0, 8), 8), 0, 0xffff); + } + + // Filter coeffcient + if ((m_k1ramp.ramp() != 0) && + ((m_k1ramp.slow() == 0) || (bitfield(m_filtcount, 0, 3) == 0))) + { + m_filter.set_k1( + clamp(m_filter.k1() + sign_ext(m_k1ramp.ramp(), 8), 0, 0xffff)); + } + if ((m_k2ramp.ramp() != 0) && + ((m_k2ramp.slow() == 0) || (bitfield(m_filtcount, 0, 3) == 0))) + { + m_filter.set_k2( + clamp(m_filter.k2() + sign_ext(m_k2ramp.ramp(), 8), 0, 0xffff)); + } + + m_ecount--; + } + m_filtcount = bitfield(m_filtcount + 1, 0, 3); + + // Update IRQ + m_alu.irq_exec(m_host.m_intf, m_host.m_irqv, voice); +} + +// Compressed format +s16 es5506_core::voice_t::decompress(u8 sample) +{ + u8 exponent = bitfield(sample, 5, 3); + u8 mantissa = bitfield(sample, 0, 5); + return (exponent > 0) + ? s16(((bitfield(mantissa, 4) ? 0x10 : ~0x1f) | bitfield(mantissa, 0, 4)) + << (4 + (exponent - 1))) + : s16(((bitfield(mantissa, 4) ? ~0xf : 0) | bitfield(mantissa, 0, 4)) << 4); +} + +// volume calculation +s32 es5506_core::voice_t::volume_calc(u16 volume, s32 in) +{ + u8 exponent = bitfield(volume, 12, 4); + u8 mantissa = bitfield(volume, 4, 8); + return (in * s32(0x100 | mantissa)) >> (20 - exponent); +} + +void es5506_core::reset() +{ + es550x_shared_core::reset(); + for (auto &elem : m_voice) + { + elem.reset(); + } + + m_read_latch = 0xffffffff; + m_write_latch = 0xffffffff; + m_w_st = 0; + m_w_end = 0; + m_lr_end = 0; + m_w_st_curr = 0; + m_w_end_curr = 0; + m_mode.reset(); + m_bclk.reset(); + m_lrclk.reset(32); + m_wclk = 0; + m_wclk_lr = false; + m_output_bit = 0; + for (auto &elem : m_ch) + { + elem.reset(); + } + for (auto &elem : m_output) + { + elem.reset(); + } + for (auto &elem : m_output_temp) + { + elem.reset(); + } + for (auto &elem : m_output_latch) + { + elem.reset(); + } +} + +void es5506_core::voice_t::reset() +{ + es550x_shared_core::es550x_voice_t::reset(); + m_lvol = 0; + m_rvol = 0; + m_lvramp = 0; + m_rvramp = 0; + m_ecount = 0; + m_k2ramp.reset(); + m_k1ramp.reset(); + m_filtcount = 0; + m_ch.reset(); + m_mute = false; +} + +// Accessors +u8 es5506_core::host_r(u8 address) +{ + if (!m_host_intf.host_access()) + { + m_ha = address; + if (m_e.rising_edge()) + { // update directly + m_hd = read(m_ha, true); + } + else + { + m_host_intf.set_strobe(true); + } + } + return m_hd; +} + +void es5506_core::host_w(u8 address, u8 data) +{ + if (!m_host_intf.host_access()) + { + m_ha = address; + m_hd = data; + if (m_e.rising_edge()) + { // update directly + write(m_ha, m_hd); + } + else + { + m_host_intf.set_strobe(false); + } + } +} + +u8 es5506_core::read(u8 address, bool cpu_access) +{ + const u8 byte = bitfield(address, 0, 2); // byte select + const u8 shift = 24 - (byte << 3); + if (byte != 0) + { // Return already latched register if not highest byte is accessing + return bitfield(m_read_latch, shift, 8); + } + + address = bitfield(address, 2, 4); // 4 bit address for CPU access + + // get read register + m_read_latch = regs_r(m_page, address, cpu_access); + + return bitfield(m_read_latch, 24, 8); +} + +void es5506_core::write(u8 address, u8 data) +{ + const u8 byte = bitfield(address, 0, 2); // byte select + const u8 shift = 24 - (byte << 3); + address = bitfield(address, 2, 4); // 4 bit address for CPU access + + // Update register latch + m_write_latch = (m_write_latch & ~(0xff << shift)) | (u32(data) << shift); + + if (byte != 3) + { // Wait until lowest byte is writed + return; + } + + regs_w(m_page, address, m_write_latch); + + // Reset latch + m_write_latch = 0; +} + +u32 es5506_core::regs_r(u8 page, u8 address, bool cpu_access) +{ + u32 read_latch = 0xffffffff; + // Global registers + if (address >= 13) + { + switch (address) + { + case 13: // POT (Pot A/D Register) + read_latch = (read_latch & ~0x3ff) | bitfield(m_intf.adc_r(), 0, 10); + break; + case 14: // IRQV (Interrupting voice vector) + read_latch = (read_latch & ~0x9f) | (m_irqv.irqb() ? 0x80 : 0) | + bitfield(m_irqv.voice(), 0, 5); + if (cpu_access) + { + m_irqv.clear(); + if (bool(bitfield(read_latch, 7)) != m_irqv.irqb()) + { + m_voice[m_irqv.voice()].irq_update(m_intf, m_irqv); + } + } + break; + case 15: // PAGE (Page select register) + read_latch = (read_latch & ~0x7f) | bitfield(m_page, 0, 7); + break; + } + } + else + { + // Channel registers are Write only + if (bitfield(page, 6)) + { + if (!cpu_access) // CPU can't read here + { + switch (address) + { + case 0: // CH0L (Channel 0 Left) + case 2: // CH1L (Channel 1 Left) + case 4: // CH2L (Channel 2 Left) + case 6: // CH3L (Channel 3 Left) + case 8: // CH4L (Channel 4 Left) + case 10: // CH5L (Channel 5 Left) + read_latch = m_ch[bitfield(address, 1, 3)].left(); + break; + case 1: // CH0R (Channel 0 Right) + case 3: // CH1R (Channel 1 Right) + case 5: // CH2R (Channel 2 Right) + case 7: // CH3R (Channel 3 Right) + case 9: // CH4R (Channel 4 Right) + case 11: // CH5R (Channel 5 Right) + read_latch = m_ch[bitfield(address, 1, 3)].right(); + break; + } + } + } + else + { + const u8 voice = bitfield(page, 0, 5); // Voice select + voice_t &v = m_voice[voice]; + if (bitfield(page, 5)) // Page 32 - 63 + { + switch (address) + { + case 0: // CR (Control Register) + read_latch = (read_latch & ~0xffff) | (v.alu().stop() << 0) | + (v.alu().lei() ? 0x0004 : 0x0000) | (v.alu().loop() << 3) | + (v.alu().irqe() ? 0x0020 : 0x0000) | + (v.alu().dir() ? 0x0040 : 0x0000) | + (v.alu().irq() ? 0x0080 : 0x0000) | + (bitfield(v.filter().lp(), 0, 2) << 8) | (v.cr().ca() << 10) | + (v.cr().cmpd() ? 0x2000 : 0x0000) | (v.cr().bs() << 14); + break; + case 1: // START (Loop Start Register) + read_latch = (read_latch & ~0xfffff800) | (v.alu().start() & 0xfffff800); + break; + case 2: // END (Loop End Register) + read_latch = (read_latch & ~0xffffff80) | (v.alu().end() & 0xffffff80); + break; + case 3: // ACCUM (Accumulator Register) + read_latch = v.alu().accum(); + break; + case 4: // O4(n-1) (Filter 4 Temp Register) + if (cpu_access) + { + read_latch = + (read_latch & ~0x3ffff) | bitfield(v.filter().o4_1(), 0, 18); + } + else + { + read_latch = v.filter().o4_1(); + } + break; + case 5: // O3(n-2) (Filter 3 Temp Register #2) + if (cpu_access) + { + read_latch = + (read_latch & ~0x3ffff) | bitfield(v.filter().o3_2(), 0, 18); + } + else + { + read_latch = v.filter().o3_2(); + } + break; + case 6: // O3(n-1) (Filter 3 Temp Register #1) + if (cpu_access) + { + read_latch = + (read_latch & ~0x3ffff) | bitfield(v.filter().o3_1(), 0, 18); + } + else + { + read_latch = v.filter().o3_1(); + } + break; + case 7: // O2(n-2) (Filter 2 Temp Register #2) + if (cpu_access) + { + read_latch = + (read_latch & ~0x3ffff) | bitfield(v.filter().o2_2(), 0, 18); + } + else + { + read_latch = v.filter().o2_2(); + } + break; + case 8: // O2(n-1) (Filter 2 Temp Register #1) + if (cpu_access) + { + read_latch = + (read_latch & ~0x3ffff) | bitfield(v.filter().o2_1(), 0, 18); + } + else + { + read_latch = v.filter().o2_1(); + } + break; + case 9: // O1(n-1) (Filter 1 Temp Register) + if (cpu_access) + { + read_latch = + (read_latch & ~0x3ffff) | bitfield(v.filter().o1_1(), 0, 18); + } + else + { + read_latch = v.filter().o1_1(); + } + break; + case 10: // W_ST (Word Clock Start Register) + read_latch = (read_latch & ~0x7f) | bitfield(m_w_st, 0, 7); + break; + case 11: // W_END (Word Clock End Register) + read_latch = (read_latch & ~0x7f) | bitfield(m_w_end, 0, 7); + break; + case 12: // LR_END (Left/Right Clock End Register) + read_latch = (read_latch & ~0x7f) | bitfield(m_lr_end, 0, 7); + break; + } + } + else // Page 0 - 31 + { + switch (address) + { + case 0: // CR (Control Register) + read_latch = (read_latch & ~0xffff) | (v.alu().stop() << 0) | + (v.alu().lei() ? 0x0004 : 0x0000) | (v.alu().loop() << 3) | + (v.alu().irqe() ? 0x0020 : 0x0000) | + (v.alu().dir() ? 0x0040 : 0x0000) | + (v.alu().irq() ? 0x0080 : 0x0000) | + (bitfield(v.filter().lp(), 0, 2) << 8) | (v.cr().ca() << 10) | + (v.cr().cmpd() ? 0x2000 : 0x0000) | (v.cr().bs() << 14); + break; + case 1: // FC (Frequency Control) + read_latch = (read_latch & ~0x1ffff) | bitfield(v.alu().fc(), 0, 17); + break; + case 2: // LVOL (Left Volume) + read_latch = (read_latch & ~0xffff) | bitfield(v.lvol(), 0, 16); + break; + case 3: // LVRAMP (Left Volume Ramp) + read_latch = (read_latch & ~0xff00) | (bitfield(v.lvramp(), 0, 8) << 8); + break; + case 4: // RVOL (Right Volume) + read_latch = (read_latch & ~0xffff) | bitfield(v.rvol(), 0, 16); + break; + case 5: // RVRAMP (Right Volume Ramp) + read_latch = (read_latch & ~0xff00) | (bitfield(v.rvramp(), 0, 8) << 8); + break; + case 6: // ECOUNT (Envelope Counter) + read_latch = (read_latch & ~0x01ff) | bitfield(v.ecount(), 0, 9); + break; + case 7: // K2 (Filter Cutoff Coefficient #2) + read_latch = (read_latch & ~0xffff) | bitfield(v.filter().k2(), 0, 16); + break; + case 8: // K2RAMP (Filter Cutoff Coefficient #2 Ramp) + read_latch = (read_latch & ~0xff01) | + (bitfield(v.k2ramp().ramp(), 0, 8) << 8) | + (v.k2ramp().slow() ? 0x0001 : 0x0000); + break; + case 9: // K1 (Filter Cutoff Coefficient #1) + read_latch = (read_latch & ~0xffff) | bitfield(v.filter().k1(), 0, 16); + break; + case 10: // K1RAMP (Filter Cutoff Coefficient #1 Ramp) + read_latch = (read_latch & ~0xff01) | + (bitfield(v.k1ramp().ramp(), 0, 8) << 8) | + (v.k1ramp().slow() ? 0x0001 : 0x0000); + break; + case 11: // ACT (Number of voices) + read_latch = (read_latch & ~0x1f) | bitfield(m_active, 0, 5); + break; + case 12: // MODE (Global Mode) + read_latch = + (read_latch & ~0x1f) | (m_mode.lrclk_en() ? 0x01 : 0x00) | + (m_mode.wclk_en() ? 0x02 : 0x00) | (m_mode.bclk_en() ? 0x04 : 0x00) | + (m_mode.master() ? 0x08 : 0x00) | (m_mode.dual() ? 0x10 : 0x00); + break; + } + } + } + } + + return read_latch; +} + +void es5506_core::regs_w(u8 page, u8 address, u32 data) +{ + // Global registers + if (address >= 13) + { + switch (address) + { + case 13: // POT (Pot A/D Register) + // Read only + break; + case 14: // IRQV (Interrupting voice vector) + // Read only + break; + case 15: // PAGE (Page select register) + m_page = bitfield(data, 0, 7); + break; + } + } + else + { + // Channel registers are Write only, and for test purposes + if (bitfield(page, 6)) + { + switch (address) + { + case 0: // CH0L (Channel 0 Left) + case 2: // CH1L (Channel 1 Left) + case 4: // CH2L (Channel 2 Left) + case 6: // CH3L (Channel 3 Left) + case 8: // CH4L (Channel 4 Left) + case 10: // CH5L (Channel 5 Left) + m_ch[bitfield(address, 1, 3)].set_left( + sign_ext(bitfield(data, 0, 23), 23)); + break; + case 1: // CH0R (Channel 0 Right) + case 3: // CH1R (Channel 1 Right) + case 5: // CH2R (Channel 2 Right) + case 7: // CH3R (Channel 3 Right) + case 9: // CH4R (Channel 4 Right) + case 11: // CH5R (Channel 5 Right) + m_ch[bitfield(address, 1, 3)].set_right( + sign_ext(bitfield(data, 0, 23), 23)); + break; + } + } + else + { + const u8 voice = bitfield(page, 0, 5); // Voice select + voice_t &v = m_voice[voice]; + if (bitfield(page, 5)) // Page 32 - 63 + { + switch (address) + { + case 0: // CR (Control Register) + v.alu().set_stop(bitfield(data, 0, 2)); + v.alu().set_lei(bitfield(data, 2)); + v.alu().set_loop(bitfield(data, 3, 2)); + v.alu().set_irqe(bitfield(data, 5)); + v.alu().set_dir(bitfield(data, 6)); + v.alu().set_irq(bitfield(data, 7)); + v.filter().set_lp(bitfield(data, 8, 2)); + v.cr().set_ca(std::min(5, bitfield(data, 10, 3))); + v.cr().set_cmpd(bitfield(data, 13)); + v.cr().set_bs(bitfield(data, 14, 2)); + break; + case 1: // START (Loop Start Register) + v.alu().set_start(data & 0xfffff800); + break; + case 2: // END (Loop End Register) + v.alu().set_end(data & 0xffffff80); + break; + case 3: // ACCUM (Accumulator Register) + v.alu().set_accum(data); + break; + case 4: // O4(n-1) (Filter 4 Temp Register) + v.filter().set_o4_1(sign_ext(bitfield(data, 0, 18), 18)); + break; + case 5: // O3(n-2) (Filter 3 Temp Register #2) + v.filter().set_o3_2(sign_ext(bitfield(data, 0, 18), 18)); + break; + case 6: // O3(n-1) (Filter 3 Temp Register #1) + v.filter().set_o3_1(sign_ext(bitfield(data, 0, 18), 18)); + break; + case 7: // O2(n-2) (Filter 2 Temp Register #2) + v.filter().set_o2_2(sign_ext(bitfield(data, 0, 18), 18)); + break; + case 8: // O2(n-1) (Filter 2 Temp Register #1) + v.filter().set_o2_1(sign_ext(bitfield(data, 0, 18), 18)); + break; + case 9: // O1(n-1) (Filter 1 Temp Register) + v.filter().set_o1_1(sign_ext(bitfield(data, 0, 18), 18)); + break; + case 10: // W_ST (Word Clock Start Register) + m_w_st = bitfield(data, 0, 7); + break; + case 11: // W_END (Word Clock End Register) + m_w_end = bitfield(data, 0, 7); + break; + case 12: // LR_END (Left/Right Clock End Register) + m_lr_end = bitfield(data, 0, 7); + m_lrclk.set_width(m_lr_end); + break; + } + } + else // Page 0 - 31 + { + switch (address) + { + case 0: // CR (Control Register) + v.alu().set_stop(bitfield(data, 0, 2)); + v.alu().set_lei(bitfield(data, 2)); + v.alu().set_loop(bitfield(data, 3, 2)); + v.alu().set_irqe(bitfield(data, 5)); + v.alu().set_dir(bitfield(data, 6)); + v.alu().set_irq(bitfield(data, 7)); + v.filter().set_lp(bitfield(data, 8, 2)); + v.cr().set_ca(std::min(5, bitfield(data, 10, 3))); + v.cr().set_cmpd(bitfield(data, 13)); + v.cr().set_bs(bitfield(data, 14, 2)); + break; + case 1: // FC (Frequency Control) + v.alu().set_fc(bitfield(data, 0, 17)); + break; + case 2: // LVOL (Left Volume) + v.set_lvol(bitfield(data, 0, 16)); + break; + case 3: // LVRAMP (Left Volume Ramp) + v.set_lvramp(bitfield(data, 8, 8)); + break; + case 4: // RVOL (Right Volume) + v.set_rvol(bitfield(data, 0, 16)); + break; + case 5: // RVRAMP (Right Volume Ramp) + v.set_rvramp(bitfield(data, 8, 8)); + break; + case 6: // ECOUNT (Envelope Counter) + v.set_ecount(bitfield(data, 0, 9)); + break; + case 7: // K2 (Filter Cutoff Coefficient #2) + v.filter().set_k2(bitfield(data, 0, 16)); + break; + case 8: // K2RAMP (Filter Cutoff Coefficient #2 Ramp) + v.k2ramp().write(data); + break; + case 9: // K1 (Filter Cutoff Coefficient #1) + v.filter().set_k1(bitfield(data, 0, 16)); + break; + case 10: // K1RAMP (Filter Cutoff Coefficient #1 Ramp) + v.k1ramp().write(data); + break; + case 11: // ACT (Number of voices) + m_active = std::max(4, bitfield(data, 0, 5)); + break; + case 12: // MODE (Global Mode) + m_mode.write(data); + break; + } + } + } + } +} diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5506.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5506.hpp new file mode 100644 index 000000000..b59a96dcf --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5506.hpp @@ -0,0 +1,379 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Ensoniq ES5506 emulation core +*/ + +#ifndef _VGSOUND_EMU_SRC_ES5506_HPP +#define _VGSOUND_EMU_SRC_ES5506_HPP + +#pragma once + +#include "es550x.hpp" + +// ES5506 specific +class es5506_core : public es550x_shared_core +{ + private: + class output_t : public vgsound_emu_core + { + public: + output_t(s32 left = 0, s32 right = 0) + : vgsound_emu_core("es5506_output") + , m_left(left) + , m_right(right) + { + } + + void reset() + { + m_left = 0; + m_right = 0; + } + + inline void copy_output(output_t &src) + { + m_left = src.left(); + m_right = src.right(); + } + + inline s32 clamp20(s32 in) { return clamp(in, -0x80000, 0x7ffff); } + + inline void clamp20(output_t &src) + { + m_left = clamp20(src.left()); + m_right = clamp20(src.right()); + } + + inline void clamp20() + { + m_left = clamp20(m_left); + m_right = clamp20(m_right); + } + + // setters + inline void set_left(s32 left) { m_left = clamp20(left); } + + inline void set_right(s32 right) { m_right = clamp20(right); } + + void serial_in(bool ch, u8 in) + { + if (ch) // Right output + { + m_right = (m_right << 1) | (in ? 1 : 0); + } + else // Left output + { + m_left = (m_left << 1) | (in ? 1 : 0); + } + } + + // getters + inline s32 left() { return m_left; } + + inline s32 right() { return m_right; } + + output_t &operator+=(output_t &src) + { + m_left = clamp20(m_left + src.left()); + m_right = clamp20(m_right + src.right()); + return *this; + } + + output_t &operator>>(s32 shift) + { + m_left >>= shift; + m_right >>= shift; + return *this; + } + + private: + s32 m_left = 0; + s32 m_right = 0; + }; + + // es5506 voice classes + class voice_t : public es550x_voice_t + { + private: + // es5506 Filter ramp class + class filter_ramp_t : public vgsound_emu_core + { + public: + filter_ramp_t() + : vgsound_emu_core("es5506_filter_ramp") + , m_slow(0) + , m_ramp(0) + { + } + + void reset() + { + m_slow = 0; + m_ramp = 0; + }; + + // Setters + inline void write(u16 data) + { + m_slow = data & 1; + m_ramp = (data >> 8) & 0xff; + } + + // Getters + inline bool slow() { return m_slow; } + + inline u16 ramp() { return m_ramp; } + + private: + u16 m_slow : 1; // Slow mode flag + u16 m_ramp = 8; // Ramp value + }; + + public: + // constructor + voice_t(es5506_core &host) + : es550x_voice_t("es5506_voice", 21, 11, true) + , m_host(host) + , m_lvol(0) + , m_rvol(0) + , m_lvramp(0) + , m_rvramp(0) + , m_ecount(0) + , m_k2ramp(filter_ramp_t()) + , m_k1ramp(filter_ramp_t()) + , m_filtcount(0) + , m_ch(output_t()) + , m_mute(false) + { + } + + // internal state + virtual void reset() override; + virtual void fetch(u8 voice, u8 cycle) override; + virtual void tick(u8 voice) override; + + // Setters + inline void set_lvol(s32 lvol) { m_lvol = lvol; } + + inline void set_rvol(s32 rvol) { m_rvol = rvol; } + + inline void set_lvramp(s32 lvramp) { m_lvramp = lvramp; } + + inline void set_rvramp(s32 rvramp) { m_rvramp = rvramp; } + + inline void set_ecount(s16 ecount) { m_ecount = ecount; } + + // Getters + inline s32 lvol() { return m_lvol; } + + inline s32 rvol() { return m_rvol; } + + inline s32 lvramp() { return m_lvramp; } + + inline s32 rvramp() { return m_rvramp; } + + inline s16 ecount() { return m_ecount; } + + inline filter_ramp_t &k2ramp() { return m_k2ramp; } + + inline filter_ramp_t &k1ramp() { return m_k1ramp; } + + output_t &ch() { return m_ch; } + + // for debug/preview only + inline void set_mute(bool mute) { m_mute = mute; } + + inline s32 left_out() { return m_mute ? 0 : m_ch.left(); } + + inline s32 right_out() { return m_mute ? 0 : m_ch.right(); } + + private: + // accessors, getters, setters + s16 decompress(u8 sample); + s32 volume_calc(u16 volume, s32 in); + + // registers + es5506_core &m_host; + // Volume register: 4 bit exponent, 8 bit mantissa + // 4 LSBs are used for fine control of ramp increment for hardware envelope + s32 m_lvol = 0; // Left volume + s32 m_rvol = 0; // Right volume + // Envelope + s32 m_lvramp = 0; // Left volume ramp + s32 m_rvramp = 0; // Righr volume ramp + s16 m_ecount = 0; // Envelope counter + filter_ramp_t m_k2ramp; // Filter coefficient 2 Ramp + filter_ramp_t m_k1ramp; // Filter coefficient 1 Ramp + u8 m_filtcount = 0; // Internal counter for slow mode + output_t m_ch; // channel output + bool m_mute = false; // mute flag (for debug purpose) + }; + + // 5 bit mode + class mode_t : public vgsound_emu_core + { + public: + mode_t() + : vgsound_emu_core("es5506_mode") + , m_lrclk_en(1) + , m_wclk_en(1) + , m_bclk_en(1) + , m_master(0) + , m_dual(0) + { + } + + // internal states + void reset() + { + m_lrclk_en = 1; + m_wclk_en = 1; + m_bclk_en = 1; + m_master = 0; + m_dual = 0; + } + + // accessors + void write(u8 data) + { + m_lrclk_en = (data >> 0) & 1; + m_wclk_en = (data >> 1) & 1; + m_bclk_en = (data >> 2) & 1; + m_master = (data >> 3) & 1; + m_dual = (data >> 4) & 1; + } + + // getters + bool lrclk_en() { return m_lrclk_en; } + + bool wclk_en() { return m_wclk_en; } + + bool bclk_en() { return m_bclk_en; } + + bool master() { return m_master; } + + bool dual() { return m_dual; } + + private: + u8 m_lrclk_en : 1; // Set LRCLK to output + u8 m_wclk_en : 1; // Set WCLK to output + u8 m_bclk_en : 1; // Set BCLK to output + u8 m_master : 1; // Set memory mode to master + u8 m_dual : 1; // Set dual chip config + }; + + public: + // constructor + es5506_core(es550x_intf &intf) + : es550x_shared_core("es5506", 32, intf) + , m_voice{*this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this, + *this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this, + *this, *this, *this, *this, *this, *this, *this, *this, *this, *this} + , m_read_latch(0) + , m_write_latch(0) + , m_w_st(0) + , m_w_end(0) + , m_lr_end(0) + , m_mode(mode_t()) + , m_w_st_curr(0) + , m_w_end_curr(0) + , m_bclk(clock_pulse_t(4, 0)) + , m_lrclk(clock_pulse_t(32, 1)) + , m_wclk(0) + , m_wclk_lr(false) + , m_output_bit(0) + , m_ch{output_t()} + , m_output{output_t()} + , m_output_temp{output_t()} + , m_output_latch{output_t()} + { + } + + // host interface + u8 host_r(u8 address); + void host_w(u8 address, u8 data); + + // internal state + virtual void reset() override; + virtual void tick() override; + + // less cycle accurate, but also less cpu heavy update routine + void tick_perf(); + + // clock outputs + inline bool bclk() { return m_bclk.current_edge(); } + + inline bool bclk_rising_edge() { return m_bclk.rising_edge(); } + + inline bool bclk_falling_edge() { return m_bclk.falling_edge(); } + + // 6 stereo output channels + inline s32 lout(u8 ch) { return m_output[std::min(5, ch & 0x7)].left(); } + + inline s32 rout(u8 ch) { return m_output[std::min(5, ch & 0x7)].right(); } + + //----------------------------------------------------------------- + // + // for preview/debug purpose only, not for serious emulators + // + //----------------------------------------------------------------- + + // bypass chips host interface for debug purpose only + u8 read(u8 address, bool cpu_access = false); + void write(u8 address, u8 data); + + u32 regs_r(u8 page, u8 address, bool cpu_access = false); + void regs_w(u8 page, u8 address, u32 data); + + u8 regs8_r(u8 page, u8 address) + { + u8 prev = m_page; + m_page = page; + u8 ret = read(address, false); + m_page = prev; + return ret; + } + + inline void set_mute(u8 ch, bool mute) { m_voice[ch & 0x1f].set_mute(mute); } + + // per-voice outputs + inline s32 voice_lout(u8 voice) { return (voice < 32) ? m_voice[voice].left_out() : 0; } + + inline s32 voice_rout(u8 voice) { return (voice < 32) ? m_voice[voice].right_out() : 0; } + + protected: + virtual inline u8 max_voices() override { return 32; } + + virtual void voice_tick() override; + + private: + std::array m_voice; // 32 voices + + // Host interfaces + u32 m_read_latch = 0; // 32 bit register latch for host read + u32 m_write_latch = 0; // 32 bit register latch for host write + + // Serial register + u8 m_w_st = 0; // Word clock start register + u8 m_w_end = 0; // Word clock end register + u8 m_lr_end = 0; // Left/Right clock end register + mode_t m_mode; // Global mode + + // Serial related stuffs + u8 m_w_st_curr = 0; // Word clock start, current status + u8 m_w_end_curr = 0; // Word clock end register + clock_pulse_t m_bclk; // BCLK clock (CLKIN / 4), freely running clock + clock_pulse_t m_lrclk; // LRCLK + s16 m_wclk = 0; // WCLK + bool m_wclk_lr = false; // WCLK, L/R output select + s8 m_output_bit = 0; // Bit position in output + std::array m_ch; // 6 stereo output channels + std::array m_output; // Serial outputs + std::array m_output_temp; // temporary signal for serial output + std::array m_output_latch; // output latch +}; + +#endif diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x.cpp new file mode 100644 index 000000000..7de7f0497 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x.cpp @@ -0,0 +1,34 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Ensoniq ES5504/ES5505/ES5506 emulation core +*/ + +#include "es550x.hpp" + +// Shared functions +void es550x_shared_core::reset() +{ + m_host_intf.reset(); + m_ha = 0; + m_hd = 0; + m_page = 0; + m_irqv.reset(); + m_active = max_voices() - 1; + m_voice_cycle = 0; + m_voice_fetch = 0; + m_voice_update = false; + m_voice_end = false; + m_clkin.reset(); + m_cas.reset(); + m_e.reset(); +} + +void es550x_shared_core::es550x_voice_t::reset() +{ + m_cr.reset(); + m_alu.reset(); + m_filter.reset(); +} diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x.hpp new file mode 100644 index 000000000..4457c2440 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x.hpp @@ -0,0 +1,610 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Ensoniq ES5504/ES5505/ES5506 emulation core +*/ + +#ifndef _VGSOUND_EMU_SRC_ES550X_HPP +#define _VGSOUND_EMU_SRC_ES550X_HPP + +#pragma once + +#include "../core/util.hpp" + +// ES5504/ES5505/ES5506 interface +class es550x_intf : public vgsound_emu_core +{ + public: + es550x_intf() + : vgsound_emu_core("es550x_intf") + { + } + + virtual void e_pin(bool state) {} // E output + + virtual void bclk(bool state) {} // BCLK output (serial specific) + + virtual void lrclk(bool state) {} // LRCLK output (serial specific) + + virtual void wclk(bool state) {} // WCLK output (serial specific) + + virtual void irqb(bool state) {} // irqb output + + virtual u16 adc_r() { return 0; } // ADC input + + virtual void adc_w(u16 data) {} // ADC output + + virtual s16 read_sample(u8 voice, u8 bank, u32 address) { return 0; } +}; + +// Shared functions for ES5504/ES5505/ES5506 +class es550x_shared_core : public vgsound_emu_core +{ + friend class es550x_intf; // es550x specific memory interface + + private: + const u8 m_max_voices = 32; + + protected: + // Interrupt bits + class es550x_irq_t : public vgsound_emu_core + { + public: + es550x_irq_t() + : vgsound_emu_core("es550x_irq") + , m_voice(0) + , m_irqb(1) + { + } + + void reset() + { + m_voice = 0; + m_irqb = 1; + } + + // setter + void set(u8 index) + { + m_irqb = 0; + m_voice = index & 0x1f; + } + + void clear() + { + m_irqb = 1; + m_voice = 0; + } + + // getter + inline bool irqb() { return m_irqb; } + + inline u8 voice() { return m_voice; } + + inline u8 get() { return (m_irqb << 7) | (m_voice & 0x1f); } + + u8 m_voice : 5; + u8 m_irqb : 1; + }; + + // Common voice class + class es550x_voice_t : public vgsound_emu_core + { + private: + // Common control bits + class es550x_control_t : public vgsound_emu_core + { + public: + es550x_control_t() + : vgsound_emu_core("es550x_voice_control") + , m_ca(0) + , m_adc(0) + , m_bs(0) + , m_cmpd(0) + { + } + + void reset() + { + m_ca = 0; + m_adc = 0; + m_bs = 0; + m_cmpd = 0; + } + + // setters + inline void set_ca(u8 ca) { m_ca = ca & 0xf; } + + inline void set_adc(bool adc) { m_adc = adc ? 1 : 0; } + + inline void set_bs(u8 bs) { m_bs = bs & 0x3; } + + inline void set_cmpd(bool cmpd) { m_cmpd = cmpd ? 1 : 0; } + + // getters + inline u8 ca() { return m_ca; } + + inline bool adc() { return m_adc; } + + inline u8 bs() { return m_bs; } + + inline bool cmpd() { return m_cmpd; } + + protected: + // Channel assign - + // 4 bit (16 channel or Bank) for ES5504 + // 2 bit (4 stereo channels) for ES5505 + // 3 bit (6 stereo channels) for ES5506 + u8 m_ca : 4; + + // ES5504 Specific + u8 m_adc : 1; // Start ADC + + // ES5505/ES5506 Specific + u8 m_bs : 2; // Bank bit (1 bit for ES5505, 2 bit for ES5506) + u8 m_cmpd : 1; // Use compressed sample format (ES5506) + }; + + // Accumulator + class es550x_alu_t : public vgsound_emu_core + { + public: + es550x_alu_t(u8 integer, u8 fraction, bool transwave) + : vgsound_emu_core("es550x_voice_alu") + , m_integer(integer) + , m_fraction(fraction) + , m_total_bits(integer + fraction) + , m_accum_mask( + u32(std::min(~0, u64(u64(1) << u64(integer + fraction)) - 1))) + , m_transwave(transwave) + , m_fc(0) + , m_start(0) + , m_end(0) + , m_accum(0) + , m_sample({0}) + { + } + + // configurations + const u8 m_integer; + const u8 m_fraction; + const u8 m_total_bits; + const u32 m_accum_mask; + const bool m_transwave; + + // internal states + void reset(); + bool tick(); + + void loop_exec(); + bool busy(); + s32 interpolation(); + u32 get_accum_integer(); + + void irq_exec(es550x_intf &intf, es550x_irq_t &irqv, u8 index); + + void irq_update(es550x_intf &intf, es550x_irq_t &irqv) + { + intf.irqb(irqv.irqb() ? false : true); + } + + // setters + inline void set_stop0(bool stop0) { m_cr.set_stop0(stop0); } + + inline void set_stop1(bool stop1) { m_cr.set_stop1(stop1); } + + inline void set_lpe(bool lpe) { m_cr.set_lpe(lpe); } + + inline void set_ble(bool ble) { m_cr.set_ble(ble); } + + inline void set_irqe(bool irqe) { m_cr.set_irqe(irqe); } + + inline void set_dir(bool dir) { m_cr.set_dir(dir); } + + inline void set_irq(bool irq) { m_cr.set_irq(irq); } + + inline void set_lei(bool lei) { m_cr.set_lei(lei); } + + inline void set_stop(u8 stop) { m_cr.set_stop(stop); } + + inline void set_loop(u8 loop) { m_cr.set_loop(loop); } + + inline void set_fc(u32 fc) { m_fc = fc; } + + inline void set_start(u32 start, u32 mask = ~0) + { + m_start = (m_start & ~mask) | (start & mask); + } + + inline void set_end(u32 end, u32 mask = ~0) + { + m_end = (m_end & ~mask) | (end & mask); + } + + inline void set_accum(u32 accum, u32 mask = ~0) + { + m_accum = (m_accum & ~mask) | (accum & mask); + } + + inline void set_sample(u8 slot, s32 sample) { m_sample[slot & 1] = sample; } + + // getters + inline bool stop0() { return m_cr.stop0(); } + + inline bool stop1() { return m_cr.stop1(); } + + inline bool lpe() { return m_cr.lpe(); } + + inline bool ble() { return m_cr.ble(); } + + inline bool irqe() { return m_cr.irqe(); } + + inline bool dir() { return m_cr.dir(); } + + inline bool irq() { return m_cr.irq(); } + + inline bool lei() { return m_cr.lei(); } + + inline u8 stop() { return m_cr.stop(); } + + inline u8 loop() { return m_cr.loop(); } + + inline u32 fc() { return m_fc; } + + inline u32 start() { return m_start; } + + inline u32 end() { return m_end; } + + inline u32 accum() { return m_accum; } + + inline s32 sample(u8 slot) { return m_sample[slot & 1]; } + + private: + class es550x_alu_cr_t : public vgsound_emu_core + { + public: + es550x_alu_cr_t() + : vgsound_emu_core("es550x_voice_alu_cr") + , m_stop0(0) + , m_stop1(0) + , m_lpe(0) + , m_ble(0) + , m_irqe(0) + , m_dir(0) + , m_irq(0) + , m_lei(0) + { + } + + void reset() + { + m_stop0 = 0; + m_stop1 = 0; + m_lpe = 0; + m_ble = 0; + m_irqe = 0; + m_dir = 0; + m_irq = 0; + m_lei = 0; + } + + // setters + inline void set_stop0(bool stop0) { m_stop0 = stop0 ? 1 : 0; } + + inline void set_stop1(bool stop1) { m_stop1 = stop1 ? 1 : 0; } + + inline void set_lpe(bool lpe) { m_lpe = lpe ? 1 : 0; } + + inline void set_ble(bool ble) { m_ble = ble ? 1 : 0; } + + inline void set_irqe(bool irqe) { m_irqe = irqe ? 1 : 0; } + + inline void set_dir(bool dir) { m_dir = dir ? 1 : 0; } + + inline void set_irq(bool irq) { m_irq = irq ? 1 : 0; } + + inline void set_lei(bool lei) { m_lei = lei ? 1 : 0; } + + inline void set_stop(u8 stop) + { + m_stop0 = (stop >> 0) & 1; + m_stop1 = (stop >> 1) & 1; + } + + inline void set_loop(u8 loop) + { + m_lpe = (loop >> 0) & 1; + m_ble = (loop >> 1) & 1; + } + + // getters + inline bool stop0() { return m_stop0; } + + inline bool stop1() { return m_stop1; } + + inline bool lpe() { return m_lpe; } + + inline bool ble() { return m_ble; } + + inline bool irqe() { return m_irqe; } + + inline bool dir() { return m_dir; } + + inline bool irq() { return m_irq; } + + inline bool lei() { return m_lei; } + + inline u8 stop() { return (m_stop0 << 0) | (m_stop1 << 1); } + + inline u8 loop() { return (m_lpe << 0) | (m_ble << 1); } + + private: + u8 m_stop0 : 1; // Stop with ALU + u8 m_stop1 : 1; // Stop with processor + u8 m_lpe : 1; // Loop enable + u8 m_ble : 1; // Bidirectional loop enable + u8 m_irqe : 1; // IRQ enable + u8 m_dir : 1; // Playback direction + u8 m_irq : 1; // IRQ bit + u8 m_lei : 1; // Loop end ignore (ES5506 specific) + }; + + es550x_alu_cr_t m_cr; + // Frequency - + // 6 integer, 9 fraction for ES5504/ES5505 + // 6 integer, 11 fraction for ES5506 + u32 m_fc = 0; + u32 m_start = 0; // Start register + u32 m_end = 0; // End register + + // Accumulator - + // 20 integer, 9 fraction for ES5504/ES5505 + // 21 integer, 11 fraction for ES5506 + u32 m_accum = 0; + // Samples + std::array m_sample = {0}; + }; + + // Filter + class es550x_filter_t : public vgsound_emu_core + { + public: + es550x_filter_t() + : vgsound_emu_core("es550x_voice_filter") + , m_lp(0) + , m_k2(0) + , m_k1(0) + { + for (std::array &elem : m_o) + { + std::fill(elem.begin(), elem.end(), 0); + } + } + + void reset(); + void tick(s32 in); + + // setters + inline void set_lp(u8 lp) { m_lp = lp & 3; } + + inline void set_k2(s32 k2) { m_k2 = k2; } + + inline void set_k1(s32 k1) { m_k1 = k1; } + + inline void set_o1_1(s32 o1_1) { m_o[1][0] = o1_1; } + + inline void set_o2_1(s32 o2_1) { m_o[2][0] = o2_1; } + + inline void set_o2_2(s32 o2_2) { m_o[2][1] = o2_2; } + + inline void set_o3_1(s32 o3_1) { m_o[3][0] = o3_1; } + + inline void set_o3_2(s32 o3_2) { m_o[3][1] = o3_2; } + + inline void set_o4_1(s32 o4_1) { m_o[4][0] = o4_1; } + + // getters + inline u8 lp() { return m_lp; } + + inline s32 k2() { return m_k2; } + + inline s32 k1() { return m_k1; } + + inline s32 o1_1() { return m_o[1][0]; } + + inline s32 o2_1() { return m_o[2][0]; } + + inline s32 o2_2() { return m_o[2][1]; } + + inline s32 o3_1() { return m_o[3][0]; } + + inline s32 o3_2() { return m_o[3][1]; } + + inline s32 o4_1() { return m_o[4][0]; } + + private: + void lp_exec(s32 coeff, s32 in, s32 out); + void hp_exec(s32 coeff, s32 in, s32 out); + + // Registers + u8 m_lp = 0; // Filter mode + // Filter coefficient registers + // 12 bit for filter calculation, 4 + // LSBs are used for fine control of ramp increment for + // hardware envelope (ES5506) + s32 m_k2 = 0; // Filter coefficient 2 + s32 m_k1 = 0; // Filter coefficient 1 + + // Filter storage registers + std::array, 5> m_o; + }; + + public: + es550x_voice_t(std::string tag, u8 integer, u8 fraction, bool transwave) + : vgsound_emu_core(tag) + , m_cr(es550x_control_t()) + , m_alu(integer, fraction, transwave) + , m_filter(es550x_filter_t()) + { + } + + // internal state + virtual void reset(); + virtual void fetch(u8 voice, u8 cycle) = 0; + virtual void tick(u8 voice) = 0; + + void irq_update(es550x_intf &intf, es550x_irq_t &irqv) + { + m_alu.irq_update(intf, irqv); + } + + // Getters + es550x_control_t &cr() { return m_cr; } + + es550x_alu_t &alu() { return m_alu; } + + es550x_filter_t &filter() { return m_filter; } + + protected: + es550x_control_t m_cr; + es550x_alu_t m_alu; + es550x_filter_t m_filter; + }; + + // Host interfaces + class host_interface_flag_t : public vgsound_emu_core + { + public: + host_interface_flag_t() + : vgsound_emu_core("es550x_host_interface_flag") + , m_host_access(0) + , m_host_access_strobe(0) + , m_rw(0) + , m_rw_strobe(0) + { + } + + // Accessors + void reset() + { + m_host_access = 0; + m_host_access_strobe = 0; + m_rw = 0; + m_rw_strobe = 0; + } + + // Setters + void set_strobe(bool rw) + { + m_rw_strobe = rw ? 1 : 0; + m_host_access_strobe = 1; + } + + void clear_strobe() { m_host_access_strobe = 0; } + + void clear_host_access() { m_host_access = 0; } + + void update_strobe() + { + m_rw = m_rw_strobe; + m_host_access = m_host_access_strobe; + } + + // Getters + bool host_access() { return m_host_access; } + + bool rw() { return m_rw; } + + private: + u8 m_host_access : 1; // Host access trigger + u8 m_host_access_strobe : 1; // Host access strobe + u8 m_rw : 1; // R/W state + u8 m_rw_strobe : 1; // R/W strobe + }; + + public: + // internal state + virtual void reset(); + + virtual void tick() {} + + // clock outputs + inline bool _cas() { return m_cas.current_edge(); } + + inline bool _cas_rising_edge() { return m_cas.rising_edge(); } + + inline bool _cas_falling_edge() { return m_cas.falling_edge(); } + + inline bool e() { return m_e.current_edge(); } + + inline bool e_rising_edge() { return m_e.rising_edge(); } + + inline bool e_falling_edge() { return m_e.falling_edge(); } + + //----------------------------------------------------------------- + // + // for preview/debug purpose only, not for serious emulators + // + //----------------------------------------------------------------- + + // voice cycle + inline u8 voice_cycle() { return m_voice_cycle; } + + // voice update flag + inline bool voice_update() { return m_voice_update; } + + inline bool voice_end() { return m_voice_end; } + + protected: + // constructor + es550x_shared_core(std::string tag, const u8 voice, es550x_intf &intf) + : vgsound_emu_core(tag) + , m_max_voices(voice) + , m_intf(intf) + , m_host_intf(host_interface_flag_t()) + , m_ha(0) + , m_hd(0) + , m_page(0) + , m_irqv(es550x_irq_t()) + , m_active(m_max_voices - 1) + , m_voice_cycle(0) + , m_voice_fetch(0) + , m_voice_update(0) + , m_voice_end(0) + , m_clkin(clock_pulse_t(1, 0)) + , m_cas(clock_pulse_t(2, 1)) + , m_e(clock_pulse_t(4, 0)) + { + } + + // Constants + virtual inline u8 max_voices() { return m_max_voices; } + + // Shared registers, functions + virtual void voice_tick() {} // voice tick + + es550x_intf &m_intf; // es550x specific memory interface + host_interface_flag_t m_host_intf; // Host interface flag + u8 m_ha = 0; // Host address (4 bit) + u16 m_hd = 0; // Host data (16 bit for ES5504/ES5505, 8 bit for ES5506) + u8 m_page = 0; // Page + es550x_irq_t m_irqv; // Voice interrupt vector registers + + // Internal states + u8 m_active = 31; // Activated voices + // -1, ~25 for ES5504, + // ~32 for ES5505/ES5506 + u8 m_voice_cycle = 0; // Voice cycle + u8 m_voice_fetch = 0; // Voice fetch cycle + bool m_voice_update = false; // Voice update flag + bool m_voice_end = false; // End of one voice cycle flag + clock_pulse_t m_clkin; // CLKIN clock + clock_pulse_t m_cas; // /CAS clock (CLKIN / 4), falling edge of + // CLKIN trigger this clock + clock_pulse_t m_e; // E clock (CLKIN / 8), + // falling edge of CLKIN trigger this clock +}; + +#endif diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x_alu.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x_alu.cpp new file mode 100644 index 000000000..f058f0546 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x_alu.cpp @@ -0,0 +1,131 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Ensoniq ES5504/ES5505/ES5506 Shared Accumulator emulation core +*/ + +#include "es550x.hpp" + +// Accumulator functions +void es550x_shared_core::es550x_voice_t::es550x_alu_t::reset() +{ + m_cr.reset(); + m_fc = 0; + m_start = 0; + m_end = 0; + m_accum = 0; + m_sample[0] = m_sample[1] = 0; +} + +bool es550x_shared_core::es550x_voice_t::es550x_alu_t::busy() { return m_cr.stop() == 0; } + +bool es550x_shared_core::es550x_voice_t::es550x_alu_t::tick() +{ + if (m_cr.dir()) + { + m_accum -= m_fc; + } + else + { + m_accum += m_fc; + } + + m_accum &= m_accum_mask; + return ((!m_cr.lei()) && + (((m_cr.dir()) && (m_accum < m_start)) || ((!m_cr.dir()) && (m_accum > m_end)))) + ? true + : false; +} + +void es550x_shared_core::es550x_voice_t::es550x_alu_t::loop_exec() +{ + if (m_cr.irqe()) + { // Set IRQ + m_cr.set_irq(true); + } + + if (m_cr.dir()) // Reverse playback + { + if (m_cr.lpe()) // Loop enable + { + if (m_cr.ble()) // Bidirectional + { + m_cr.set_dir(false); + m_accum = m_start + (m_start - m_accum); + } + else + { // Normal + m_accum = m_end - (m_start - m_accum); + } + } + else if (m_cr.ble() && m_transwave) // m_transwave + { + m_cr.set_loop(0); + m_cr.set_lei(true); // Loop end ignore + m_accum = m_end - (m_start - m_accum); + } + else + { // Stop + m_cr.set_stop0(true); + } + } + else + { + if (m_cr.lpe()) // Loop enable + { + if (m_cr.ble()) // Bidirectional + { + m_cr.set_dir(true); + m_accum = m_end - (m_end - m_accum); + } + else + { // Normal + m_accum = (m_accum - m_end) + m_start; + } + } + else if (m_cr.ble() && m_transwave) // m_transwave + { + m_cr.set_loop(0); + m_cr.set_lei(true); // Loop end ignore + m_accum = (m_accum - m_end) + m_start; + } + else + { // Stop + m_cr.set_stop0(true); + } + } +} + +s32 es550x_shared_core::es550x_voice_t::es550x_alu_t::interpolation() +{ + // SF = S1 + ACCfr * (S2 - S1) + return m_sample[0] + ((bitfield(m_accum, std::max(0, m_fraction - 9), 9) * + (m_sample[1] - m_sample[0])) >> + 9); +} + +u32 es550x_shared_core::es550x_voice_t::es550x_alu_t::get_accum_integer() +{ + return bitfield(m_accum, m_fraction, m_integer); +} + +void es550x_shared_core::es550x_voice_t::es550x_alu_t::irq_exec(es550x_intf &intf, + es550x_irq_t &irqv, + u8 index) +{ + const bool prev = irqv.irqb(); + if (m_cr.irq()) + { + if (irqv.irqb()) + { + irqv.set(index); + m_cr.set_irq(false); + } + } + if (prev != irqv.irqb()) + { + irq_update(intf, irqv); + } +} diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x_filter.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x_filter.cpp new file mode 100644 index 000000000..360cab813 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x_filter.cpp @@ -0,0 +1,72 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Ensoniq ES5504/ES5505/ES5506 Shared Filter emulation core +*/ + +#include "es550x.hpp" + +// Filter functions +void es550x_shared_core::es550x_voice_t::es550x_filter_t::reset() +{ + m_lp = 0; + m_k2 = 0; + m_k1 = 0; + for (std::array &elem : m_o) + { + std::fill(elem.begin(), elem.end(), 0); + } +} + +void es550x_shared_core::es550x_voice_t::es550x_filter_t::tick(s32 in) +{ + // set sample input + m_o[0][0] = in; + + s32 coeff_k1 = s32(bitfield(m_k1, 4, 12)); // 12 MSB used + s32 coeff_k2 = s32(bitfield(m_k2, 4, 12)); // 12 MSB used + + // First and second stage: LP/K1, LP/K1 Fixed + lp_exec(coeff_k1, 0, 1); + lp_exec(coeff_k1, 1, 2); + switch (m_lp) + { + case 0: // LP3 = 0, LP4 = 0: HP/K2, HP/K2 + default: + hp_exec(coeff_k2, 2, 3); + hp_exec(coeff_k2, 3, 4); + break; + case 1: // LP3 = 0, LP4 = 1: HP/K2, LP/K1 + lp_exec(coeff_k1, 2, 3); + hp_exec(coeff_k2, 3, 4); + break; + case 2: // LP3 = 1, LP4 = 0: LP/K2, LP/K2 + lp_exec(coeff_k2, 2, 3); + lp_exec(coeff_k2, 3, 4); + break; + case 3: // LP3 = 1, LP4 = 1: LP/K2, LP/K1 + lp_exec(coeff_k1, 2, 3); + lp_exec(coeff_k2, 3, 4); + break; + } +} + +void es550x_shared_core::es550x_voice_t::es550x_filter_t::lp_exec(s32 coeff, s32 in, s32 out) +{ + // Store previous filter data + m_o[out][1] = m_o[out][0]; + + // Yn = K*(Xn - Yn-1) + Yn-1 + m_o[out][0] = ((coeff * (m_o[in][0] - m_o[out][0])) / 4096) + m_o[out][0]; +} + +void es550x_shared_core::es550x_voice_t::es550x_filter_t::hp_exec(s32 coeff, s32 in, s32 out) +{ + // Store previous filter data + m_o[out][1] = m_o[out][0]; + + // Yn = Xn - Xn-1 + K*Yn-1 + m_o[out][0] = m_o[in][0] - m_o[in][1] + ((coeff * m_o[out][0]) / 8192) + (m_o[out][0] / 2); +} diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/k005289/README.md b/extern/vgsound_emu-modified/vgsound_emu/src/k005289/README.md new file mode 100644 index 000000000..cc297b2f4 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/k005289/README.md @@ -0,0 +1,23 @@ +# Konami K005289 + +## Summary + +- 2 12 bit timer +- Address generator + +## Source code + +- k005289.hpp: Base header + - k005289.cpp: Source emulation core + +## Description + +This chip is used at infamous Konami Bubble System, for part of Wavetable sound generator. But seriously, It is just to 2 internal 12 bit timer and address generators, rather than sound generator. + +Everything except for internal counter and address are done by external logic, the chip is only has external address, frequency registers and its update pins. + +## Frequency calculation + +``` +Input clock / (4096 - Pitch input) +``` diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/k005289/k005289.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/k005289/k005289.cpp new file mode 100644 index 000000000..464ce5bc9 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/k005289/k005289.cpp @@ -0,0 +1,42 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Konami K005289 emulation core +*/ + +#include "k005289.hpp" + +void k005289_core::tick() +{ + for (timer_t &elem : m_timer) + { + elem.tick(); + } +} + +void k005289_core::reset() +{ + for (timer_t &elem : m_timer) + { + elem.reset(); + } +} + +void k005289_core::timer_t::tick() +{ + if (bitfield(++m_counter, 0, 12) == 0) + { + m_addr = bitfield(m_addr + 1, 0, 5); + m_counter = m_freq; + } +} + +void k005289_core::timer_t::reset() +{ + m_addr = 0; + m_pitch = 0; + m_freq = 0; + m_counter = 0; +} diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/k005289/k005289.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/k005289/k005289.hpp new file mode 100644 index 000000000..6fae1f2cb --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/k005289/k005289.hpp @@ -0,0 +1,83 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Konami K005289 emulation core +*/ + +#ifndef _VGSOUND_EMU_SRC_K005289_HPP +#define _VGSOUND_EMU_SRC_K005289_HPP + +#pragma once + +#include "../core/util.hpp" + +class k005289_core : public vgsound_emu_core +{ + private: + // k005289 timer classes + class timer_t : public vgsound_emu_core + { + public: + timer_t() + : vgsound_emu_core("k005289_timer") + , m_addr(0) + , m_pitch(0) + , m_freq(0) + , m_counter(0) + { + } + + // internal state + void reset(); + void tick(); + + // accessors + // Replace current frequency to lastest loaded pitch + inline void update() { m_freq = m_pitch; } + + // setters + // Load pitch data (address pin) + inline void load(u16 pitch) { m_pitch = pitch; } + + // getters + inline u8 addr() { return m_addr; } + + private: + // registers + u8 m_addr = 0; // external address pin + u16 m_pitch = 0; // pitch + u16 m_freq = 0; // current frequency + s16 m_counter = 0; // frequency counter + }; + + public: + // constructor + k005289_core() + : vgsound_emu_core("k005289") + , m_timer{timer_t()} + { + } + + // internal state + void reset(); + void tick(); + + // accessors + // TG1/2 pin + inline void update(int voice) { m_timer[voice & 1].update(); } + + // setters + // LD1/2 pin, A0...11 pin + inline void load(int voice, u16 addr) { m_timer[voice & 1].load(addr); } + + // getters + // 1QA...E/2QA...E pin + inline u8 addr(int voice) { return m_timer[voice & 1].addr(); } + + private: + std::array m_timer; +}; + +#endif diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/k007232/README.md b/extern/vgsound_emu-modified/vgsound_emu/src/k007232/README.md new file mode 100644 index 000000000..790d31077 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/k007232/README.md @@ -0,0 +1,75 @@ +# Konami K007232 + +## Summary + +- 2 voice PCM + - 7 bit unsigned, with end marker + - total accessible memory: 128 Kbyte per bank + - Per-voice bankswitchable via E clock +- External 8 bit I/O (usually for volume) + +## Source code + +- k007232.hpp: Base header + - k007232.cpp: Source emulation core + +## Description + +It's Konami's one of custom PCM sound chip, Used at their arcade hardware at mid-80s to early-90s. + +It has 2 channel of PCM, these are has its own output pins...just 7 LSB of currently fetched data. + +PCM Sample format is unique, 1 MSB is end marker and 7 LSB is actually output. (unsigned format) + +The chip itself is DACless, so Sound output and mixing control needs external logics and sound DAC. + +## Register layout + +``` + Address Bits R/W Description + 7654 3210 + + 0x0 xxxx xxxx W Channel 0 Pitch bit 0-7 + 0x1 --x- ---- W Channel 0 4 bit Frequency mode + ---x ---- W Channel 0 8 bit Frequency mode + ---- xxxx W Channel 0 Pitch bit 8-11 + + 0x2 xxxx xxxx W Channel 0 Start address bit 0-7 + 0x3 xxxx xxxx W Channel 0 Start address bit 8-15 + 0x4 ---- ---x W Channel 0 Start address bit 16 + + 0x5 R/W Channel 0 Key on trigger (R/W) + + 0x6 xxxx xxxx W Channel 1 Pitch bit 0-7 + 0x7 --x- ---- W Channel 1 4 bit Frequency mode + ---x ---- W Channel 1 8 bit Frequency mode + ---- xxxx W Channel 1 Pitch bit 8-11 + + 0x8 xxxx xxxx W Channel 1 Start address bit 0-7 + 0x9 xxxx xxxx W Channel 1 Start address bit 8-15 + 0xa ---- ---x W Channel 1 Start address bit 16 + + 0xb R/W Channel 1 Key on trigger (R/W) + + 0xc xxxx xxxx W External port write (w/SLEV pin, Usually for volume control) + + 0xd ---- --x- W Channel 1 Loop enable + ---- ---x W Channel 0 Loop enable +``` + +## Frequency calculation + +(Guesswork in 8/4 bit Frequency mode) + +``` + if 8 bit Frequency mode then + Frequency: Input clock / 4 * (256 - (Pitch bit 0 - 7)) + else if 4 bit Frequency mode then + Frequency: Input clock / 4 * (16 - (Pitch bit 8 - 11)) + else + Frequency: Input clock / 4 * (4096 - (Pitch bit 0 - 11)) +``` + +## Reference + + diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.cpp new file mode 100644 index 000000000..3798f4c81 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.cpp @@ -0,0 +1,169 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Konami K007232 core +*/ + +#include "k007232.hpp" + +void k007232_core::tick() +{ + for (int i = 0; i < 2; i++) + { + m_voice[i].tick(i); + } +} + +void k007232_core::voice_t::tick(u8 ne) +{ + if (m_busy) + { + const bool is4bit = bitfield(m_pitch, 13); // 4 bit frequency divider flag + const bool is8bit = bitfield(m_pitch, 12); // 8 bit frequency divider flag + + // update counter + if (is4bit) + { + m_counter = (m_counter & ~0x0ff) | (bitfield(bitfield(m_counter, 0, 8) + 1, 0, 8) << 0); + m_counter = (m_counter & ~0xf00) | (bitfield(bitfield(m_counter, 8, 4) + 1, 0, 4) << 8); + } + else + { + m_counter++; + } + + // handle counter carry + bool carry = + is8bit ? (bitfield(m_counter, 0, 8) == 0) + : (is4bit ? (bitfield(m_counter, 8, 4) == 0) : (bitfield(m_counter, 0, 12) == 0)); + if (carry) + { + m_counter = bitfield(m_pitch, 0, 12); + if (is4bit) // 4 bit frequency has different behavior for address + { + m_addr = (m_addr & ~0x0000f) | (bitfield(bitfield(m_addr, 0, 4) + 1, 0, 4) << 0); + m_addr = (m_addr & ~0x000f0) | (bitfield(bitfield(m_addr, 4, 4) + 1, 0, 4) << 4); + m_addr = (m_addr & ~0x00f00) | (bitfield(bitfield(m_addr, 8, 4) + 1, 0, 4) << 8); + m_addr = (m_addr & ~0x1f000) | (bitfield(bitfield(m_addr, 12, 5) + 1, 0, 5) << 12); + } + else + { + m_addr = bitfield(m_addr + 1, 0, 17); + } + } + + m_data = m_host.m_intf.read_sample(ne, bitfield(m_addr, 0, 17)); // fetch ROM + if (bitfield(m_data, 7)) // check end marker + { + if (m_loop) + { + m_addr = m_start; + } + else + { + m_busy = false; + } + } + + m_out = s8(m_data) - 0x40; // send to output (ASD/BSD) pin + } + else + { + m_out = 0; + } +} + +void k007232_core::write(u8 address, u8 data) +{ + address &= 0xf; // 4 bit for CPU write + + switch (address) + { + case 0x0: + case 0x1: + case 0x2: + case 0x3: + case 0x4: + case 0x5: // voice 0 + case 0x6: + case 0x7: + case 0x8: + case 0x9: + case 0xa: + case 0xb: // voice 1 + m_voice[(address / 6) & 1].write(address % 6, data); + break; + case 0xc: // external register with SLEV pin + m_intf.write_slev(data); + break; + case 0xd: // loop flag + m_voice[0].set_loop(bitfield(data, 0)); + m_voice[1].set_loop(bitfield(data, 1)); + break; + default: break; + } + + m_reg[address] = data; +} + +// write registers on each voices +void k007232_core::voice_t::write(u8 address, u8 data) +{ + switch (address) + { + case 0: // pitch LSB + m_pitch = (m_pitch & ~0x00ff) | data; + break; + case 1: // pitch MSB, divider + m_pitch = (m_pitch & ~0x3f00) | (u16(bitfield(data, 0, 6)) << 8); + break; + case 2: // start address bit 0-7 + m_start = (m_start & ~0x000ff) | data; + break; + case 3: // start address bit 8-15 + m_start = (m_start & ~0x0ff00) | (u32(data) << 8); + break; + case 4: // start address bit 16 + m_start = (m_start & ~0x10000) | (u32(bitfield(data, 16)) << 16); + break; + case 5: // keyon trigger + keyon(); + break; + } +} + +// key on trigger (write OR read 0x05/0x11 register) +void k007232_core::voice_t::keyon() +{ + m_busy = true; + m_counter = bitfield(m_pitch, 0, 12); + m_addr = m_start; +} + +// reset chip +void k007232_core::reset() +{ + for (auto &elem : m_voice) + { + elem.reset(); + } + + m_intf.write_slev(0); + + std::fill(m_reg.begin(), m_reg.end(), 0); +} + +// reset voice +void k007232_core::voice_t::reset() +{ + m_busy = false; + m_loop = false; + m_pitch = 0; + m_start = 0; + m_counter = 0; + m_addr = 0; + m_data = 0; + m_out = 0; +} diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.hpp new file mode 100644 index 000000000..d500f091a --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.hpp @@ -0,0 +1,115 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Konami K007232 core +*/ + +#ifndef _VGSOUND_EMU_SRC_K007232_HPP +#define _VGSOUND_EMU_SRC_K007232_HPP + +#pragma once + +#include "../core/util.hpp" + +class k007232_intf : public vgsound_emu_core +{ + public: + k007232_intf() + : vgsound_emu_core("k007232_intf") + { + } + + // NE pin is executing voice number, and used for per-voice sample bank. + virtual u8 read_sample(u8 ne, u32 address) { return 0; } + + // SLEV pin actived when 0x0c register accessed + virtual void write_slev(u8 out) {} +}; + +class k007232_core : public vgsound_emu_core +{ + friend class k007232_intf; // k007232 specific interface + + private: + class voice_t : public vgsound_emu_core + { + public: + // constructor + voice_t(k007232_core &host) + : vgsound_emu_core("k007232_voice") + , m_host(host) + , m_busy(false) + , m_loop(false) + , m_pitch(0) + , m_start(0) + , m_counter(0) + , m_addr(0) + , m_data(0) + , m_out(0) + { + } + + // internal state + void reset(); + void tick(u8 ne); + + // accessors + void write(u8 address, u8 data); + void keyon(); + + // setters + inline void set_loop(bool loop) { m_loop = loop; } + + // getters + inline s8 out() { return m_out; } + + private: + // registers + k007232_core &m_host; + bool m_busy = false; // busy status + bool m_loop = false; // loop flag + u16 m_pitch = 0; // pitch, frequency divider + u32 m_start = 0; // start position when keyon or loop start position at + // when reach end marker if loop enabled + u16 m_counter = 0; // frequency counter + u32 m_addr = 0; // current address + u8 m_data = 0; // current data + s8 m_out = 0; // current output (7 bit unsigned) + }; + + public: + // constructor + k007232_core(k007232_intf &intf) + : vgsound_emu_core("k007232") + , m_voice{*this, *this} + , m_intf(intf) + , m_reg{0} + { + } + + // host accessors + void keyon(u8 voice) { m_voice[voice & 1].keyon(); } + + void write(u8 address, u8 data); + + // internal state + void reset(); + void tick(); + + // output for each voices, ASD/BSD pin + inline s32 output(u8 voice) { return m_voice[voice & 1].out(); } + + // getters for debug, trackers, etc + inline u8 reg_r(u8 address) { return m_reg[address & 0xf]; } + + private: + std::array m_voice; + + k007232_intf &m_intf; // common memory interface + + std::array m_reg = {0}; // register pool +}; + +#endif diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/k053260/README.md b/extern/vgsound_emu-modified/vgsound_emu/src/k053260/README.md new file mode 100644 index 000000000..e34947f97 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/k053260/README.md @@ -0,0 +1,109 @@ +# Konami K053260 + +## Summary + +- 4 voice DPCM or PCM + - 8 bit signed PCM or 4 bit DPCM (unique type) + - total accessible memory: 2 MByte, 64 KByte per sample +- CPU to CPU Communication +- CPU can be accessible k053260 memory through voice register +- 2 Stereo sound input, 1 Stereo output (YM3012 DAC compatible) + +## Source code + +- k053260.hpp: Base header + - k053260.cpp: Source emulation core + +## Description + +It's one of Konami's custom PCM playback chip with CPU to CPU communication feature, and built in timer. + +It's architecture is successed from K007232, but it features various enhancements: + +it's expanded to 4 channels, Supports more memory space, 4 bit DPCM, Built in volume and stereo panning support, and Dual chip configurations. + +There's 2 stereo inputs and single stereo output, Both format is YM3012 compatible. + +## Register layout + +``` +Address Bits R/W Description + 7654 3210 + +00...03 Communication Registers + +00 xxxx xxxx R Answer from host CPU LSB +01 xxxx xxxx R Answer from host CPU MSB + +02 xxxx xxxx W Reply to host CPU LSB +03 xxxx xxxx W Reply to host CPU MSB + +08...0f Voice 0 Register + +08 xxxx xxxx W Pitch bit 0-7 +09 ---- xxxx W Pitch bit 8-11 + +0a xxxx xxxx W Source length bit 0-7 (byte wide) +0b xxxx xxxx W Source length bit 8-15 (byte wide) + +0c xxxx xxxx W Start address/ROM readback base bit 0-7 +0d xxxx xxxx W Start address/ROM readback base bit 8-15 +0e ---x xxxx W Start address/ROM readback base bit 16-20 + +0f -xxx xxxx W Volume + +10...17 Voice 1 Register +18...1f Voice 2 Register +20...27 Voice 3 Register + +28 ---- x--- W Voice 3 Key on/off trigger + ---- -x-- W Voice 2 Key on/off trigger + ---- --x- W Voice 1 Key on/off trigger + ---- ---x W Voice 0 Key on/off trigger + +29 ---- x--- R Voice 3 busy + ---- -x-- R Voice 2 busy + ---- --x- R Voice 1 busy + ---- ---x R Voice 0 busy + +2a x--- ---- W Voice 3 source format + 0--- ---- 8 bit signed PCM + 1--- ---- 4 bit ADPCM + -x-- ---- W Voice 2 source format + --x- ---- W Voice 1 source format + ---x ---- W Voice 0 source format + + ---- x--- W Voice 3 Loop enable + ---- -x-- W Voice 2 Loop enable + ---- --x- W Voice 1 Loop enable + ---- ---x W Voice 0 Loop enable + +2c --xx x--- W Voice 1 Pan angle in degrees*1 + --00 0--- Mute + --00 1--- 0 degrees + --01 0--- 24 degrees + --01 1--- 35 degrees + --10 0--- 45 degrees + --10 1--- 55 degrees + --11 0--- 66 degrees + --11 1--- 90 degrees + ---- -xxx W Voice 0 Pan angle in degrees*1 + +2d --xx x--- W Voice 3 Pan angle in degrees*1 + ---- -xxx W Voice 2 Pan angle in degrees*1 + +2e xxxx xxxx R ROM readback (use Voice 0 register) + +2f ---- x--- W AUX2 input enable + ---- -x-- W AUX1 input enable + ---- --x- W Sound enable + ---- ---x W ROM readbank enable + +*1 Actually fomula unknown, Use floating point type until explained that. +``` + +## Frequency calculation + +``` + Frequency: Input clock / (4096 - Pitch) +``` diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.cpp new file mode 100644 index 000000000..cccdbbf58 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.cpp @@ -0,0 +1,290 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Konami K053260 core +*/ + +#include "k053260.hpp" + +void k053260_core::tick() +{ + m_out[0] = m_out[1] = 0; + if (m_ctrl.sound_en()) + { + for (int i = 0; i < 4; i++) + { + m_voice[i].tick(); + m_out[0] += m_voice[i].out(0); + m_out[1] += m_voice[i].out(1); + } + } + // dac clock (YM3012 format) + u8 dac_clock = m_dac.clock(); + if (bitfield(++dac_clock, 0, 4) == 0) + { + m_intf.write_int(m_dac.state()); + u8 dac_state = m_dac.state(); + if (bitfield(++dac_state, 0) == 0) + { + m_ym3012.tick(bitfield(dac_state, 1), m_out[bitfield(dac_state, 1) ^ 1]); + } + + m_dac.set_state(bitfield(dac_state, 0, 2)); + } + m_dac.set_clock(bitfield(dac_clock, 0, 4)); +} + +void k053260_core::voice_t::tick() +{ + if (m_enable && m_busy) + { + bool update = false; + // update counter + if (bitfield(++m_counter, 0, 12) == 0) + { + if (m_bitpos < 8) + { + m_bitpos += 8; + m_addr = bitfield(m_addr + 1, 0, 21); + m_remain--; + } + if (m_adpcm) + { + m_bitpos -= 4; + update = true; + } + else + { + m_bitpos -= 8; + } + } + m_data = m_host.m_intf.read_sample(bitfield(m_addr, 0, 21)); // fetch ROM + if (update) + { + const u8 nibble = bitfield(m_data, m_bitpos & 4, 4); // get nibble from ROM + if (nibble) + { + m_adpcm_buf += bitfield(nibble, 3) ? s8(0x80 >> bitfield(nibble, 0, 3)) + : (1 << bitfield(nibble - 1, 0, 3)); + } + } + + if (m_remain < 0) // check end flag + { + if (m_loop) + { + m_addr = m_start; + m_adpcm_buf = 0; + } + else + { + m_busy = false; + } + } + // calculate output + s32 output = m_adpcm ? m_adpcm_buf : sign_ext(m_data, 8) * s32(m_volume); + // use math for now; actually fomula unknown + m_out[0] = (m_pan >= 0) ? s32(output * cos(f64(m_pan) * PI / 180)) : 0; + m_out[1] = (m_pan >= 0) ? s32(output * sin(f64(m_pan) * PI / 180)) : 0; + } + else + { + m_out[0] = m_out[1] = 0; + } +} + +u8 k053260_core::read(u8 address) +{ + address &= 0x3f; // 6 bit for CPU read + + switch (address) + { + case 0x0: + case 0x1: // Answer from host + return m_host2snd[address & 1]; + break; + case 0x29: // Voice playing status + return (m_voice[0].busy() ? 0x1 : 0x0) | (m_voice[1].busy() ? 0x2 : 0x0) | + (m_voice[2].busy() ? 0x4 : 0x0) | (m_voice[3].busy() ? 0x8 : 0x0); + case 0x2e: // ROM readback + { + if (!m_ctrl.rom_read()) + { + return 0xff; + } + + const u32 rom_addr = m_voice[0].start() + m_voice[0].length(); + m_voice[0].length_inc(); + return m_intf.read_sample(rom_addr); + } + } + return 0xff; +} + +void k053260_core::write(u8 address, u8 data) +{ + address &= 0x3f; // 6 bit for CPU write + + switch (address) + { + case 0x2: + case 0x3: // Reply to host + m_snd2host[address & 1] = data; + break; + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x0e: + case 0x0f: // voice 0 + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: // voice 1 + case 0x18: + case 0x19: + case 0x1a: + case 0x1b: + case 0x1c: + case 0x1d: + case 0x1e: + case 0x1f: // voice 2 + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: // voice 3 + m_voice[bitfield(address - 0x8, 3, 2)].write(bitfield(address, 0, 3), data); + break; + case 0x28: // keyon/off toggle + for (int i = 0; i < 4; i++) + { + if (bitfield(data, i) && (!m_voice[i].enable())) + { // rising edge (keyon) + m_voice[i].keyon(); + } + else if ((!bitfield(data, i)) && m_voice[i].enable()) + { // falling edge (keyoff) + m_voice[i].keyoff(); + } + } + break; + case 0x2a: // loop/adpcm flag + for (int i = 0; i < 4; i++) + { + m_voice[i].set_loop(bitfield(data, i)); + m_voice[i].set_adpcm(bitfield(data, i + 4)); + } + break; + case 0x2c: + m_voice[0].set_pan(bitfield(data, 0, 3)); + m_voice[1].set_pan(bitfield(data, 3, 3)); + break; + case 0x2d: + m_voice[2].set_pan(bitfield(data, 0, 3)); + m_voice[3].set_pan(bitfield(data, 3, 3)); + break; + case 0x2f: m_ctrl.write(data); break; + default: break; + } + + m_reg[address] = data; +} + +// write registers on each voices +void k053260_core::voice_t::write(u8 address, u8 data) +{ + switch (address) + { + case 0: // pitch LSB + m_pitch = (m_pitch & ~0x00ff) | data; + break; + case 1: // pitch MSB + m_pitch = (m_pitch & ~0x0f00) | (u16(bitfield(data, 0, 4)) << 8); + break; + case 2: // source length LSB + m_length = (m_length & ~0x000ff) | data; + break; + case 3: // source length MSB + m_length = (m_length & ~0x0ff00) | (u16(data) << 8); + break; + case 4: // start address bit 0-7 + m_start = (m_start & ~0x0000ff) | data; + break; + case 5: // start address bit 8-15 + m_start = (m_start & ~0x00ff00) | (u32(data) << 8); + break; + case 6: // start address bit 16-20 + m_start = (m_start & ~0x1f0000) | (u32(bitfield(data, 16, 5)) << 16); + break; + case 7: // volume + m_volume = bitfield(data, 0, 7); + break; + } +} + +// key on trigger +void k053260_core::voice_t::keyon() +{ + m_enable = m_busy = 1; + m_counter = bitfield(m_pitch, 0, 12); + m_addr = m_start; + m_remain = m_length; + m_bitpos = 4; + m_adpcm_buf = 0; + std::fill(m_out.begin(), m_out.end(), 0); +} + +// key off trigger +void k053260_core::voice_t::keyoff() { m_enable = m_busy = 0; } + +// reset chip +void k053260_core::reset() +{ + for (auto &elem : m_voice) + { + elem.reset(); + } + + m_intf.write_int(0); + + std::fill(m_host2snd.begin(), m_host2snd.end(), 0); + std::fill(m_snd2host.begin(), m_snd2host.end(), 0); + m_ctrl.reset(); + m_dac.reset(); + + std::fill(m_reg.begin(), m_reg.end(), 0); + std::fill(m_out.begin(), m_out.end(), 0); +} + +// reset voice +void k053260_core::voice_t::reset() +{ + m_enable = 0; + m_busy = 0; + m_loop = 0; + m_adpcm = 0; + m_pitch = 0; + m_start = 0; + m_length = 0; + m_volume = 0; + m_pan = -1; + m_counter = 0; + m_addr = 0; + m_remain = 0; + m_bitpos = 4; + m_data = 0; + m_adpcm_buf = 0; + m_out[0] = m_out[1] = 0; +} diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.hpp new file mode 100644 index 000000000..bca78a1f4 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.hpp @@ -0,0 +1,262 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Konami K053260 core +*/ + +#ifndef _VGSOUND_EMU_SRC_K053260_HPP +#define _VGSOUND_EMU_SRC_K053260_HPP + +#pragma once + +#include "../core/util.hpp" + +class k053260_intf : public vgsound_emu_core +{ + public: + k053260_intf() + : vgsound_emu_core("k053260_intf") + { + } + + virtual u8 read_sample(u32 address) { return 0; } // sample fetch + + virtual void write_int(u8 out) {} // timer interrupt +}; + +class k053260_core : public vgsound_emu_core +{ + friend class k053260_intf; // k053260 specific interface + + private: + const int pan_dir[8] = {-1, 0, 24, 35, 45, 55, 66, 90}; // pan direction + + class voice_t : public vgsound_emu_core + { + public: + // constructor + voice_t(k053260_core &host) + : vgsound_emu_core("k053260_voice") + , m_host(host) + , m_enable(0) + , m_busy(0) + , m_loop(0) + , m_adpcm(0) + , m_pitch(0) + , m_start(0) + , m_length(0) + , m_volume(0) + , m_pan(-1) + , m_counter(0) + , m_addr(0) + , m_remain(0) + , m_bitpos(4) + , m_data(0) + , m_adpcm_buf(0) + , m_out{0} + { + } + + // internal state + void reset(); + void tick(); + + // accessors + void write(u8 address, u8 data); + void keyon(); + void keyoff(); + + // setters + inline void set_enable(bool enable) { m_enable = enable ? 1 : 0; } + + inline void set_busy(bool busy) { m_busy = busy ? 1 : 0; } + + inline void set_loop(bool loop) { m_loop = loop ? 1 : 0; } + + inline void set_adpcm(bool adpcm) { m_adpcm = adpcm ? 1 : 0; } + + inline void length_inc() { m_length = (m_length + 1) & 0xffff; } + + inline void set_pan(u8 pan) { m_pan = m_host.pan_dir[pan & 7]; } + + // getters + inline bool enable() { return m_enable; } + + inline bool busy() { return m_busy; } + + inline u32 start() { return m_start; } + + inline u16 length() { return m_length; } + + inline s32 out(u8 ch) { return m_out[ch & 1]; } + + private: + // registers + k053260_core &m_host; + u16 m_enable : 1; // enable flag + u16 m_busy : 1; // busy status + u16 m_loop : 1; // loop flag + u16 m_adpcm : 1; // ADPCM flag + u16 m_pitch : 12; // pitch + u32 m_start = 0; // start position + u16 m_length = 0; // source length + u8 m_volume = 0; // master volume + int m_pan = -1; // master pan + u16 m_counter = 0; // frequency counter + u32 m_addr = 0; // current address + s32 m_remain = 0; // remain for end sample + u8 m_bitpos = 4; // bit position for ADPCM decoding + u8 m_data = 0; // current data + s8 m_adpcm_buf = 0; // ADPCM buffer + std::array m_out = {0}; // current output + }; + + class ctrl_t + { + public: + ctrl_t() + : m_rom_read(0) + , m_sound_en(0) + , m_input_en(0) + { + } + + void reset() + { + m_rom_read = 0; + m_sound_en = 0; + m_input_en = 0; + } + + void write(u8 data) + { + m_rom_read = (data >> 0) & 1; + m_sound_en = (data >> 1) & 1; + m_input_en = (data >> 2) & 3; + } + + // getters + inline bool rom_read() { return m_rom_read; } + + inline bool sound_en() { return m_sound_en; } + + inline u8 input_en() { return m_input_en; } + + private: + u8 m_rom_read : 1; // ROM readback + u8 m_sound_en : 1; // Sound enable + u8 m_input_en : 2; // Input enable + }; + + class ym3012_t + { + public: + ym3012_t() + : m_in{0} + , m_out{0} + { + } + + void reset() + { + std::fill(m_in.begin(), m_in.end(), 0); + std::fill(m_out.begin(), m_out.end(), 0); + } + + void tick(u8 ch, s32 in) + { + m_out[(ch & 1)] = m_in[(ch & 1)]; + m_in[(ch & 1) ^ 1] = in; + } + + private: + std::array m_in = {0}; + std::array m_out = {0}; + }; + + class dac_t + { + public: + dac_t() + : m_clock(0) + , m_state(0) + { + } + + void reset() + { + m_clock = 0; + m_state = 0; + } + + inline void set_clock(u8 clock) { m_clock = clock; } + + inline void set_state(u8 state) { m_state = state; } + + inline u8 clock() { return m_clock; } + + inline u8 state() { return m_state; } + + private: + u8 m_clock : 4; // DAC clock (16 clock) + u8 m_state : 2; // DAC state (4 state - SAM1, SAM2) + }; + + public: + // constructor + k053260_core(k053260_intf &intf) + : vgsound_emu_core("k053260") + , m_voice{*this, *this, *this, *this} + , m_intf(intf) + , m_host2snd{0} + , m_snd2host{0} + , m_ctrl(ctrl_t()) + , m_ym3012(ym3012_t()) + , m_dac(dac_t()) + , m_reg{0} + , m_out{0} + { + } + + // communications + inline u8 snd2host_r(u8 address) { return m_snd2host[address & 1]; } + + inline void host2snd_w(u8 address, u8 data) { m_host2snd[address & 1] = data; } + + // sound accessors + u8 read(u8 address); + void write(u8 address, u8 data); + + // internal state + void reset(); + void tick(); + + // getters for debug, trackers, etc + inline s32 output(u8 ch) { return m_out[ch & 1]; } // output for each channels + + inline u8 reg_r(u8 address) { return m_reg[address & 0x3f]; } + + inline s32 voice_out(u8 voice, u8 ch) + { + return (voice < 4) ? m_voice[voice].out(ch & 1) : 0; + } + + private: + std::array m_voice; + k053260_intf &m_intf; // common memory interface + + std::array m_host2snd = {0}; + std::array m_snd2host = {0}; + + ctrl_t m_ctrl; // chip control + + ym3012_t m_ym3012; // YM3012 output + dac_t m_dac; // YM3012 interface + + std::array m_reg = {0}; // register pool + std::array m_out = {0}; // stereo output +}; + +#endif diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/msm6295/README.md b/extern/vgsound_emu-modified/vgsound_emu/src/msm6295/README.md new file mode 100644 index 000000000..626b69187 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/msm6295/README.md @@ -0,0 +1,67 @@ +# OKI MSM6295 + +## Summary + +- 4 voice Dialogic ADPCM + - total accessible memory: 256 KByte +- Clock divider via SS pin + +## Source code + +- msm6295.hpp: Base header + - msm6295.cpp: Source emulation core + +### Dependencies + +- vox.hpp: Dialogic ADPCM decoder header + - vox.cpp: Dialogic ADPCM decoder source + +## Description + +It is 4 channel ADPCM playback chip from OKI semiconductor. It was becomes de facto standard for ADPCM playback in arcade machine, due to cost performance. + +The chip itself is pretty barebone: there is no "register" in chip. It can't control volume and pitch in currently playing channels, only stopable them. And volume is must be determined at playback start command. + +## Command format + + Playback command (2 byte): + +``` +Byte Bit Description + 76543210 +0 1xxxxxxx Phrase select (Header stored in ROM) +1 x000---- Play channel 4 + 0x00---- Play channel 3 + 00x0---- Play channel 2 + 000x---- Play channel 1 + ----xxxx Volume + ----0000 0.0dB + ----0001 -3.2dB + ----0010 -6.0dB + ----0011 -9.2dB + ----0100 -12.0dB + ----0101 -14.5dB + ----0110 -18.0dB + ----0111 -20.5dB + ----1000 -24.0dB +``` + + Suspend command (1 byte): + +``` +Byte Bit Description + 76543210 +0 0x------ Suspend channel 4 + 0-x----- Suspend channel 3 + 0--x---- Suspend channel 2 + 0---x--- Suspend channel 1 +``` + +## Frequency calculation + +``` + if (SS) then + Frequency = Input clock / 165 + else then + Frequency = Input clock / 132 +``` diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/msm6295/msm6295.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/msm6295/msm6295.cpp new file mode 100644 index 000000000..e09c45d90 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/msm6295/msm6295.cpp @@ -0,0 +1,179 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + OKI MSM6295 emulation core +*/ + +#include "msm6295.hpp" + +void msm6295_core::tick() +{ + if (m_counter < 4) + { + m_voice[m_counter].tick(); + m_out_temp += m_voice[m_counter].out(); + } + if ((++m_counter) >= (m_ss ? 5 : 4)) + { + // command handler + if (m_command_pending) + { + if (bitfield(m_command, 7)) // play voice + { + if ((++m_clock) >= 15) + { + if (bitfield(m_next_command, 4, 4) != 0) + { + for (int i = 0; i < 4; i++) + { + if (bitfield(m_next_command, 4 + i)) + { + if (!m_voice[i].busy()) + { + m_voice[i].set_command(m_command); + m_voice[i].set_volume(bitfield(m_next_command, 0, 4)); + } + // voices aren't be playable simultaneously at once + break; + } + } + } + m_command = 0; + m_command_pending = false; + m_clock = 0; + } + } + else if (bitfield(m_next_command, 7)) // select phrase + { + if ((++m_clock) >= 15) + { + m_command = m_next_command; + m_command_pending = false; + m_clock = 0; + } + } + else + { + if (bitfield(m_next_command, 3, 4) != 0) // suspend voices + { + for (int i = 0; i < 4; i++) + { + if (bitfield(m_next_command, 3 + i)) + { + if (m_voice[i].busy()) + { + m_voice[i].set_command(m_next_command); + } + } + } + m_next_command &= ~0x78; + } + m_command_pending = false; + } + } + m_out = m_out_temp; + m_out_temp = 0; + m_counter = 0; + } +} + +void msm6295_core::reset() +{ + for (auto &elem : m_voice) + { + elem.reset(); + } + + m_command = 0; + m_next_command = 0; + m_command_pending = false; + m_clock = 0; + m_counter = 0; + m_out = 0; + m_out_temp = 0; +} + +void msm6295_core::voice_t::tick() +{ + if (!m_busy) + { + if (bitfield(m_command, 7)) + { + // get phrase header (stored in data memory) + const u32 phrase = bitfield(m_command, 0, 7) << 3; + // Start address + m_addr = (bitfield(m_host.m_intf.read_byte(phrase | 0), 0, 2) << 16) | + (m_host.m_intf.read_byte(phrase | 1) << 8) | + (m_host.m_intf.read_byte(phrase | 2) << 0); + // End address + m_end = (bitfield(m_host.m_intf.read_byte(phrase | 3), 0, 2) << 16) | + (m_host.m_intf.read_byte(phrase | 4) << 8) | + (m_host.m_intf.read_byte(phrase | 5) << 0); + m_nibble = 4; // MSB first, LSB second + m_command = 0; + m_busy = true; + vox_decoder_t::reset(); + } + m_out = 0; + } + else + { + // playback + if ((++m_clock) >= 33) + { + bool is_end = (m_command != 0); // suspend + decode(bitfield(m_host.m_intf.read_byte(m_addr), m_nibble, 4)); + if (m_nibble <= 0) + { + m_nibble = 4; + if (++m_addr > m_end) + { + is_end = true; + } + } + else + { + m_nibble -= 4; + } + if (is_end) + { + m_command = 0; + m_busy = false; + } + m_out = (step() * m_volume) >> 7; // scale out to 12 bit output + m_clock = 0; + } + } +} + +void msm6295_core::voice_t::reset() +{ + vox_decoder_t::reset(); + m_clock = 0; + m_busy = false; + m_command = 0; + m_addr = 0; + m_nibble = 0; + m_end = 0; + m_volume = 0; + m_out = 0; + m_mute = false; +} + +// accessors +u8 msm6295_core::busy_r() +{ + return (m_voice[0].busy() ? 0x01 : 0x00) | (m_voice[1].busy() ? 0x02 : 0x00) | + (m_voice[2].busy() ? 0x04 : 0x00) | (m_voice[3].busy() ? 0x08 : 0x00); +} + +void msm6295_core::command_w(u8 data) +{ + if (!m_command_pending) + { + m_next_command = data; + m_command_pending = true; + } +} diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/msm6295/msm6295.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/msm6295/msm6295.hpp new file mode 100644 index 000000000..04326ae6d --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/msm6295/msm6295.hpp @@ -0,0 +1,142 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + OKI MSM6295 emulation core +*/ + +#ifndef _VGSOUND_EMU_SRC_MSM6295_HPP +#define _VGSOUND_EMU_SRC_MSM6295_HPP + +#pragma once + +#include "../core/util.hpp" +#include "../core/vox/vox.hpp" + +class msm6295_core : public vox_core +{ + friend class vgsound_emu_mem_intf; // common memory interface + + private: + // Internal volume table, 9 step + const s32 m_volume_table[9] = {32 /* 0.0dB */, + 22 /* -3.2dB */, + 16 /* -6.0dB */, + 11 /* -9.2dB */, + 8 /* -12.0dB */, + 6 /* -14.5dB */, + 4 /* -18.0dB */, + 3 /* -20.5dB */, + 2 /* -24.0dB */}; // scale out to 5 bit for optimization + + // msm6295 voice classes + class voice_t : vox_decoder_t + { + public: + // constructor + voice_t(msm6295_core &host) + : vox_decoder_t(host, false) + , m_host(host) + , m_clock(0) + , m_busy(false) + , m_command(0) + , m_addr(0) + , m_nibble(0) + , m_end(0) + , m_volume(0) + , m_out(0) + , m_mute(false) + { + } + + // internal state + virtual void reset() override; + void tick(); + + // Setters + inline void set_command(u8 command) { m_command = command; } + + inline void set_volume(s32 volume) + { + m_volume = (volume < 9) ? m_host.m_volume_table[volume] : 0; + } + + inline void set_mute(bool mute) { m_mute = mute; } + + // Getters + inline bool busy() { return m_busy; } + + inline s32 out() { return m_mute ? 0 : m_out; } + + private: + // accessors, getters, setters + // registers + msm6295_core &m_host; // host core + u16 m_clock = 0; // clock counter + bool m_busy = false; // busy status + u8 m_command = 0; // current command + u32 m_addr = 0; // current address + s8 m_nibble = 0; // current nibble + u32 m_end = 0; // end address + s32 m_volume = 0; // volume + s32 m_out = 0; // output + // for preview only + bool m_mute = false; // mute flag + }; + + public: + // constructor + msm6295_core(vgsound_emu_mem_intf &intf) + : vox_core("msm6295") + , m_voice{*this, *this, *this, *this} + , m_intf(intf) + , m_ss(false) + , m_command(0) + , m_next_command(0) + , m_command_pending(false) + , m_clock(0) + , m_counter(0) + , m_out(0) + , m_out_temp(0) + { + } + + // accessors, getters, setters + u8 busy_r(); + void command_w(u8 data); + + inline void ss_w(bool ss) { m_ss = ss; } // SS pin + + // internal state + void reset(); + void tick(); + + inline s32 out() { return m_out; } // built in 12 bit DAC + + // for preview + inline void voice_mute(u8 voice, bool mute) + { + if (voice < 4) + { + m_voice[voice].set_mute(mute); + } + } + + inline s32 voice_out(u8 voice) { return (voice < 4) ? m_voice[voice].out() : 0; } + + private: + std::array m_voice; + vgsound_emu_mem_intf &m_intf; // common memory interface + + bool m_ss = false; // SS pin controls divider, input clock / 33 * (SS ? 5 : 4) + u8 m_command = 0; // Command byte + u8 m_next_command = 0; // Next command + bool m_command_pending = false; // command pending flag + u16 m_clock = 0; // clock counter + u16 m_counter = 0; // another clock counter + s32 m_out = 0; // 12 bit output + s32 m_out_temp = 0; // temporary buffer of above +}; + +#endif diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/n163/README.md b/extern/vgsound_emu-modified/vgsound_emu/src/n163/README.md new file mode 100644 index 000000000..984356be7 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/n163/README.md @@ -0,0 +1,88 @@ +# Namco 163 + +## Summary + +- 1 to 8 voice wavetable + - 4 bit unsigned + - each waveforms are can be placed to anywhere in internal RAM, and its size is can be variable. +- activated voice count can be changed any time, multiplexed output + +## Source code + +- n163.hpp: Base header + - n163.cpp: Source emulation core + +## Description + +This chip is one of NES mapper with sound expansion, This one is by Namco. + +It has 1 to 8 wavetable channels, All channel registers and waveforms are stored to internal RAM. 4 bit Waveforms are freely allocatable, and its length is variables; its can be stores many short waveforms or few long waveforms in RAM. + +But waveforms are needs to squash, reallocate to avoid conflict with channel register area, each channel register size is 8 bytes per channels. + +Sound output is time division multiplexed, it's can be captured only single channels output at once. in reason, More activated channels are less sound quality. + +## Sound register layout + +``` +Address Bit Description + 7654 3210 + +78-7f Channel 0 +78 xxxx xxxx Channel 0 Pitch input bit 0-7 +79 xxxx xxxx Channel 0 Accumulator bit 0-7 +7a xxxx xxxx Channel 0 Pitch input bit 8-15 +7b xxxx xxxx Channel 0 Accumulator bit 8-15 +7c xxxx xx-- Channel 0 Waveform length, 256 - (x * 4) + ---- --xx Channel 0 Pitch input bit 16-17 +7d xxxx xxxx Channel 0 Accumulator bit 16-23 +7e xxxx xxxx Channel 0 Waveform base offset + xxxx xxx- RAM byte (0 to 127) + ---- ---x RAM nibble + ---- ---0 Low nibble + ---- ---1 High nibble +7f ---- xxxx Channel 0 Volume + +7f Number of active channels +7f -xxx ---- Number of active channels + -000 ---- Channel 0 activated + -001 ---- Channel 1 activated + -010 ---- Channel 2 activated + ... + -110 ---- Channel 6 activated + -111 ---- Channel 7 activated + +70-77 Channel 1 (Optional if activated) +68-6f Channel 2 (Optional if activated) +... +48-4f Channel 6 (Optional if activated) +40-47 Channel 7 (Optional if activated) +``` + +Rest of RAM area are for 4 bit Waveform and/or scratchpad. + +## Waveform format + +Each waveform byte has 2 nibbles packed, fetches LSB first, MSB next. + +``` + ---- xxxx 4 bit waveform, LSB + xxxx ---- Same as above, MSB +``` + +Waveform address: Waveform base offset + Bit 16 to 23 of Accumulator, 1 LSB of result is nibble select, 7 MSB of result is Byte address in RAM. + +## Frequency calculation + +``` + Frequency: Pitch input * ((Input clock * 15 * Number of activated voices) / 65536) +``` + +## Technical notice + +This core only outputs raw output from chip (or accumulated output, see below); any kind of off-chip stuff needs to implemented outside core. + +There's to way for reduce N163 noises: reduce channel limit and demultiplex: + +- Channel limit is runtime changeable and it makes some usable effects. +- Demultiplex is used for "non-ear destroyable" emulators, but less hardware accurate. (when LPF and RF filter is not considered) This core is support both, You can choose output behavior diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/n163/n163.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/n163/n163.cpp new file mode 100644 index 000000000..783bf2df3 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/n163/n163.cpp @@ -0,0 +1,120 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Namco 163 Sound emulation core +*/ + +#include "n163.hpp" + +void n163_core::tick() +{ + if (m_multiplex) + { + m_out = 0; + } + // 0xe000-0xe7ff Disable sound bits (bit 6, bit 0 to 5 are CPU ROM Bank + // 0x8000-0x9fff select.) + if (m_disable) + { + if (!m_multiplex) + { + m_out = 0; + } + return; + } + + // tick per each clock + const u32 freq = m_ram[m_voice_cycle + 0] | (u32(m_ram[m_voice_cycle + 2]) << 8) | + (bitfield(m_ram[m_voice_cycle + 4], 0, 2) << 16); // 18 bit frequency + + u32 accum = m_ram[m_voice_cycle + 1] | (u32(m_ram[m_voice_cycle + 3]) << 8) | + (u32(m_ram[m_voice_cycle + 5]) << 16); // 24 bit accumulator + + const u16 length = 256 - (m_ram[m_voice_cycle + 4] & 0xfc); + const u8 addr = m_ram[m_voice_cycle + 6] + bitfield(accum, 16, 8); + const s16 wave = (bitfield(m_ram[bitfield(addr, 1, 7)], bitfield(addr, 0) << 2, 4) - 8); + const s16 volume = bitfield(m_ram[m_voice_cycle + 7], 0, 4); + + // get per-voice output + const s16 voice_out = (wave * volume); + m_voice_out[(m_voice_cycle >> 3) & 7] = voice_out; + + // accumulate address + accum = bitfield(accum + freq, 0, 24); + if (bitfield(accum, 16, 8) >= length) + { + accum = bitfield(accum, 0, 18); + } + + // writeback to register + m_ram[m_voice_cycle + 1] = bitfield(accum, 0, 8); + m_ram[m_voice_cycle + 3] = bitfield(accum, 8, 8); + m_ram[m_voice_cycle + 5] = bitfield(accum, 16, 8); + + // update voice cycle + bool flush = m_multiplex ? true : false; + m_voice_cycle -= 0x8; + if (m_voice_cycle < (0x78 - (bitfield(m_ram[0x7f], 4, 3) << 3))) + { + if (!m_multiplex) + { + flush = true; + } + m_voice_cycle = 0x78; + } + + // output 4 bit waveform and volume, multiplexed + m_acc += voice_out; + if (flush) + { + m_out = m_acc / (m_multiplex ? 1 : (bitfield(m_ram[0x7f], 4, 3) + 1)); + m_acc = 0; + } +} + +void n163_core::reset() +{ + // reset this chip + m_disable = false; + m_multiplex = true; + std::fill(m_ram.begin(), m_ram.end(), 0); + m_voice_cycle = 0x78; + m_addr_latch.reset(); + m_out = 0; + m_acc = 0; +} + +// accessor +void n163_core::addr_w(u8 data) +{ + // 0xf800-0xffff Sound address, increment + m_addr_latch.write(data); +} + +void n163_core::data_w(u8 data, bool cpu_access) +{ + // 0x4800-0x4fff Sound data write + m_ram[m_addr_latch.addr()] = data; + + // address latch increment + if (cpu_access && m_addr_latch.incr()) + { + m_addr_latch.addr_inc(); + } +} + +u8 n163_core::data_r(bool cpu_access) +{ + // 0x4800-0x4fff Sound data read + const u8 ret = m_ram[m_addr_latch.addr()]; + + // address latch increment + if (cpu_access && m_addr_latch.incr()) + { + m_addr_latch.addr_inc(); + } + + return ret; +} diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/n163/n163.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/n163/n163.hpp new file mode 100644 index 000000000..1c04d1f36 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/n163/n163.hpp @@ -0,0 +1,109 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Namco 163 Sound emulation core +*/ + +#ifndef _VGSOUND_EMU_SRC_N163_HPP +#define _VGSOUND_EMU_SRC_N163_HPP + +#pragma once + +#include "../core/util.hpp" + +class n163_core : public vgsound_emu_core +{ + private: + // Address latch + class addr_latch_t : public vgsound_emu_core + { + public: + addr_latch_t() + : vgsound_emu_core("namco_163_addr_latch") + , m_addr(0) + , m_incr(0) + { + } + + void reset() + { + m_addr = 0; + m_incr = 0; + } + + // accessors + inline void write(u8 data) + { + m_addr = (data >> 0) & 0x7f; + m_incr = (data >> 7) & 0x01; + } + + inline void addr_inc() { m_addr = (m_addr + 1) & 0x7f; } + + // getters + inline u8 addr() { return m_addr; } + + inline bool incr() { return m_incr; } + + private: + u8 m_addr : 7; + u8 m_incr : 1; + }; + + public: + n163_core() + : vgsound_emu_core("namco_163") + , m_disable(false) + , m_ram{0} + , m_voice_cycle(0x78) + , m_addr_latch(addr_latch_t()) + , m_out(0) + , m_voice_out{0} + , m_multiplex(true) + , m_acc(0) + { + } + + // accessors, getters, setters + void addr_w(u8 data); + void data_w(u8 data, bool cpu_access = false); + u8 data_r(bool cpu_access = false); + + inline void set_disable(bool disable) { m_disable = disable; } + + // internal state + void reset(); + void tick(); + + // sound output pin + inline s16 out() { return m_out; } + + // register pool + inline u8 reg(u8 addr) { return m_ram[addr & 0x7f]; } + + inline void set_multiplex(bool multiplex = true) { m_multiplex = multiplex; } + + // preview only + inline u8 voice_cycle() { return m_voice_cycle; } + + inline s16 voice_out(u8 voice) + { + return (voice <= ((m_ram[0x7f] >> 4) & 7)) ? m_voice_out[7 - voice] : 0; + } + + private: + bool m_disable = false; + std::array m_ram = {0}; // internal 128 byte RAM + u8 m_voice_cycle = 0x78; // Voice cycle for processing + addr_latch_t m_addr_latch; // address latch + s16 m_out = 0; // output + + std::array m_voice_out = {0}; // per-voice output, for preview only + // demultiplex related + bool m_multiplex = true; // multiplex flag, but less noisy = inaccurate! + s16 m_acc = 0; // accumulated output +}; + +#endif diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/scc/README.md b/extern/vgsound_emu-modified/vgsound_emu/src/scc/README.md new file mode 100644 index 000000000..2a66bd8cb --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/scc/README.md @@ -0,0 +1,314 @@ +# Konami SCC + +## Summary + +- 5 voice wavetable + - 8 bit signed, 32 width long for each voice + - each voice has separated waveform space + - 4th and 5th voice shares waveform (K051649) +- MegaROM mapper (K051649) or MegaRAM mapper (K052539) + +## Source code + +- scc.hpp: Base header + - scc.cpp: Source emulation core + +## Description + +Konami SCC means "Sound Creative Chip", it's actually MSX MegaROM/RAM Mapper with 5 channel Wavetable sound generator. + +It was first appeared at 1987, F-1 Spirit and Nemesis 2/Gradius 2 for MSX. then several MSX cartridges used that until 1990, Metal Gear 2: Solid Snake. Even after MSX is discontinued, it was still used at some low-end arcade and amusement hardwares. and some Third-party MSX utilities still support this due to its market shares. + +There's 2 SCC types: + +- K051649 (or simply known as SCC) + This chip is used for MSX MegaROM Mapper, some arcade machines. + Channel 4 and 5 must be share waveform, other channels has its own waveforms. + +- K052539 (also known as SCC+) + This chip is used for MSX MegaRAM Mapper (Konami Sound Cartridges for Snatcher/SD Snatcher). All channels can be has its own waveforms, and also has backward compatibility mode with K051649. + +## Register layout + +### K051649 + +- 4000-bfff MegaROM Mapper + +``` +Address Bit R/W Description + 7654 3210 + +4000-5fff xxxx xxxx R Bank page 0 +c000-dfff mirror of 4000-5fff + +6000-7fff xxxx xxxx R Bank page 1 +e000-ffff mirror of 6000-7fff + +8000-9fff xxxx xxxx R Bank page 2 +0000-1fff mirror of 8000-9fff + +a000-bfff xxxx xxxx R Bank page 3 +2000-3fff mirror of a000-bfff +``` + +- 5000-57ff, 7000-77ff, 9000-97ff, b000-b7ff Bank select + +``` +Address Bit R/W Description + 7654 3210 + +5000 --xx xxxx W Bank select, Page 0 +5001-57ff Mirror of 5000 + +7000 --xx xxxx W Bank select, Page 1 +7001-77ff Mirror of 7000 + +9000 --xx xxxx W Bank select, Page 2 + --11 1111 W SCC Enable +9001-97ff Mirror of 9000 + +b000 --xx xxxx W Bank select, Page 3 +b001-b7ff Mirror of b000 +``` + +- 9800-9fff SCC register + +``` +9800-987f Waveform + +Address Bit R/W Description + 7654 3210 + +9800-981f xxxx xxxx R/W Channel 0 Waveform (32 byte, 8 bit signed) +9820-983f xxxx xxxx R/W Channel 1 "" +9840-985f xxxx xxxx R/W Channel 2 "" +9860-987f xxxx xxxx R/W Channel 3/4 "" + +9880-9889 Pitch + +9880 xxxx xxxx W Channel 0 Pitch LSB +9881 ---- xxxx W Channel 0 Pitch MSB +9882 xxxx xxxx W Channel 1 Pitch LSB +9883 ---- xxxx W Channel 1 Pitch MSB +9884 xxxx xxxx W Channel 2 Pitch LSB +9885 ---- xxxx W Channel 2 Pitch MSB +9886 xxxx xxxx W Channel 3 Pitch LSB +9887 ---- xxxx W Channel 3 Pitch MSB +9888 xxxx xxxx W Channel 4 Pitch LSB +9889 ---- xxxx W Channel 4 Pitch MSB + +9888-988e Volume + +988a ---- xxxx W Channel 0 Volume +988b ---- xxxx W Channel 1 Volume +988c ---- xxxx W Channel 2 Volume +988d ---- xxxx W Channel 3 Volume +988e ---- xxxx W Channel 4 Volume + +988f ---x ---- W Channel 4 Output enable/disable flag + ---- x--- W Channel 3 Output enable/disable flag + ---- -x-- W Channel 2 Output enable/disable flag + ---- --x- W Channel 1 Output enable/disable flag + ---- ---x W Channel 0 Output enable/disable flag + +9890-989f Mirror of 9880-988f + +98a0-98bf xxxx xxxx R Channel 4 Waveform + +98e0 x--- ---- W Waveform rotate flag for channel 4 + -x-- ---- W Waveform rotate flag for all channels + --x- ---- W Reset waveform position after pitch writes + ---- --x- W 8 bit frequency + ---- --0x W 4 bit frequency + +98e1-98ff Mirror of 98e0 + +9900-9fff Mirror of 9800-98ff +``` + +### K052539 + +- 4000-bfff MegaRAM Mapper + +``` +Address Bit R/W Description + 7654 3210 + +4000-5fff xxxx xxxx R/W Bank page 0 +c000-dfff xxxx xxxx R/W "" + +6000-7fff xxxx xxxx R/W Bank page 1 +e000-ffff xxxx xxxx R/W "" + +8000-9fff xxxx xxxx R/W Bank page 2 +0000-1fff xxxx xxxx R/W "" + +a000-bfff xxxx xxxx R/W Bank page 3 +2000-3fff xxxx xxxx R/W "" +``` + +- 5000-57ff, 7000-77ff, 9000-97ff, b000-b7ff Bank select + +``` +Address Bit R/W Description + 7654 3210 + +5000 xxxx xxxx W Bank select, Page 0 +5001-57ff Mirror of 5000 + +7000 xxxx xxxx W Bank select, Page 1 +7001-77ff Mirror of 7000 + +9000 xxxx xxxx W Bank select, Page 2 + --11 1111 W SCC Enable (SCC Compatible mode) +9001-97ff Mirror of 9000 + +b000 xxxx xxxx W Bank select, Page 3 + 1--- ---- W SCC+ Enable (SCC+ mode) +b001-b7ff Mirror of b000 +``` + +- bffe-bfff Mapper configuration + +``` +Address Bit R/W Description + 7654 3210 + +bffe --x- ---- W SCC operation mode + --0- ---- W SCC Compatible mode + --1- ---- W SCC+ mode + ---x ---- W RAM write/Bank select toggle for all Bank pages + ---0 ---- W Bank select enable + ---1 ---- W RAM write enable + ---0 -x-- W RAM write/Bank select toggle for Bank page 2 + ---0 --x- W RAM write/Bank select toggle for Bank page 1 + ---0 ---x W RAM write/Bank select toggle for Bank page 0 + +bfff Mirror of bffe +``` + +- 9800-9fff SCC Compatible mode register + +``` +9800-987f Waveform + +Address Bit R/W Description + 7654 3210 + +9800-981f xxxx xxxx R/W Channel 0 Waveform (32 byte, 8 bit signed) +9820-983f xxxx xxxx R/W Channel 1 "" +9840-985f xxxx xxxx R/W Channel 2 "" +9860-987f xxxx xxxx R/W Channel 3/4 "" + +9880-9889 Pitch + +9880 xxxx xxxx W Channel 0 Pitch LSB +9881 ---- xxxx W Channel 0 Pitch MSB +9882 xxxx xxxx W Channel 1 Pitch LSB +9883 ---- xxxx W Channel 1 Pitch MSB +9884 xxxx xxxx W Channel 2 Pitch LSB +9885 ---- xxxx W Channel 2 Pitch MSB +9886 xxxx xxxx W Channel 3 Pitch LSB +9887 ---- xxxx W Channel 3 Pitch MSB +9888 xxxx xxxx W Channel 4 Pitch LSB +9889 ---- xxxx W Channel 4 Pitch MSB + +9888-988e Volume + +988a ---- xxxx W Channel 0 Volume +988b ---- xxxx W Channel 1 Volume +988c ---- xxxx W Channel 2 Volume +988d ---- xxxx W Channel 3 Volume +988e ---- xxxx W Channel 4 Volume + +988f ---x ---- W Channel 4 Output enable/disable flag + ---- x--- W Channel 3 Output enable/disable flag + ---- -x-- W Channel 2 Output enable/disable flag + ---- --x- W Channel 1 Output enable/disable flag + ---- ---x W Channel 0 Output enable/disable flag + +9890-989f Mirror of 9880-988f + +98a0-98bf xxxx xxxx R Channel 4 Waveform + +98c0 -x-- ---- W Waveform rotate flag for all channels + --x- ---- W Reset waveform position after pitch writes + ---- --x- W 8 bit frequency + ---- --0x W 4 bit frequency + +98c1-98df Mirror of 98c0 + + 9900-9fff Mirror of 9800-98ff +``` + +- b800-bfff SCC+ mode register + +``` +b800-b89f Waveform + +Address Bit R/W Description + 7654 3210 + +b800-b81f xxxx xxxx R/W Channel 0 Waveform (32 byte, 8 bit signed) +b820-b83f xxxx xxxx R/W Channel 1 "" +b840-b85f xxxx xxxx R/W Channel 2 "" +b860-b87f xxxx xxxx R/W Channel 3 "" +b880-b89f xxxx xxxx R/W Channel 3 "" + +b8a0-b8a9 Pitch + +b8a0 xxxx xxxx W Channel 0 Pitch LSB +b8a1 ---- xxxx W Channel 0 Pitch MSB +b8a2 xxxx xxxx W Channel 1 Pitch LSB +b8a3 ---- xxxx W Channel 1 Pitch MSB +b8a4 xxxx xxxx W Channel 2 Pitch LSB +b8a5 ---- xxxx W Channel 2 Pitch MSB +b8a6 xxxx xxxx W Channel 3 Pitch LSB +b8a7 ---- xxxx W Channel 3 Pitch MSB +b8a8 xxxx xxxx W Channel 4 Pitch LSB +b8a9 ---- xxxx W Channel 4 Pitch MSB + +b8a8-b8ae Volume + +b8aa ---- xxxx W Channel 0 Volume +b8ab ---- xxxx W Channel 1 Volume +b8ac ---- xxxx W Channel 2 Volume +b8ad ---- xxxx W Channel 3 Volume +b8ae ---- xxxx W Channel 4 Volume + +b8af ---x ---- W Channel 4 Output enable/disable flag + ---- x--- W Channel 3 Output enable/disable flag + ---- -x-- W Channel 2 Output enable/disable flag + ---- --x- W Channel 1 Output enable/disable flag + ---- ---x W Channel 0 Output enable/disable flag + +b8b0-b8bf Mirror of b8a0-b8af + +b8c0 -x-- ---- W Waveform rotate flag for all channels + --x- ---- W Reset waveform position after pitch writes + ---- --x- W 8 bit frequency + ---- --0x W 4 bit frequency + +b8c1-b8df Mirror of b8c0 + +b900-bfff Mirror of b800-b8ff +``` + +## Frequency calculation + +``` + if 8 bit frequency then + Frequency = Input clock / ((bit 0 to 7 of Pitch input) + 1) + else if 4 bit frequency then + Frequency = Input clock / ((bit 8 to 11 of Pitch input) + 1) + else + Frequency = Input clock / (Pitch input + 1) +``` + +## Reference + + + + + + diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/scc/scc.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/scc/scc.cpp new file mode 100644 index 000000000..07cbb60e8 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/scc/scc.cpp @@ -0,0 +1,461 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst + Konami SCC emulation core +*/ + +#include "scc.hpp" + +// shared SCC features +void scc_core::tick() +{ + m_out = 0; + for (auto &elem : m_voice) + { + elem.tick(); + m_out += elem.out(); + } +} + +void scc_core::voice_t::tick() +{ + if (m_pitch >= 9) // or voice is halted + { + // update counter - Post decrement + const u16 temp = m_counter; + if (m_host.m_test.freq_4bit()) // 4 bit frequency mode + { + m_counter = (m_counter & ~0x0ff) | (bitfield(bitfield(m_counter, 0, 8) - 1, 0, 8) << 0); + m_counter = (m_counter & ~0xf00) | (bitfield(bitfield(m_counter, 8, 4) - 1, 0, 4) << 8); + } + else + { + m_counter = bitfield(m_counter - 1, 0, 12); + } + + // handle counter carry + const bool carry = m_host.m_test.freq_8bit() + ? (bitfield(temp, 0, 8) == 0) + : (m_host.m_test.freq_4bit() ? (bitfield(temp, 8, 4) == 0) + : (bitfield(temp, 0, 12) == 0)); + if (carry) + { + m_addr = bitfield(m_addr + 1, 0, 5); + m_counter = m_pitch; + } + } + // get output + if (m_enable) + { + m_out = (m_wave[m_addr] * m_volume) >> 4; // scale to 11 bit digital output + } + else + { + m_out = 0; + } +} + +void scc_core::reset() +{ + for (auto &elem : m_voice) + { + elem.reset(); + } + + m_test.reset(); + m_out = 0; + std::fill(m_reg.begin(), m_reg.end(), 0); +} + +void scc_core::voice_t::reset() +{ + std::fill(m_wave.begin(), m_wave.end(), 0); + m_enable = false; + m_pitch = 0; + m_volume = 0; + m_addr = 0; + m_counter = 0; + m_out = 0; +} + +// SCC accessors +u8 scc_core::wave_r(bool is_sccplus, u8 address) +{ + u8 ret = 0xff; + const u8 voice = bitfield(address, 5, 3); + if (voice > 4) + { + return ret; + } + + u8 wave_addr = bitfield(address, 0, 5); + + if (m_test.rotate()) + { // rotate flag + wave_addr = bitfield(wave_addr + m_voice[voice].addr(), 0, 5); + } + + if (!is_sccplus) + { + if (voice == 3) // rotate voice 3~4 flag + { + if (m_test.rotate4() || m_test.rotate()) + { // rotate flag + wave_addr = + bitfield(bitfield(address, 0, 5) + m_voice[3 + m_test.rotate()].addr(), 0, 5); + } + } + } + ret = m_voice[voice].wave(wave_addr); + + return ret; +} + +void scc_core::wave_w(bool is_sccplus, u8 address, u8 data) +{ + if (m_test.rotate()) + { // write protected + return; + } + + const u8 voice = bitfield(address, 5, 3); + if (voice > 4) + { + return; + } + + const u8 wave_addr = bitfield(address, 0, 5); + + if (!is_sccplus) + { + if (((voice >= 3) && m_test.rotate4()) || (voice >= 4)) + { // Ignore if write protected, or voice 4 + return; + } + if (voice >= 3) // voice 3, 4 shares waveform + { + m_voice[3].set_wave(wave_addr, data); + m_voice[4].set_wave(wave_addr, data); + } + else + { + m_voice[voice].set_wave(wave_addr, data); + } + } + else + { + m_voice[voice].set_wave(wave_addr, data); + } +} + +void scc_core::freq_vol_enable_w(u8 address, u8 data) +{ + // *0-*f Pitch, Volume, Enable + address = bitfield(address, 0, 4); // mask address to 4 bit + const u8 voice_freq = bitfield(address, 1, 3); + switch (address) + { + case 0x0: // 0x*0 Voice 0 Pitch LSB + case 0x2: // 0x*2 Voice 1 Pitch LSB + case 0x4: // 0x*4 Voice 2 Pitch LSB + case 0x6: // 0x*6 Voice 3 Pitch LSB + case 0x8: // 0x*8 Voice 4 Pitch LSB + if (m_test.resetpos()) + { // Reset address + m_voice[voice_freq].reset_addr(); + } + m_voice[voice_freq].set_pitch(data, 0x0ff); + break; + case 0x1: // 0x*1 Voice 0 Pitch MSB + case 0x3: // 0x*3 Voice 1 Pitch MSB + case 0x5: // 0x*5 Voice 2 Pitch MSB + case 0x7: // 0x*7 Voice 3 Pitch MSB + case 0x9: // 0x*9 Voice 4 Pitch MSB + if (m_test.resetpos()) + { // Reset address + m_voice[voice_freq].reset_addr(); + } + m_voice[voice_freq].set_pitch(u16(bitfield(data, 0, 4)) << 8, 0xf00); + break; + case 0xa: // 0x*a Voice 0 Volume + case 0xb: // 0x*b Voice 1 Volume + case 0xc: // 0x*c Voice 2 Volume + case 0xd: // 0x*d Voice 3 Volume + case 0xe: // 0x*e Voice 4 Volume + m_voice[address - 0xa].set_volume(bitfield(data, 0, 4)); + break; + case 0xf: // 0x*f Enable/Disable flag + m_voice[0].set_enable(bitfield(data, 0)); + m_voice[1].set_enable(bitfield(data, 1)); + m_voice[2].set_enable(bitfield(data, 2)); + m_voice[3].set_enable(bitfield(data, 3)); + m_voice[4].set_enable(bitfield(data, 4)); + break; + } +} + +void k051649_scc_core::scc_w(bool is_sccplus, u8 address, u8 data) +{ + const u8 voice = bitfield(address, 5, 3); + switch (voice) + { + case 0b000: // 0x00-0x1f Voice 0 Waveform + case 0b001: // 0x20-0x3f Voice 1 Waveform + case 0b010: // 0x40-0x5f Voice 2 Waveform + case 0b011: // 0x60-0x7f Voice 3/4 Waveform + wave_w(false, address, data); + break; + case 0b100: // 0x80-0x9f Pitch, Volume, Enable + freq_vol_enable_w(address, data); + break; + case 0b111: // 0xe0-0xff Test register + m_test.set_freq_4bit(bitfield(data, 0)); + m_test.set_freq_8bit(bitfield(data, 1)); + m_test.set_resetpos(bitfield(data, 5)); + m_test.set_rotate(bitfield(data, 6)); + m_test.set_rotate4(bitfield(data, 7)); + break; + } + m_reg[address] = data; +} + +void k052539_scc_core::scc_w(bool is_sccplus, u8 address, u8 data) +{ + const u8 voice = bitfield(address, 5, 3); + if (is_sccplus) + { + switch (voice) + { + case 0b000: // 0x00-0x1f Voice 0 Waveform + case 0b001: // 0x20-0x3f Voice 1 Waveform + case 0b010: // 0x40-0x5f Voice 2 Waveform + case 0b011: // 0x60-0x7f Voice 3 Waveform + case 0b100: // 0x80-0x9f Voice 4 Waveform + wave_w(true, address, data); + break; + case 0b101: // 0xa0-0xbf Pitch, Volume, Enable + freq_vol_enable_w(address, data); + break; + case 0b110: // 0xc0-0xdf Test register + m_test.set_freq_4bit(bitfield(data, 0)); + m_test.set_freq_8bit(bitfield(data, 1)); + m_test.set_resetpos(bitfield(data, 5)); + m_test.set_rotate(bitfield(data, 6)); + break; + default: break; + } + } + else + { + switch (voice) + { + case 0b000: // 0x00-0x1f Voice 0 Waveform + case 0b001: // 0x20-0x3f Voice 1 Waveform + case 0b010: // 0x40-0x5f Voice 2 Waveform + case 0b011: // 0x60-0x7f Voice 3/4 Waveform + wave_w(false, address, data); + break; + case 0b100: // 0x80-0x9f Pitch, Volume, Enable + freq_vol_enable_w(address, data); + break; + case 0b110: // 0xc0-0xdf Test register + m_test.set_freq_4bit(bitfield(data, 0)); + m_test.set_freq_8bit(bitfield(data, 1)); + m_test.set_resetpos(bitfield(data, 5)); + m_test.set_rotate(bitfield(data, 6)); + break; + default: break; + } + } + m_reg[address] = data; +} + +u8 k051649_scc_core::scc_r(bool is_sccplus, u8 address) +{ + const u8 voice = bitfield(address, 5, 3); + const u8 wave = bitfield(address, 0, 5); + u8 ret = 0xff; + switch (voice) + { + case 0b000: // 0x00-0x1f Voice 0 Waveform + case 0b001: // 0x20-0x3f Voice 1 Waveform + case 0b010: // 0x40-0x5f Voice 2 Waveform + case 0b011: // 0x60-0x7f Voice 3 Waveform + case 0b101: // 0xa0-0xbf Voice 4 Waveform + ret = wave_r(false, (std::min(4, voice) << 5) | wave); + break; + } + return ret; +} + +u8 k052539_scc_core::scc_r(bool is_sccplus, u8 address) +{ + const u8 voice = bitfield(address, 5, 3); + const u8 wave = bitfield(address, 0, 5); + u8 ret = 0xff; + if (is_sccplus) + { + switch (voice) + { + case 0b000: // 0x00-0x1f Voice 0 Waveform + case 0b001: // 0x20-0x3f Voice 1 Waveform + case 0b010: // 0x40-0x5f Voice 2 Waveform + case 0b011: // 0x60-0x7f Voice 3 Waveform + case 0b100: // 0x80-0x9f Voice 4 Waveform + ret = wave_r(true, address); + break; + } + } + else + { + switch (voice) + { + case 0b000: // 0x00-0x1f Voice 0 Waveform + case 0b001: // 0x20-0x3f Voice 1 Waveform + case 0b010: // 0x40-0x5f Voice 2 Waveform + case 0b011: // 0x60-0x7f Voice 3 Waveform + case 0b101: // 0xa0-0xbf Voice 4 Waveform + ret = wave_r(false, (std::min(4, voice) << 5) | wave); + break; + } + } + return ret; +} + +// Mapper +void k051649_core::reset() +{ + k051649_scc_core::reset(); + m_mapper.reset(); + m_scc_enable = false; +} + +void k052539_core::reset() +{ + k052539_scc_core::reset(); + m_mapper.reset(); + m_scc_enable = false; + m_is_sccplus = false; +} + +void k051649_core::k051649_mapper_t::reset() +{ + m_bank[0] = 0; + m_bank[1] = 1; + m_bank[2] = 2; + m_bank[3] = 3; +} + +void k052539_core::k052539_mapper_t::reset() +{ + m_bank[0] = 0; + m_bank[1] = 1; + m_bank[2] = 2; + m_bank[3] = 3; + std::fill(m_ram_enable.begin(), m_ram_enable.end(), false); +} + +// Mapper accessors +u8 k051649_core::read(u16 address) +{ + if ((bitfield(address, 11, 5) == 0b10011) && m_scc_enable) + { + return scc_r(false, u8(address)); + } + + return m_intf.read_byte((u32(m_mapper.bank(bitfield(address, 13, 2) ^ 2)) << 13) | + bitfield(address, 0, 13)); +} + +u8 k052539_core::read(u16 address) +{ + if ((bitfield(address, 11, 5) == 0b10011) && m_scc_enable && (!m_is_sccplus)) + { + return scc_r(false, u8(address)); + } + + if ((bitfield(address, 11, 5) == 0b10111) && m_scc_enable && m_is_sccplus) + { + return scc_r(true, u8(address)); + } + + return m_intf.read_byte((u32(m_mapper.bank(bitfield(address, 13, 2) ^ 2)) << 13) | + bitfield(address, 0, 13)); +} + +void k051649_core::write(u16 address, u8 data) +{ + const u16 bank = bitfield(address, 13, 2) ^ 2; + switch (bitfield(address, 11, 5)) + { + case 0b01010: // 0x5000-0x57ff Bank 0 + case 0b01110: // 0x7000-0x77ff Bank 1 + case 0b10010: // 0x9000-0x97ff Bank 2 + case 0b10110: // 0xb000-0xb7ff Bank 3 + m_mapper.set_bank(bank, data); + m_scc_enable = (bitfield(m_mapper.bank(2), 0, 6) == 0x3f); + break; + case 0b10011: // 0x9800-9fff SCC + if (m_scc_enable) + { + scc_w(false, u8(address), data); + } + break; + } +} + +void k052539_core::write(u16 address, u8 data) +{ + u8 prev = 0; + bool update = false; + const u16 bank = bitfield(address, 13, 2) ^ 2; + const bool ram_enable = m_mapper.ram_enable(bank); + if (ram_enable) + { + m_intf.write_byte((u32(m_mapper.bank(bank)) << 13) | bitfield(address, 0, 13), data); + } + switch (bitfield(address, 11, 5)) + { + case 0b01010: // 0x5000-0x57ff Bank 0 + case 0b01110: // 0x7000-0x77ff Bank 1 + case 0b10010: // 0x9000-0x97ff Bank 2 + case 0b10110: // 0xb000-0xb7ff Bank 3 + if (!ram_enable) + { + prev = m_mapper.bank(bank); + m_mapper.set_bank(bank, data); + update = prev ^ m_mapper.bank(bank); + } + break; + case 0b10011: // 0x9800-0x9fff SCC + if ((!ram_enable) && m_scc_enable && (!m_is_sccplus)) + { + scc_w(false, u8(address), data); + } + break; + case 0b10111: // 0xb800-0xbfff SCC+, Mapper configuration + if (bitfield(address, 1, 10) == 0x3ff) + { + m_mapper.set_ram_enable(0, bitfield(data, 4) || bitfield(data, 0)); + m_mapper.set_ram_enable(1, bitfield(data, 4) || bitfield(data, 1)); + m_mapper.set_ram_enable(2, bitfield(data, 4) || bitfield(data, 2)); + m_mapper.set_ram_enable(3, bitfield(data, 4)); + prev = (m_is_sccplus ? 1 : 0); + m_is_sccplus = bitfield(data, 5); + update = prev ^ (m_is_sccplus ? 1 : 0); + } + else if ((!ram_enable) && m_scc_enable && m_is_sccplus) + { + scc_w(true, u8(address), data); + } + break; + } + if (update) + { + m_scc_enable = + m_is_sccplus ? bitfield(m_mapper.bank(3), 7) : (bitfield(m_mapper.bank(2), 0, 6) == 0x3f); + } +} diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/scc/scc.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/scc/scc.hpp new file mode 100644 index 000000000..8824d2535 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/scc/scc.hpp @@ -0,0 +1,320 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst + Konami SCC emulation core +*/ + +#ifndef _VGSOUND_EMU_SRC_SCC_HPP +#define _VGSOUND_EMU_SRC_SCC_HPP + +#pragma once + +#include "../core/util.hpp" + +// shared for SCCs +class scc_core : public vgsound_emu_core +{ + private: + // classes + class voice_t : public vgsound_emu_core + { + public: + // constructor + voice_t(scc_core &host) + : vgsound_emu_core("scc_voice") + , m_host(host) + , m_wave{0} + , m_enable(false) + , m_pitch(0) + , m_volume(0) + , m_addr(0) + , m_counter(0) + , m_out(0) + { + } + + // internal state + void reset(); + void tick(); + + // accessors + inline void reset_addr() { m_addr = 0; } + + // setters + inline void set_wave(u8 addr, s8 wave) { m_wave[addr & 0x1f] = wave; } + + inline void set_enable(bool enable) { m_enable = enable; } + + inline void set_pitch(u16 pitch, u16 mask = 0xfff) + { + m_pitch = (m_pitch & ~(mask & 0xfff)) | (pitch & (mask & 0xfff)); + m_counter = m_pitch; + } + + inline void set_volume(u8 volume) { m_volume = volume & 0xf; } + + // getters + inline s8 wave(u8 addr) { return m_wave[addr & 0x1f]; } + + inline u8 addr() { return m_addr; } + + inline s32 out() { return m_out; } + + private: + // registers + scc_core &m_host; + std::array m_wave = {0}; // internal waveform + bool m_enable = false; // output enable flag + u16 m_pitch : 12; // pitch + u16 m_volume : 4; // volume + u8 m_addr = 0; // waveform pointer + u16 m_counter = 0; // frequency counter + s32 m_out = 0; // current output + }; + + class test_t : public vgsound_emu_core + { + public: + // constructor + test_t() + : vgsound_emu_core("scc_test") + , m_freq_4bit(0) + , m_freq_8bit(0) + , m_resetpos(0) + , m_rotate(0) + , m_rotate4(0) + { + } + + void reset() + { + m_freq_4bit = 0; + m_freq_8bit = 0; + m_resetpos = 0; + m_rotate = 0; + m_rotate4 = 0; + } + + // setters + inline void set_freq_4bit(bool freq_4bit) { m_freq_4bit = freq_4bit; } + + inline void set_freq_8bit(bool freq_8bit) { m_freq_8bit = freq_8bit; } + + inline void set_resetpos(bool resetpos) { m_resetpos = resetpos; } + + inline void set_rotate(bool rotate) { m_rotate = rotate; } + + inline void set_rotate4(bool rotate4) { m_rotate4 = rotate4; } + + // getters + inline bool freq_4bit() { return m_freq_4bit; } + + inline bool freq_8bit() { return m_freq_8bit; } + + inline bool resetpos() { return m_resetpos; } + + inline bool rotate() { return m_rotate; } + + inline bool rotate4() { return m_rotate4; } + + private: + u8 m_freq_4bit : 1; // 4 bit frequency + u8 m_freq_8bit : 1; // 8 bit frequency + u8 m_resetpos : 1; // reset counter after pitch writes + u8 m_rotate : 1; // rotate and write protect waveform for all channels + u8 m_rotate4 : 1; // same as above but for channel 4 only + }; + + public: + // constructor + scc_core(std::string tag) + : vgsound_emu_core(tag) + , m_voice{*this, *this, *this, *this, *this} + , m_test(test_t()) + , m_out(0) + , m_reg{0} + { + } + + // destructor + virtual ~scc_core() {} + + // accessors + virtual u8 scc_r(bool is_sccplus, u8 address) = 0; + virtual void scc_w(bool is_sccplus, u8 address, u8 data) = 0; + + // internal state + virtual void reset(); + void tick(); + + // getters + inline s32 out() { return m_out; } // output to DA0...DA10 pin + + inline u8 reg(u8 address) { return m_reg[address]; } + + // for preview + inline s32 voice_out(u8 voice) { return (voice < 5) ? m_voice[voice].out() : 0; } + + protected: + // accessor + u8 wave_r(bool is_sccplus, u8 address); + void wave_w(bool is_sccplus, u8 address, u8 data); + void freq_vol_enable_w(u8 address, u8 data); + + // internal values + std::array m_voice; // 5 voices + + test_t m_test; // test register + s32 m_out = 0; // output to DA0...10 + + std::array m_reg = {0}; // register pool +}; + +// SCC core +class k051649_scc_core : public scc_core +{ + public: + // constructor + k051649_scc_core(std::string tag = "k051649_scc") + : scc_core(tag) + { + } + + // accessors + virtual u8 scc_r(bool is_sccplus, u8 address) override; + virtual void scc_w(bool is_sccplus, u8 address, u8 data) override; +}; + +class k052539_scc_core : public k051649_scc_core +{ + public: + // constructor + k052539_scc_core(std::string tag = "k052539_scc") + : k051649_scc_core(tag) + { + } + + // accessors + virtual u8 scc_r(bool is_sccplus, u8 address) override; + virtual void scc_w(bool is_sccplus, u8 address, u8 data) override; +}; + +// MegaROM Mapper with SCC +class k051649_core : public k051649_scc_core +{ + friend class vgsound_emu_mem_intf; // for megaROM mapper + + private: + // mapper classes + class k051649_mapper_t : public vgsound_emu_core + { + public: + k051649_mapper_t() + : vgsound_emu_core("k051649_mapper") + , m_bank{0, 1, 2, 3} + { + } + + // internal state + void reset(); + + // setters + inline void set_bank(u8 slot, u8 bank) { m_bank[slot & 3] = bank; } + + // getters + inline u8 bank(u8 slot) { return m_bank[slot & 3]; } + + private: + // registers + u8 m_bank[4] = {0, 1, 2, 3}; + }; + + public: + // constructor + k051649_core(vgsound_emu_mem_intf &intf) + : k051649_scc_core("k051649") + , m_intf(intf) + , m_mapper(k051649_mapper_t()) + , m_scc_enable(false) + { + } + + // accessors + u8 read(u16 address); + void write(u16 address, u8 data); + + virtual void reset() override; + + private: + vgsound_emu_mem_intf m_intf; + k051649_mapper_t m_mapper; + bool m_scc_enable = false; +}; + +// MegaRAM Mapper with SCC +class k052539_core : public k052539_scc_core +{ + friend class vgsound_emu_mem_intf; // for megaRAM mapper + + private: + // mapper classes + class k052539_mapper_t : public vgsound_emu_core + { + public: + k052539_mapper_t() + : vgsound_emu_core("k052539_mapper") + , m_bank{0, 1, 2, 3} + , m_ram_enable{false} + { + } + + // internal state + void reset(); + + // setters + inline void set_bank(u8 slot, u8 bank) { m_bank[slot & 3] = bank; } + + inline void set_ram_enable(u8 slot, bool ram_enable) + { + m_ram_enable[slot & 3] = ram_enable; + } + + // getters + inline u8 bank(u8 slot) { return m_bank[slot & 3]; } + + inline bool ram_enable(u8 slot) { return m_ram_enable[slot & 3]; } + + private: + // registers + std::array m_bank = {0, 1, 2, 3}; + std::array m_ram_enable = {false}; + }; + + public: + // constructor + k052539_core(vgsound_emu_mem_intf &intf) + : k052539_scc_core("k052539") + , m_intf(intf) + , m_mapper(k052539_mapper_t()) + , m_scc_enable(false) + , m_is_sccplus(false) + { + } + + // accessors + u8 read(u16 address); + void write(u16 address, u8 data); + + virtual void reset() override; + + private: + vgsound_emu_mem_intf m_intf; + k052539_mapper_t m_mapper; + bool m_scc_enable = false; + bool m_is_sccplus = false; +}; + +#endif diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/template/template.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/template/template.cpp new file mode 100644 index 000000000..d927860a0 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/template/template.cpp @@ -0,0 +1,33 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): (Author name) + Template for sound emulation core +*/ + +#include "template.hpp" + +void template_core::tick() +{ + // tick per each clock +} + +void template_core::reset() +{ + // reset this chip + std::fill(m_array.begin(), m_array.end(), 0); // std::fill() for fill std::array, std::vector +} + +/* +template voice function +void template_core::voice_t::tick() +{ + // tick per each voice +} + +void template_core::voice_t::reset() +{ + // reset this voice +} +*/ diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/template/template.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/template/template.hpp new file mode 100644 index 000000000..ca8f91b53 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/template/template.hpp @@ -0,0 +1,88 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): (Author name) + Template for sound emulation core, also guideline +*/ + +#ifndef _VGSOUND_EMU_SRC_TEMPLATE_HPP // _VGSOUND_EMU_ABSOLUTE_PATH_OF_THIS_FILE +#define _VGSOUND_EMU_SRC_TEMPLATE_HPP + +#pragma once + +#include "../core/util.hpp" + +class template_core : public vgsound_emu_core +{ + friend class vgsound_emu_mem_intf; // common memory interface if exists + + private: // protected: if shares between inheritances + // place classes and local constants here if exists + + // template voice classes + class voice_t : public vgsound_emu_core + { + public: + // constructor + voice_t(template_core &host) + : vgsound_emu_core("your_voice_tag_here") + , m_host(host) + , m_something(0) + { + } + + // internal state + void reset(); + void tick(); + + // accessors, getters, setters + + // setters + void set_something(s32 something) { m_something = something; } + + // getters + s32 something() { return m_something; } + + private: + // registers + template_core &m_host; + s32 m_something = 0; // register + }; + + public: + // place constructor and destructor, getter and setter for local variables, + // off-chip interfaces, update routine here only if can't be local + + // constructor + template_core(vgsound_emu_mem_intf &intf) + : vgsound_emu_core("your_core_tag_here") + // initialize all variables in constructor, because constructor is also executable + // anywhere, and it works as initializer. + , m_voice{*this} + , m_intf(intf) + , m_array{0} + , m_vector{0} + { + } + + // accessors, getters, setters + + // internal state + void reset(); + void tick(); + + protected: + // place local variables and functions here if shares between inheritances + + private: + // place local variables and functions here + + std::array m_voice; // voice classes + vgsound_emu_mem_intf &m_intf; // common memory interface + std::array m_array = { + 0}; // std::array for static size array + std::vector m_vector = {0}; // std::vector for variable size array +}; + +#endif diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/vrcvi/README.md b/extern/vgsound_emu-modified/vgsound_emu/src/vrcvi/README.md new file mode 100644 index 000000000..5b2db0519 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/vrcvi/README.md @@ -0,0 +1,97 @@ + +# Konami VRC VI + +## Summary + +- 2 voice pulse wave + - 8 level duty or volume only mode +- 1 voice sawtooth wave +- Internal mapper and timer + +## Source code + +- vrcvi.hpp: Base header + - vrcvi.cpp: Source emulation core + +## Description + +It's one of NES mapper with built-in sound chip, and also one of 2 Konami VRCs with this feature. (rest one has OPLL derivatives.) + +It's also DACless like other sound chip and mapper-with-sound manufactured by konami, the Chips 6 bit digital sound output is needs converted to analog sound output when you it want to make some sounds, or send to sound mixer. + +Its are used for Akumajou Densetsu (Japan release of Castlevania III), Madara, Esper Dream 2. + +The chip is installed in 351951 PCB and 351949A PCB. + +351951 PCB is used exclusivly for Akumajou Densetsu, Small board has VRC VI, PRG and CHR ROM. + +- It's configuration also calls VRC6a, iNES mapper 024. + +351949A PCB is for Last 2 titles with VRC VI, Bigger board has VRC VI, PRG and CHR ROM, and Battery Backed 8K x 8 bit SRAM. + +- Additionally, It's PRG A0 and A1 bit to VRC VI input is swapped, compare to above. +- It's configuration also calls VRC6b, iNES mapper 026. + +The chip itself has 053328, 053329, 053330 Revision, but Its difference between revision is unknown. + +Like other mappers for NES, It has internal timer - Its timer can be sync with scanline like other Konami mapper in this era. + +## Register layout + +- Sound and Timer only; 351951 PCB case, 351949A swaps xxx1 and xxx2 + +``` +Address Bits Description + 7654 3210 + +9000-9002 Pulse 1 + +9000 x--- ---- Pulse 1 Duty ignore + -xxx ---- Pulse 1 Duty cycle + ---- xxxx Pulse 1 Volume +9001 xxxx xxxx Pulse 1 Pitch bit 0-7 +9002 x--- ---- Pulse 1 Enable + ---- xxxx Pulse 1 Pitch bit 8-11 + +9003 Sound control + +9003 ---- -x-- 4 bit Frequency mode + ---- -0x- 8 bit Frequency mode + ---- ---x Halt + +a000-a002 Pulse 2 + +a000 x--- ---- Pulse 2 Duty ignore + -xxx ---- Pulse 2 Duty cycle + ---- xxxx Pulse 2 Volume +a001 xxxx xxxx Pulse 2 Pitch bit 0-7 +a002 x--- ---- Pulse 2 Enable + ---- xxxx Pulse 2 Pitch bit 8-11 + +b000-b002 Sawtooth + +b000 --xx xxxx Sawtooth Accumulate Rate +b001 xxxx xxxx Sawtooth Pitch bit 0-7 +b002 x--- ---- Sawtooth Enable + ---- xxxx Sawtooth Pitch bit 8-11 + +f000-f002 IRQ Timer + +f000 xxxx xxxx IRQ Timer latch +f001 ---- -0-- Sync with scanline + ---- --x- Enable timer + ---- ---x Enable timer after IRQ Acknowledge +f002 ---- ---- IRQ Acknowledge +``` + +# Frequency calculation + +``` + if 4 bit Frequency Mode then + Frequency: Input clock / (bit 8 to 11 of Pitch + 1) + end else if 8 bit Frequency Mode then + Frequency: Input clock / (bit 4 to 11 of Pitch + 1) + end else then + Frequency: Input clock / (Pitch + 1) + end +``` diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/vrcvi/vrcvi.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/vrcvi/vrcvi.cpp new file mode 100644 index 000000000..dc59120f3 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/vrcvi/vrcvi.cpp @@ -0,0 +1,260 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Konami VRC VI sound emulation core +*/ + +#include "vrcvi.hpp" + +void vrcvi_core::tick() +{ + m_out = 0; + if (!m_control.halt()) // Halt flag + { + // tick per each clock + for (auto &elem : m_pulse) + { + m_out += elem.get_output(); // add 4 bit pulse output + } + m_out += m_sawtooth.get_output(); // add 5 bit sawtooth output + } + if (m_timer.tick()) + { + m_timer.counter_tick(); + } +} + +void vrcvi_core::reset() +{ + for (auto &elem : m_pulse) + { + elem.reset(); + } + + m_sawtooth.reset(); + m_timer.reset(); + m_control.reset(); + m_out = 0; +} + +bool vrcvi_core::alu_t::tick() +{ + if (m_divider.enable()) + { + const u16 temp = m_counter; + // post decrement + if (bitfield(m_host.m_control.shift(), 1)) + { + m_counter = (m_counter & 0x0ff) | (bitfield(bitfield(m_counter, 8, 4) - 1, 0, 4) << 8); + m_counter = (m_counter & 0xf00) | (bitfield(bitfield(m_counter, 0, 8) - 1, 0, 8) << 0); + } + else if (bitfield(m_host.m_control.shift(), 0)) + { + m_counter = (m_counter & 0x00f) | (bitfield(bitfield(m_counter, 4, 8) - 1, 0, 8) << 4); + m_counter = (m_counter & 0xff0) | (bitfield(bitfield(m_counter, 0, 4) - 1, 0, 4) << 0); + } + else + { + m_counter = bitfield(bitfield(m_counter, 0, 12) - 1, 0, 12); + } + + // carry handling + bool carry = bitfield(m_host.m_control.shift(), 1) + ? (bitfield(temp, 8, 4) == 0) + : (bitfield(m_host.m_control.shift(), 0) ? (bitfield(temp, 4, 8) == 0) + : (bitfield(temp, 0, 12) == 0)); + if (carry) + { + m_counter = m_divider.divider(); + } + + return carry; + } + return false; +} + +bool vrcvi_core::pulse_t::tick() +{ + if (!m_divider.enable()) + { + return false; + } + + if (vrcvi_core::alu_t::tick()) + { + m_cycle = bitfield(m_cycle + 1, 0, 4); + } + + return m_control.mode() ? true : ((m_cycle > m_control.duty()) ? true : false); +} + +bool vrcvi_core::sawtooth_t::tick() +{ + if (!m_divider.enable()) + { + return false; + } + + if (vrcvi_core::alu_t::tick()) + { + if (bitfield(m_cycle++, 0)) + { // Even step only + m_accum += m_rate; + } + if (m_cycle >= 14) // Reset accumulator at every 14 cycles + { + m_accum = 0; + m_cycle = 0; + } + } + return (m_accum == 0) ? false : true; +} + +s8 vrcvi_core::pulse_t::get_output() +{ + // add 4 bit pulse output + m_out = tick() ? m_control.volume() : 0; + return m_out; +} + +s8 vrcvi_core::sawtooth_t::get_output() +{ + // add 5 bit sawtooth output + m_out = tick() ? bitfield(m_accum, 3, 5) : 0; + return m_out; +} + +void vrcvi_core::alu_t::reset() +{ + m_divider.reset(); + m_counter = 0; + m_cycle = 0; + m_out = 0; +} + +void vrcvi_core::pulse_t::reset() +{ + vrcvi_core::alu_t::reset(); + m_control.reset(); +} + +void vrcvi_core::sawtooth_t::reset() +{ + vrcvi_core::alu_t::reset(); + m_rate = 0; + m_accum = 0; +} + +bool vrcvi_core::timer_t::tick() +{ + if (m_timer_control.enable()) + { + if (!m_timer_control.sync()) // scanline sync mode + { + m_prescaler -= 3; + if (m_prescaler <= 0) + { + m_prescaler += 341; + return true; + } + } + } + return (m_timer_control.enable() && m_timer_control.sync()) ? true : false; +} + +void vrcvi_core::timer_t::counter_tick() +{ + if (bitfield(++m_counter, 0, 8) == 0) + { + m_counter = m_counter_latch; + irq_set(); + } +} + +void vrcvi_core::timer_t::reset() +{ + m_timer_control.reset(); + m_prescaler = 341; + m_counter = m_counter_latch = 0; + irq_clear(); +} + +// Accessors + +void vrcvi_core::alu_t::divider_t::write(bool msb, u8 data) +{ + if (msb) + { + m_divider = (m_divider & ~0xf00) | (bitfield(data, 0, 4) << 8); + m_enable = bitfield(data, 7); + } + else + { + m_divider = (m_divider & ~0x0ff) | data; + } +} + +void vrcvi_core::pulse_w(u8 voice, u8 address, u8 data) +{ + pulse_t &v = m_pulse[voice]; + switch (address) + { + case 0x00: // Control - 0x9000 (Pulse 1), 0xa000 (Pulse 2) + v.control().write(data); + break; + case 0x01: // Pitch LSB - 0x9001/0x9002 (Pulse 1), 0xa001/0xa002 (Pulse 2) + v.divider().write(false, data); + break; + case 0x02: // Pitch MSB, Enable/Disable - 0x9002/0x9001 (Pulse 1), 0xa002/0xa001 (Pulse 2) + v.divider().write(true, data); + if (!v.divider().enable()) + { // Reset duty cycle + v.clear_cycle(); + } + break; + } +} + +void vrcvi_core::saw_w(u8 address, u8 data) +{ + switch (address) + { + case 0x00: // Sawtooth Accumulate - 0xb000 + m_sawtooth.set_rate(bitfield(data, 0, 6)); + break; + case 0x01: // Pitch LSB - 0xb001/0xb002 (Sawtooth) + m_sawtooth.divider().write(false, data); + break; + case 0x02: // Pitch MSB, Enable/Disable - 0xb002/0xb001 (Sawtooth) + m_sawtooth.divider().write(true, data); + if (!m_sawtooth.divider().enable()) + { // Reset accumulator + m_sawtooth.clear_accum(); + } + break; + } +} + +void vrcvi_core::timer_w(u8 address, u8 data) +{ + switch (address) + { + case 0x00: // Timer latch - 0xf000 + m_timer.set_counter_latch(data); + break; + case 0x01: // Timer control - 0xf001/0xf002 + m_timer.timer_control_w(data); + break; + case 0x02: // IRQ Acknowledge - 0xf002/0xf001 + m_timer.irq_ack(); + break; + } +} + +void vrcvi_core::control_w(u8 data) +{ + // Global control - 0x9003 + m_control.write(data); +} diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/vrcvi/vrcvi.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/vrcvi/vrcvi.hpp new file mode 100644 index 000000000..f163114c0 --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/vrcvi/vrcvi.hpp @@ -0,0 +1,407 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Konami VRC VI sound emulation core +*/ + +#ifndef _VGSOUND_EMU_SRC_VRCVI_HPP +#define _VGSOUND_EMU_SRC_VRCVI_HPP + +#pragma once + +#include "../core/util.hpp" + +class vrcvi_intf : public vgsound_emu_core +{ + public: + vrcvi_intf() + : vgsound_emu_core("vrc_vi_intf") + { + } + + virtual void irq_w(bool irq) {} +}; + +class vrcvi_core : public vgsound_emu_core +{ + friend class vrcvi_intf; + + private: + // Common ALU for sound channels + class alu_t : public vgsound_emu_core + { + private: + class divider_t : public vgsound_emu_core + { + public: + divider_t() + : vgsound_emu_core("vrc_vi_frequency_divider") + , m_divider(0) + , m_enable(0) + { + } + + void reset() + { + m_divider = 0; + m_enable = 0; + } + + void write(bool msb, u8 data); + + // getters + inline u16 divider() { return m_divider; } + + inline bool enable() { return m_enable; } + + private: + u16 m_divider : 12; // divider (pitch) + u16 m_enable : 1; // channel disable flag + }; + + public: + alu_t(std::string tag, vrcvi_core &host) + : vgsound_emu_core(tag) + , m_host(host) + , m_divider(divider_t()) + , m_counter(0) + , m_cycle(0) + , m_out(0) + { + } + + virtual void reset(); + virtual bool tick(); + + virtual s8 get_output() + { + m_out = 0; + return 0; + } + + // accessors + inline void clear_cycle() { m_cycle = 0; } + + // getters + divider_t ÷r() { return m_divider; } + + inline u16 counter() { return m_counter; } + + inline u8 cycle() { return m_cycle; } + + // for previwe/debug only + inline s8 out() { return m_out; } + + protected: + vrcvi_core &m_host; + divider_t m_divider; + u16 m_counter = 0; // clock counter + u8 m_cycle = 0; // clock cycle + s8 m_out = 0; // output per channel + }; + + // 2 Pulse channels + class pulse_t : public alu_t + { + private: + // Control bits + class pulse_control_t + { + public: + pulse_control_t() + : m_mode(0) + , m_duty(0) + , m_volume(0) + { + } + + void reset() + { + m_mode = 0; + m_duty = 0; + m_volume = 0; + } + + // accessors + inline void write(u8 data) + { + m_mode = (data >> 7) & 0x1; + m_duty = (data >> 4) & 0x7; + m_volume = (data >> 0) & 0xf; + } + + // getters + inline bool mode() { return m_mode; } + + inline u8 duty() { return m_duty; } + + inline u8 volume() { return m_volume; } + + private: + u8 m_mode : 1; // duty toggle flag + u8 m_duty : 3; // 3 bit duty cycle + u8 m_volume : 4; // 4 bit volume + }; + + public: + pulse_t(vrcvi_core &host) + : alu_t("vrc_vi_pulse", host) + , m_control(pulse_control_t()) + { + } + + virtual void reset() override; + virtual bool tick() override; + virtual s8 get_output() override; + + // getters + pulse_control_t &control() { return m_control; } + + private: + pulse_control_t m_control; + }; + + // 1 Sawtooth channel + class sawtooth_t : public alu_t + { + public: + sawtooth_t(vrcvi_core &host) + : alu_t("vrc_vi_sawtooth", host) + , m_rate(0) + , m_accum(0) + { + } + + virtual void reset() override; + virtual bool tick() override; + virtual s8 get_output() override; + + // accessors + inline void clear_accum() { m_accum = 0; } + + // setters + inline void set_rate(u8 rate) { m_rate = rate; } + + // getters + inline u8 rate() { return m_rate; } + + inline u8 accum() { return m_accum; } + + private: + u8 m_rate = 0; // sawtooth accumulate rate + u8 m_accum = 0; // sawtooth accumulator, high 5 bit is accumulated to output + }; + + // Internal timer + class timer_t : public vgsound_emu_core + { + private: + // Control bits + class timer_control_t : public vgsound_emu_core + { + public: + timer_control_t() + : vgsound_emu_core("vrc_vi_timer_control") + , m_irq_trigger(0) + , m_enable_ack(0) + , m_enable(0) + , m_sync(0) + { + } + + void reset() + { + m_irq_trigger = 0; + m_enable_ack = 0; + m_enable = 0; + m_sync = 0; + } + + // accessors + inline void irq_set(bool irq) { m_irq_trigger = irq ? 1 : 0; } + + // setters + inline void set_enable_ack(bool enable_ack) + { + m_enable_ack = enable_ack ? 1 : 0; + } + + inline void set_enable(bool enable) { m_enable = enable ? 1 : 0; } + + inline void set_sync(bool sync) { m_sync = sync ? 1 : 0; } + + // getters + inline bool irq_trigger() { return m_irq_trigger; } + + inline bool enable_ack() { return m_enable_ack; } + + inline bool enable() { return m_enable; } + + inline bool sync() { return m_sync; } + + private: + u8 m_irq_trigger : 1; + u8 m_enable_ack : 1; + u8 m_enable : 1; + u8 m_sync : 1; + }; + + public: + timer_t(vrcvi_core &host) + : vgsound_emu_core("vrc_vi_timer") + , m_host(host) + , m_timer_control(timer_control_t()) + , m_prescaler(341) + , m_counter(0) + , m_counter_latch(0) + { + } + + void reset(); + bool tick(); + void counter_tick(); + + // IRQ update + void update() { m_host.m_intf.irq_w(m_timer_control.irq_trigger()); } + + void irq_set() + { + if (!m_timer_control.irq_trigger()) + { + m_timer_control.irq_set(true); + update(); + } + } + + void irq_clear() + { + if (m_timer_control.irq_trigger()) + { + m_timer_control.irq_set(false); + update(); + } + } + + // accessors + void reset_counter() + { + m_counter = m_counter_latch; + m_prescaler = 341; + } + + void timer_control_w(u8 data) + { + m_timer_control.set_enable_ack((data >> 0) & 1); + m_timer_control.set_enable((data >> 1) & 1); + m_timer_control.set_sync((data >> 2) & 1); + if (m_timer_control.enable()) + { + reset_counter(); + } + irq_clear(); + } + + void irq_ack() + { + irq_clear(); + m_timer_control.set_enable(m_timer_control.enable_ack()); + } + + // setters + inline void set_counter_latch(u8 counter_latch) { m_counter_latch = counter_latch; } + + // getters + timer_control_t &timer_control() { return m_timer_control; } + + inline s16 prescaler() { return m_prescaler; } + + inline u8 counter() { return m_counter; } + + inline u8 counter_latch() { return m_counter_latch; } + + private: + vrcvi_core &m_host; // host core + timer_control_t m_timer_control; // timer control bits + s16 m_prescaler = 341; // prescaler + u8 m_counter = 0; // clock counter + u8 m_counter_latch = 0; // clock counter latch + }; + + class global_control_t : public vgsound_emu_core + { + public: + global_control_t() + : vgsound_emu_core("vrc_vi_global_control") + , m_halt(0) + , m_shift(0) + { + } + + void reset() + { + m_halt = 0; + m_shift = 0; + } + + // accessors + inline void write(u8 data) + { + m_halt = (data >> 0) & 1; + m_shift = (data >> 1) & 3; + } + + // getters + inline bool halt() { return m_halt; } + + inline u8 shift() { return m_shift; } + + private: + u8 m_halt : 1; // halt sound + u8 m_shift : 2; // 4/8 bit right shift + }; + + public: + // constructor + vrcvi_core(vrcvi_intf &intf) + : vgsound_emu_core("vrc_vi") + , m_intf(intf) + , m_pulse{*this, *this} + , m_sawtooth(*this) + , m_timer(*this) + , m_control(global_control_t()) + , m_out(0) + { + } + + // accessors, getters, setters + void pulse_w(u8 voice, u8 address, u8 data); + void saw_w(u8 address, u8 data); + void timer_w(u8 address, u8 data); + void control_w(u8 data); + + // internal state + void reset(); + void tick(); + + // 6 bit output + inline s8 out() { return m_out; } + + // for debug/preview only + inline s8 pulse_out(u8 pulse) { return (pulse < 2) ? m_pulse[pulse].out() : 0; } + + inline s8 sawtooth_out() { return m_sawtooth.out(); } + + private: + vrcvi_intf &m_intf; + + std::array m_pulse; // 2 pulse channels + sawtooth_t m_sawtooth; // sawtooth channel + timer_t m_timer; // internal timer + global_control_t m_control; // control + + s8 m_out = 0; // 6 bit output +}; + +#endif diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/x1_010/README.md b/extern/vgsound_emu-modified/vgsound_emu/src/x1_010/README.md new file mode 100644 index 000000000..656e5c60f --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/x1_010/README.md @@ -0,0 +1,97 @@ + +# Seta/Allumer X1-010 + +## Summary + +- 16 voice wavetable or PCM + - 8 bit signed for both wavetable and PCM + - 128 width long waveform + - wavetable playback must be paired with envelope + - envelope shape is 4 bit stereo, 128 width long waveform + - waveform and envelope shape stored at each half area on RAM space + - total accessible memory for PCM: 1 MByte + +## Source code + +- x1_010.hpp: Base header + - x1_010.cpp: Source emulation core + +## Description + +the chip has 16 voices, all voices can be switchable to Wavetable or PCM sample playback mode. It has also 2 output channels, but no known hardware using this feature for stereo sound. + +Wavetable needs to paired with envelope, it's always enabled and similar as AY PSG's one but its shape is stored at RAM. + +PCM volume is stored by each register. + +Both volume is 4bit per output. + +Everything except PCM sample is stored at paired 8 bit RAM. + +## RAM layout + +common case: Address bit 12 is swapped when RAM is shared with CPU + +### Voice registers (0000...007f) + +0000...0007 Voice #0 Register + +``` +Address Bits Description + 7654 3210 +0 x--- ---- Frequency divider* + ---- -x-- Envelope one-shot mode + ---- --x- Sound format + ---- --0- PCM + ---- --1- Wavetable + ---- ---x Keyon/off +PCM case: +1 xxxx xxxx Volume (Each nibble is for each output) + +2 xxxx xxxx Frequency + +4 xxxx xxxx Start address / 4096 + +5 xxxx xxxx 0x100 - (End address / 4096) +Wavetable case: +1 ---x xxxx Wavetable data select + +2 xxxx xxxx Frequency LSB +3 xxxx xxxx "" MSB + +4 xxxx xxxx Envelope period + +5 ---x xxxx Envelope shape select (!= 0 : Reserved for Voice registers) +``` + +0008...000f Voice #1 Register +... +0078...007f Voice #15 Register + +### Envelope shape data (0080...0fff) + +Same format as volume; Each nibble is for each output + +0080...00ff Envelope shape #1 data +0100...017f Envelope shape #2 data +... +0f80...0fff Envelope shape #31 data + +### Waveform data (1000...1fff) + +1000...107f Waveform #0 data +1080...10ff Waveform #1 data +... +1f80...1fff Waveform #31 data + +## Frequency calculation + +``` +Wavetable, Divider Clear: Frequency value * (Input clock / 524288) +Wavetable, Divider Set: Frequency value * (Input clock / 1048576) +PCM, Divider Clear: Frequency value * (Input clock / 8192) +PCM, Divider Set: Frequency value * (Input clock / 16384) +Envelope: Envelope period * (Input clock / 524288) - Frequency divider not affected? +``` + +Frequency divider is higher precision or just right shift? needs verification. diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/x1_010/x1_010.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/x1_010/x1_010.cpp new file mode 100644 index 000000000..23b7df72b --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/x1_010/x1_010.cpp @@ -0,0 +1,163 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Seta/Allumer X1-010 Emulation core +*/ + +#include "x1_010.hpp" + +void x1_010_core::tick() +{ + // reset output + m_out[0] = m_out[1] = 0; + for (voice_t &elem : m_voice) + { + elem.tick(); + m_out[0] += elem.out(0); + m_out[1] += elem.out(1); + } +} + +void x1_010_core::voice_t::tick() +{ + m_out[0] = m_out[1] = 0; + if (m_flag.keyon()) + { + if (m_flag.wavetable()) // Wavetable + { + // envelope, each nibble is for each output + u8 vol = + m_host.m_envelope[(bitfield(m_end_envshape, 0, 5) << 7) | bitfield(m_env_acc, 10, 7)]; + m_vol_out[0] = bitfield(vol, 4, 4); + m_vol_out[1] = bitfield(vol, 0, 4); + m_env_acc += m_start_envfreq; + if (m_flag.env_oneshot() && bitfield(m_env_acc, 17)) + { + m_flag.set_keyon(false); + } + else + { + m_env_acc = bitfield(m_env_acc, 0, 17); + } + // get wavetable data + m_data = m_host.m_wave[(bitfield(m_vol_wave, 0, 5) << 7) | bitfield(m_acc, 11, 7)]; + m_acc = bitfield(m_acc + (m_freq << (1 - m_flag.div())), 0, 18); + } + else // PCM sample + { + // volume register, each nibble is for each output + m_vol_out[0] = bitfield(m_vol_wave, 4, 4); + m_vol_out[1] = bitfield(m_vol_wave, 0, 4); + // get PCM sample + m_data = m_host.m_intf.read_byte(bitfield(m_acc, 5, 20)); + m_acc += u32(bitfield(m_freq, 0, 8)) << (1 - m_flag.div()); + if ((m_acc >> 17) > u32(0xff ^ m_end_envshape)) + { + m_flag.set_keyon(false); + } + } + m_out[0] = m_data * m_vol_out[0]; + m_out[1] = m_data * m_vol_out[1]; + } +} + +u8 x1_010_core::ram_r(u16 offset) +{ + if (offset & 0x1000) + { // wavetable data + return m_wave[offset & 0xfff]; + } + else if (offset & 0xf80) + { // envelope shape data + return m_envelope[offset & 0xfff]; + } + else + { // channel register + return m_voice[bitfield(offset, 3, 4)].reg_r(offset & 0x7); + } +} + +void x1_010_core::ram_w(u16 offset, u8 data) +{ + if (offset & 0x1000) + { // wavetable data + m_wave[offset & 0xfff] = data; + } + else if (offset & 0xf80) + { // envelope shape data + m_envelope[offset & 0xfff] = data; + } + else + { // channel register + m_voice[bitfield(offset, 3, 4)].reg_w(offset & 0x7, data); + } +} + +u8 x1_010_core::voice_t::reg_r(u8 offset) +{ + switch (offset & 0x7) + { + case 0x00: + return (m_flag.div() << 7) | (m_flag.env_oneshot() << 2) | (m_flag.wavetable() << 1) | + (m_flag.keyon() << 0); + case 0x01: return m_vol_wave; + case 0x02: return bitfield(m_freq, 0, 8); + case 0x03: return bitfield(m_freq, 8, 8); + case 0x04: return m_start_envfreq; + case 0x05: return m_end_envshape; + default: break; + } + return 0; +} + +void x1_010_core::voice_t::reg_w(u8 offset, u8 data) +{ + switch (offset & 0x7) + { + case 0x00: + { + const bool prev_keyon = m_flag.keyon(); + m_flag.write(data); + if (!prev_keyon && m_flag.keyon()) // Key on + { + m_acc = m_flag.wavetable() ? 0 : (u32(m_start_envfreq) << 17); + m_env_acc = 0; + } + break; + } + case 0x01: m_vol_wave = data; break; + case 0x02: m_freq = (m_freq & 0xff00) | data; break; + case 0x03: m_freq = (m_freq & 0x00ff) | (u16(data) << 8); break; + case 0x04: m_start_envfreq = data; break; + case 0x05: m_end_envshape = data; break; + default: break; + } +} + +void x1_010_core::voice_t::reset() +{ + m_flag.reset(); + m_vol_wave = 0; + m_freq = 0; + m_start_envfreq = 0; + m_end_envshape = 0; + m_acc = 0; + m_env_acc = 0; + m_data = 0; + m_vol_out.fill(0); + m_out.fill(0); +} + +void x1_010_core::reset() +{ + for (auto &elem : m_voice) + { + elem.reset(); + } + + m_envelope.fill(0); + m_wave.fill(0); + m_out.fill(0); +} diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/x1_010/x1_010.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/x1_010/x1_010.hpp new file mode 100644 index 000000000..34a4ab99a --- /dev/null +++ b/extern/vgsound_emu-modified/vgsound_emu/src/x1_010/x1_010.hpp @@ -0,0 +1,179 @@ +/* + License: Zlib + see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details + + Copyright holder(s): cam900 + Seta/Allumer X1-010 Emulation core +*/ + +#ifndef _VGSOUND_EMU_SRC_X1_010_HPP +#define _VGSOUND_EMU_SRC_X1_010_HPP + +#pragma once + +#include "../core/util.hpp" + +class x1_010_core : public vgsound_emu_core +{ + friend class vgsound_emu_mem_intf; + + private: + // 16 voices in chip + class voice_t : public vgsound_emu_core + { + private: + class flag_t : public vgsound_emu_core + { + public: + flag_t() + : vgsound_emu_core("x1_010_voice_flag") + , m_div(0) + , m_env_oneshot(0) + , m_wavetable(0) + , m_keyon(0) + { + } + + // internal state + void reset() + { + m_div = 0; + m_env_oneshot = 0; + m_wavetable = 0; + m_keyon = 0; + } + + // register accessor + inline void write(u8 data) + { + m_div = (data >> 7) & 1; + m_env_oneshot = (data >> 2) & 1; + m_wavetable = (data >> 1) & 1; + m_keyon = (data >> 0) & 1; + } + + // Setters + inline void set_keyon(bool keyon) { m_keyon = keyon; } + + // Getters + inline bool div() { return m_div; } + + inline bool env_oneshot() { return m_env_oneshot; } + + inline bool wavetable() { return m_wavetable; } + + inline bool keyon() { return m_keyon; } + + private: + u8 m_div : 1; + u8 m_env_oneshot : 1; + u8 m_wavetable : 1; + u8 m_keyon : 1; + }; + + public: + // constructor + voice_t(x1_010_core &host) + : vgsound_emu_core("x1_010_voice") + , m_host(host) + , m_flag(flag_t()) + , m_vol_wave(0) + , m_freq(0) + , m_start_envfreq(0) + , m_end_envshape(0) + , m_acc(0) + , m_env_acc(0) + , m_data(0) + , m_vol_out{0} + , m_out{0} + { + } + + // internal state + void reset(); + void tick(); + + // register accessor + u8 reg_r(u8 offset); + void reg_w(u8 offset, u8 data); + + // getters + inline s32 out(u8 ch) { return m_out[ch & 1]; } + + private: + // host flag + x1_010_core &m_host; + // registers + flag_t m_flag; + u8 m_vol_wave = 0; + u16 m_freq = 0; + u8 m_start_envfreq = 0; + u8 m_end_envshape = 0; + + // internal registers + u32 m_acc = 0; + u32 m_env_acc = 0; + s8 m_data = 0; + std::array m_vol_out = {0}; + + // for preview only + std::array m_out = {0}; + }; + + public: + // constructor + x1_010_core(vgsound_emu_mem_intf &intf) + : vgsound_emu_core("x1_010") + , m_voice{*this, + *this, + *this, + *this, + *this, + *this, + *this, + *this, + *this, + *this, + *this, + *this, + *this, + *this, + *this, + *this} + , m_intf(intf) + , m_envelope{0} + , m_wave{0} + , m_out{0} + { + } + + // register accessor + u8 ram_r(u16 offset); + void ram_w(u16 offset, u8 data); + + // getters + inline s32 output(u8 ch) { return m_out[ch & 1]; } + + // internal state + void reset(); + void tick(); + + // for preview only + inline s32 voice_out(u8 voice, u8 ch) + { + return (voice < 16) ? m_voice[voice].out(ch & 1) : 0; + } + + private: + std::array m_voice; + vgsound_emu_mem_intf &m_intf; + + // RAM + std::array m_envelope = {0}; + std::array m_wave = {0}; + + // output data + std::array m_out = {0}; +}; + +#endif diff --git a/instruments/FM/bass/acoustic bass.dmp b/instruments/FM/bass/Acoustic Bass 2.dmp similarity index 100% rename from instruments/FM/bass/acoustic bass.dmp rename to instruments/FM/bass/Acoustic Bass 2.dmp diff --git a/instruments/FM/bass/Acoustic Bass.dmp b/instruments/FM/bass/Acoustic Bass.dmp new file mode 100644 index 000000000..fb37c096d Binary files /dev/null and b/instruments/FM/bass/Acoustic Bass.dmp differ diff --git a/instruments/FM/bass/Basses.opm b/instruments/FM/bass/Basses.opm new file mode 100644 index 000000000..0b308cbf8 --- /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/Electric Finger Bass.dmp b/instruments/FM/bass/Electric Finger Bass.dmp new file mode 100644 index 000000000..e189f5f5d 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 000000000..f191ec76e 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 000000000..2d49e919c 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 000000000..2be2803ce Binary files /dev/null and b/instruments/FM/bass/Electric Slap 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 000000000..9ac496227 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 000000000..2feefab3f Binary files /dev/null and b/instruments/FM/bass/SC-55 SynthBass101.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 000000000..08f8faca4 Binary files /dev/null and b/instruments/FM/bass/Yamaha MU Synth Bass 2.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 000000000..a60d21e9c 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 000000000..181d90d7f Binary files /dev/null and b/instruments/FM/effect/GEN_Wind.fui differ diff --git a/instruments/FM/guitar/Acoustic Nylon Guitar.dmp b/instruments/FM/guitar/Acoustic Nylon Guitar.dmp index b79addcf1..35fac263c 100644 Binary files a/instruments/FM/guitar/Acoustic Nylon Guitar.dmp 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 index 64e77b9c8..295a29d6e 100644 Binary files a/instruments/FM/guitar/Acoustic Steel Guitar.dmp 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 000000000..1bf6e88fb 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 000000000..bfe789894 Binary files /dev/null and b/instruments/FM/guitar/Banjo.opni differ diff --git a/instruments/FM/guitar/Electric Guitar Harmonics.dmp b/instruments/FM/guitar/Electric Guitar Harmonics.dmp index 07f210328..14a14b118 100644 Binary files a/instruments/FM/guitar/Electric Guitar Harmonics.dmp and b/instruments/FM/guitar/Electric Guitar Harmonics.dmp differ diff --git a/instruments/FM/guitar/Koto.opni b/instruments/FM/guitar/Koto.opni new file mode 100644 index 000000000..9bac79bae Binary files /dev/null and b/instruments/FM/guitar/Koto.opni differ diff --git a/instruments/FM/guitar/Oud.opni b/instruments/FM/guitar/Oud.opni new file mode 100644 index 000000000..029a0c2a5 Binary files /dev/null and b/instruments/FM/guitar/Oud.opni differ diff --git a/instruments/FM/guitar/Shamisen (Regular Pluck).opni b/instruments/FM/guitar/Shamisen (Regular Pluck).opni new file mode 100644 index 000000000..c25acca1e 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 000000000..ed9c9497d 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 000000000..3427fe2a7 Binary files /dev/null and b/instruments/FM/guitar/Sitar.opni differ diff --git a/instruments/FM/guitar/Tamboura (Bass Sitar).opni b/instruments/FM/guitar/Tamboura (Bass Sitar).opni new file mode 100644 index 000000000..e6514b73c Binary files /dev/null and b/instruments/FM/guitar/Tamboura (Bass Sitar).opni differ diff --git a/instruments/FM/keys/brickblock369 Harpsichord.dmp b/instruments/FM/keys/brickblock369 Harpsichord.dmp index a2aba1f2d..0f5ea587d 100644 Binary files a/instruments/FM/keys/brickblock369 Harpsichord.dmp and b/instruments/FM/keys/brickblock369 Harpsichord.dmp differ diff --git a/instruments/FM/percussion/Kalimba.fui b/instruments/FM/percussion/Kalimba.fui new file mode 100644 index 000000000..128deca3f Binary files /dev/null and b/instruments/FM/percussion/Kalimba.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 000000000..9fae9177b 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 000000000..dfb5eba1e Binary files /dev/null and b/instruments/OPL/Low Overdriven Guitar (Square Carrier).fui differ diff --git a/instruments/other/(SMS) 2-Arp Chord High.dmp b/instruments/other/(SMS) 2-Arp Chord High.dmp index 934017362..845babb2e 100644 Binary files a/instruments/other/(SMS) 2-Arp Chord High.dmp and b/instruments/other/(SMS) 2-Arp Chord High.dmp differ diff --git a/instruments/other/(SMS) 2-Arp Major Low.dmp b/instruments/other/(SMS) 2-Arp Major Low.dmp index 532c24565..f65623f0d 100644 Binary files a/instruments/other/(SMS) 2-Arp Major Low.dmp and b/instruments/other/(SMS) 2-Arp Major Low.dmp differ diff --git a/instruments/other/(SMS) 2-Arp Minor Low.dmp b/instruments/other/(SMS) 2-Arp Minor Low.dmp index 608e54832..5345dd459 100644 Binary files a/instruments/other/(SMS) 2-Arp Minor Low.dmp and b/instruments/other/(SMS) 2-Arp Minor Low.dmp differ diff --git a/instruments/other/(SMS) 3-Arp High.dmp b/instruments/other/(SMS) 3-Arp High.dmp index a5ebf9759..d0fab8e51 100644 Binary files a/instruments/other/(SMS) 3-Arp High.dmp and b/instruments/other/(SMS) 3-Arp High.dmp differ diff --git a/instruments/other/(SMS) 3-Arp Major.dmp b/instruments/other/(SMS) 3-Arp Major.dmp index 3d284983e..d9f9e3a7d 100644 Binary files a/instruments/other/(SMS) 3-Arp Major.dmp and b/instruments/other/(SMS) 3-Arp Major.dmp differ diff --git a/instruments/other/(SMS) 3-Arp Minor.dmp b/instruments/other/(SMS) 3-Arp Minor.dmp index a28275fb7..f36e80e97 100644 Binary files a/instruments/other/(SMS) 3-Arp Minor.dmp and b/instruments/other/(SMS) 3-Arp Minor.dmp differ diff --git a/instruments/other/(SMS) Arp Snare.dmp b/instruments/other/(SMS) Arp Snare.dmp index fa5734d4a..713595429 100644 Binary files a/instruments/other/(SMS) Arp Snare.dmp and b/instruments/other/(SMS) Arp Snare.dmp differ diff --git a/instruments/other/(SMS) Attack.dmp b/instruments/other/(SMS) Attack.dmp index 0be930625..ac5d24fff 100644 Binary files a/instruments/other/(SMS) Attack.dmp and b/instruments/other/(SMS) Attack.dmp differ diff --git a/instruments/other/(SMS) Buzz Noise.dmp b/instruments/other/(SMS) Buzz Noise.dmp index 8b7dfd97d..d1bc39212 100644 Binary files a/instruments/other/(SMS) Buzz Noise.dmp and b/instruments/other/(SMS) Buzz Noise.dmp differ diff --git a/instruments/other/(SMS) Crash.dmp b/instruments/other/(SMS) Crash.dmp index f9855bde3..6f05ff802 100644 Binary files a/instruments/other/(SMS) Crash.dmp and b/instruments/other/(SMS) Crash.dmp differ diff --git a/instruments/other/(SMS) Decay Noise.dmp b/instruments/other/(SMS) Decay Noise.dmp index 9a829f443..65e45cf8e 100644 Binary files a/instruments/other/(SMS) Decay Noise.dmp and b/instruments/other/(SMS) Decay Noise.dmp differ diff --git a/instruments/other/(SMS) Decay.dmp b/instruments/other/(SMS) Decay.dmp index 7f42d6ec0..8c2688d13 100644 Binary files a/instruments/other/(SMS) Decay.dmp and b/instruments/other/(SMS) Decay.dmp differ diff --git a/instruments/other/(SMS) Down Slider.dmp b/instruments/other/(SMS) Down Slider.dmp index 5d0b6b27b..cc13d5faa 100644 Binary files a/instruments/other/(SMS) Down Slider.dmp and b/instruments/other/(SMS) Down Slider.dmp differ diff --git a/instruments/other/(SMS) Hi-Hat & Note.dmp b/instruments/other/(SMS) Hi-Hat & Note.dmp index 3f92dc68e..cff02382f 100644 Binary files a/instruments/other/(SMS) Hi-Hat & Note.dmp and b/instruments/other/(SMS) Hi-Hat & Note.dmp differ diff --git a/instruments/other/(SMS) Hi-Hat Closed.dmp b/instruments/other/(SMS) Hi-Hat Closed.dmp index 24f92bf29..5e4e60a80 100644 Binary files a/instruments/other/(SMS) Hi-Hat Closed.dmp and b/instruments/other/(SMS) Hi-Hat Closed.dmp differ diff --git a/instruments/other/(SMS) Hi-Hat Open.dmp b/instruments/other/(SMS) Hi-Hat Open.dmp index 6726e12d3..ae6e88e73 100644 Binary files a/instruments/other/(SMS) Hi-Hat Open.dmp and b/instruments/other/(SMS) Hi-Hat Open.dmp differ diff --git a/instruments/other/(SMS) Kick Noise.dmp b/instruments/other/(SMS) Kick Noise.dmp index 8f2d3cbf9..b76fb8861 100644 Binary files a/instruments/other/(SMS) Kick Noise.dmp and b/instruments/other/(SMS) Kick Noise.dmp differ diff --git a/instruments/other/(SMS) Multi Slider.dmp b/instruments/other/(SMS) Multi Slider.dmp index abd3094a1..4f2bd3fc7 100644 Binary files a/instruments/other/(SMS) Multi Slider.dmp and b/instruments/other/(SMS) Multi Slider.dmp differ diff --git a/instruments/other/(SMS) Obvious Crash.dmp b/instruments/other/(SMS) Obvious Crash.dmp index 54c7387e0..0f54a85e3 100644 Binary files a/instruments/other/(SMS) Obvious Crash.dmp and b/instruments/other/(SMS) Obvious Crash.dmp differ diff --git a/instruments/other/(SMS) Record Scratch Down.dmp b/instruments/other/(SMS) Record Scratch Down.dmp index 8e28c4180..b3f4b07fa 100644 Binary files a/instruments/other/(SMS) Record Scratch Down.dmp and b/instruments/other/(SMS) Record Scratch Down.dmp differ diff --git a/instruments/other/(SMS) Record Scratch Up.dmp b/instruments/other/(SMS) Record Scratch Up.dmp index b17b654e2..a710de048 100644 Binary files a/instruments/other/(SMS) Record Scratch Up.dmp and b/instruments/other/(SMS) Record Scratch Up.dmp differ diff --git a/instruments/other/(SMS) Retrig.dmp b/instruments/other/(SMS) Retrig.dmp index 65c776102..b2822b8c1 100644 Binary files a/instruments/other/(SMS) Retrig.dmp and b/instruments/other/(SMS) Retrig.dmp differ diff --git a/instruments/other/(SMS) Ride.dmp b/instruments/other/(SMS) Ride.dmp index 01070ff71..45c4b5fec 100644 Binary files a/instruments/other/(SMS) Ride.dmp and b/instruments/other/(SMS) Ride.dmp differ diff --git a/instruments/other/(SMS) Snare.dmp b/instruments/other/(SMS) Snare.dmp index c0e32b212..d624b92ae 100644 Binary files a/instruments/other/(SMS) Snare.dmp and b/instruments/other/(SMS) Snare.dmp differ diff --git a/instruments/other/(SMS) Splash.dmp b/instruments/other/(SMS) Splash.dmp index e758a67b9..f32960724 100644 Binary files a/instruments/other/(SMS) Splash.dmp and b/instruments/other/(SMS) Splash.dmp differ diff --git a/instruments/other/(SMS) Thump & Note.dmp b/instruments/other/(SMS) Thump & Note.dmp index 41fb6d51d..3dccb56c2 100644 Binary files a/instruments/other/(SMS) Thump & Note.dmp and b/instruments/other/(SMS) Thump & Note.dmp differ diff --git a/instruments/other/(SMS) Tim Follin 6-Arp Fast Major.dmp b/instruments/other/(SMS) Tim Follin 6-Arp Fast Major.dmp index e306203cb..6440474cf 100644 Binary files a/instruments/other/(SMS) Tim Follin 6-Arp Fast Major.dmp and b/instruments/other/(SMS) Tim Follin 6-Arp Fast Major.dmp differ diff --git a/instruments/other/(SMS) Tim Follin 6-Arp Fast Minor.dmp b/instruments/other/(SMS) Tim Follin 6-Arp Fast Minor.dmp index a845fc1cb..105421b62 100644 Binary files a/instruments/other/(SMS) Tim Follin 6-Arp Fast Minor.dmp and b/instruments/other/(SMS) Tim Follin 6-Arp Fast Minor.dmp differ diff --git a/instruments/other/(SMS) Tim Follin 6-Arp Slow Major.dmp b/instruments/other/(SMS) Tim Follin 6-Arp Slow Major.dmp index 9536150c6..11eeafb4a 100644 Binary files a/instruments/other/(SMS) Tim Follin 6-Arp Slow Major.dmp and b/instruments/other/(SMS) Tim Follin 6-Arp Slow Major.dmp differ diff --git a/instruments/other/(SMS) Tim Follin 6-Arp Slow Minor.dmp b/instruments/other/(SMS) Tim Follin 6-Arp Slow Minor.dmp index 166a1cb3a..3cbc3f380 100644 Binary files a/instruments/other/(SMS) Tim Follin 6-Arp Slow Minor.dmp and b/instruments/other/(SMS) Tim Follin 6-Arp Slow Minor.dmp differ diff --git a/instruments/other/(SMS) Tim Follin Lead.dmp b/instruments/other/(SMS) Tim Follin Lead.dmp index 08a3a0815..69ab7d40d 100644 Binary files a/instruments/other/(SMS) Tim Follin Lead.dmp and b/instruments/other/(SMS) Tim Follin Lead.dmp differ diff --git a/instruments/other/(SMS) Tom A.dmp b/instruments/other/(SMS) Tom A.dmp index 8976d0cf5..813de521e 100644 Binary files a/instruments/other/(SMS) Tom A.dmp and b/instruments/other/(SMS) Tom A.dmp differ diff --git a/instruments/other/(SMS) Tom B.dmp b/instruments/other/(SMS) Tom B.dmp index 664021f6d..e06cccb81 100644 Binary files a/instruments/other/(SMS) Tom B.dmp and b/instruments/other/(SMS) Tom B.dmp differ diff --git a/instruments/other/(SMS) Up Slider.dmp b/instruments/other/(SMS) Up Slider.dmp index 765f156e8..b57cfffea 100644 Binary files a/instruments/other/(SMS) Up Slider.dmp and b/instruments/other/(SMS) Up Slider.dmp differ diff --git a/instruments/other/(SMS) Variable.dmp b/instruments/other/(SMS) Variable.dmp index 7ed3209f5..16a5c0f76 100644 Binary files a/instruments/other/(SMS) Variable.dmp and b/instruments/other/(SMS) Variable.dmp differ diff --git a/instruments/other/(SMS) Whistle.dmp b/instruments/other/(SMS) Whistle.dmp index 0fd9127c0..1ed679bb8 100644 Binary files a/instruments/other/(SMS) Whistle.dmp and b/instruments/other/(SMS) Whistle.dmp differ diff --git a/instruments/other/2A03 Noise Hi-Hat Closed.fui b/instruments/other/2A03 Noise Hi-Hat Closed.fui new file mode 100644 index 000000000..a24dff830 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 000000000..26889b0ad 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 000000000..7cf815e1a 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 000000000..0bcd5dac1 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 000000000..9beaea862 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 000000000..f22630105 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 000000000..e91831c8a 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 000000000..7ed4b8ac9 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 000000000..111f1da7e 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 000000000..b8c03a2de Binary files /dev/null and b/instruments/other/AY snare.fui differ diff --git a/papers/doc/4-instrument/n163.md b/papers/doc/4-instrument/n163.md index 0bd914453..ca4dd2e7c 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/6-sample/README.md b/papers/doc/6-sample/README.md index 0c038fdb9..4fbb6c914 100644 --- a/papers/doc/6-sample/README.md +++ b/papers/doc/6-sample/README.md @@ -4,29 +4,54 @@ 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.6, the following sound chips have sample support: - - NES/Ricoh 2A03 (with DPCM support 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, or if you are using an instrument with Sample type) - - PC Engine/TurboGrafx 16/Huc6280 (same conditions as above) - - Amiga/Paula (on all channels) - - Arcade/SEGA PCM (same as above) - - Neo Geo/Neo Geo CD (on the last 7 channels (6 if you are using Neo Geo CD) only and can be resampled the same way as above) - - Seta/Allumer X1-010 (same as YM2612) - - Atari Lynx - - MSM6258 and MSM6295 - - YMU759/MA-2 (last channel only) - - QSound - - ZX Spectrum 48k - - RF5C68 - - WonderSwan - - Tildearrow Sound Unit - - VERA (last channel only) - - Y8590 (last channel only) - - And a few more that I've forgotten to mention. +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 @@ -34,11 +59,8 @@ You can actually tweak your samples in Furnace's sample editor, which can be acc In there, you can modify certain data pertaining to your sample, such as the: - 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, from 0Hz (no sample movement) to 65535Hz (65.5kHz). + - the sample rate. - what frequencies to filter, along with filter level/sweep and resonance options (much like the C64) - and many more. The changes you make will be applied as soon as you've committed them to your sample, but they can be undoed and redoed, just like text. - -# tips -If you have a sample you wanna use that is about 44100 or anything over 32000Hz, downsample the sample to 32000Hz so that the pitch of the sample in Furnace stays like the original audio file. You can do this in Audacity by going to the bottom left of the screen (If you see "Project Rate (Hz)" you are there), change the project rate to 32000Hz and save the file to wav in Audacity using "File -> Export -> Export as WAV". diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 6d428f3eb..080e72690 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -27,6 +27,7 @@ this is a list of systems that Furnace supports, including each system's effects - [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) @@ -35,5 +36,6 @@ this is a list of systems that Furnace supports, including each system's effects - [Konami VRC6](vrc6.md) - [Famicom Disk System](fds.md) - [Nintendo MMC5](mmc5.md) +- [SNES](snes.md) Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but... diff --git a/papers/doc/7-systems/dac.md b/papers/doc/7-systems/dac.md new file mode 100644 index 000000000..856db2cd0 --- /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 faf305c60..d483bd57c 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 6057fa3c9..efdc3c506 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 61e931394..c7952a55b 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/snes.md b/papers/doc/7-systems/snes.md new file mode 100644 index 000000000..5bb1ae5f1 --- /dev/null +++ b/papers/doc/7-systems/snes.md @@ -0,0 +1,18 @@ +# Super NES + +The successor to NES to compete with Genesis. Now packing with superior graphics and sample-based audio. Also known as Super Famicom. + +Its audio subsystem, developed by Sony, features the DSP chip, SPC700 microcontroller and 64KB of dedicated SRAM used by both. This whole system itself is pretty much a separate computer that the main CPU needs to upload its program and samples to. + +The DSP chip can + +Furnace communicates with the DSP directly and provide a full 64KB memory. This memory might be reduced excessively on ROM export to make up for playback engine and pattern data. + +# effects + +Note: this chip has a signed left/right level. Which can be used for inverted (surround) stereo. A signed 8-bit value means 80 - FF = -128 - -1. Other values work normally. A value of -128 is not recommended as it could cause overflows. + +- `10xx`: Set echo feedback level. This effect will apply to all channels. +- `11xx`: Set echo left level (signed 8-bit). This effect will apply to all channels. +- `12xx`: Set echo right level (signed 8-bit). This effect will apply to all channels. +- `13xx`: Set the length of the echo delay buffer. This will also affect the size of the sample RAM! diff --git a/papers/doc/7-systems/soundunit.md b/papers/doc/7-systems/soundunit.md index d9d0abd70..34cc16b4e 100644 --- a/papers/doc/7-systems/soundunit.md +++ b/papers/doc/7-systems/soundunit.md @@ -1,5 +1,5 @@ # tildearrow Sound Unit -This is a fantasy sound chip, used in the specs2 fantasy computer designed by tildearrow. It includes native support for sample playback, but with only 8KB of sample data. Since 0.6pre1, this sound chip is no longer hidden by default and can be accessed through the module creation screen and can be added or removed. +This is a fantasy sound chip, used in the specs2 fantasy computer designed by tildearrow. It includes native support for sample playback, but with only 8KB or 64KB of sample data, depending on the configuration used. Since 0.6pre1, this sound chip is no longer hidden by default and can be accessed through the module creation screen and can be added or removed. # effects @@ -12,7 +12,7 @@ This is a fantasy sound chip, used in the specs2 fantasy computer designed by ti - 5: periodic noise - 6: XOR sine - 7: XOR triangle -- `12xx`: set waveform (0 to 7F) +- `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 @@ -25,7 +25,7 @@ This is a fantasy sound chip, used in the specs2 fantasy computer designed by ti - `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 low byte +- `1Axx`: set cutoff sweep period high byte - `1Bxx`: set frequency sweep boundary - `1Cxx`: set volume sweep boundary - `1Dxx`: set cutoff sweep boundary diff --git a/papers/doc/7-systems/x1-010.md b/papers/doc/7-systems/x1-010.md index 411afb3e7..80b5b41a3 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 1c6cd74f3..9e25284bf 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/export-tech.md b/papers/export-tech.md index 3bf715469..dbfadece3 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 bef8b1f81..1d4e7fc76 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,6 +32,22 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: +- 116: Furnace 0.6pre1.5 +- 115: Furnace dev115 +- 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 @@ -237,6 +253,12 @@ size | description | - 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 @@ -318,7 +340,13 @@ size | description 1 | volume macro still applies after end (>=99) or reserved 1 | broken outVol (>=99) or reserved 1 | E1xy and E2xy stop on same note (>=100) or reserved - 8 | 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 + 1 | automatic system name detection (>=115) or reserved + | - this one isn't a compatibility flag, but it's here for convenience... + 3 | reserved --- | **virtual tempo data** 2 | virtual tempo numerator of first song (>=96) or reserved 2 | virtual tempo denominator of first song (>=96) or reserved @@ -328,6 +356,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 @@ -428,6 +463,7 @@ size | description | - 29: SNES | - 30: Sound Unit | - 31: Namco WSG + | - 32: OPL (drums) 1 | reserved STR | instrument name --- | **FM instrument data** @@ -466,7 +502,12 @@ size | description 1 | vib 1 | ws 1 | ksr - 12 | reserved + 1 | operator enabled (>=114) or reserved + 1 | KVS mode (>=115) or reserved + | - 0: off + | - 1: on + | - 2: auto (depending on alg) + 10 | reserved --- | **Game Boy instrument data** 1 | volume 1 | direction @@ -519,7 +560,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 @@ -527,6 +579,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 @@ -794,6 +847,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 @@ -804,13 +999,53 @@ size | description 4 | "WAVE" block ID 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 @@ -819,7 +1054,7 @@ size | description 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 diff --git a/papers/zsm-format.md b/papers/zsm-format.md new file mode 100644 index 000000000..739281254 --- /dev/null +++ b/papers/zsm-format.md @@ -0,0 +1,142 @@ +# ZSM format specification + +#### Zsound Repo + +ZSM is part of the Zsound suite of Commander X16 audio tools found at:
+https://github.com/ZeroByteOrg/zsound/ + + +#### Current ZSM Revision: 1 + +ZSM is a standard specifying both a data stream format and a file structure for containing the data stream. This document provides the standard for both the ZSM stream format and for the ZSM container file format. + +Whenever it becomes necessary to modify the ZSM standard in such a way that existing software will not be compatible with files using the newer standard, this version number will be incremented, up to a maximum value of 254. + +Version 255 (-1) is reserved for internal use by the player + +#### Headerless Data File Format: + + Since Kernal version r39, it is possible to load data files that do not have the CBM 2-byte load-to-address header. As of version r41, this functionality is equally accessible in the standard interactive BASIC interface. As the "PRG" header is no longer necessary, ZSM files will NOT contain this header in order to appear as any other common data file such as ``.wav``, ``.png``, etc. As such, users and programs must use the "headerless mode" when loading a ZSM into memory on the Commander X16. The previously-suggested dummy PRG header has been incorporated to the ZSM header as a magic header for file identity verification purposes. + + +## ZSM file composition + + Offset|Length|Field + --|--|-- + 0x00|16|ZSM HEADER + 0x10|variable|ZSM STREAM + ?|?|(optional) PCM HEADER + ?|variable|(optional) PCM DATA + +### ZSM Header + +The ZSM header is 16 bytes long. + +- All multi-byte values are little endian unless specified otherwise +- All offsets are relative to the beginning of the ZSM header + +Offset|Length|Field|Description +---|---|---|--- +0x00|2|Magic Header| The string 'zm' (binary 0x7a 0x6d) +0x02|1|Version| ZSM Version. 0-0xFE (0xFF is reserved) +0x03|3|Loop Point|Offset to the starting point of song loop. 0 = no loop. +0x06|3|PCM offset|Offset to the beginning of the PCM index table (if present). 0 = no PCM header or data is present. +0x09|1|FM channel mask|Bit 0-7 are set if the corresponding OPM channel is used by the music. +0x0a|2|PSG channel mask|Bits 0-15 are set if the corresponding PSG channel is used by the music. +0x0c|2|Tick Rate|The rate (in Hz) for song delay ticks. *60Hz (one tick per frame) is recommended.* +0x0e|2|reserved| Reserved for future use. Set to zero. + + +### ZSM Music Data Stream Format + +Byte 0|Byte 1 (variable)|Byte n|Byte n+1 (variable)|...|End of stream +---|---|---|---|---|--- +CMD|DATA|CMD|DATA|...|0x80 + +#### CMD (command) byte values +CMD bytes are bit-packed to hold a command Type ID and a value (n) as follows: + +CMD|Bit Pattern|Type|Arg. Bytes|Action +---|--|--|--|----- +0x00-0x3F|`00nnnnnn`|PSG write|1 | Write the following byte into PSG register offset *n*. (from 0x1F9C0 in VRAM) + 0x40 |`01000000`|EXTCMD |1+?| The following byte is an extension command. (see below for EXTCMD syntax) + 0x41-0x7F|`01nnnnnn`|FM write |2*n* | Write the following *n* reg/val pairs into the YM2151. +0x80|`10000000`|EOF |0 |This byte MUST be present at the end of the data stream. Player may loop or halt as necessary. +0x81-0xFF|`1nnnnnnn`|Delay |0 |Delay *n* ticks. + +#### EXTCMD: +The EXTCMD byte is formatted as `ccnnnnnn` where `c`=channel and `n`=number of bytes that follow. If the player wishes to ignore a channel, it can simply advance `n` bytes and continue processing. See EXTCMD Channel Specifications below for more details. + +### PCM Header + +The size and contents of the PCM header table is not yet decided. This will depend largely on the strucure of EXTCMD channel 0, and be covered in detail in that specification. + +Any offset values contained in the PCM data header block will be relative to the beginning of the PCM header, not the ZSM header. The intention is to present the digital audio portion as a set of digi clips ("samples" in tracker terminology) whose playback can be triggered by EXTCMD channel zero. + +### PCM Sample Data + +This will be a blob of PCM data with no internal formatting. Indeces / format information / loop points / etc regarding this blob will be provided via the PCM header. The end of this blob will be the end of the ZSM file. + + +## EXTCMD Channel Scifications + +Extension commands provide optional functionality within a ZSM music file. EXTCMD may be ignored by any player. EXTCMD defines 4 "channels" of message streams. Players may implement support for any, all, or none of the channels as desired. An EXTCMD may specify up to 63 bytes of data. If more data than this is required, then it must be broken up into multiple EXTCMDs. + +##### EXTCMD in ZSM stream context: + +...|CMD 0x40|EXTCMD|N bytes|CMD|... +---|---|---|---|---|--- + +##### EXTCMD byte format: + +Bit Pattern|C|N +--|--|-- +`ccnnnnnn`|Extension Channel ID|Number of bytes that follow + + + +##### EXTCMD Channels: +0. PCM instrument channel +1. Expansion Sound Devices +2. Synchronization events +3. Custom + +The formatting of the data within these 4 channels is presently a work in progress. Definitions for channels 0-3 will be part of the official ZSM specifications and implemented in the Zsound library. Significant changes within one of these three channels' structure may result in a new ZSM version number being issued. The formatting and content of the 3 official EXTCMD channels will be covered here. + +The Custom channel data may take whatever format is desired for any particular purpose with the understanding that the general ecosystem of ZSM-aware applications will most likely ignore them. + +### EXTCMD Channel: +#### 0: PCM audio + +The structure of data within this channel is not yet defined. + +#### 1: Expansion Sound Devices + +This channel is for data intended for "well-known" expansion hardware used with the Commander X16. As the community adopts various expansion hardware, such devices will be given a standard "ID" number so that all ZSM files will agree on which device is being referenced by expansion HW data. + +The specification of new chip IDs should not affect the format of ZSM itself, and thus will not result in a ZSM version update. Players will simply need to update their list of known hardware. + +Players implementing this channel should implement detection routines during init to determine which (if any) expansion hardware is present. Any messages intended for a chip that is not present in the system should be skipped. + +An expansion HW write will contain the following data: + +Chip ID|Nuber of writes (`N`)| `N` tuples of data +--|--|-- +one byte|one byte|N * tuple_size bytes + +- The total number of bytes MUST equal exactly the number of bytes specified in the preceding EXTCMD. +- The tuple_size is determined by the needs of the device, and thus will be specified per-device along with its chip ID assignment. This is likely to be 1-3 bytes for most devices. + +There are currently no supported expansion HW IDs assigned. + +#### 2: Synchronization Events + +The purpose of this channel is to provide for music synchronization cues that applications may use to perform operations in sync with the music (such as when the Goombas jump in New Super Mario Bros in time with the BOP! BOP! notes in the music). It is intended for the reference player to provide a sync channel callback, passing the data bytes to the callback function, and then to proceed with playback. + +The data structure within this channel is not yet defined. It is our intention to work with the community in order to collaborate on a useful structure. + +#### 3: Custom + +The purpose for this channel is that any project with an idea that does not fit neatly into the above categories may pack data into the project's music files in whatever form is required. It should be understood that these ZSMs will not be expected to use the extended behaviors outside of the project they were designed for. The music itself, however, should play properly. The only constraint is that the data must conform to the EXTCMD byte - supplying exactly the specified number of bytes per EXTCMD. + +The reference playback library in Zsound will implement this channel as a simple callback passing the memory location and data size to the referenced function, and take no further action internally. diff --git a/scripts/Cross-Linux-armhf.cmake b/scripts/Cross-Linux-armhf.cmake new file mode 100644 index 000000000..3e490ae12 --- /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 000000000..c115a807e --- /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/jack.cpp b/src/audio/jack.cpp index 6e7b7bf1f..f9502cf4f 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/sdlAudio.cpp b/src/audio/sdlAudio.cpp index 5c11a0342..f1d0e1e9d 100644 --- a/src/audio/sdlAudio.cpp +++ b/src/audio/sdlAudio.cpp @@ -110,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/check/check_dirent_type.c b/src/check/check_dirent_type.c new file mode 100644 index 000000000..e65a0d6be --- /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 000000000..653721e6d --- /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 000000000..87bf76dda --- /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/cli/cli.h b/src/cli/cli.h new file mode 100644 index 000000000..55ad36b38 --- /dev/null +++ b/src/cli/cli.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. + */ + +#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(); +}; + +#endif diff --git a/src/engine/brrUtils.c b/src/engine/brrUtils.c new file mode 100644 index 000000000..123ec165d --- /dev/null +++ b/src/engine/brrUtils.c @@ -0,0 +1,88 @@ +/* brrUtils - BRR audio codec utilities + * Copyright (C) 2022 tildearrow + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "brrUtils.h" + +long brrEncode(short* buf, unsigned char* out, long len) { + if (len==0) return 0; + // TODO + return 0; +} + +#define DO_ONE_SAMPLE \ + if (next&8) next|=0xfffffff8; \ +\ + next<<=(buf[0]>>4); /* range */ \ +\ + switch (control&0xc) { /* filter */ \ + case 0: \ + break; \ + case 4: \ + next+=(last1*15)/16; \ + break; \ + case 8: \ + next+=((last1*61)/32)-((last2*15)/16); \ + break; \ + case 12: \ + next+=((last1*115)/64)-((last2*13)/16); \ + break; \ + } \ +\ + if (next>32767) next=32767; \ + if (next<-32768) next=-32768; \ +\ + last2=last1; \ + last1=next; \ + *out=next; \ + out++; + +long brrDecode(unsigned char* buf, short* out, long len) { + if (len==0) return 0; + + long total=0; + + int last1=0; + int last2=0; + int next=0; + + // don't read out of bounds + len-=8; + + for (long i=0; i>4; + DO_ONE_SAMPLE; + + next=buf[j]&15; + DO_ONE_SAMPLE; + } + + // end bit + total+=16; + if (control&1) break; + buf+=9; + } + + return total; +} diff --git a/src/engine/brrUtils.h b/src/engine/brrUtils.h new file mode 100644 index 000000000..ba3a6f534 --- /dev/null +++ b/src/engine/brrUtils.h @@ -0,0 +1,52 @@ +/* brrUtils - BRR audio codec utilities + * Copyright (C) 2022 tildearrow + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _BRR_UTILS_H +#define _BRR_UTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * read len samples from buf, encode in BRR and output to out. + * @param buf input data. + * @param out output buffer. shall be at least 9*(len/16) shorts in size. + * @param len input length (should be a multiple of 16. if it isn't, the output will be padded). + * @return number of written samples. + */ +long brrEncode(short* buf, unsigned char* out, long len); + +/** + * read len bytes from buf, decode BRR and output to out. + * @param buf input data. + * @param out output buffer. shall be at least 16*(len/9) shorts in size. + * @param len input length (shall be a multiple of 9). + * @return number of written bytes. + */ +long brrDecode(unsigned char* buf, short* out, long len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/engine/config.cpp b/src/engine/config.cpp index 92866a9f9..38cb2b042 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 d57b88e76..dd0c20548 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) @@ -155,6 +167,7 @@ enum DivDispatchCmds { DIV_CMD_X1_010_ENVELOPE_PERIOD, DIV_CMD_X1_010_ENVELOPE_SLIDE, DIV_CMD_X1_010_AUTO_ENVELOPE, + DIV_CMD_X1_010_SAMPLE_BANK_SLOT, DIV_CMD_WS_SWEEP_TIME, DIV_CMD_WS_SWEEP_AMOUNT, @@ -191,6 +204,8 @@ enum DivDispatchCmds { DIV_CMD_SU_SYNC_PERIOD_LOW, DIV_CMD_SU_SYNC_PERIOD_HIGH, + DIV_CMD_ADPCMA_GLOBAL_VOLUME, + DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol DIV_CMD_MAX @@ -414,11 +429,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 219a21414..a5134110e 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,10 @@ #include "platform/scc.h" #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 +220,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 +403,12 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do dispatch=new DivPlatformNamcoWSG; ((DivPlatformNamcoWSG*)dispatch)->setDeviceType(30); break; + 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 8cefbd676..9fc8b1af1 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" @@ -27,15 +29,11 @@ #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 "sfWrapper.h" #endif @@ -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; @@ -787,7 +1123,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(); @@ -797,6 +1133,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; @@ -831,7 +1172,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]; @@ -1078,6 +1419,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; @@ -1221,6 +1690,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { speedAB=false; playing=true; skipping=true; + memset(walked,0,8192); for (int i=0; isetSkipRegisterWrites(true); while (playing && curOrder1)) { if (nextTick(preserveDrift)) { skipping=false; return; @@ -1353,6 +1823,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<midiOut!=NULL) { + output->midiOut->send(TAMidiMessage(TA_MIDI_MACHINE_STOP,0,0)); + for (int i=0; i=0) { + output->midiOut->send(TAMidiMessage(0x80|(i&15),chan[i].curMidiNote,0)); + } + } + } for (int i=0; idispatch(DivCommand(DIV_CMD_GET_VOLMAX,dispatchChanOfChan[i]))<<8)|0xff; @@ -1618,6 +2105,7 @@ void DivEngine::previewSample(int sample, int note, int pStart, int pEnd) { BUSY_BEGIN; sPreview.pBegin=pStart; sPreview.pEnd=pEnd; + sPreview.dir=false; if (sample<0 || sample>=(int)song.sample.size()) { sPreview.sample=-1; sPreview.pos=0; @@ -1891,11 +2379,13 @@ void DivEngine::delInstrument(int index) { song.ins.erase(song.ins.begin()+index); song.insLen=song.ins.size(); for (int i=0; ipatLen; 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]--; + } } } } @@ -1906,7 +2396,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; @@ -1918,50 +2411,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); @@ -1989,7 +2494,7 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) { lastError="invalid wavetable header/data!"; delete wave; delete[] buf; - return false; + return NULL; } } else { try { @@ -2030,7 +2535,7 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) { } else { delete wave; delete[] buf; - return false; + return NULL; } } } catch (EndOfFileException& e) { @@ -2048,7 +2553,7 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) { } else { delete wave; delete[] buf; - return false; + return NULL; } } } @@ -2056,17 +2561,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) { @@ -2082,7 +2580,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; @@ -2092,17 +2593,35 @@ int DivEngine::addSample() { song.sampleLen=sampleCount+1; sPreview.sample=-1; sPreview.pos=0; + sPreview.dir=false; 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=""; @@ -2135,7 +2654,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"); @@ -2143,7 +2661,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) { @@ -2151,7 +2669,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); @@ -2161,7 +2679,7 @@ int DivEngine::addSampleFromFile(const char* path) { BUSY_END; lastError="file is empty!"; delete sample; - return -1; + return NULL; } if (len==(SIZE_MAX>>1)) { @@ -2169,7 +2687,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) { @@ -2177,7 +2695,7 @@ 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; @@ -2190,22 +2708,16 @@ 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; SFWrapper sfWrap; @@ -2217,15 +2729,15 @@ int DivEngine::addSampleFromFile(const char* path) { 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."; sfWrap.doClose(); BUSY_END; - return -1; + return NULL; } void* buf=NULL; sf_count_t sampleLen=sizeof(short); @@ -2313,31 +2825,200 @@ int DivEngine::addSampleFromFile(const char* path) { sample->centerRate=si.samplerate*pow(2.0,pitch/(12.0 * 100.0)); if(inst.loop_count && inst.loops[0].mode >= SF_LOOP_FORWARD) { + sample->loop=true; + sample->loopMode=(DivSampleLoopMode)(inst.loops[0].mode-SF_LOOP_FORWARD); sample->loopStart=inst.loops[0].start; sample->loopEnd=inst.loops[0].end; - sample->loopMode=DivSampleLoopMode(int(inst.loops[0].mode-SF_LOOP_NONE)); if(inst.loops[0].end < (unsigned int)sampleCount) sampleCount=inst.loops[0].end; } + else + sample->loop=false; } if (sample->centerRate<4000) sample->centerRate=4000; if (sample->centerRate>64000) sample->centerRate=64000; sfWrap.doClose(); - saveLock.lock(); - song.sample.push_back(sample); - song.sampleLen=sampleCount+1; - saveLock.unlock(); - renderSamples(); 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; + sPreview.dir=false; saveLock.lock(); if (index>=0 && index<(int)song.sample.size()) { delete song.sample[index]; @@ -2511,13 +3192,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; + } } } } @@ -2554,6 +3237,7 @@ bool DivEngine::moveSampleUp(int which) { BUSY_BEGIN; sPreview.sample=-1; sPreview.pos=0; + sPreview.dir=false; DivSample* prev=song.sample[which]; saveLock.lock(); song.sample[which]=song.sample[which-1]; @@ -2593,6 +3277,7 @@ bool DivEngine::moveSampleDown(int which) { BUSY_BEGIN; sPreview.sample=-1; sPreview.pos=0; + sPreview.dir=false; DivSample* prev=song.sample[which]; saveLock.lock(); song.sample[which]=song.sample[which+1]; @@ -2605,7 +3290,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; @@ -2617,7 +3302,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; @@ -2669,7 +3354,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) { @@ -2690,7 +3375,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) { @@ -2702,7 +3387,7 @@ void DivEngine::autoNoteOff(int ch, int note, int vol) { //if (ch<0 || ch>=chans) return; for (int i=0; iquit(); if (output->midiIn) { if (output->midiIn->isDeviceOpen()) { logI("closing MIDI input."); @@ -3092,53 +3750,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(); @@ -3154,6 +3780,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 5352746f3..596829f05 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 "0.6pre1" -#define DIV_ENGINE_VERSION 100 - +#define DIV_VERSION "dev117" +#define DIV_ENGINE_VERSION 117 // 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,7 +64,7 @@ enum DivStatusView { enum DivAudioEngines { DIV_AUDIO_JACK=0, DIV_AUDIO_SDL=1, - + DIV_AUDIO_NULL=126, DIV_AUDIO_DUMMY=127 }; @@ -160,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]; @@ -178,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}, @@ -186,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; @@ -203,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, @@ -213,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), @@ -225,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)); @@ -276,6 +306,8 @@ enum DivChanTypes { DIV_CH_OP=5 }; +extern const char* cmdName[]; + class DivEngine { DivDispatchContainer disCont[32]; TAAudio* output; @@ -297,6 +329,7 @@ class DivEngine { bool stopExport; bool halted; bool forceMono; + bool clampSamples; bool cmdStreamEnabled; bool softLocked; bool firstTick; @@ -323,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; @@ -342,21 +377,23 @@ class DivEngine { struct SamplePreview { int sample; int wave; - unsigned int pos; - bool dir; + int pos; int pBegin, pEnd; + bool dir; SamplePreview(): sample(-1), wave(-1), pos(0), dir(false), pBegin(-1), - pEnd(-1) {} + pEnd(-1), + dir(false) {} } sPreview; short vibTable[64]; int reversePitchTable[4096]; int pitchTable[4096]; + char c163NameCS[1024]; int midiBaseChan; bool midiPoly; size_t midiAgeCounter; @@ -398,6 +435,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); @@ -417,7 +455,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); @@ -455,7 +493,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. @@ -467,7 +505,11 @@ 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 to ZSM. + SafeWriter* saveZSM(unsigned int zsmrate=60, bool loop=true); + // 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 @@ -479,9 +521,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(); @@ -500,6 +549,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 @@ -511,6 +561,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); @@ -573,14 +626,17 @@ 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); // 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); @@ -657,7 +713,7 @@ class DivEngine { // is playing bool isPlaying(); - + // is running bool isRunning(); @@ -686,8 +742,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); @@ -695,8 +754,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); @@ -735,7 +800,7 @@ class DivEngine { void autoNoteOn(int chan, int ins, int note, int vol=-1); void autoNoteOff(int chan, int note, int vol=-1); void autoNoteOffAll(); - + // set whether autoNoteIn is mono or poly void setAutoNotePoly(bool poly); @@ -756,7 +821,7 @@ class DivEngine { // get dispatch channel state void* getDispatchChanState(int chan); - + // get register pool unsigned char* getRegisterPool(int sys, int& size, int& depth); @@ -792,7 +857,7 @@ class DivEngine { // set the console mode. void setConsoleMode(bool enable); - + // get metronome bool getMetronome(); @@ -853,7 +918,10 @@ 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); @@ -865,7 +933,7 @@ class DivEngine { // get warnings String getWarnings(); - + // switch master bool switchMaster(); @@ -1011,6 +1079,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 e34cd33b5..0d8817ebf 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; @@ -172,6 +176,11 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.volMacroLinger=false; ds.brokenOutVol=true; // ??? ds.e1e2StopOnSameNote=true; + ds.brokenPortaArp=false; + ds.snNoLowPeriods=true; + ds.disableSampleMacro=true; + ds.delayBehavior=0; + ds.jumpTreatment=2; // 1.1 compat flags if (ds.version>24) { @@ -188,6 +197,15 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } */ + // Genesis detuned on Defle v10 and earlier + /*if (ds.version<19 && ds.system[0]==DIV_SYSTEM_GENESIS) { + ds.tuning=443.23; + }*/ + // C64 detuned on Defle v11 and earlier + /*if (ds.version<21 && (ds.system[0]==DIV_SYSTEM_C64_6581 || ds.system[0]==DIV_SYSTEM_C64_8580)) { + ds.tuning=433.2; + }*/ + logI("reading module data..."); if (ds.version>0x0c) { ds.subsong[0]->hilightA=reader.readC(); @@ -319,7 +337,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } logD("%d name: %s",i,ins->name.c_str()); if (ds.version<0x0b) { - // instruments in ancient versions were all FM or STD. + // instruments in ancient versions were all FM. mode=1; } else { mode=reader.readC(); @@ -348,6 +366,12 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { if (ds.system[0]==DIV_SYSTEM_YMU759) { ins->type=DIV_INS_OPL; } + if (ds.system[0]==DIV_SYSTEM_ARCADE) { + ins->type=DIV_INS_OPM; + } + if ((ds.system[0]==DIV_SYSTEM_NES || ds.system[0]==DIV_SYSTEM_NES_VRC7 || ds.system[0]==DIV_SYSTEM_NES_FDS) && ins->type==DIV_INS_STD) { + ins->type=DIV_INS_NES; + } if (mode) { // FM if (ds.version>0x05) { @@ -441,6 +465,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ins->fm.op[j].ssgEnv=reader.readC(); } } + if (ds.version<0x12) { // before version 10 all ops were responsive to volume + ins->fm.op[j].kvs=1; + } logD("OP%d: AM %d AR %d DAM %d DR %d DVB %d EGT %d KSL %d MULT %d RR %d SL %d SUS %d TL %d VIB %d WS %d RS %d DT %d D2R %d SSG-EG %d",j, ins->fm.op[j].am, @@ -502,6 +529,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(); @@ -587,7 +622,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]) { @@ -597,7 +634,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!"); } } @@ -798,7 +834,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { sample->rate=ymuSampleRate*400; } if (ds.version>0x15) { - sample->depth=DivSampleDepth(reader.readC()); + 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=DIV_SAMPLE_DEPTH_16BIT; @@ -821,6 +857,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); @@ -906,6 +949,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(); @@ -1047,6 +1092,24 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { 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; + } + if (ds.version<115) { + ds.autoSystem=false; + } + if (ds.version<117) { + ds.disableSampleMacro=true; + } ds.isDMF=false; reader.readS(); // reserved @@ -1314,9 +1377,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); @@ -1448,7 +1517,37 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } else { reader.readC(); } - for (int i=0; i<8; i++) { + 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(); + } + if (ds.version>=115) { + ds.autoSystem=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=117) { + ds.disableSampleMacro=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<2; i++) { reader.readC(); } } @@ -1473,7 +1572,23 @@ 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)); + ds.autoSystem=true; + } + + // read subsongs + if (ds.version>=95) { for (int i=0; iname=reader.readString(); sample->samples=reader.readI(); - sample->loopEnd=sample->samples; + 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=DivSampleDepth(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(); - if (sample->loopStart<0) { - sample->loopMode=DIV_SAMPLE_LOOPMODE_ONESHOT; - sample->loopStart=0; - } else { - sample->loopMode=DIV_SAMPLE_LOOPMODE_FORWARD; + sample->loopEnd=reader.readI(); + sample->loop=(sample->loopStart>=0)&&(sample->loopEnd>=0); + + 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(); + sample->loop=(sample->loopStart>=0)&&(sample->loopEnd>=0); + } 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 + for (int pos=0; pos>8)|((unsigned short)data[pos]<<8); + } +#endif + if (pitch!=5) { logD("%d: scaling from %d...",i,pitch); } @@ -1773,6 +1925,57 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } } + // convert OPM/NES instrument types + if (ds.version<117) { + int opnCount=0; + int opmCount=0; + int snCount=0; + int nesCount=0; + for (int i=0; iopnCount) { + for (DivInstrument* i: ds.ins) { + if (i->type==DIV_INS_FM) i->type=DIV_INS_OPM; + } + } + if (nesCount>snCount) { + for (DivInstrument* i: ds.ins) { + if (i->type==DIV_INS_STD) i->type=DIV_INS_NES; + } + } + } + if (active) quitDispatch(); BUSY_BEGIN_SOFT; saveLock.lock(); @@ -1825,6 +2028,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; @@ -1840,26 +2044,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; } @@ -1895,14 +2106,9 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { loopLen=0; } if (loopLen>=2) { - if (loopEndloopStart=loopStart; - if (sample->loopStart<0) { - sample->loopMode=DIV_SAMPLE_LOOPMODE_ONESHOT; - sample->loopStart=0; - } else { - sample->loopMode=DIV_SAMPLE_LOOPMODE_FORWARD; - } + sample->loopEnd=loopEnd; + sample->loop=(sample->loopStart>=0)&&(sample->loopEnd>=0); } sample->init(slen); ds.sample.push_back(sample); @@ -2216,6 +2422,688 @@ 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); + s->loop=(s->loopStart>=0)&&(s->loopEnd>=0); + } + 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); \ @@ -2406,7 +3294,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { unsigned char insType=reader.readC(); switch (insType) { case 1: - ins->type=DIV_INS_STD; + ins->type=DIV_INS_NES; break; case 2: // TODO: tell VRC6 and VRC6 saw instruments apart ins->type=DIV_INS_VRC6; @@ -2433,7 +3321,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { // instrument data switch (ins->type) { - case DIV_INS_STD: { + case DIV_INS_NES: { unsigned int totalSeqs=reader.readI(); if (totalSeqs>5) { logE("%d: too many sequences!",insIndex); @@ -2449,7 +3337,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { const int dpcmNotes=(blockVersion>=2)?96:72; for (int j=0; jamiga.noteMap[j].ind=(short)((unsigned char)reader.readC())-1; + 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 @@ -2505,6 +3393,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(); @@ -2688,6 +3577,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 @@ -2782,15 +3673,27 @@ 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; + } } } } @@ -2935,7 +3838,13 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeC(song.volMacroLinger); w->writeC(song.brokenOutVol); w->writeC(song.e1e2StopOnSameNote); - for (int i=0; i<8; i++) { + w->writeC(song.brokenPortaArp); + w->writeC(song.snNoLowPeriods); + w->writeC(song.delayBehavior); + w->writeC(song.jumpTreatment); + w->writeC(song.autoSystem); + w->writeC(song.disableSampleMacro); + for (int i=0; i<2; i++) { w->writeC(0); } @@ -2956,6 +3865,14 @@ 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); @@ -3034,20 +3951,40 @@ 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->writeC((unsigned char)(sample->depth)); + w->writeI(sample->centerRate); + w->writeC(sample->depth); + w->writeC(0); // reserved w->writeC(0); - w->writeS(sample->centerRate); - w->writeI((sample->loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT)?-1:sample->loopStart); + w->writeC(0); + w->writeI(sample->loop?sample->loopStart:-1); + w->writeI(sample->loop?sample->loopEnd:-1); + 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); @@ -3074,7 +4011,13 @@ 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); @@ -3297,7 +4240,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { } for (DivInstrument* i: song.ins) { - if (i->type==DIV_INS_FM) { + if (i->type==DIV_INS_FM || i->type==DIV_INS_OPM) { addWarning("no FM macros in .dmf format"); break; } @@ -3308,15 +4251,40 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { w->writeString(i->name,true); // safety check - if (!isFMSystem(sys) && i->type!=DIV_INS_STD && i->type!=DIV_INS_FDS) { - i->type=DIV_INS_STD; + if (!isFMSystem(sys) && i->type!=DIV_INS_STD && i->type!=DIV_INS_NES && i->type!=DIV_INS_FDS) { + switch (song.system[0]) { + case DIV_SYSTEM_GB: + i->type=DIV_INS_GB; + break; + case DIV_SYSTEM_NES: + i->type=DIV_INS_NES; + 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; + if (!isSTDSystem(sys) && i->type!=DIV_INS_FM && i->type!=DIV_INS_OPM) { + if (sys==DIV_SYSTEM_ARCADE) { + i->type=DIV_INS_OPM; + } else { + i->type=DIV_INS_FM; + } } - w->writeC((i->type==DIV_INS_FM || i->type==DIV_INS_OPLL)?1:0); - if (i->type==DIV_INS_FM || i->type==DIV_INS_OPLL) { // FM + w->writeC((i->type==DIV_INS_FM || i->type==DIV_INS_OPM || i->type==DIV_INS_OPLL)?1:0); + if (i->type==DIV_INS_FM || i->type==DIV_INS_OPM || i->type==DIV_INS_OPLL) { // FM w->writeC(i->fm.alg); w->writeC(i->fm.fb); w->writeC(i->fm.fms); @@ -3350,47 +4318,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); } @@ -3440,20 +4440,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 } } @@ -3472,7 +4491,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 b7ca22c1e..83361130b 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; @@ -231,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 @@ -246,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++) { @@ -732,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]); } @@ -802,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; @@ -1262,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)); @@ -1280,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 @@ -1497,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]); } @@ -1638,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 514c5673d..e944fdbf3 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -71,8 +71,11 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(op.ws); w->writeC(op.ksr); + w->writeC(op.enable); + w->writeC(op.kvs); + // reserved - for (int k=0; k<12; k++) { + for (int k=0; k<10; k++) { w->writeC(0); } } @@ -132,7 +135,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); @@ -387,11 +390,11 @@ void DivInstrument::putInsData(SafeWriter* w) { // sample map w->writeC(amiga.useNoteMap); if (amiga.useNoteMap) { - for (int i=0; i<120; i++) { - w->writeI(amiga.noteMap[i].freq); + for (int note=0; note<120; note++) { + w->writeI(amiga.noteMap[note].freq); } - for (int i=0; i<120; i++) { - w->writeS(amiga.noteMap[i].ind); + for (int note=0; note<120; note++) { + w->writeS(amiga.noteMap[note].map); } } @@ -528,12 +531,142 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(0); } + // Sound Unit + w->writeC(amiga.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 + // @tildearrow please update this + w->writeC(snes.useEnv); + w->writeC(0); + w->writeC(0); + 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(); + } + + if (version>=115) { + op.kvs=reader.readC(); + } else { + op.kvs=2; + reader.readC(); + } + // reserved - for (int k=0; k<12; k++) reader.readC(); + for (int k=0; k<10; k++) reader.readC(); } // GB @@ -659,10 +805,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) { @@ -715,10 +861,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]; @@ -921,26 +1067,26 @@ 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) { - for (int i=0; i<120; i++) { - amiga.noteMap[i].freq=reader.readI(); + for (int note=0; note<120; note++) { + amiga.noteMap[note].freq=reader.readI(); } - for (int i=0; i<120; i++) { - amiga.noteMap[i].ind=reader.readS(); + for (int note=0; note<120; note++) { + amiga.noteMap[note].map=reader.readS(); } } } @@ -992,14 +1138,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(); @@ -1075,6 +1221,157 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { for (int k=0; k<23; k++) reader.readC(); } + // Sound Unit + if (version>=104) { + amiga.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(); + reader.readC(); + 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 0024378cc..b6d35935b 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -62,6 +62,16 @@ enum DivInstrumentType: unsigned short { DIV_INS_SU=30, DIV_INS_NAMCO=31, DIV_INS_OPL_DRUMS=32, + DIV_INS_OPM=33, + DIV_INS_NES=34, + DIV_INS_MSM6258=35, + DIV_INS_MSM6295=36, + DIV_INS_ADPCMA=37, + DIV_INS_ADPCMB=38, + DIV_INS_SEGAPCM=39, + DIV_INS_QSOUND=40, + DIV_INS_YMZ280B=41, + DIV_INS_RF5C68=42, DIV_INS_MAX, DIV_INS_NULL }; @@ -89,6 +99,7 @@ struct DivInstrumentFM { bool enable; unsigned char am, ar, dr, mult, rr, sl, tl, dt2, rs, dt, d2r, ssgEnv; unsigned char dam, dvb, egt, ksl, sus, vib, ws, ksr; // YMU759/OPL/OPZ + unsigned char kvs; Operator(): enable(true), am(0), @@ -110,7 +121,8 @@ struct DivInstrumentFM { sus(0), vib(0), ws(0), - ksr(0) {} + ksr(0), + kvs(2) {} } op[4]; DivInstrumentFM(): alg(0), @@ -167,21 +179,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)); @@ -263,12 +274,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 { @@ -308,15 +339,14 @@ struct DivInstrumentC64 { }; struct DivInstrumentAmiga { - struct NoteMap { + struct SampleMap { int freq; - short ind; + short map; unsigned char reversed; - - NoteMap(): - freq(0), - ind(-1), - reversed(false) {} + SampleMap(int f=0, short m=-1, unsigned char r=0): + freq(f), + map(m), + reversed(r) {} }; struct TransWaveSlice { @@ -381,9 +411,10 @@ struct DivInstrumentAmiga { short initSample; bool reversed; bool useNoteMap; + bool useSample; bool useWave; unsigned char waveLen; - NoteMap noteMap[120]; + SampleMap noteMap[120]; TransWave transWave; std::vector transWaveMap; @@ -395,7 +426,7 @@ struct DivInstrumentAmiga { if (useNoteMap) { if (note<0) note=0; if (note>119) note=119; - return noteMap[note].ind; + return noteMap[note].map; } return initSample; } @@ -430,9 +461,22 @@ struct DivInstrumentAmiga { initSample(0), reversed(false), useNoteMap(false), + useSample(false), useWave(false), waveLen(31), - transWaveMap(1) {} + transWave(TransWave()), + transWaveMap(1) { + for (SampleMap& elem: noteMap) { + elem=SampleMap(); + } + } +}; + +struct DivInstrumentX1_010 { + int bankSlot; + + DivInstrumentX1_010(): + bankSlot(0) {} }; struct DivInstrumentN163 { @@ -459,43 +503,6 @@ struct DivInstrumentFDS { } }; -struct DivInstrumentES5506 { - struct Filter { - enum FilterMode: unsigned char { // filter mode for pole 4,3 - FILTER_MODE_HPK2_HPK2, - 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) {} - }; - signed int lVol, rVol; - Filter filter; - Envelope envelope; - DivInstrumentES5506(): - lVol(0xffff), - rVol(0xffff) {} -}; - struct DivInstrumentMultiPCM { unsigned char ar, d1r, dl, d2r, rr, rc; unsigned char lfo, vib, am; @@ -553,6 +560,70 @@ struct DivInstrumentWaveSynth { param4(0) {} }; +struct DivInstrumentSoundUnit { + bool switchRoles; + DivInstrumentSoundUnit(): + 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; @@ -562,11 +633,14 @@ struct DivInstrument { DivInstrumentGB gb; DivInstrumentC64 c64; DivInstrumentAmiga amiga; + DivInstrumentX1_010 x1_010; DivInstrumentN163 n163; DivInstrumentFDS fds; - DivInstrumentES5506 es5506; DivInstrumentMultiPCM multipcm; DivInstrumentWaveSynth ws; + DivInstrumentSoundUnit su; + DivInstrumentES5506 es5506; + DivInstrumentSNES snes; /** * save the instrument to a SafeWriter. @@ -588,6 +662,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 06f04682e..955481ce0 100644 --- a/src/engine/macroInt.cpp +++ b/src/engine/macroInt.cpp @@ -32,6 +32,19 @@ void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tic had=false; return; } + if (delay>0) { + delay--; + had=false; + return; + } + if (began && source.delay>0) { + delay=source.delay; + } else { + delay=source.speed-1; + } + if (began) { + began=false; + } if (finished) { finished=false; } @@ -41,16 +54,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 +253,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 +265,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 839625558..5208dc542 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 0a561376b..77255e084 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 54366e1ba..358e54aa0 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 e884d29b0..7a49fccfa 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 (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[i].audPos>=s->loopEnd) || (chan[i].audPos>=s->samples) || (chan[i].audPos>=131071)) { - if (s->isLoopable()) { - chan[i].audPos=s->loopStart; - } else { - chan[i].sample=-1; - } + if (s->isLoopable() && chan[i].audPos>=MIN(131071,(unsigned int)s->loopEnd)) { + 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 e7372e63b..a0a80d6ce 100644 --- a/src/engine/platform/amiga.h +++ b/src/engine/platform/amiga.h @@ -86,6 +86,7 @@ class DivPlatformAmiga: public DivDispatch { int sep1, sep2; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: @@ -105,7 +106,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 1be61fa23..477430994 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -50,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]; @@ -172,7 +67,7 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si w.addrOrVal=true; } } - + OPM_Clock(&fm,NULL,NULL,NULL,NULL); OPM_Clock(&fm,NULL,NULL,NULL,NULL); OPM_Clock(&fm,NULL,NULL,NULL,NULL); @@ -182,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]; } @@ -211,7 +106,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz delay=1; } } - + fm_ymfm->generate(&out_ymfm); for (int i=0; i<8; i++) { @@ -225,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]; } @@ -256,7 +151,7 @@ void DivPlatformArcade::tick(bool sysTick) { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -266,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) { @@ -345,7 +231,7 @@ void DivPlatformArcade::tick(bool sysTick) { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -369,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]; @@ -399,7 +289,7 @@ void DivPlatformArcade::tick(bool sysTick) { } if (m.tl.had) { op.tl=127-m.tl.val; - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -461,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; } } @@ -484,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); @@ -494,7 +390,7 @@ int DivPlatformArcade::dispatch(DivCommand c) { for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator op=chan[c.chan].state.op[i]; - if (isOutput[chan[c.chan].state.alg][i]) { + if (KVS(c.chan,i)) { if (!chan[c.chan].active || chan[c.chan].insChanged) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } @@ -552,7 +448,7 @@ int DivPlatformArcade::dispatch(DivCommand c) { for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; - if (isOutput[chan[c.chan].state.alg][i]) { + if (KVS(c.chan,i)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -616,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; } @@ -643,7 +545,7 @@ int DivPlatformArcade::dispatch(DivCommand c) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; op.tl=c.value2; - if (isOutput[chan[c.chan].state.alg][c.value]) { + if (KVS(c.chan,c.value)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -827,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: @@ -843,7 +746,7 @@ void DivPlatformArcade::forceIns() { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator op=chan[i].state.op[j]; - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -930,19 +833,16 @@ void DivPlatformArcade::reset() { } lastBusy=60; - pcmCycles=0; - pcmL=0; - pcmR=0; delay=0; amDepth=0x7f; 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); - - extMode=false; } void DivPlatformArcade::setFlags(unsigned int flags) { diff --git a/src/engine/platform/arcade.h b/src/engine/platform/arcade.h index daa883620..70265be39 100644 --- a/src/engine/platform/arcade.h +++ b/src/engine/platform/arcade.h @@ -43,9 +43,9 @@ class DivPlatformArcade: public DivPlatformOPM { int freq, baseFreq, pitch, pitch2, note; int ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset, opMaskChanged; int vol, outVol; - unsigned char chVolL, chVolR; + unsigned char chVolL, chVolR, opMask; void macroInit(DivInstrument* which) { std.init(which); pitch2=0; @@ -68,23 +68,24 @@ class DivPlatformArcade: public DivPlatformOPM { portaPause(false), furnacePCM(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(0), chVolL(127), - chVolR(127) {} + chVolR(127), + opMask(15) {} }; Channel chan[8]; DivDispatchOscBuffer* oscBuf[8]; opm_t fm; int baseFreqOff; - int pcmL, pcmR, pcmCycles; unsigned char amDepth, pmDepth; ymfm::ym2151* fm_ymfm; ymfm::ym2151::output_data out_ymfm; DivArcadeInterface iface; - bool extMode, useYMFM; + bool useYMFM; bool isMuted[8]; @@ -94,6 +95,7 @@ class DivPlatformArcade: public DivPlatformOPM { void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len); void acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: @@ -115,7 +117,6 @@ class DivPlatformArcade: public DivPlatformOPM { 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 073776105..9f4e0dc28 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -69,54 +69,92 @@ 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; -} +/* C program to generate this table: -void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t len) { - if (ayBufLen +#include + +int main(int argc, char** argv) { + for (int i=0; i<256; i++) { + if ((i&15)==0) printf("\n "); + printf(" %d,",(int)round(pow((double)i/255.0,0.36)*15.0)); + } +} +*/ + +const unsigned char dacLogTableAY[256]={ + 0, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15 +}; + +void DivPlatformAY8910::runDAC() { + for (int i=0; i<3; i++) { + if (chan[i].psgMode.dac && chan[i].dac.sample!=-1) { + chan[i].dac.period+=chan[i].dac.rate; + bool end=false; + bool changed=false; + int prevOut=chan[i].dac.out; + while (chan[i].dac.period>rate && !end) { + DivSample* s=parent->getSample(chan[i].dac.sample); + if (s->samples<=0) { + chan[i].dac.sample=-1; + immWrite(0x08+i,0); + end=true; + break; + } + unsigned char dacData=dacLogTableAY[(unsigned char)s->data8[chan[i].dac.pos]^0x80]; + chan[i].dac.out=MAX(0,dacData-(15-chan[i].outVol)); + if (prevOut!=chan[i].dac.out) { + prevOut=chan[i].dac.out; + changed=true; + } + chan[i].dac.pos++; + if (s->isLoopable() && chan[i].dac.pos>=s->loopEnd) { + chan[i].dac.pos=s->loopStart; + } else if (chan[i].dac.pos>=(int)s->samples) { + chan[i].dac.sample=-1; + //immWrite(0x08+i,0); + end=true; + break; + } + chan[i].dac.period-=rate; + } + if (changed && !end) { + if (!isMuted[i]) { + immWrite(0x08+i,chan[i].dac.out); + } + } } } +} + +void DivPlatformAY8910::checkWrites() { while (!writes.empty()) { QueuedWrite w=writes.front(); if (intellivision) { @@ -129,27 +167,47 @@ void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t l regPool[w.addr&0x0f]=w.val; writes.pop(); } - ay->sound_stream_update(ayBuf,len); - if (sunsoft) { - for (size_t i=0; idata[oscBuf[ch]->needle++]=ayBuf[ch][i]; + runDAC(); + checkWrites(); + + ay->sound_stream_update(ayBuf,1); + bufL[i+start]=ayBuf[0][0]; + bufR[i+start]=bufL[i+start]; + + oscBuf[0]->data[oscBuf[0]->needle++]=sunsoftVolTable[31-(ay->lastIndx&31)]>>3; + oscBuf[1]->data[oscBuf[1]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>5)&31)]>>3; + oscBuf[2]->data[oscBuf[2]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>10)&31)]>>3; + } + } else { + for (size_t i=0; isound_stream_update(ayBuf,1); + if (stereo) { + bufL[i+start]=ayBuf[0][0]+ayBuf[1][0]+((ayBuf[2][0]*stereoSep)>>8); + bufR[i+start]=((ayBuf[0][0]*stereoSep)>>8)+ayBuf[1][0]+ayBuf[2][0]; + } else { + bufL[i+start]=ayBuf[0][0]+ayBuf[1][0]+ayBuf[2][0]; + bufR[i+start]=bufL[i+start]; + } + + oscBuf[0]->data[oscBuf[0]->needle++]=ayBuf[0][0]<<2; + oscBuf[1]->data[oscBuf[1]->needle++]=ayBuf[1][0]<<2; + oscBuf[2]->data[oscBuf[2]->needle++]=ayBuf[2][0]<<2; } } } @@ -157,22 +215,22 @@ void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t l void DivPlatformAY8910::updateOutSel(bool immediate) { if (immediate) { immWrite(0x07, - ~((chan[0].psgMode&1)| - ((chan[1].psgMode&1)<<1)| - ((chan[2].psgMode&1)<<2)| - ((chan[0].psgMode&2)<<2)| - ((chan[1].psgMode&2)<<3)| - ((chan[2].psgMode&2)<<4)| + ~((chan[0].psgMode.getTone())| + ((chan[1].psgMode.getTone())<<1)| + ((chan[2].psgMode.getTone())<<2)| + ((chan[0].psgMode.getNoise())<<2)| + ((chan[1].psgMode.getNoise())<<3)| + ((chan[2].psgMode.getNoise())<<4)| ((!ioPortA)<<6)| ((!ioPortB)<<7))); } else { rWrite(0x07, - ~((chan[0].psgMode&1)| - ((chan[1].psgMode&1)<<1)| - ((chan[2].psgMode&1)<<2)| - ((chan[0].psgMode&2)<<2)| - ((chan[1].psgMode&2)<<3)| - ((chan[2].psgMode&2)<<4)| + ~((chan[0].psgMode.getTone())| + ((chan[1].psgMode.getTone())<<1)| + ((chan[2].psgMode.getTone())<<2)| + ((chan[0].psgMode.getNoise())<<2)| + ((chan[1].psgMode.getNoise())<<3)| + ((chan[2].psgMode.getNoise())<<4)| ((!ioPortA)<<6)| ((!ioPortB)<<7))); } @@ -185,40 +243,35 @@ void DivPlatformAY8910::tick(bool sysTick) { if (chan[i].std.vol.had) { chan[i].outVol=MIN(15,chan[i].std.vol.val)-(15-(chan[i].vol&15)); if (chan[i].outVol<0) chan[i].outVol=0; - if (isMuted[i]) { - rWrite(0x08+i,0); - } else if (intellivision && (chan[i].psgMode&4)) { - rWrite(0x08+i,(chan[i].outVol&0xc)<<2); - } else { - rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); + if (!chan[i].psgMode.dac) { + if (isMuted[i]) { + rWrite(0x08+i,0); + } else if (intellivision && (chan[i].psgMode.getEnvelope())) { + rWrite(0x08+i,(chan[i].outVol&0xc)<<2); + } else { + rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode.getEnvelope())<<2)); + } } } 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); } if (chan[i].std.wave.had) { - chan[i].psgMode=(chan[i].std.wave.val+1)&7; - if (isMuted[i]) { - rWrite(0x08+i,0); - } else if (intellivision && (chan[i].psgMode&4)) { - rWrite(0x08+i,(chan[i].outVol&0xc)<<2); - } else { - rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); + if (!chan[i].psgMode.dac) { + chan[i].psgMode=(chan[i].std.wave.val+1)&7; + if (isMuted[i]) { + rWrite(0x08+i,0); + } else if (intellivision && (chan[i].psgMode.getEnvelope())) { + rWrite(0x08+i,(chan[i].outVol&0xc)<<2); + } else { + rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode.getEnvelope())<<2)); + } } } if (chan[i].std.pitch.had) { @@ -232,6 +285,20 @@ void DivPlatformAY8910::tick(bool sysTick) { } if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1) { + if (chan[i].psgMode.dac) { + if (dumpWrites) addWrite(0xffff0002+(i<<8),0); + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AY); + chan[i].dac.sample=ins->amiga.getSample(chan[i].note); + if (chan[i].dac.sample<0 || chan[i].dac.sample>=parent->song.sampleLen) { + if (dumpWrites) { + rWrite(0x08+i,0); + addWrite(0xffff0000+(i<<8),chan[i].dac.sample); + } + chan[i].dac.pos=0; + chan[i].dac.period=0; + chan[i].keyOn=true; + } + } oldWrites[0x08+i]=-1; oldWrites[0x0d]=-1; } @@ -252,6 +319,19 @@ void DivPlatformAY8910::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); + if (chan[i].dac.furnaceDAC) { + double off=1.0; + if (chan[i].dac.sample>=0 && chan[i].dac.samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[i].dac.sample); + if (s->centerRate<1) { + off=1.0; + } else { + off=8363.0/(double)s->centerRate; + } + } + chan[i].dac.rate=((double)rate*((sunsoft||clockSel)?8.0:16.0))/(double)(MAX(1,off*chan[i].freq)); + if (dumpWrites) addWrite(0xffff0001+(i<<8),chan[i].dac.rate); + } if (chan[i].freq>4095) chan[i].freq=4095; if (chan[i].keyOn) { //rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); @@ -307,6 +387,62 @@ int DivPlatformAY8910::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AY); + if (!parent->song.disableSampleMacro && (ins->type==DIV_INS_AMIGA || ins->amiga.useSample)) { + chan[c.chan].psgMode.dac=true; + } else if (chan[c.chan].dac.furnaceDAC) { + chan[c.chan].psgMode.dac=false; + } + if (chan[c.chan].psgMode.dac) { + if (skipRegisterWrites) break; + if (!parent->song.disableSampleMacro && (ins->type==DIV_INS_AMIGA || ins->amiga.useSample)) { + chan[c.chan].dac.sample=ins->amiga.getSample(c.value); + if (chan[c.chan].dac.sample<0 || chan[c.chan].dac.sample>=parent->song.sampleLen) { + chan[c.chan].dac.sample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + break; + } else { + if (dumpWrites) { + rWrite(0x08+c.chan,0); + addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample); + } + } + chan[c.chan].dac.pos=0; + chan[c.chan].dac.period=0; + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].macroInit(ins); + if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + //chan[c.chan].keyOn=true; + chan[c.chan].dac.furnaceDAC=true; + } else { + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + } + chan[c.chan].dac.sample=12*sampleBank+chan[c.chan].note%12; + if (chan[c.chan].dac.sample>=parent->song.sampleLen) { + chan[c.chan].dac.sample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + break; + } else { + if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample); + } + chan[c.chan].dac.pos=0; + chan[c.chan].dac.period=0; + chan[c.chan].dac.rate=parent->getSample(chan[c.chan].dac.sample)->rate*2048; + if (dumpWrites) { + rWrite(0x08+c.chan,0); + addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dac.rate); + } + chan[c.chan].dac.furnaceDAC=false; + } + break; + } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); chan[c.chan].freqChanged=true; @@ -318,16 +454,21 @@ int DivPlatformAY8910::dispatch(DivCommand c) { if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } - if (isMuted[c.chan]) { - rWrite(0x08+c.chan,0); - } else if (intellivision && (chan[c.chan].psgMode&4)) { - rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2); - } else { - rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + if (!chan[c.chan].psgMode.dac) { + if (isMuted[c.chan]) { + rWrite(0x08+c.chan,0); + } else if (intellivision && (chan[c.chan].psgMode.getEnvelope())) { + rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2); + } else { + rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode.getEnvelope())<<2)); + } } break; } case DIV_CMD_NOTE_OFF: + chan[c.chan].dac.sample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + chan[c.chan].psgMode.dac=false; chan[c.chan].keyOff=true; chan[c.chan].active=false; chan[c.chan].macroInit(NULL); @@ -341,14 +482,16 @@ int DivPlatformAY8910::dispatch(DivCommand c) { if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } - if (isMuted[c.chan]) { - rWrite(0x08+c.chan,0); - } else { - if (chan[c.chan].active) { - if (intellivision && (chan[c.chan].psgMode&4)) { - rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2); - } else { - rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + if (!chan[c.chan].psgMode.dac) { + if (isMuted[c.chan]) { + rWrite(0x08+c.chan,0); + } else { + if (chan[c.chan].active) { + if (intellivision && (chan[c.chan].psgMode.getEnvelope())) { + rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2); + } else { + rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode.getEnvelope())<<2)); + } } } } @@ -398,15 +541,17 @@ int DivPlatformAY8910::dispatch(DivCommand c) { break; } case DIV_CMD_STD_NOISE_MODE: - if (c.value<16) { - chan[c.chan].psgMode=(c.value+1)&7; - if (isMuted[c.chan]) { - rWrite(0x08+c.chan,0); - } else if (chan[c.chan].active) { - if (intellivision && (chan[c.chan].psgMode&4)) { - rWrite(0x08+c.chan,(chan[c.chan].outVol&0xc)<<2); - } else { - rWrite(0x08+c.chan,(chan[c.chan].outVol&15)|((chan[c.chan].psgMode&4)<<2)); + if (!chan[c.chan].psgMode.dac) { + if (c.value<16) { + chan[c.chan].psgMode=(c.value+1)&7; + if (isMuted[c.chan]) { + rWrite(0x08+c.chan,0); + } else if (chan[c.chan].active) { + if (intellivision && (chan[c.chan].psgMode.getEnvelope())) { + rWrite(0x08+c.chan,(chan[c.chan].outVol&0xc)<<2); + } else { + rWrite(0x08+c.chan,(chan[c.chan].outVol&15)|((chan[c.chan].psgMode.getEnvelope())<<2)); + } } } } @@ -418,16 +563,16 @@ int DivPlatformAY8910::dispatch(DivCommand c) { ayEnvMode=c.value>>4; rWrite(0x0d,ayEnvMode); if (c.value&15) { - chan[c.chan].psgMode|=4; + chan[c.chan].psgMode.envelope|=1; } else { - chan[c.chan].psgMode&=~4; + chan[c.chan].psgMode.envelope&=~1; } if (isMuted[c.chan]) { rWrite(0x08+c.chan,0); - } else if (intellivision && (chan[c.chan].psgMode&4)) { + } else if (intellivision && (chan[c.chan].psgMode.getEnvelope())) { rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2); } else { - rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode.getEnvelope())<<2)); } break; case DIV_CMD_AY_ENVELOPE_LOW: @@ -464,6 +609,15 @@ int DivPlatformAY8910::dispatch(DivCommand c) { updateOutSel(true); immWrite(14+(c.value?1:0),(c.value?portBVal:portAVal)); break; + case DIV_CMD_SAMPLE_MODE: + chan[c.chan].psgMode.dac=(c.value>0)?1:0; + break; + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + break; case DIV_ALWAYS_SET_VOLUME: return 0; break; @@ -471,9 +625,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,10 +646,14 @@ void DivPlatformAY8910::muteChannel(int ch, bool mute) { isMuted[ch]=mute; if (isMuted[ch]) { rWrite(0x08+ch,0); - } else if (intellivision && (chan[ch].psgMode&4) && chan[ch].active) { - rWrite(0x08+ch,(chan[ch].vol&0xc)<<2); - } else if (chan[ch].active) { - rWrite(0x08+ch,(chan[ch].outVol&15)|((chan[ch].psgMode&4)<<2)); + } else if (chan[ch].active && chan[ch].psgMode.dac) { + rWrite(0x08+ch,chan[ch].dac.out); + } else { + if (intellivision && (chan[ch].psgMode.getEnvelope()) && chan[ch].active) { + rWrite(0x08+ch,(chan[ch].vol&0xc)<<2); + } else if (chan[ch].active) { + rWrite(0x08+ch,(chan[ch].outVol&15)|((chan[ch].psgMode.getEnvelope())<<2)); + } } } @@ -551,12 +712,6 @@ void DivPlatformAY8910::reset() { pendingWrites[i]=-1; } - lastBusy=60; - dacMode=0; - dacPeriod=0; - dacPos=0; - dacRate=0; - dacSample=-1; sampleBank=0; ayEnvPeriod=0; ayEnvMode=0; @@ -687,6 +842,7 @@ void DivPlatformAY8910::setFlags(unsigned int flags) { ay->device_reset(); stereo=(flags>>6)&1; + stereoSep=(flags>>8)&255; } int DivPlatformAY8910::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { diff --git a/src/engine/platform/ay.h b/src/engine/platform/ay.h index f67a2ad97..ec9a14fe5 100644 --- a/src/engine/platform/ay.h +++ b/src/engine/platform/ay.h @@ -31,20 +31,83 @@ class DivPlatformAY8910: public DivDispatch { }; inline unsigned char regRemap(unsigned char reg) { return intellivision?AY8914RegRemap[reg&0x0f]:reg&0x0f; } struct Channel { - unsigned char freqH, freqL; + struct PSGMode { + unsigned char tone: 1; + unsigned char noise: 1; + unsigned char envelope: 1; + unsigned char dac: 1; + + unsigned char getTone() { + return dac?0:(tone<<0); + } + + unsigned char getNoise() { + return dac?0:(noise<<1); + } + + unsigned char getEnvelope() { + return dac?0:(envelope<<2); + } + + PSGMode& operator=(unsigned char s) { + tone=(s>>0)&1; + noise=(s>>1)&1; + envelope=(s>>2)&1; + dac=(s>>3)&1; + return *this; + } + + PSGMode(): + tone(1), + noise(0), + envelope(0), + dac(0) {} + } psgMode; + + struct DAC { + int sample, rate, period, pos, out; + unsigned char furnaceDAC: 1; + + DAC(): + sample(-1), + rate(0), + period(0), + pos(0), + out(0), + furnaceDAC(0) {} + } dac; + int freq, baseFreq, note, pitch, pitch2; int ins; - unsigned char psgMode, autoEnvNum, autoEnvDen; + unsigned char autoEnvNum, autoEnvDen; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; int vol, outVol; - unsigned char pan; DivMacroInt std; void macroInit(DivInstrument* which) { std.init(which); pitch2=0; } - Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), note(0), pitch(0), pitch2(0), ins(-1), psgMode(1), autoEnvNum(0), autoEnvDen(0), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(15), pan(3) {} + Channel(): + psgMode(PSGMode()), + dac(DAC()), + freq(0), + baseFreq(0), + note(0), + pitch(0), + pitch2(0), + ins(-1), + autoEnvNum(0), + autoEnvDen(0), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + portaPause(false), + inPorta(false), + vol(0), + outVol(15) {} }; Channel chan[3]; bool isMuted[3]; @@ -60,12 +123,8 @@ class DivPlatformAY8910: public DivDispatch { unsigned char regPool[16]; unsigned char lastBusy; - bool dacMode; - int dacPeriod; - int dacRate; - int dacPos; - int dacSample; unsigned char sampleBank; + unsigned char stereoSep; int delay; @@ -86,8 +145,11 @@ class DivPlatformAY8910: public DivDispatch { short* ayBuf[3]; size_t ayBufLen; + void runDAC(); + void checkWrites(); void updateOutSel(bool immediate=false); - + + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: @@ -112,7 +174,6 @@ 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): diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 0fad4025b..a1182a809 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -77,62 +77,80 @@ 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; -} +/* C program to generate this table: -void DivPlatformAY8930::acquire(short* bufL, short* bufR, size_t start, size_t len) { - if (ayBufLen +#include + +int main(int argc, char** argv) { + for (int i=0; i<256; i++) { + if ((i&15)==0) printf("\n "); + printf(" %d,",(int)round(pow((double)i/255.0,0.3)*31.0)); + } +} +*/ + +const unsigned char dacLogTableAY8930[256]={ + 0, 6, 7, 8, 9, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, + 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31 +}; + +void DivPlatformAY8930::runDAC() { for (int i=0; i<3; i++) { - delete[] ayBuf[i]; - ayBuf[i]=new short[ayBufLen]; + if (chan[i].psgMode.dac && chan[i].dac.sample!=-1) { + chan[i].dac.period+=chan[i].dac.rate; + bool end=false; + bool changed=false; + int prevOut=chan[i].dac.out; + while (chan[i].dac.period>rate && !end) { + DivSample* s=parent->getSample(chan[i].dac.sample); + if (s->samples<=0) { + chan[i].dac.sample=-1; + immWrite(0x08+i,0); + end=true; + break; + } + unsigned char dacData=dacLogTableAY8930[(unsigned char)s->data8[chan[i].dac.pos]^0x80]; + chan[i].dac.out=MAX(0,dacData-(31-chan[i].outVol)); + if (prevOut!=chan[i].dac.out) { + prevOut=chan[i].dac.out; + changed=true; + } + chan[i].dac.pos++; + if (s->isLoopable() && chan[i].dac.pos>=s->loopEnd) { + chan[i].dac.pos=s->loopStart; + } else if (chan[i].dac.pos>=(int)s->samples) { + chan[i].dac.sample=-1; + //immWrite(0x08+i,0); + end=true; + break; + } + chan[i].dac.period-=rate; + } + if (changed && !end) { + if (!isMuted[i]) { + immWrite(0x08+i,chan[i].dac.out); + } + } } } +} + +void DivPlatformAY8930::checkWrites() { while (!writes.empty()) { QueuedWrite w=writes.front(); ay->address_w(w.addr); @@ -144,45 +162,55 @@ void DivPlatformAY8930::acquire(short* bufL, short* bufR, size_t start, size_t l } writes.pop(); } - ay->sound_stream_update(ayBuf,len); - if (stereo) { - for (size_t i=0; idata[oscBuf[ch]->needle++]=ayBuf[ch][i]; + for (size_t i=0; isound_stream_update(ayBuf,1); + if (stereo) { + bufL[i+start]=ayBuf[0][0]+ayBuf[1][0]+((ayBuf[2][0]*stereoSep)>>8); + bufR[i+start]=((ayBuf[0][0]*stereoSep)>>8)+ayBuf[1][0]+ayBuf[2][0]; + } else { + bufL[i+start]=ayBuf[0][0]+ayBuf[1][0]+ayBuf[2][0]; + bufR[i+start]=bufL[i+start]; } + + oscBuf[0]->data[oscBuf[0]->needle++]=ayBuf[0][0]<<2; + oscBuf[1]->data[oscBuf[1]->needle++]=ayBuf[1][0]<<2; + oscBuf[2]->data[oscBuf[2]->needle++]=ayBuf[2][0]<<2; } } void DivPlatformAY8930::updateOutSel(bool immediate) { if (immediate) { immWrite(0x07, - ~((chan[0].psgMode&1)| - ((chan[1].psgMode&1)<<1)| - ((chan[2].psgMode&1)<<2)| - ((chan[0].psgMode&2)<<2)| - ((chan[1].psgMode&2)<<3)| - ((chan[2].psgMode&2)<<4)| + ~((chan[0].psgMode.getTone())| + ((chan[1].psgMode.getTone())<<1)| + ((chan[2].psgMode.getTone())<<2)| + ((chan[0].psgMode.getNoise())<<2)| + ((chan[1].psgMode.getNoise())<<3)| + ((chan[2].psgMode.getNoise())<<4)| ((!ioPortA)<<6)| ((!ioPortB)<<7))); } else { rWrite(0x07, - ~((chan[0].psgMode&1)| - ((chan[1].psgMode&1)<<1)| - ((chan[2].psgMode&1)<<2)| - ((chan[0].psgMode&2)<<2)| - ((chan[1].psgMode&2)<<3)| - ((chan[2].psgMode&2)<<4)| + ~((chan[0].psgMode.getTone())| + ((chan[1].psgMode.getTone())<<1)| + ((chan[2].psgMode.getTone())<<2)| + ((chan[0].psgMode.getNoise())<<2)| + ((chan[1].psgMode.getNoise())<<3)| + ((chan[2].psgMode.getNoise())<<4)| ((!ioPortA)<<6)| ((!ioPortB)<<7))); } @@ -207,36 +235,31 @@ void DivPlatformAY8930::tick(bool sysTick) { if (chan[i].std.vol.had) { chan[i].outVol=MIN(31,chan[i].std.vol.val)-(31-(chan[i].vol&31)); if (chan[i].outVol<0) chan[i].outVol=0; - if (isMuted[i]) { - rWrite(0x08+i,0); - } else { - rWrite(0x08+i,(chan[i].outVol&31)|((chan[i].psgMode&4)<<3)); + if (!chan[i].psgMode.dac) { + if (isMuted[i]) { + rWrite(0x08+i,0); + } else { + rWrite(0x08+i,(chan[i].outVol&31)|((chan[i].psgMode.getEnvelope())<<3)); + } } } 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,chan[i].std.duty.val); } if (chan[i].std.wave.had) { + if (!chan[i].psgMode.dac) { chan[i].psgMode=(chan[i].std.wave.val+1)&7; if (isMuted[i]) { rWrite(0x08+i,0); } else { - rWrite(0x08+i,(chan[i].outVol&31)|((chan[i].psgMode&4)<<3)); + rWrite(0x08+i,(chan[i].outVol&31)|((chan[i].psgMode.getEnvelope())<<3)); + } } } if (chan[i].std.pitch.had) { @@ -250,6 +273,20 @@ void DivPlatformAY8930::tick(bool sysTick) { } if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1) { + if (chan[i].psgMode.dac) { + if (dumpWrites) addWrite(0xffff0002+(i<<8),0); + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AY8930); + chan[i].dac.sample=ins->amiga.getSample(chan[i].note); + if (chan[i].dac.sample<0 || chan[i].dac.sample>=parent->song.sampleLen) { + if (dumpWrites) { + rWrite(0x08+i,0); + addWrite(0xffff0000+(i<<8),chan[i].dac.sample); + } + chan[i].dac.pos=0; + chan[i].dac.period=0; + chan[i].keyOn=true; + } + } oldWrites[0x08+i]=-1; oldWrites[regMode[i]]=-1; } @@ -281,6 +318,19 @@ void DivPlatformAY8930::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); + if (chan[i].dac.furnaceDAC) { + double off=1.0; + if (chan[i].dac.sample>=0 && chan[i].dac.samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[i].dac.sample); + if (s->centerRate<1) { + off=1.0; + } else { + off=8363.0/(double)s->centerRate; + } + } + chan[i].dac.rate=((double)chipClock*4.0)/(double)(MAX(1,off*chan[i].freq)); + if (dumpWrites) addWrite(0xffff0001+(i<<8),chan[i].dac.rate); + } if (chan[i].freq>65535) chan[i].freq=65535; if (chan[i].keyOn) { if (chan[i].insChanged) { @@ -338,6 +388,62 @@ int DivPlatformAY8930::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AY8930); + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { + chan[c.chan].psgMode.dac=true; + } else if (chan[c.chan].dac.furnaceDAC) { + chan[c.chan].psgMode.dac=false; + } + if (chan[c.chan].psgMode.dac) { + if (skipRegisterWrites) break; + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { + chan[c.chan].dac.sample=ins->amiga.getSample(c.value); + if (chan[c.chan].dac.sample<0 || chan[c.chan].dac.sample>=parent->song.sampleLen) { + chan[c.chan].dac.sample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + break; + } else { + if (dumpWrites) { + rWrite(0x08+c.chan,0); + addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample); + } + } + chan[c.chan].dac.pos=0; + chan[c.chan].dac.period=0; + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].macroInit(ins); + if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + //chan[c.chan].keyOn=true; + chan[c.chan].dac.furnaceDAC=true; + } else { + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + } + chan[c.chan].dac.sample=12*sampleBank+chan[c.chan].note%12; + if (chan[c.chan].dac.sample>=parent->song.sampleLen) { + chan[c.chan].dac.sample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + break; + } else { + if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample); + } + chan[c.chan].dac.pos=0; + chan[c.chan].dac.period=0; + chan[c.chan].dac.rate=parent->getSample(chan[c.chan].dac.sample)->rate*4096; + if (dumpWrites) { + rWrite(0x08+c.chan,0); + addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dac.rate); + } + chan[c.chan].dac.furnaceDAC=false; + } + break; + } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); chan[c.chan].freqChanged=true; @@ -349,14 +455,19 @@ int DivPlatformAY8930::dispatch(DivCommand c) { if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } - if (isMuted[c.chan]) { - rWrite(0x08+c.chan,0); - } else { - rWrite(0x08+c.chan,(chan[c.chan].vol&31)|((chan[c.chan].psgMode&4)<<3)); + if (!chan[c.chan].psgMode.dac) { + if (isMuted[c.chan]) { + rWrite(0x08+c.chan,0); + } else { + rWrite(0x08+c.chan,(chan[c.chan].vol&31)|((chan[c.chan].psgMode.getEnvelope())<<3)); + } } break; } case DIV_CMD_NOTE_OFF: + chan[c.chan].dac.sample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + chan[c.chan].psgMode.dac=false; chan[c.chan].keyOff=true; chan[c.chan].active=false; chan[c.chan].macroInit(NULL); @@ -370,13 +481,15 @@ int DivPlatformAY8930::dispatch(DivCommand c) { if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } - if (isMuted[c.chan]) { - rWrite(0x08+c.chan,0); - } else { - if (chan[c.chan].active) rWrite(0x08+c.chan,(chan[c.chan].vol&31)|((chan[c.chan].psgMode&4)<<3)); + if (!chan[c.chan].psgMode.dac) { + if (isMuted[c.chan]) { + rWrite(0x08+c.chan,0); + } else { + if (chan[c.chan].active) rWrite(0x08+c.chan,(chan[c.chan].vol&31)|((chan[c.chan].psgMode.getEnvelope())<<3)); + } + break; } break; - break; } case DIV_CMD_GET_VOLUME: { return chan[c.chan].vol; @@ -423,11 +536,13 @@ int DivPlatformAY8930::dispatch(DivCommand c) { } case DIV_CMD_STD_NOISE_MODE: if (c.value<0x10) { - chan[c.chan].psgMode=(c.value+1)&7; - if (isMuted[c.chan]) { - rWrite(0x08+c.chan,0); - } else if (chan[c.chan].active) { - rWrite(0x08+c.chan,(chan[c.chan].outVol&31)|((chan[c.chan].psgMode&4)<<3)); + if (!chan[c.chan].psgMode.dac) { + chan[c.chan].psgMode=(c.value+1)&7; + if (isMuted[c.chan]) { + rWrite(0x08+c.chan,0); + } else if (chan[c.chan].active) { + rWrite(0x08+c.chan,(chan[c.chan].outVol&31)|((chan[c.chan].psgMode.getEnvelope())<<3)); + } } } else { chan[c.chan].duty=c.value&15; @@ -441,14 +556,14 @@ int DivPlatformAY8930::dispatch(DivCommand c) { chan[c.chan].envelope.mode=c.value>>4; rWrite(regMode[c.chan],chan[c.chan].envelope.mode); if (c.value&15) { - chan[c.chan].psgMode|=4; + chan[c.chan].psgMode.envelope|=1; } else { - chan[c.chan].psgMode&=~4; + chan[c.chan].psgMode.envelope&=~1; } if (isMuted[c.chan]) { rWrite(0x08+c.chan,0); } else { - rWrite(0x08+c.chan,(chan[c.chan].vol&31)|((chan[c.chan].psgMode&4)<<3)); + rWrite(0x08+c.chan,(chan[c.chan].vol&31)|((chan[c.chan].psgMode.getEnvelope())<<3)); } break; case DIV_CMD_AY_ENVELOPE_LOW: @@ -496,6 +611,15 @@ int DivPlatformAY8930::dispatch(DivCommand c) { updateOutSel(true); immWrite(14+(c.value?1:0),(c.value?portBVal:portAVal)); break; + case DIV_CMD_SAMPLE_MODE: + chan[c.chan].psgMode.dac=(c.value>0)?1:0; + break; + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + break; case DIV_ALWAYS_SET_VOLUME: return 0; break; @@ -506,6 +630,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: @@ -522,7 +647,11 @@ void DivPlatformAY8930::muteChannel(int ch, bool mute) { if (isMuted[ch]) { rWrite(0x08+ch,0); } else if (chan[ch].active) { - rWrite(0x08+ch,(chan[ch].outVol&31)|((chan[ch].psgMode&4)<<3)); + if (chan[ch].psgMode.dac) { + rWrite(0x08+ch,chan[ch].dac.out&31); + } else { + rWrite(0x08+ch,(chan[ch].outVol&31)|((chan[ch].psgMode.getEnvelope())<<3)); + } } } @@ -577,6 +706,7 @@ void DivPlatformAY8930::reset() { pendingWrites[i]=-1; } + sampleBank=0; ayNoiseAnd=2; ayNoiseOr=0; delay=0; @@ -665,6 +795,7 @@ void DivPlatformAY8930::setFlags(unsigned int flags) { } stereo=(flags>>6)&1; + stereoSep=(flags>>8)&255; } int DivPlatformAY8930::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { diff --git a/src/engine/platform/ay8930.h b/src/engine/platform/ay8930.h index 10ac7736b..441e82148 100644 --- a/src/engine/platform/ay8930.h +++ b/src/engine/platform/ay8930.h @@ -38,20 +38,86 @@ class DivPlatformAY8930: public DivDispatch { slideLow(0), slide(0) {} } envelope; - unsigned char freqH, freqL; + + struct PSGMode { + unsigned char tone: 1; + unsigned char noise: 1; + unsigned char envelope: 1; + unsigned char dac: 1; + + unsigned char getTone() { + return dac?0:(tone<<0); + } + + unsigned char getNoise() { + return dac?0:(noise<<1); + } + + unsigned char getEnvelope() { + return dac?0:(envelope<<2); + } + + PSGMode& operator=(unsigned char s) { + tone=(s>>0)&1; + noise=(s>>1)&1; + envelope=(s>>2)&1; + dac=(s>>3)&1; + return *this; + } + + PSGMode(): + tone(1), + noise(0), + envelope(0), + dac(0) {} + } psgMode; + + struct DAC { + int sample, rate, period, pos, out; + unsigned char furnaceDAC: 1; + + DAC(): + sample(-1), + rate(0), + period(0), + pos(0), + out(0), + furnaceDAC(0) {} + } dac; + int freq, baseFreq, note, pitch, pitch2; int ins; - unsigned char psgMode, autoEnvNum, autoEnvDen, duty; + unsigned char autoEnvNum, autoEnvDen, duty; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; int vol, outVol; - unsigned char pan; DivMacroInt std; void macroInit(DivInstrument* which) { std.init(which); pitch2=0; } - Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), note(0), pitch(0), pitch2(0), ins(-1), psgMode(1), autoEnvNum(0), autoEnvDen(0), duty(4), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(31), pan(3) {} + Channel(): + envelope(Envelope()), + psgMode(PSGMode()), + dac(DAC()), + freq(0), + baseFreq(0), + note(0), + pitch(0), + pitch2(0), + ins(-1), + autoEnvNum(0), + autoEnvDen(0), + duty(4), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + portaPause(false), + inPorta(false), + vol(0), + outVol(31) {} }; Channel chan[3]; bool isMuted[3]; @@ -66,8 +132,11 @@ class DivPlatformAY8930: public DivDispatch { DivDispatchOscBuffer* oscBuf[3]; unsigned char regPool[32]; unsigned char ayNoiseAnd, ayNoiseOr; + unsigned char stereoSep; bool bank; + unsigned char sampleBank; + int delay; bool extMode, stereo, clockSel; @@ -79,9 +148,12 @@ class DivPlatformAY8930: public DivDispatch { short* ayBuf[3]; size_t ayBufLen; + void runDAC(); + void checkWrites(); void updateOutSel(bool immediate=false); void immWrite(unsigned char a, unsigned char v); - + + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: @@ -103,7 +175,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 48a078803..89d609cec 100644 --- a/src/engine/platform/bubsyswsg.cpp +++ b/src/engine/platform/bubsyswsg.cpp @@ -39,21 +39,12 @@ 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; htick(); + k005289.tick(); // Wavetable part for (int i=0; i<2; i++) { @@ -61,7 +52,7 @@ void DivPlatformBubSysWSG::acquire(short* bufL, short* bufR, size_t start, size_ oscBuf[i]->data[oscBuf[i]->needle++]=0; continue; } else { - chanOut=chan[i].waveROM[k005289->addr(i)]*(regPool[2+i]&0xf); + chanOut=chan[i].waveROM[k005289.addr(i)]*(regPool[2+i]&0xf); out+=chanOut; if (writeOscBuf==0) { oscBuf[i]->data[oscBuf[i]->needle++]=chanOut<<7; @@ -101,18 +92,9 @@ void DivPlatformBubSysWSG::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 || chan[i].ws.activeChanged()) { @@ -140,9 +122,9 @@ void DivPlatformBubSysWSG::tick(bool sysTick) { chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>4095) chan[i].freq=4095; - k005289->load(i,chan[i].freq); + k005289.load(i,chan[i].freq); rWrite(i,chan[i].freq); - k005289->update(i); + k005289.update(i); if (chan[i].keyOn) { // ??? } @@ -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: @@ -312,7 +295,7 @@ void DivPlatformBubSysWSG::reset() { if (dumpWrites) { addWrite(0xffffffff,0); } - k005289->reset(); + k005289.reset(); } bool DivPlatformBubSysWSG::isStereo() { @@ -364,7 +347,6 @@ int DivPlatformBubSysWSG::init(DivEngine* p, int channels, int sugRate, unsigned oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); - k005289=new k005289_core(); reset(); return 2; } @@ -373,7 +355,6 @@ void DivPlatformBubSysWSG::quit() { for (int i=0; i<2; i++) { delete oscBuf[i]; } - delete k005289; } DivPlatformBubSysWSG::~DivPlatformBubSysWSG() { diff --git a/src/engine/platform/bubsyswsg.h b/src/engine/platform/bubsyswsg.h index e288493c1..34bdad3dc 100644 --- a/src/engine/platform/bubsyswsg.h +++ b/src/engine/platform/bubsyswsg.h @@ -24,7 +24,7 @@ #include #include "../macroInt.h" #include "../waveSynth.h" -#include "sound/k005289/k005289.hpp" +#include "vgsound_emu/src/k005289/k005289.hpp" class DivPlatformBubSysWSG: public DivDispatch { struct Channel { @@ -60,9 +60,10 @@ class DivPlatformBubSysWSG: public DivDispatch { bool isMuted[2]; unsigned char writeOscBuf; - k005289_core* k005289; + k005289_core k005289; unsigned short regPool[4]; - void updateWave(int ch); + void updateWave(int ch); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: void acquire(short* bufL, short* bufR, size_t start, size_t len); @@ -85,7 +86,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 a55b53906..ad1113470 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,62 +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=sid.get_dc(0); + 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]-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; + 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; + } } } } @@ -150,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); @@ -369,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: @@ -459,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() { @@ -512,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); @@ -540,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 @@ -563,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 d9dc08040..52685ce72 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,18 @@ 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 putDispatchChip(void*,int); 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 +104,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/dummy.h b/src/engine/platform/dummy.h index b8601059b..0b5181f01 100644 --- a/src/engine/platform/dummy.h +++ b/src/engine/platform/dummy.h @@ -33,7 +33,8 @@ class DivPlatformDummy: public DivDispatch { Channel chan[128]; DivDispatchOscBuffer* oscBuf[128]; bool isMuted[128]; - unsigned char chans; + unsigned char chans; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: void acquire(short* bufL, short* bufR, size_t start, size_t len); diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp index ea4b83e9d..7622a0990 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 f655a7777..2721876e9 100644 --- a/src/engine/platform/fds.h +++ b/src/engine/platform/fds.h @@ -77,7 +77,8 @@ class DivPlatformFDS: public DivDispatch { unsigned char regPool[128]; void updateWave(); - + + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); void doWrite(unsigned short addr, unsigned char data); @@ -104,7 +105,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/fmsharedbase.h b/src/engine/platform/fmsharedbase.h index 640997392..15baacefa 100644 --- a/src/engine/platform/fmsharedbase.h +++ b/src/engine/platform/fmsharedbase.h @@ -23,6 +23,8 @@ #include "../dispatch.h" #include +#define KVS(x,y) ((chan[x].state.op[y].kvs==2 && isOutput[chan[x].state.alg][y]) || chan[x].state.op[y].kvs==1) + class DivPlatformFMBase: public DivDispatch { protected: const bool isOutput[8][4]={ diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 999d31b91..fea89c71d 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 fe2f6e51b..347f528bd 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,21 +57,43 @@ 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(); - void updateWave(); + void updateWave(); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: void acquire(short* bufL, short* bufR, size_t start, size_t len); @@ -80,14 +107,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 cf7fb1013..39e7644a5 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -27,108 +27,6 @@ #define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6]))) -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; @@ -153,15 +51,13 @@ void DivPlatformGenesis::processDAC() { if (chan[i].dacPeriod>=(chipClock/576)) { if (s->samples>0) { while (chan[i].dacPeriod>=(chipClock/576)) { - chan[i].dacPos++; - if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[i].dacPos>=s->loopEnd) || (chan[i].dacPos>=s->samples)) { - if (s->isLoopable() && !chan[i].getDacDirection()) { - 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>=(unsigned int)s->loopEnd)) { + 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); } @@ -202,14 +98,12 @@ void DivPlatformGenesis::processDAC() { } } chan[5].dacPos++; - if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[5].dacPos>=s->loopEnd) || (chan[5].dacPos>=s->samples)) { - if (s->isLoopable() && !chan[5].getDacDirection()) { - chan[5].dacPos=s->loopStart; - } else { - chan[5].dacSample=-1; - if (parent->song.brokenDACMode) { - rWrite(0x2b,0); - } + if (!chan[5].dacDirection && (s->isLoopable() && chan[5].dacPos>=(unsigned int)s->loopEnd)) { + 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; @@ -360,7 +254,7 @@ void DivPlatformGenesis::tick(bool sysTick) { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -369,26 +263,40 @@ 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; - 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 (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)); + } } } @@ -419,7 +327,7 @@ void DivPlatformGenesis::tick(bool sysTick) { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -439,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]; @@ -472,7 +384,7 @@ void DivPlatformGenesis::tick(bool sysTick) { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -571,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; } } @@ -588,7 +501,7 @@ void DivPlatformGenesis::muteChannel(int ch, bool mute) { if (isMuted[ch]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[ch].state.alg][j]) { + if (KVS(ch,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[ch].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -688,6 +601,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); @@ -701,7 +619,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (isMuted[c.chan]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[c.chan].state.alg][i]) { + if (KVS(c.chan,i)) { if (!chan[c.chan].active || chan[c.chan].insChanged) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } @@ -774,7 +692,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (isMuted[c.chan]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[c.chan].state.alg][i]) { + if (KVS(c.chan,i)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -947,7 +865,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (isMuted[c.chan]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[c.chan].state.alg][c.value]) { + if (KVS(c.chan,c.value)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -1138,7 +1056,7 @@ void DivPlatformGenesis::forceIns() { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index 1322b3ebe..d81ba9bc6 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -45,9 +45,9 @@ class DivPlatformGenesis: public DivPlatformOPN { unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, note; int ins; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset, opMaskChanged; int vol, outVol; - unsigned char pan; + unsigned char pan, opMask; bool dacMode; int dacPeriod; @@ -86,9 +86,11 @@ class DivPlatformGenesis: public DivPlatformOPN { furnaceDac(false), inPorta(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(0), pan(3), + opMask(15), dacMode(false), dacPeriod(0), dacRate(0), @@ -118,7 +120,8 @@ class DivPlatformGenesis: public DivPlatformOPN { bool ladder; unsigned char dacVolTable[128]; - + + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); inline void processDAC(); @@ -149,7 +152,6 @@ class DivPlatformGenesis: public DivPlatformOPN { 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(): diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 1c44bcfb9..84b906bec 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -24,6 +24,9 @@ #define CHIP_FREQBASE fmFreqBase #define CHIP_DIVIDER fmDivBase +#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6]))) +#define IS_EXTCH_MUTED (isOpMuted[0] && isOpMuted[1] && isOpMuted[2] && isOpMuted[3]) + int DivPlatformGenesisExt::dispatch(DivCommand c) { if (c.chan<2) { return DivPlatformGenesis::dispatch(c); @@ -67,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; @@ -121,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: { @@ -205,7 +209,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { op.tl=c.value2; if (isOpMuted[ch]) { rWrite(baseAddr+0x40,127); - } else if (isOutput[chan[2].state.alg][c.value]) { + } else if (KVS(2,c.value)) { rWrite(baseAddr+0x40,127-VOL_SCALE_LOG(127-op.tl,opChan[ch].vol&0x7f,127)); } else { rWrite(baseAddr+0x40,op.tl); @@ -388,13 +392,15 @@ void DivPlatformGenesisExt::muteChannel(int ch, bool mute) { if (isOpMuted[ch-2]) { rWrite(baseAddr+0x40,127); immWrite(baseAddr+0x40,127); - } else if (isOutput[chan[2].state.alg][ordch]) { + } else if (KVS(2,ordch)) { rWrite(baseAddr+0x40,127-VOL_SCALE_LOG(127-op.tl,opChan[ch-2].vol&0x7f,127)); immWrite(baseAddr+0x40,127-VOL_SCALE_LOG(127-op.tl,opChan[ch-2].vol&0x7f,127)); } else { 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]={ @@ -410,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)); @@ -418,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); } } @@ -447,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; } } @@ -478,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); } @@ -501,7 +526,7 @@ void DivPlatformGenesisExt::forceIns() { if (i==2 && extMode) { // extended channel if (isOpMuted[j]) { rWrite(baseAddr+0x40,127); - } else if (isOutput[chan[i].state.alg][j]) { + } else if (KVS(i,j)) { rWrite(baseAddr+0x40,127-VOL_SCALE_LOG(127-op.tl,opChan[j].vol&0x7f,127)); } else { rWrite(baseAddr+0x40,op.tl); @@ -510,7 +535,7 @@ void DivPlatformGenesisExt::forceIns() { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -525,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; @@ -542,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) { diff --git a/src/engine/platform/genesisext.h b/src/engine/platform/genesisext.h index 07e0d5cc3..ffc83d46c 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,11 +46,13 @@ class DivPlatformGenesisExt: public DivPlatformGenesis { keyOff(false), portaPause(false), inPorta(false), + mask(true), vol(0), pan(3) {} }; OpChannel opChan[4]; bool isOpMuted[4]; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: int dispatch(DivCommand c); diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index 4fd30db98..4db41bcfb 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -34,6 +34,7 @@ #define WRITE_STEREO(v) rWrite(0x50,(v)) #define CHIP_DIVIDER 64 +#define CHIP_FREQBASE 16000000 #if defined( _MSC_VER ) @@ -129,19 +130,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); + WRITE_OUTPUT(i,CLAMP((s->data8[chan[i].samplePos]*chan[i].outVol)>>7,-128,127)); } + chan[i].samplePos++; - 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>=s->loopEnd) { + chan[i].samplePos=s->loopStart; + } else if (chan[i].samplePos>=(int)s->samples) { + chan[i].sample=-1; } } } @@ -179,7 +165,7 @@ void DivPlatformLynx::tick(bool sysTick) { chan[i].std.next(); if (chan[i].std.vol.had) { if (chan[i].pcm) { - chan[i].outVol=((chan[i].vol&127)*MIN(64,chan[i].std.vol.val))>>6; + chan[i].outVol=((chan[i].vol&127)*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul; } else { chan[i].outVol=((chan[i].vol&127)*MIN(127,chan[i].std.vol.val))>>7; } @@ -187,22 +173,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=NOTE_FREQUENCY(chan[i].actualNote); chan[i].freqChanged=true; } } @@ -233,6 +206,10 @@ void DivPlatformLynx::tick(bool sysTick) { if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1) { + if (chan[i].pcm && chan[i].sample>=0 && chan[i].samplesong.sampleLen) { + chan[i].sampleAccum=0; + chan[i].samplePos=0; + } WRITE_LFSR(i, 0); WRITE_OTHER(i, 0); } @@ -249,7 +226,7 @@ void DivPlatformLynx::tick(bool sysTick) { off=(double)s->centerRate/8363.0; } } - chan[i].sampleFreq=off*parent->calcFreq(chan[i].sampleBaseFreq,chan[i].pitch,false,2,chan[i].pitch2,1,1); + chan[i].sampleFreq=off*parent->calcFreq(chan[i].sampleBaseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE); } else { if (chan[i].lfsr >= 0) { WRITE_LFSR(i, (chan[i].lfsr&0xff)); @@ -279,11 +256,12 @@ int DivPlatformLynx::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_MIKEY); - chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA); + chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:127; + chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->amiga.useSample); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); if (chan[c.chan].pcm) { - chan[c.chan].sampleBaseFreq=parent->calcBaseFreq(1.0,1.0,c.value,false); + chan[c.chan].sampleBaseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].sample=ins->amiga.getSample(c.value); chan[c.chan].sampleAccum=0; chan[c.chan].samplePos=0; @@ -296,7 +274,7 @@ int DivPlatformLynx::dispatch(DivCommand c) { } chan[c.chan].active=true; WRITE_VOLUME(c.chan,(isMuted[c.chan]?0:(chan[c.chan].vol&127))); - chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_MIKEY)); + chan[c.chan].macroInit(ins); if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } @@ -376,7 +354,7 @@ int DivPlatformLynx::dispatch(DivCommand c) { int whatAMess=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_PERIODIC(whatAMess); if (chan[c.chan].pcm) { - chan[c.chan].sampleBaseFreq=parent->calcBaseFreq(1.0,1.0,whatAMess,false); + chan[c.chan].sampleBaseFreq=NOTE_FREQUENCY(whatAMess); } chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; @@ -387,6 +365,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 849c3182b..a6c1c91c1 100644 --- a/src/engine/platform/lynx.h +++ b/src/engine/platform/lynx.h @@ -48,6 +48,7 @@ class DivPlatformLynx: public DivDispatch { unsigned char pan; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, pcm; signed char vol, outVol; + int macroVolMul; void macroInit(DivInstrument* which) { std.init(which); pitch2=0; @@ -77,12 +78,14 @@ class DivPlatformLynx: public DivDispatch { inPorta(false), pcm(false), vol(127), - outVol(127) {} + outVol(127), + macroVolMul(127) {} }; Channel chan[4]; DivDispatchOscBuffer* oscBuf[4]; bool isMuted[4]; - std::unique_ptr mikey; + std::unique_ptr mikey; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: void acquire(short* bufL, short* bufR, size_t start, size_t len); @@ -104,7 +107,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 99e8043d1..7d271046e 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)); } dacPos++; - if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || (dacPos>=s->samples)) { - if (s->isLoopable()) { - dacPos=s->loopStart; - } else { - dacSample=-1; - } + if (s->isLoopable() && dacPos>=(unsigned int)s->loopEnd) { + dacPos=s->loopStart; + } else if (dacPos>=s->samples) { + dacSample=-1; } dacPeriod-=rate; } else { @@ -112,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; @@ -173,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) { @@ -203,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; @@ -284,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; @@ -317,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; @@ -325,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 f09092d55..0da3da32e 100644 --- a/src/engine/platform/mmc5.h +++ b/src/engine/platform/mmc5.h @@ -67,7 +67,8 @@ class DivPlatformMMC5: public DivDispatch { unsigned char writeOscBuf; struct _mmc5* mmc5; unsigned char regPool[128]; - + + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: @@ -89,7 +90,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 731bf8f28..ded3899ef 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, @@ -102,14 +90,56 @@ void DivPlatformMSM6258::acquire(short* bufL, short* bufR, size_t start, size_t } void DivPlatformMSM6258::tick(bool sysTick) { - // nothing + for (int i=0; i<1; i++) { + if (!parent->song.disableSampleMacro) { + chan[i].std.next(); + if (chan[i].std.duty.had) { + if (rateSel!=(chan[i].std.duty.val&3)) { + rateSel=chan[i].std.duty.val&3; + rWrite(12,rateSel); + } + } + if (chan[i].std.panL.had) { + if (chan[i].pan!=(chan[i].std.panL.val&3)) { + chan[i].pan=chan[i].std.panL.val&3; + rWrite(2,chan[i].pan); + } + } + if (chan[i].std.ex1.had) { + if (clockSel!=(chan[i].std.ex1.val&1)) { + clockSel=chan[i].std.ex1.val&1; + rWrite(8,clockSel); + } + } + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val && chan[i].active) { + chan[i].keyOn=true; + } + } + } + if (chan[i].keyOn || chan[i].keyOff) { + samplePos=0; + rWrite(0,1); // turn off + if (chan[i].active && !chan[i].keyOff) { + if (sample>=0 && samplesong.sampleLen) { + rWrite(0,2); + } else { + sample=-1; + } + } else { + sample=-1; + } + chan[i].keyOn=false; + chan[i].keyOff=false; + } + } } int DivPlatformMSM6258::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); - if (ins->type==DIV_INS_AMIGA) { + if (ins->type==DIV_INS_MSM6258 || ins->type==DIV_INS_AMIGA) { chan[c.chan].furnacePCM=true; } else { chan[c.chan].furnacePCM=false; @@ -121,6 +151,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) { @@ -129,8 +160,6 @@ int DivPlatformMSM6258::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - rWrite(0,1); - rWrite(0,2); } else { break; } @@ -144,8 +173,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); + chan[c.chan].active=true; + chan[c.chan].keyOn=true; } break; } @@ -153,18 +182,12 @@ int DivPlatformMSM6258::dispatch(DivCommand c) { chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; - rWrite(0,1); // turn off - sample=-1; - samplePos=0; chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; - rWrite(0,1); // turn off - sample=-1; - samplePos=0; chan[c.chan].std.release(); break; case DIV_CMD_ENV_RELEASE: diff --git a/src/engine/platform/msm6258.h b/src/engine/platform/msm6258.h index cd975c8f8..f06bdc261 100644 --- a/src/engine/platform/msm6258.h +++ b/src/engine/platform/msm6258.h @@ -87,6 +87,7 @@ class DivPlatformMSM6258: public DivDispatch { int delay, updateOsc, sample, samplePos; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: @@ -109,7 +110,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 fc5f9ea30..a898ba150 100644 --- a/src/engine/platform/msm6295.cpp +++ b/src/engine/platform/msm6295.cpp @@ -30,15 +30,6 @@ 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; - } - return NULL; -} - u8 DivPlatformMSM6295::read_byte(u32 address) { if (adpcmMem==NULL || address>=getSampleMemCapacity(0)) { return 0; @@ -77,9 +68,11 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t delay=w.delay; } } else { - delay--; + delay-=3; } + msm.tick(); + msm.tick(); msm.tick(); bufL[h]=msm.out()<<4; @@ -88,21 +81,54 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t 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.voice_out(i)<<6; } } } } void DivPlatformMSM6295::tick(bool sysTick) { - // nothing + for (int i=0; i<4; i++) { + if (!parent->song.disableSampleMacro) { + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=VOL_SCALE_LOG(chan[i].std.vol.val,chan[i].vol,8); + } + if (chan[i].std.duty.had) { + if (rateSel!=(chan[i].std.duty.val&1)) { + rateSel=chan[i].std.duty.val&1; + rWrite(12,!rateSel); + } + } + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val && chan[i].active) { + chan[i].keyOn=true; + } + } + } + if (chan[i].keyOn || chan[i].keyOff) { + rWriteDelay(0,(8<=0 && chan[i].samplesong.sampleLen) { + rWrite(0,0x80|chan[i].sample); // set phrase + rWrite(0,(16<getIns(chan[c.chan].ins,DIV_INS_FM); - if (ins->type==DIV_INS_AMIGA) { + if (ins->type==DIV_INS_MSM6295 || ins->type==DIV_INS_AMIGA) { chan[c.chan].furnacePCM=true; } else { chan[c.chan].furnacePCM=false; @@ -122,7 +148,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - rWriteDelay(0,(8<getSample(12*sampleBank+c.value%12); chan[c.chan].sample=12*sampleBank+c.value%12; - rWriteDelay(0,(8< -#include "sound/oki/msm6295.hpp" +#include "vgsound_emu/src/msm6295/msm6295.hpp" class DivPlatformMSM6295: public DivDispatch, public vgsound_emu_mem_intf { protected: @@ -46,8 +46,8 @@ class DivPlatformMSM6295: public DivDispatch, public vgsound_emu_mem_intf { keyOff(false), furnacePCM(false), hardReset(false), - vol(0), - outVol(15), + vol(8), + outVol(8), sample(-1) {} }; Channel chan[4]; @@ -57,7 +57,7 @@ class DivPlatformMSM6295: public DivDispatch, public vgsound_emu_mem_intf { unsigned short addr; unsigned char val; unsigned short delay; - QueuedWrite(unsigned short a, unsigned char v, unsigned short d=32): + QueuedWrite(unsigned short a, unsigned char v, unsigned short d=96): addr(a), val(v), delay(d) {} @@ -74,6 +74,7 @@ class DivPlatformMSM6295: public DivDispatch, public vgsound_emu_mem_intf { bool rateSel=false, rateSelInit=false; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: @@ -97,7 +98,6 @@ class DivPlatformMSM6295: public DivDispatch, public vgsound_emu_mem_intf { virtual void poke(std::vector& wlist) override; virtual void setFlags(unsigned int flags) override; virtual const char** getRegisterSheet() override; - virtual const char* getEffectName(unsigned char effect) override; virtual const void* getSampleMem(int index) override; virtual size_t getSampleMemCapacity(int index) override; virtual size_t getSampleMemUsage(int index) override; diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 50f9d8363..1597d0fa2 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; idata[oscBuf[i]->needle++]=n163.chan_out(i)<<7; + oscBuf[i]->data[oscBuf[i]->needle++]=n163.voice_out(i)<<7; } // command queue @@ -234,18 +189,9 @@ void DivPlatformN163::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) { 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 5e44d3dd8..0ff969b64 100644 --- a/src/engine/platform/n163.h +++ b/src/engine/platform/n163.h @@ -24,7 +24,7 @@ #include #include "../macroInt.h" #include "../waveSynth.h" -#include "sound/n163/n163.hpp" +#include "vgsound_emu/src/n163/n163.hpp" class DivPlatformN163: public DivDispatch { struct Channel { @@ -89,6 +89,7 @@ class DivPlatformN163: public DivDispatch { unsigned char regPool[128]; void updateWave(int ch, int wave, int pos, int len); void updateWaveCh(int ch); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: @@ -110,7 +111,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 6bcf43d27..236790e37 100644 --- a/src/engine/platform/namcowsg.cpp +++ b/src/engine/platform/namcowsg.cpp @@ -151,18 +151,6 @@ 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) { while (!writes.empty()) { QueuedWrite w=writes.front(); @@ -218,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()) { @@ -442,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 4ab81bdce..56a8ba3cd 100644 --- a/src/engine/platform/namcowsg.h +++ b/src/engine/platform/namcowsg.h @@ -74,6 +74,7 @@ class DivPlatformNamcoWSG: public DivDispatch { int devType, chans; unsigned char regPool[512]; void updateWave(int ch); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: void acquire(short* bufL, short* bufR, size_t start, size_t len); @@ -96,7 +97,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 ae0aa84cd..06962d351 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 "11xx: Write to delta modulation counter (0 to 7F)"; - break; - case 0x12: - return "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)"; - 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); @@ -109,12 +88,10 @@ void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) { } \ } \ dacPos++; \ - if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || (dacPos>=s->samples)) { \ - if (s->isLoopable()) { \ - dacPos=s->loopStart; \ - } else { \ - dacSample=-1; \ - } \ + if (s->isLoopable() && dacPos>=(unsigned int)s->loopEnd) { \ + dacPos=s->loopStart; \ + } else if (dacPos>=s->samples) { \ + dacSample=-1; \ } \ dacPeriod-=rate; \ } else { \ @@ -138,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); } } } @@ -242,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; @@ -356,9 +320,10 @@ void DivPlatformNES::tick(bool sysTick) { unsigned int dpcmAddr=parent->getSample(dacSample)->offDPCM; unsigned int dpcmLen=(parent->getSample(dacSample)->lengthDPCM+15)>>4; if (dpcmLen>255) dpcmLen=255; + goingToLoop=parent->getSample(dacSample)->isLoopable(); // write DPCM rWrite(0x4015,15); - rWrite(0x4010,calcDPCMRate(dacRate)); + rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0)); rWrite(0x4012,(dpcmAddr>>6)&0xff); rWrite(0x4013,dpcmLen&0xff); rWrite(0x4015,31); @@ -366,7 +331,7 @@ void DivPlatformNES::tick(bool sysTick) { } } else { if (dpcmMode) { - rWrite(0x4010,calcDPCMRate(dacRate)); + rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0)); } } if (dumpWrites && !dpcmMode) addWrite(0xffff0001,dacRate); @@ -421,9 +386,10 @@ int DivPlatformNES::dispatch(DivCommand c) { unsigned int dpcmAddr=parent->getSample(dacSample)->offDPCM; unsigned int dpcmLen=(parent->getSample(dacSample)->lengthDPCM+15)>>4; if (dpcmLen>255) dpcmLen=255; + goingToLoop=parent->getSample(dacSample)->isLoopable(); // write DPCM rWrite(0x4015,15); - rWrite(0x4010,calcDPCMRate(dacRate)); + rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0)); rWrite(0x4012,(dpcmAddr>>6)&0xff); rWrite(0x4013,dpcmLen&0xff); rWrite(0x4015,31); @@ -574,6 +540,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: @@ -647,6 +614,7 @@ void DivPlatformNES::reset() { sampleBank=0; dpcmBank=0; dpcmMode=false; + goingToLoop=false; if (useNP) { nes1_NP->Reset(); diff --git a/src/engine/platform/nes.h b/src/engine/platform/nes.h index 35c51df74..2a9923fa6 100644 --- a/src/engine/platform/nes.h +++ b/src/engine/platform/nes.h @@ -73,11 +73,13 @@ class DivPlatformNES: public DivDispatch { bool dpcmMode; bool dacAntiClickOn; bool useNP; + bool goingToLoop; struct NESAPU* nes; xgm::NES_APU* nes1_NP; xgm::NES_DMC* nes2_NP; unsigned char regPool[128]; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); void doWrite(unsigned short addr, unsigned char data); @@ -106,7 +108,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 9373d0cd8..5d81f0834 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -26,6 +26,8 @@ #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 KVSL(x,y) ((chan[x].state.op[orderedOpsL1[ops==4][y]].kvs==2 && isOutputL[ops==4][chan[x].state.alg][y]) || chan[x].state.op[orderedOpsL1[ops==4][y]].kvs==1) + #define CHIP_FREQBASE chipFreqBase // N = invalid @@ -138,6 +140,11 @@ const bool isOutputL[2][4][4]={ #undef N +const int orderedOpsL1[2][4]={ + {0, 1, 0, 1}, // 2-op + {0, 2, 1, 3} // 4-op +}; + const int orderedOpsL[4]={ 0,2,1,3 }; @@ -152,98 +159,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]; @@ -293,7 +208,7 @@ 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; } @@ -341,7 +256,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]; + } } } @@ -378,7 +295,7 @@ void DivPlatformOPL::tick(bool sysTick) { if (isMuted[i]) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { - if (isOutputL[ops==4][chan[i].state.alg][j] || i>melodicChans) { + if (KVSL(i,j) || i>melodicChans) { rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG(63-op.tl,chan[i].outVol&0x3f,63))|(op.ksl<<6)); } else { rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6)); @@ -389,18 +306,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) { @@ -505,7 +413,7 @@ void DivPlatformOPL::tick(bool sysTick) { if (isMuted[i]) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { - if (isOutputL[ops==4][chan[i].state.alg][j] || i>melodicChans) { + if (KVSL(i,j) || i>melodicChans) { rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG(63-op.tl,chan[i].outVol&0x3f,63))|(op.ksl<<6)); } else { rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6)); @@ -577,35 +485,50 @@ void DivPlatformOPL::tick(bool sysTick) { chan[adpcmChan].std.next(); if (chan[adpcmChan].std.vol.had) { - chan[adpcmChan].outVol=(chan[adpcmChan].vol*MIN(64,chan[adpcmChan].std.vol.val))/64; + chan[adpcmChan].outVol=(chan[adpcmChan].vol*MIN(chan[adpcmChan].macroVolMul,chan[adpcmChan].std.vol.val))/chan[adpcmChan].macroVolMul; immWrite(18,chan[adpcmChan].outVol); } 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].std.phaseReset.had) { + if ((chan[adpcmChan].std.phaseReset.val==1) && chan[adpcmChan].active) { + chan[adpcmChan].keyOn=true; } } } - if (chan[adpcmChan].freqChanged) { + if (chan[adpcmChan].freqChanged || chan[adpcmChan].keyOn || chan[adpcmChan].keyOff) { if (chan[adpcmChan].sample>=0 && chan[adpcmChan].samplesong.sampleLen) { double off=65535.0*(double)(parent->getSample(chan[adpcmChan].sample)->centerRate)/8363.0; chan[adpcmChan].freq=parent->calcFreq(chan[adpcmChan].baseFreq,chan[adpcmChan].pitch,false,4,chan[adpcmChan].pitch2,(double)chipClock/144,off); } else { chan[adpcmChan].freq=0; } + if (chan[adpcmChan].fixedFreq>0) chan[adpcmChan].freq=chan[adpcmChan].fixedFreq; + if (pretendYMU) { // YMU759 only does 4KHz or 8KHz + if (chan[adpcmChan].freq>7500) { + chan[adpcmChan].freq=10922; // 8KHz + } else { + chan[adpcmChan].freq=5461; // 4KHz + } + } immWrite(16,chan[adpcmChan].freq&0xff); immWrite(17,(chan[adpcmChan].freq>>8)&0xff); + if (chan[adpcmChan].keyOn || chan[adpcmChan].keyOff) { + immWrite(7,0x01); // reset + if (chan[adpcmChan].active && chan[adpcmChan].keyOn && !chan[adpcmChan].keyOff) { + if (chan[adpcmChan].sample>=0 && chan[adpcmChan].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[adpcmChan].sample); + immWrite(7,(s->isLoopable())?0xb0:0xa0); // start/repeat + } + } + chan[adpcmChan].keyOn=false; + chan[adpcmChan].keyOff=false; + } chan[adpcmChan].freqChanged=false; } } @@ -718,7 +641,7 @@ void DivPlatformOPL::muteChannel(int ch, bool mute) { if (isMuted[ch]) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { - if (isOutputL[ops==4][chan[ch].state.alg][i] || ch>melodicChans) { + if (KVSL(ch,i) || ch>melodicChans) { rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG(63-op.tl,chan[ch].outVol&0x3f,63))|(op.ksl<<6)); } else { rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6)); @@ -749,7 +672,8 @@ int DivPlatformOPL::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: { if (c.chan==adpcmChan) { // ADPCM DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); - if (ins->type==DIV_INS_AMIGA) { + chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255; + if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_ADPCMB) { chan[c.chan].furnacePCM=true; } else { chan[c.chan].furnacePCM=false; @@ -757,6 +681,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (skipRegisterWrites) break; if (chan[c.chan].furnacePCM) { chan[c.chan].macroInit(ins); + chan[c.chan].fixedFreq=0; if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(18,chan[c.chan].outVol); @@ -765,13 +690,11 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); immWrite(8,0); - immWrite(7,0x01); // reset immWrite(9,(s->offB>>2)&0xff); immWrite(10,(s->offB>>10)&0xff); 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 if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); @@ -792,25 +715,30 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].macroInit(NULL); chan[c.chan].outVol=chan[c.chan].vol; if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + break; + } + chan[c.chan].sample=12*sampleBank+c.value%12; + if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + DivSample* s=parent->getSample(12*sampleBank+c.value%12); + immWrite(8,0); + immWrite(9,(s->offB>>2)&0xff); + immWrite(10,(s->offB>>10)&0xff); + int end=s->offB+s->lengthB-1; + immWrite(11,(end>>2)&0xff); + immWrite(12,(end>>10)&0xff); + int freq=(65536.0*(double)s->rate)/(double)chipRateBase; + chan[c.chan].fixedFreq=freq; + immWrite(16,freq&0xff); + immWrite(17,(freq>>8)&0xff); + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + } else { immWrite(7,0x01); // reset immWrite(9,0); immWrite(10,0); immWrite(11,0); immWrite(12,0); - break; } - DivSample* s=parent->getSample(12*sampleBank+c.value%12); - immWrite(8,0); - immWrite(7,0x01); // reset - immWrite(9,(s->offB>>2)&0xff); - immWrite(10,(s->offB>>10)&0xff); - 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)chipRateBase; - immWrite(16,freq&0xff); - immWrite(17,(freq>>8)&0xff); } break; } @@ -872,6 +800,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; @@ -884,7 +819,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (isMuted[c.chan]) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { - if (isOutputL[ops==4][chan[c.chan].state.alg][i] || c.chan>melodicChans) { + if (KVSL(c.chan,i) || c.chan>melodicChans) { rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG(63-op.tl,chan[c.chan].outVol&0x3f,63))|(op.ksl<<6)); } else { rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6)); @@ -955,19 +890,11 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_OFF: - if (c.chan==adpcmChan) { - immWrite(7,0x01); // reset - break; - } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; break; case DIV_CMD_NOTE_OFF_ENV: - if (c.chan==adpcmChan) { - immWrite(7,0x01); // reset - break; - } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; @@ -1000,7 +927,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (isMuted[c.chan]) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { - if (isOutputL[ops==4][chan[c.chan].state.alg][i] || c.chan>melodicChans) { + if (KVSL(c.chan,i) || c.chan>melodicChans) { rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG(63-op.tl,chan[c.chan].outVol&0x3f,63))|(op.ksl<<6)); } else { rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6)); @@ -1149,7 +1076,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (isMuted[c.chan]) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { - if (isOutputL[ops==4][chan[c.chan].state.alg][c.value] || c.chan>melodicChans) { + if (KVSL(c.chan,c.value) || c.chan>melodicChans) { rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG(63-op.tl,chan[c.chan].outVol&0x3f,63))|(op.ksl<<6)); } else { rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6)); @@ -1379,7 +1306,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (isMuted[c.chan]) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { - if (isOutputL[ops==4][chan[c.chan].state.alg][i] || c.chan>melodicChans) { + if (KVSL(c.chan,i) || c.chan>melodicChans) { rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG(63-op.tl,chan[c.chan].outVol&0x3f,63))|(op.ksl<<6)); } else { rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6)); @@ -1396,7 +1323,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (isMuted[c.chan]) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { - if (isOutputL[ops==4][chan[c.chan].state.alg][c.value] || c.chan>melodicChans) { + if (KVSL(c.chan,c.value) || c.chan>melodicChans) { rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG(63-op.tl,chan[c.chan].outVol&0x3f,63))|(op.ksl<<6)); } else { rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6)); @@ -1434,6 +1361,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: @@ -1470,7 +1400,7 @@ void DivPlatformOPL::forceIns() { if (isMuted[i]) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { - if (isOutputL[ops==4][chan[i].state.alg][j] || i>melodicChans) { + if (KVSL(i,j) || i>melodicChans) { rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG(63-op.tl,chan[i].outVol&0x3f,63))|(op.ksl<<6)); } else { rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6)); @@ -1519,6 +1449,15 @@ DivMacroInt* DivPlatformOPL::getChanMacroInt(int ch) { DivDispatchOscBuffer* DivPlatformOPL::getOscBuffer(int ch) { if (ch>=18) return NULL; + if (oplType==3 && ch<12) { + if (chan[ch&(~1)].fourOp) { + if (ch&1) { + return oscBuf[ch-1]; + } else { + return oscBuf[ch+1]; + } + } + } return oscBuf[ch]; } @@ -1620,7 +1559,7 @@ void DivPlatformOPL::reset() { } bool DivPlatformOPL::isStereo() { - return true; + return (oplType==3 || oplType==759); } bool DivPlatformOPL::keyOffAffectsArp(int ch) { @@ -1677,7 +1616,7 @@ 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; @@ -1691,6 +1630,7 @@ void DivPlatformOPL::setOPLType(int type, bool drums) { pretendYMU=true; adpcmChan=16; } else if (type==4) { + chipFreqBase=32768*684; downsample=true; } break; diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index 3d6497367..43f02790f 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -44,6 +44,7 @@ class DivPlatformOPL: public DivDispatch { bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnacePCM, inPorta, fourOp, hardReset; int vol, outVol; unsigned char pan; + int macroVolMul; void macroInit(DivInstrument* which) { std.init(which); pitch2=0; @@ -70,7 +71,8 @@ class DivPlatformOPL: public DivDispatch { fourOp(false), hardReset(false), vol(0), - pan(3) { + pan(3), + macroVolMul(64) { state.ops=2; } }; @@ -116,6 +118,7 @@ class DivPlatformOPL: public DivDispatch { int toFreq(int freq); double NOTE_ADPCMB(int note); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len); @@ -145,7 +148,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 08292be68..5c6981588 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 }; @@ -169,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) { @@ -314,8 +243,9 @@ void DivPlatformOPLL::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>262143) chan[i].freq=262143; - int freqt=toFreq(chan[i].freq)+chan[i].pitch2; + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>65535) chan[i].freq=65535; + int freqt=toFreq(chan[i].freq); chan[i].freqL=freqt&0xff; if (i>=6 && properDrums) { immWrite(0x10+drumSlot[i],freqt&0xff); @@ -831,6 +761,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 dad660dea..3f243057b 100644 --- a/src/engine/platform/opll.h +++ b/src/engine/platform/opll.h @@ -93,6 +93,7 @@ class DivPlatformOPLL: public DivDispatch { int octave(int freq); int toFreq(int freq); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len); @@ -122,7 +123,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 82c8dff24..19b71a1be 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)); + signed char dacData=((signed char)((unsigned char)s->data8[chan[i].dacPos]^0x80))>>3; + chan[i].dacOut=CLAMP(dacData,-16,15); + if (!isMuted[i]) { + chWrite(i,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[i].outVol)); + chWrite(i,0x06,chan[i].dacOut&0x1f); + } else { + chWrite(i,0x04,0xc0); + chWrite(i,0x06,0x10); + } chan[i].dacPos++; - if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[i].dacPos>=s->loopEnd) || (chan[i].dacPos>=s->samples)) { - if (s->isLoopable()) { - chan[i].dacPos=s->loopStart; - } else { - chan[i].dacSample=-1; - } + if (s->isLoopable() && chan[i].dacPos>=(unsigned int)s->loopEnd) { + chan[i].dacPos=s->loopStart; + } else if (chan[i].dacPos>=s->samples) { + chan[i].dacSample=-1; } chan[i].dacPeriod-=rate; } @@ -117,7 +101,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 +119,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 +144,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 +169,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 +203,21 @@ void DivPlatformPCE::tick(bool sysTick) { } chan[i].freqChanged=true; } + if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) { + if (chan[i].furnaceDac && chan[i].pcm) { + if (chan[i].active && chan[i].dacSample>=0 && chan[i].dacSamplesong.sampleLen) { + chan[i].dacPos=0; + chan[i].dacPeriod=0; + chWrite(i,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[i].vol)); + addWrite(0xffff0000+(i<<8),chan[i].dacSample); + chan[i].keyOn=true; + } + } + 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); } } @@ -262,13 +258,14 @@ int DivPlatformPCE::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PCE); - if (ins->type==DIV_INS_AMIGA) { + chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:31; + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { chan[c.chan].pcm=true; } else if (chan[c.chan].furnaceDac) { chan[c.chan].pcm=false; } if (chan[c.chan].pcm) { - if (ins->type==DIV_INS_AMIGA) { + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { chan[c.chan].furnaceDac=true; if (skipRegisterWrites) break; chan[c.chan].dacSample=ins->amiga.getSample(c.value); @@ -278,7 +275,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { break; } else { if (dumpWrites) { - chWrite(c.chan,0x04,0xdf); + chWrite(c.chan,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[c.chan].vol)); addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample); } } @@ -291,6 +288,9 @@ int DivPlatformPCE::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].macroInit(ins); + if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } //chan[c.chan].keyOn=true; } else { chan[c.chan].furnaceDac=false; @@ -310,7 +310,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { chan[c.chan].dacPeriod=0; chan[c.chan].dacRate=parent->getSample(chan[c.chan].dacSample)->rate; if (dumpWrites) { - chWrite(c.chan,0x04,0xdf); + chWrite(c.chan,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[c.chan].vol)); addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dacRate); } } @@ -362,7 +362,9 @@ int DivPlatformPCE::dispatch(DivCommand c) { chan[c.chan].vol=c.value; if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; - if (chan[c.chan].active) chWrite(c.chan,0x04,0x80|chan[c.chan].outVol); + if (chan[c.chan].active && !chan[c.chan].pcm) { + chWrite(c.chan,0x04,0x80|chan[c.chan].outVol); + } } } break; @@ -445,6 +447,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: @@ -462,6 +465,10 @@ int DivPlatformPCE::dispatch(DivCommand c) { void DivPlatformPCE::muteChannel(int ch, bool mute) { isMuted[ch]=mute; chWrite(ch,0x05,isMuted[ch]?0:chan[ch].pan); + if (!isMuted[ch] && (chan[ch].pcm && chan[ch].dacSample!=-1)) { + chWrite(ch,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[ch].outVol)); + chWrite(ch,0x06,chan[ch].dacOut&0x1f); + } } void DivPlatformPCE::forceIns() { @@ -556,10 +563,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 +593,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 +603,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 870b5218a..ec0308340 100644 --- a/src/engine/platform/pce.h +++ b/src/engine/platform/pce.h @@ -28,13 +28,14 @@ class DivPlatformPCE: public DivDispatch { struct Channel { - int freq, baseFreq, pitch, pitch2, note; - int dacPeriod, dacRate; + int freq, baseFreq, pitch, pitch2, note, antiClickPeriodCount, antiClickWavePos; + int dacPeriod, dacRate, dacOut; 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; + int macroVolMul; DivMacroInt std; DivWaveSynth ws; void macroInit(DivInstrument* which) { @@ -47,8 +48,11 @@ class DivPlatformPCE: public DivDispatch { pitch(0), pitch2(0), note(0), + antiClickPeriodCount(0), + antiClickWavePos(0), dacPeriod(0), dacRate(0), + dacOut(0), dacPos(0), dacSample(-1), ins(-1), @@ -62,13 +66,16 @@ class DivPlatformPCE: public DivDispatch { noise(false), pcm(false), furnaceDac(false), + deferredWaveUpdate(false), vol(31), outVol(31), - wave(-1) {} + wave(-1), + macroVolMul(31) {} }; Channel chan[6]; DivDispatchOscBuffer* oscBuf[6]; bool isMuted[6]; + bool antiClickEnabled; struct QueuedWrite { unsigned char addr; unsigned char val; @@ -84,6 +91,7 @@ class DivPlatformPCE: public DivDispatch { PCE_PSG* pce; unsigned char regPool[128]; void updateWave(int ch); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: void acquire(short* bufL, short* bufR, size_t start, size_t len); @@ -105,7 +113,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 000000000..f7db0ef4c --- /dev/null +++ b/src/engine/platform/pcmdac.cpp @@ -0,0 +1,409 @@ +/** + * 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.useWave) && chan.audDir)?-(chan.freq>>16):(chan.freq>>16); + chan.audSub+=(chan.freq&0xffff); + if (chan.audSub>=0x10000) { + chan.audSub-=0x10000; + chan.audPos+=((!chan.useWave) && chan.audDir)?-1:1; + } + if (chan.useWave) { + if (chan.audPos>=(int)chan.audLen) { + chan.audPos%=chan.audLen; + chan.audDir=false; + } + output=(chan.ws.output[chan.audPos]^0x80)<<8; + } else { + DivSample* s=parent->getSample(chan.sample); + if (s->samples>0) { + if (chan.audDir) { + if (s->isLoopable()) { + switch (s->loopMode) { + case DIV_SAMPLE_LOOP_FORWARD: + case DIV_SAMPLE_LOOP_PINGPONG: + if (chan.audPosloopStart) { + chan.audPos=s->loopStart+(s->loopStart-chan.audPos); + chan.audDir=false; + } + break; + case DIV_SAMPLE_LOOP_BACKWARD: + if (chan.audPosloopStart) { + chan.audPos=s->loopEnd-1-(s->loopStart-chan.audPos); + chan.audDir=true; + } + break; + default: + if (chan.audPos<0) { + chan.sample=-1; + } + break; + } + } else if (chan.audPos>=(int)s->samples) { + chan.sample=-1; + } + } else { + if (s->isLoopable()) { + switch (s->loopMode) { + case DIV_SAMPLE_LOOP_FORWARD: + if (chan.audPos>=s->loopEnd) { + chan.audPos=(chan.audPos+s->loopStart)-s->loopEnd; + chan.audDir=false; + } + break; + case DIV_SAMPLE_LOOP_BACKWARD: + case DIV_SAMPLE_LOOP_PINGPONG: + if (chan.audPos>=s->loopEnd) { + chan.audPos=s->loopEnd-1-(s->loopEnd-1-chan.audPos); + chan.audDir=true; + } + break; + default: + if (chan.audPos>=(int)s->samples) { + chan.sample=-1; + } + break; + } + } else if (chan.audPos>=(int)s->samples) { + chan.sample=-1; + } + } + if (chan.audPos>=0 && chan.audPos<(int)s->samples) { + 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.audDir=false; + 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; + if (chan.insChanged) { + if (chan.wave<0) { + chan.wave=0; + chan.ws.setWidth(chan.audLen); + 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.audDir=false; + 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,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.audDir=false; + 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 000000000..7292f6ddf --- /dev/null +++ b/src/engine/platform/pcmdac.h @@ -0,0 +1,102 @@ +/** + * 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; + bool audDir; + unsigned int audLoc; + unsigned short audLen; + 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), + audDir(false), + 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 putDispatchChip(void*,int); + 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 57c72b677..ab3b8a7ca 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -190,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; @@ -351,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) { @@ -471,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: diff --git a/src/engine/platform/pcspkr.h b/src/engine/platform/pcspkr.h index cb6f070fe..48bf1bf56 100644 --- a/src/engine/platform/pcspkr.h +++ b/src/engine/platform/pcspkr.h @@ -84,6 +84,7 @@ class DivPlatformPCSpeaker: public DivDispatch { unsigned short freq, lastFreq; unsigned char regPool[2]; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); void beepFreq(int freq, int delay=0); @@ -113,7 +114,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 9b3c99886..c314edf6f 100644 --- a/src/engine/platform/pet.cpp +++ b/src/engine/platform/pet.cpp @@ -37,15 +37,6 @@ const char** DivPlatformPET::getRegisterSheet() { return regCheatSheet6522; } -const char* DivPlatformPET::getEffectName(unsigned char effect) { - switch (effect) { - case 0x10: - return "10xx: Change waveform"; - break; - } - return NULL; -} - // high-level emulation of 6522 shift register and driver software for now void DivPlatformPET::rWrite(unsigned int addr, unsigned char val) { bool hwSROutput=((regPool[11]>>2)&7)==4; @@ -113,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) { @@ -133,8 +115,14 @@ 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)-2; if (chan.freq>65535) chan.freq=65535; @@ -239,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 06c7e736a..3046cc263 100644 --- a/src/engine/platform/pet.h +++ b/src/engine/platform/pet.h @@ -62,6 +62,7 @@ class DivPlatformPET: public DivDispatch { bool isMuted; unsigned char regPool[16]; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: void acquire(short* bufL, short* bufR, size_t start, size_t len); @@ -80,7 +81,6 @@ 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(); diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index 33996a924..4f7d2545f 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; h>6; + if (chan[i].isNewQSound) { + chan[i].outVol=((chan[i].vol&0xff)*MIN(16383,chan[i].std.vol.val))/16383; + chan[i].resVol=((chan[i].vol&0xff)*MIN(16383,chan[i].std.vol.val))/255; + } else { + chan[i].outVol=((chan[i].vol&0xff)*chan[i].std.vol.val)>>6; + chan[i].resVol=chan[i].outVol<<4; + } // Check if enabled and write volume if (chan[i].active) { - rWrite(q1_reg_map[Q1V_VOL][i], chan[i].outVol << 4); + rWrite(q1_reg_map[Q1V_VOL][i],chan[i].resVol); } } uint16_t qsound_bank = 0; @@ -301,32 +289,34 @@ void DivPlatformQSound::tick(bool sysTick) { qsound_bank = 0x8000 | (s->offQSound >> 16); qsound_addr = s->offQSound & 0xffff; - int length = s->isLoopable()?s->loopEnd:s->samples; + int loopStart=s->loopStart; + int length = s->loopEnd; if (length > 65536 - 16) { length = 65536 - 16; } - if ((!s->isLoopable()) || s->loopStart>=length) { + if (loopStart == -1 || loopStart >= length) { qsound_end = s->offQSound + length + 15; qsound_loop = 15; } else { qsound_end = s->offQSound + length; - qsound_loop = length - s->loopStart; + qsound_loop = length - loopStart; } } 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].isNewQSound && chan[i].std.duty.had) { + chan[i].echo=CLAMP(chan[i].std.duty.val,0,32767); + immWrite(Q1_ECHO+i,chan[i].echo&0x7fff); + } + if (chan[i].isNewQSound && chan[i].std.ex1.had) { + immWrite(Q1_ECHO_FEEDBACK,chan[i].std.ex1.val&0x3fff); + } + if (chan[i].isNewQSound && chan[i].std.ex2.had) { + immWrite(Q1_ECHO_LENGTH,0xfff-(2725-CLAMP(chan[i].std.ex2.val&0xfff,0,2725))); } if (chan[i].std.pitch.had) { if (chan[i].std.pitch.mode) { @@ -346,6 +336,11 @@ void DivPlatformQSound::tick(bool sysTick) { if (chan[i].std.panL.had || chan[i].std.panR.had) { immWrite(Q1_PAN+i,chan[i].panning+0x110+(chan[i].surround?0:0x30)); } + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1 && chan[i].active && (chan[i].sample>=0 && chan[i].samplesong.sampleLen)) { + chan[i].keyOn=true; + } + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA); double off=1.0; @@ -358,7 +353,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); @@ -368,16 +363,21 @@ void DivPlatformQSound::tick(bool sysTick) { //logV("ch %d bank=%04x, addr=%04x, end=%04x, loop=%04x!",i,qsound_bank,qsound_addr,qsound_end,qsound_loop); // Write sample address. Enable volume if (!chan[i].std.vol.had) { - rWrite(q1_reg_map[Q1V_VOL][i], chan[i].vol << 4); + if (chan[i].isNewQSound) { + chan[i].resVol=(chan[i].vol*16383)/255; + } else { + chan[i].resVol=chan[i].vol<<4; + } + rWrite(q1_reg_map[Q1V_VOL][i],chan[i].resVol); } } if (chan[i].keyOff) { // Disable volume - rWrite(q1_reg_map[Q1V_VOL][i], 0); - rWrite(q1_reg_map[Q1V_FREQ][i], 0); + rWrite(q1_reg_map[Q1V_VOL][i],0); + rWrite(q1_reg_map[Q1V_FREQ][i],0); } else if (chan[i].active) { //logV("ch %d frequency set to %04x, off=%f, note=%d, %04x!",i,chan[i].freq,off,chan[i].note,QS_NOTE_FREQUENCY(chan[i].note)); - rWrite(q1_reg_map[Q1V_FREQ][i], chan[i].freq); + rWrite(q1_reg_map[Q1V_FREQ][i],chan[i].freq); } if (chan[i].keyOn) chan[i].keyOn=false; if (chan[i].keyOff) chan[i].keyOff=false; @@ -390,6 +390,7 @@ int DivPlatformQSound::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); + chan[c.chan].isNewQSound=(ins->type==DIV_INS_QSOUND); chan[c.chan].sample=ins->amiga.getSample(c.value); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value); @@ -406,6 +407,11 @@ int DivPlatformQSound::dispatch(DivCommand c) { chan[c.chan].macroInit(ins); if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; + if (chan[c.chan].isNewQSound) { + chan[c.chan].resVol=(chan[c.chan].outVol*16383)/255; + } else { + chan[c.chan].resVol=chan[c.chan].outVol<<4; + } } break; } @@ -430,8 +436,13 @@ int DivPlatformQSound::dispatch(DivCommand c) { if (!chan[c.chan].std.vol.has) { // Check if enabled and write volume chan[c.chan].outVol=c.value; - if (chan[c.chan].active && c.chan < 16) { - rWrite(q1_reg_map[Q1V_VOL][c.chan], chan[c.chan].outVol << 4); + if (chan[c.chan].isNewQSound) { + chan[c.chan].resVol=(chan[c.chan].outVol*16383)/255; + } else { + chan[c.chan].resVol=chan[c.chan].outVol<<4; + } + if (chan[c.chan].active && c.chan<16) { + rWrite(q1_reg_map[Q1V_VOL][c.chan],chan[c.chan].resVol); } } } @@ -447,7 +458,8 @@ int DivPlatformQSound::dispatch(DivCommand c) { immWrite(Q1_PAN+c.chan,chan[c.chan].panning+0x110+(chan[c.chan].surround?0:0x30)); break; case DIV_CMD_QSOUND_ECHO_LEVEL: - immWrite(Q1_ECHO+c.chan, c.value << 7); + chan[c.chan].echo=c.value<<7; + immWrite(Q1_ECHO+c.chan,chan[c.chan].echo&0x7fff); break; case DIV_CMD_QSOUND_ECHO_FEEDBACK: immWrite(Q1_ECHO_FEEDBACK, c.value << 6); @@ -496,6 +508,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 285760138..5e2a32fc7 100644 --- a/src/engine/platform/qsound.h +++ b/src/engine/platform/qsound.h @@ -28,13 +28,12 @@ class DivPlatformQSound: public DivDispatch { struct Channel { int freq, baseFreq, pitch, pitch2; - unsigned short audLen; - unsigned int audPos; int sample, wave, ins; int note; int panning; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, surround; - int vol, outVol; + int echo; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, surround, isNewQSound; + int vol, outVol, resVol; DivMacroInt std; void macroInit(DivInstrument* which) { std.init(which); @@ -45,12 +44,11 @@ class DivPlatformQSound: public DivDispatch { baseFreq(0), pitch(0), pitch2(0), - audLen(0), - audPos(0), sample(-1), ins(-1), note(0), panning(0x10), + echo(0), active(false), insChanged(true), freqChanged(false), @@ -59,8 +57,10 @@ class DivPlatformQSound: public DivDispatch { inPorta(false), useWave(false), surround(true), + isNewQSound(false), vol(255), - outVol(255) {} + outVol(255), + resVol(4096) {} }; Channel chan[19]; DivDispatchOscBuffer* oscBuf[19]; @@ -72,6 +72,7 @@ class DivPlatformQSound: public DivDispatch { struct qsound_chip chip; unsigned short regPool[512]; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: @@ -96,7 +97,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 2106a5a72..86d373722 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) { @@ -83,23 +79,14 @@ void DivPlatformRF5C68::tick(bool sysTick) { for (int i=0; i<8; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { - chan[i].outVol=((chan[i].vol&0xff)*chan[i].std.vol.val)>>6; + chan[i].outVol=((chan[i].vol&0xff)*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul; chWrite(i,0,chan[i].outVol); } 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,13 @@ 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].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1 && chan[i].active) { + chan[i].audPos=0; + chan[i].setPos=true; + } } if (chan[i].setPos) { // force keyon @@ -142,7 +135,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); @@ -175,6 +168,7 @@ int DivPlatformRF5C68::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); + chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255; chan[c.chan].sample=ins->amiga.getSample(c.value); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); @@ -265,6 +259,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 +387,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->getLoopEndPosition(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 6946b4900..bcbb3da2e 100644 --- a/src/engine/platform/rf5c68.h +++ b/src/engine/platform/rf5c68.h @@ -34,6 +34,7 @@ class DivPlatformRF5C68: public DivDispatch { int panning; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, setPos; int vol, outVol; + int macroVolMul; DivMacroInt std; void macroInit(DivInstrument* which) { std.init(which); @@ -57,7 +58,8 @@ class DivPlatformRF5C68: public DivDispatch { inPorta(false), setPos(false), vol(255), - outVol(255) {} + outVol(255), + macroVolMul(64) {} }; Channel chan[8]; DivDispatchOscBuffer* oscBuf[8]; @@ -69,6 +71,7 @@ class DivPlatformRF5C68: public DivDispatch { size_t sampleMemLen; rf5c68_device rf5c68; unsigned char regPool[144]; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: @@ -92,7 +95,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 c562838a2..b7a77d968 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 0efd498d6..d2092efb9 100644 --- a/src/engine/platform/saa.h +++ b/src/engine/platform/saa.h @@ -72,6 +72,7 @@ class DivPlatformSAA1099: public DivDispatch { size_t saaBufLen; unsigned char saaEnv[2]; unsigned char saaNoise[2]; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); void acquire_saaSound(short* bufL, short* bufR, size_t start, size_t len); @@ -96,7 +97,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 8175bc01b..725c53da9 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; hdata[oscBuf[i]->needle++]=scc->chan_out(i)<<7; + oscBuf[i]->data[oscBuf[i]->needle++]=scc->voice_out(i)<<7; } } } @@ -124,18 +115,9 @@ void DivPlatformSCC::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 || 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: diff --git a/src/engine/platform/scc.h b/src/engine/platform/scc.h index 43a8be5ae..20dcf4a97 100644 --- a/src/engine/platform/scc.h +++ b/src/engine/platform/scc.h @@ -24,7 +24,7 @@ #include #include "../macroInt.h" #include "../waveSynth.h" -#include "sound/scc/scc.hpp" +#include "vgsound_emu/src/scc/scc.hpp" class DivPlatformSCC: public DivDispatch { struct Channel { @@ -65,6 +65,7 @@ class DivPlatformSCC: public DivDispatch { unsigned char regBase; unsigned char regPool[225]; void updateWave(int ch); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: void acquire(short* bufL, short* bufR, size_t start, size_t len); @@ -84,7 +85,6 @@ 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); diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 250b6f2d2..650f54c91 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 (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[i].pcm.pos>=(s->loopEnd<<8)) || (chan[i].pcm.pos>=(s->samples<<8))) { - if (s->isLoopable()) { - chan[i].pcm.pos=s->loopStart<<8; - } else { - chan[i].pcm.sample=-1; - } + if (s->isLoopable() && chan[i].pcm.pos>=((unsigned int)s->loopEnd<<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; @@ -87,7 +76,7 @@ void DivPlatformSegaPCM::tick(bool sysTick) { if (parent->song.newSegaPCM) { if (chan[i].std.vol.had) { - chan[i].outVol=(chan[i].vol*MIN(64,chan[i].std.vol.val))>>6; + chan[i].outVol=(chan[i].vol*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul; chan[i].chVolL=(chan[i].outVol*chan[i].chPanL)/127; chan[i].chVolR=(chan[i].outVol*chan[i].chPanR)/127; if (dumpWrites) { @@ -99,22 +88,13 @@ 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) { - if (parent->song.newSegaPCM) { + if (parent->song.newSegaPCM) if (chan[i].std.panL.had) { + if (chan[i].isNewSegaPCM) { chan[i].chPanL=chan[i].std.panL.val&127; chan[i].chVolL=(chan[i].outVol*chan[i].chPanL)/127; } else { @@ -125,8 +105,8 @@ void DivPlatformSegaPCM::tick(bool sysTick) { } } - if (chan[i].std.panR.had) { - if (parent->song.newSegaPCM) { + if (parent->song.newSegaPCM) if (chan[i].std.panR.had) { + if (chan[i].isNewSegaPCM) { chan[i].chPanR=chan[i].std.panR.val&127; chan[i].chVolR=(chan[i].outVol*chan[i].chPanR)/127; } else { @@ -146,13 +126,14 @@ void DivPlatformSegaPCM::tick(bool sysTick) { } chan[i].freqChanged=true; } - /*if (chan[i].keyOn || chan[i].keyOff) { - chan[i].keyOff=false; - }*/ - } - for (int i=0; i<16; i++) { - if (chan[i].freqChanged) { + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1 && chan[i].active) { + chan[i].keyOn=true; + } + } + + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { chan[i].freq=chan[i].baseFreq+(chan[i].pitch>>1)-64; if (chan[i].furnacePCM) { double off=1.0; @@ -166,6 +147,56 @@ void DivPlatformSegaPCM::tick(bool sysTick) { } } chan[i].freqChanged=false; + if (chan[i].keyOn || chan[i].keyOff) { + if (chan[i].keyOn && !chan[i].keyOff) { + if (dumpWrites) { + addWrite(0x10086+(i<<3),3); + } + chan[i].pcm.pos=0; + if (chan[i].furnacePCM) { + if (dumpWrites) { // Sega PCM writes + DivSample* s=parent->getSample(chan[i].pcm.sample); + int loopStart=s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT); + int actualLength=(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)); + if (actualLength>0xfeff) actualLength=0xfeff; + addWrite(0x10086+(i<<3),3+((s->offSegaPCM>>16)<<3)); + addWrite(0x10084+(i<<3),(s->offSegaPCM)&0xff); + addWrite(0x10085+(i<<3),(s->offSegaPCM>>8)&0xff); + addWrite(0x10006+(i<<3),MIN(255,((s->offSegaPCM&0xffff)+actualLength-1)>>8)); + if (loopStart<0 || loopStart>=actualLength) { + addWrite(0x10086+(i<<3),2+((s->offSegaPCM>>16)<<3)); + } else { + int loopPos=(s->offSegaPCM&0xffff)+loopStart+s->loopOffP; + addWrite(0x10004+(i<<3),loopPos&0xff); + addWrite(0x10005+(i<<3),(loopPos>>8)&0xff); + addWrite(0x10086+(i<<3),((s->offSegaPCM>>16)<<3)); + } + } + } else { + if (dumpWrites) { // Sega PCM writes + DivSample* s=parent->getSample(chan[i].pcm.sample); + int loopStart=s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT); + int actualLength=(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)); + if (actualLength>65536) actualLength=65536; + addWrite(0x10086+(i<<3),3+((s->offSegaPCM>>16)<<3)); + addWrite(0x10084+(i<<3),(s->offSegaPCM)&0xff); + addWrite(0x10085+(i<<3),(s->offSegaPCM>>8)&0xff); + addWrite(0x10006+(i<<3),MIN(255,((s->offSegaPCM&0xffff)+actualLength-1)>>8)); + if (loopStart<0 || loopStart>=actualLength) { + addWrite(0x10086+(i<<3),2+((s->offSegaPCM>>16)<<3)); + } else { + int loopPos=(s->offSegaPCM&0xffff)+loopStart+s->loopOffP; + addWrite(0x10004+(i<<3),loopPos&0xff); + addWrite(0x10005+(i<<3),(loopPos>>8)&0xff); + addWrite(0x10086+(i<<3),((s->offSegaPCM>>16)<<3)); + } + addWrite(0x10007+(i<<3),chan[i].pcm.freq); + } + } + } + chan[i].keyOn=false; + chan[i].keyOff=false; + } } } } @@ -179,7 +210,9 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); if (skipRegisterWrites) break; - if (ins->type==DIV_INS_AMIGA) { + if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SEGAPCM) { + chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:127; + chan[c.chan].isNewSegaPCM=(ins->type==DIV_INS_SEGAPCM); chan[c.chan].pcm.sample=ins->amiga.getSample(c.value); if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) { chan[c.chan].pcm.sample=-1; @@ -192,7 +225,6 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { } break; } - chan[c.chan].pcm.pos=0; if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; chan[c.chan].baseFreq=(c.value<<6); @@ -200,23 +232,8 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { } chan[c.chan].furnacePCM=true; chan[c.chan].macroInit(ins); - if (dumpWrites) { // Sega PCM writes - DivSample* s=parent->getSample(chan[c.chan].pcm.sample); - int actualLength=(int)(s->isLoopable()?s->loopEnd:s->length8); - if (actualLength>0xfeff) actualLength=0xfeff; - addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3)); - addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff); - addWrite(0x10085+(c.chan<<3),(s->offSegaPCM>>8)&0xff); - addWrite(0x10006+(c.chan<<3),MIN(255,((s->offSegaPCM&0xffff)+actualLength-1)>>8)); - if ((s->loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT) || (s->loopStart<0 || s->loopStart>=actualLength || s->loopEnd<=(unsigned int)s->loopStart || s->loopEnd>=(unsigned int)actualLength)) { - addWrite(0x10086+(c.chan<<3),2+((s->offSegaPCM>>16)<<3)); - } else { - int loopPos=(s->offSegaPCM&0xffff)+s->loopStart+s->loopOffP; - addWrite(0x10004+(c.chan<<3),loopPos&0xff); - addWrite(0x10005+(c.chan<<3),(loopPos>>8)&0xff); - addWrite(0x10086+(c.chan<<3),((s->offSegaPCM>>16)<<3)); - } - } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; } else { chan[c.chan].macroInit(NULL); if (c.value!=DIV_NOTE_NULL) { @@ -230,27 +247,10 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { } break; } - chan[c.chan].pcm.pos=0; chan[c.chan].pcm.freq=MIN(255,(parent->getSample(chan[c.chan].pcm.sample)->rate*255)/31250); chan[c.chan].furnacePCM=false; - if (dumpWrites) { // Sega PCM writes - DivSample* s=parent->getSample(chan[c.chan].pcm.sample); - int actualLength=(int)(s->isLoopable()?s->loopEnd:s->length8); - if (actualLength>65536) actualLength=65536; - addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3)); - addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff); - addWrite(0x10085+(c.chan<<3),(s->offSegaPCM>>8)&0xff); - addWrite(0x10006+(c.chan<<3),MIN(255,((s->offSegaPCM&0xffff)+actualLength-1)>>8)); - if ((s->loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT) || (s->loopStart<0 || s->loopStart>=actualLength || s->loopEnd<=(unsigned int)s->loopStart || s->loopEnd>=(unsigned int)actualLength)) { - addWrite(0x10086+(c.chan<<3),2+((s->offSegaPCM>>16)<<3)); - } else { - int loopPos=(s->offSegaPCM&0xffff)+s->loopStart+s->loopOffP; - addWrite(0x10004+(c.chan<<3),loopPos&0xff); - addWrite(0x10005+(c.chan<<3),(loopPos>>8)&0xff); - addWrite(0x10086+(c.chan<<3),((s->offSegaPCM>>16)<<3)); - } - addWrite(0x10007+(c.chan<<3),chan[c.chan].pcm.freq); - } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; } break; } @@ -278,7 +278,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } - if (parent->song.newSegaPCM) { + if (parent->song.newSegaPCM && chan[c.chan].isNewSegaPCM) { chan[c.chan].chVolL=(c.value*chan[c.chan].chPanL)/127; chan[c.chan].chVolR=(c.value*chan[c.chan].chPanR)/127; } else { @@ -302,7 +302,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { - if (parent->song.newSegaPCM) { + if (parent->song.newSegaPCM && chan[c.chan].isNewSegaPCM) { chan[c.chan].chPanL=c.value>>1; chan[c.chan].chPanR=c.value2>>1; chan[c.chan].chVolL=(chan[c.chan].outVol*chan[c.chan].chPanL)/127; @@ -365,6 +365,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: @@ -440,8 +441,6 @@ void DivPlatformSegaPCM::reset() { pcmR=0; sampleBank=0; delay=0; - amDepth=0x7f; - pmDepth=0x7f; if (dumpWrites) { for (int i=0; i<16; i++) { @@ -450,8 +449,6 @@ void DivPlatformSegaPCM::reset() { addWrite(0x10003+(i<<3),0x7f); } } - - extMode=false; } void DivPlatformSegaPCM::setFlags(unsigned int flags) { diff --git a/src/engine/platform/segapcm.h b/src/engine/platform/segapcm.h index 6edc85302..888a33848 100644 --- a/src/engine/platform/segapcm.h +++ b/src/engine/platform/segapcm.h @@ -28,13 +28,12 @@ class DivPlatformSegaPCM: public DivDispatch { protected: struct Channel { DivMacroInt std; - unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, note, ins; - signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, isNewSegaPCM; int vol, outVol; unsigned char chVolL, chVolR; unsigned char chPanL, chPanR; + int macroVolMul; struct PCMChannel { int sample; @@ -48,8 +47,6 @@ class DivPlatformSegaPCM: public DivDispatch { pitch2=0; } Channel(): - freqH(0), - freqL(0), freq(0), baseFreq(0), pitch(0), @@ -64,12 +61,15 @@ class DivPlatformSegaPCM: public DivDispatch { inPorta(false), portaPause(false), furnacePCM(false), + isNewSegaPCM(false), vol(0), outVol(0), chVolL(127), chVolR(127), chPanL(127), - chPanR(127) {} + chPanR(127), + macroVolMul(64), + pcm(PCMChannel()) {} }; Channel chan[16]; DivDispatchOscBuffer* oscBuf[16]; @@ -80,21 +80,19 @@ class DivPlatformSegaPCM: public DivDispatch { QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} }; std::queue writes; - int delay, baseFreqOff; + int delay; int pcmL, pcmR, pcmCycles; unsigned char sampleBank; unsigned char lastBusy; - unsigned char amDepth, pmDepth; unsigned char regPool[256]; - bool extMode, useYMFM; - bool isMuted[16]; short oldWrites[256]; short pendingWrites[256]; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: @@ -114,7 +112,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 f16d53351..e4d408f7b 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -38,15 +38,6 @@ const char** DivPlatformSMS::getRegisterSheet() { return stereo?regCheatSheetGG:regCheatSheetSN; } -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; -} - void DivPlatformSMS::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) { int oL=0; int oR=0; @@ -87,7 +78,7 @@ void DivPlatformSMS::acquire_nuked(short* bufL, short* bufR, size_t start, size_ if (isMuted[i]) { oscBuf[i]->data[oscBuf[i]->needle++]=0; } else { - oscBuf[i]->data[oscBuf[i]->needle++]=sn_nuked.vol_table[sn_nuked.volume_out[i]]; + oscBuf[i]->data[oscBuf[i]->needle++]=sn_nuked.vol_table[sn_nuked.volume_out[i]]*3; } } } @@ -113,7 +104,7 @@ void DivPlatformSMS::acquire_mame(short* bufL, short* bufR, size_t start, size_t if (isMuted[i]) { oscBuf[i]->data[oscBuf[i]->needle++]=0; } else { - oscBuf[i]->data[oscBuf[i]->needle++]=sn->get_channel_output(i); + oscBuf[i]->data[oscBuf[i]->needle++]=sn->get_channel_output(i)*3; } } } @@ -133,7 +124,7 @@ void DivPlatformSMS::tick(bool sysTick) { 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; @@ -141,22 +132,12 @@ void DivPlatformSMS::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); - 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; } } @@ -197,7 +178,11 @@ void DivPlatformSMS::tick(bool sysTick) { if (chan[i].freqChanged) { 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(0,0x80|i<<5|(chan[i].freq&15)); rWrite(0,chan[i].freq>>4); @@ -212,7 +197,9 @@ 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,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) { @@ -232,12 +219,8 @@ void DivPlatformSMS::tick(bool sysTick) { } 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) { @@ -353,9 +336,8 @@ int DivPlatformSMS::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; - // 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; diff --git a/src/engine/platform/sms.h b/src/engine/platform/sms.h index 35bb44bab..b382d38da 100644 --- a/src/engine/platform/sms.h +++ b/src/engine/platform/sms.h @@ -78,6 +78,7 @@ class DivPlatformSMS: public DivDispatch { QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} }; std::queue writes; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len); @@ -101,7 +102,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/snes.cpp b/src/engine/platform/snes.cpp new file mode 100644 index 000000000..ca722a419 --- /dev/null +++ b/src/engine/platform/snes.cpp @@ -0,0 +1,528 @@ +/** + * 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 "snes.h" +#include "../engine.h" +#include "../../ta-log.h" +#include + +#define CHIP_FREQBASE 131072 + +#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define chWrite(c,a,v) {rWrite((a)+(c)*16,v)} +#define sampleTableAddr(c) (sampleTableBase+(c)*4) +#define waveTableAddr(c) (sampleTableBase+8*4+(c)*9*16) + +const char* regCheatSheetSNESDSP[]={ + "VxVOLL", "x0", + "VxVOLR", "x1", + "VxPITCHL", "x2", + "VxPITCHH", "x3", + "VxSRCN", "x4", + "VxADSR1", "x5", + "VxADSR2", "x6", + "VxGAIN", "x7", + "VxENVX", "x8", + "VxOUTX", "x9", + "FIRx", "xF", + + "MVOLL", "0C", + "MVOLR", "1C", + "EVOLL", "2C", + "EVOLR", "3C", + "KON", "4C", + "KOFF", "5C", + "FLG", "6C", + "ENDX", "7C", + + "EFB", "0D", + "PMON", "2D", + "NON", "3D", + "EON", "4D", + "DIR", "5D", + "ESA", "6D", + "EDL", "7D", + NULL +}; + +const char** DivPlatformSNES::getRegisterSheet() { + return regCheatSheetSNESDSP; +} + +void DivPlatformSNES::acquire(short* bufL, short* bufR, size_t start, size_t len) { + short out[2]; + short chOut[16]; + for (size_t h=start; hdata[oscBuf[i]->needle++]=chOut[i*2]+chOut[i*2+1]; + } + } +} + +void DivPlatformSNES::tick(bool sysTick) { + // KON/KOFF can't be written several times per one sample + // so they have to be accumulated + unsigned char kon=0; + unsigned char koff=0; + for (int i=0; i<8; i++) { + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA); + bool hadGain=chan[i].std.vol.had || chan[i].std.ex1.had || chan[i].std.ex2.had; + chan[i].std.next(); + if (ins->type==DIV_INS_AMIGA && chan[i].std.vol.had) { + chWrite(i,7,MIN(127,chan[i].std.vol.val*2)); + } else if (!chan[i].useEnv && hadGain) { + if (chan[i].std.ex1.val==0) { + // direct gain + chWrite(i,7,chan[i].std.vol.val); + } else { + // inc/dec + chWrite(i,7,chan[i].std.ex2.val|((chan[i].std.ex1.val-1)<<5)|0x80); + } + } + 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].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].useWave && chan[i].std.wave.had) { + if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { + chan[i].wave=chan[i].std.wave.val; + chan[i].ws.changeWave1(chan[i].wave); + } + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-32768,32767); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + if (chan[i].std.panL.had) { + int val=chan[i].std.panL.val&0x7f; + chan[i].panL=(val<<1)|(val>>6); + } + if (chan[i].std.panR.had) { + int val=chan[i].std.panR.val&0x7f; + chan[i].panR=(val<<1)|(val>>6); + } + if (chan[i].std.panL.had || chan[i].std.panR.had) { + writeOutVol(i); + } + if (chan[i].setPos) { + // force keyon + chan[i].keyOn=true; + chan[i].setPos=false; + } else { + chan[i].audPos=0; + } + if (chan[i].useWave && chan[i].active) { + if (chan[i].ws.tick()) { + updateWave(i); + } + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + DivSample* s=parent->getSample(chan[i].sample); + double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0; + chan[i].freq=(unsigned int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE)); + if (chan[i].freq>16383) chan[i].freq=16383; + if (chan[i].keyOn) { + unsigned int start, end, loop; + size_t tabAddr=sampleTableAddr(i); + if (chan[i].useEnv) { + chWrite(i,5,ins->snes.a|(ins->snes.d<<4)|0x80); + chWrite(i,6,ins->snes.r|(ins->snes.s<<5)); + } else { + chWrite(i,5,0); + } + if (chan[i].useWave) { + start=waveTableAddr(i); + loop=start; + } else { + start=s->offSNES; + end=MIN(start+MAX(s->lengthBRR,1),getSampleMemCapacity()); + loop=MAX(start,end-1); + if (chan[i].audPos>0) { + start=start+MIN(chan[i].audPos,s->lengthBRR-1)/16*9; + } + if (s->loopStart>=0) { + loop=start+s->loopStart/16*9; + } + } + sampleMem[tabAddr+0]=start&0xff; + sampleMem[tabAddr+1]=start>>8; + sampleMem[tabAddr+2]=loop&0xff; + sampleMem[tabAddr+3]=loop>>8; + if (!hadGain) { + chWrite(i,7,0x7f); + } + kon|=(1<>8); + chan[i].freqChanged=false; + } + } + } + if (kon!=0) { + rWrite(0x4c,kon); + } + // always write KOFF as it's constantly polled + rWrite(0x5c,koff); +} + +int DivPlatformSNES::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); + if (ins->amiga.useWave) { + chan[c.chan].useWave=true; + chan[c.chan].wtLen=ins->amiga.waveLen+1; + if (chan[c.chan].insChanged) { + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + chan[c.chan].ws.setWidth(chan[c.chan].wtLen); + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + } + } + chan[c.chan].ws.init(ins,chan[c.chan].wtLen,15,chan[c.chan].insChanged); + } else { + chan[c.chan].sample=ins->amiga.getSample(c.value); + chan[c.chan].useWave=false; + } + if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { + chan[c.chan].sample=-1; + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=round(NOTE_FREQUENCY(c.value)); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].macroInit(ins); + if (ins->type==DIV_INS_SNES) { + // initialize to max gain in case of direct gain mode macro without gain level macro + chan[c.chan].std.vol.val=0x7f; + chan[c.chan].useEnv=ins->snes.useEnv; + } + chan[c.chan].insChanged=false; + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].sample=-1; + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + chan[c.chan].insChanged=true; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + writeOutVol(c.chan); + } + break; + // case DIV_CMD_GLOBAL_VOLUME: + // gblVolL=MIN(c.value,127); + // gblVolR=MIN(c.value,127); + // rWrite(0x0c,gblVolL); + // rWrite(0x1c,gblVolR); + // break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PANNING: + chan[c.chan].panL=c.value; + chan[c.chan].panR=c.value2; + writeOutVol(c.chan); + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=round(NOTE_FREQUENCY(c.value2)); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: { + chan[c.chan].baseFreq=round(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].freqChanged=true; + chan[c.chan].note=c.value; + break; + } + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_SAMPLE_POS: + chan[c.chan].audPos=c.value; + chan[c.chan].setPos=true; + break; + // TODO SNES-specific commands + case DIV_CMD_GET_VOLMAX: + return 127; + break; + default: + break; + } + return 1; +} + +void DivPlatformSNES::updateWave(int ch) { + // Due to the overflow bug in hardware's resampler, the written amplitude here is half of maximum + size_t pos=waveTableAddr(ch); + for (int i=0; i>8); + rWrite(0x0c,127); // global volume left + rWrite(0x1c,127); // global volume right + rWrite(0x6c,0); // get DSP out of reset + for (int i=0; i<8; i++) { + chan[i]=Channel(); + chan[i].std.setEngine(parent); + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,32,255); + writeOutVol(i); + chWrite(i,4,i); // source number + } +} + +bool DivPlatformSNES::isStereo() { + return true; +} + +void DivPlatformSNES::notifyInsChange(int ins) { + for (int i=0; i<8; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } +} + +void DivPlatformSNES::notifyWaveChange(int wave) { + for (int i=0; i<8; i++) { + if (chan[i].useWave && chan[i].wave==wave) { + chan[i].ws.changeWave1(wave); + if (chan[i].active) { + updateWave(i); + } + } + } +} + +void DivPlatformSNES::notifyInsDeletion(void* ins) { + for (int i=0; i<8; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformSNES::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformSNES::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +const void* DivPlatformSNES::getSampleMem(int index) { + return index == 0 ? sampleMem : NULL; +} + +size_t DivPlatformSNES::getSampleMemCapacity(int index) { + // TODO change it based on current echo buffer size + return index == 0 ? 65536 : 0; +} + +size_t DivPlatformSNES::getSampleMemUsage(int index) { + return index == 0 ? sampleMemLen : 0; +} + +void DivPlatformSNES::renderSamples() { + memset(sampleMem,0,getSampleMemCapacity()); + + // skip past sample table and wavetable buffer + size_t memPos=sampleTableBase+8*4+8*9*16; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + int length=s->lengthBRR; + int actualLength=MIN((int)(getSampleMemCapacity()-memPos)/9*9,length); + if (actualLength>0) { + s->offSNES=memPos; + memcpy(&sampleMem[memPos],s->data8,actualLength); + memPos+=actualLength; + } + if (actualLength +#include "sound/snes/SPC_DSP.h" + +class DivPlatformSNES: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, pitch2; + unsigned int audPos; + int sample, wave, ins; + int note; + int panL, panR; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos; + signed char vol; + int wtLen; + bool useEnv; + DivMacroInt std; + DivWaveSynth ws; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): + freq(0), + baseFreq(0), + pitch(0), + pitch2(0), + audPos(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(127), + wtLen(16), + useEnv(false) {} + }; + Channel chan[8]; + DivDispatchOscBuffer* oscBuf[8]; + bool isMuted[8]; + signed char gblVolL, gblVolR; + size_t sampleTableBase; + + struct QueuedWrite { + unsigned char addr; + unsigned char val; + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + }; + std::queue writes; + + signed char sampleMem[65536]; + size_t sampleMemLen; + unsigned char regPool[0x80]; + SPC_DSP dsp; + 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 isStereo(); + 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 void* getSampleMem(int index = 0); + size_t getSampleMemCapacity(int index = 0); + size_t getSampleMemUsage(int index = 0); + void renderSamples(); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + private: + void updateWave(int ch); + void writeOutVol(int ch); +}; + +#endif diff --git a/src/engine/platform/sound/ay8910.cpp b/src/engine/platform/sound/ay8910.cpp index 2c11b6e3f..c7be503e7 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 314383f57..6f4c6f318 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_fp/AUTHORS b/src/engine/platform/sound/c64_fp/AUTHORS new file mode 100644 index 000000000..b04ee0f01 --- /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 000000000..d159169d1 --- /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 000000000..0665da817 --- /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 000000000..35bc0b2ca --- /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 000000000..af636ac7f --- /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 000000000..f2aab3874 --- /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 000000000..eac790b31 --- /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 000000000..760ee5c22 --- /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 000000000..2a2dd24f7 --- /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 000000000..4b3473369 --- /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 000000000..c064a8801 --- /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 000000000..7fca331ab --- /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 000000000..a70285a8a --- /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 000000000..2166ec0da --- /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 000000000..8fb762382 --- /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 000000000..d8ae77ab8 --- /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 000000000..3d86bdcf2 --- /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 000000000..85cbd43fb --- /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 000000000..fd2a16fab --- /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 000000000..ee2b40087 --- /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 000000000..490be9b5c --- /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 000000000..99ac3bea6 --- /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 000000000..6fba9521b --- /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 000000000..7137e9407 --- /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 000000000..b26b2efcb --- /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 000000000..9d2c8f162 --- /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 000000000..8b63df130 --- /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 000000000..45d4bfb92 --- /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 000000000..a996d2230 --- /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 000000000..85b6a4e4d --- /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 000000000..50d55fef1 --- /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 000000000..6cc2b1edc --- /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 000000000..fc7ed41b7 --- /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 000000000..fe5030faf --- /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 000000000..f9183c5d5 --- /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 000000000..e5e544e7b --- /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 000000000..9fd617f6b --- /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 000000000..5291938c0 --- /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 000000000..904f65458 --- /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 000000000..adb17f9e9 --- /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 000000000..7502d96fd --- /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 000000000..81659193a --- /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 000000000..2bc80cded --- /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 000000000..5e5026fff --- /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 000000000..18eadf4ae --- /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 000000000..5078a0b1a --- /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 000000000..439008621 --- /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 000000000..3ed8b4490 --- /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 4c473997f..8836ddd29 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 ac817395f..ca3650852 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 deleted file mode 100644 index 8b21245a1..000000000 --- a/src/engine/platform/sound/k005289/k005289.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details - - Copyright holder(s): cam900 - Modifiers and Contributors for Furnace: cam900 - Konami K005289 emulation core - - This chip is used at infamous Konami Bubble System, for part of Wavetable sound generator. - But seriously, It is just to 2 internal 12 bit timer and address generators, rather than sound generator. - - Everything except for internal counter and address are done by external logic, the chip is only has external address, frequency registers and its update pins. - - Frequency calculation: Input clock / (4096 - Pitch input) -*/ - -#include "k005289.hpp" - -void k005289_core::tick() -{ - for (auto & elem : m_voice) - elem.tick(); -} - -void k005289_core::reset() -{ - for (auto & elem : m_voice) - elem.reset(); -} - -void k005289_core::voice_t::tick() -{ - if (bitfield(++counter, 0, 12) == 0) - { - addr = bitfield(addr + 1, 0, 5); - counter = freq; - } -} - -void k005289_core::voice_t::reset() -{ - addr = 0; - pitch = 0; - freq = 0; - counter = 0; -} diff --git a/src/engine/platform/sound/k005289/k005289.hpp b/src/engine/platform/sound/k005289/k005289.hpp deleted file mode 100644 index d042e1d14..000000000 --- a/src/engine/platform/sound/k005289/k005289.hpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details - - Copyright holder(s): cam900 - Modifiers and Contributors for Furnace: cam900 - Konami K005289 emulation core - - See k005289.cpp for more info. -*/ - -#include -#include - -#ifndef _VGSOUND_EMU_K005289_HPP -#define _VGSOUND_EMU_K005289_HPP - -#pragma once - -namespace k005289 -{ - typedef unsigned char u8; - typedef unsigned short u16; - typedef signed short s16; - - // get bitfield, bitfield(input, position, len) - template T bitfield(T in, u8 pos, u8 len = 1) - { - return (in >> pos) & (len ? (T(1 << len) - 1) : 1); - } -} - -using namespace k005289; -class k005289_core -{ -public: - // accessors, getters, setters - u8 addr(int voice) { return m_voice[voice & 1].addr; } // 1QA...E/2QA...E pin - void load(int voice, u16 addr) { m_voice[voice & 1].load(addr); } // LD1/2 pin, A0...11 pin - void update(int voice) { m_voice[voice & 1].update(); } // TG1/2 pin - - // internal state - void reset(); - void tick(); - -private: - // k005289 voice structs - struct voice_t - { - // internal state - void reset(); - void tick(); - - // accessors, getters, setters - void load(u16 addr) { pitch = addr; } // Load pitch data (address pin) - void update() { freq = pitch; } // Replace current frequency to lastest loaded pitch - - // registers - u8 addr = 0; // external address pin - u16 pitch = 0; // pitch - u16 freq = 0; // current frequency - s16 counter = 0; // frequency counter - }; - - voice_t m_voice[2]; -}; - -#endif diff --git a/src/engine/platform/sound/lynx/Mikey.cpp b/src/engine/platform/sound/lynx/Mikey.cpp index 270e21bdf..791336c1b 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 deleted file mode 100644 index 5d9deab1a..000000000 --- a/src/engine/platform/sound/n163/n163.cpp +++ /dev/null @@ -1,167 +0,0 @@ -/* - License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details - - Copyright holder(s): cam900 - Modifiers and Contributors for Furnace: cam900, tildearrow - Namco 163 Sound emulation core - - This chip is one of NES mapper with sound expansion, This one is by Namco. - - It has 1 to 8 wavetable channels, All channel registers and waveforms are stored to internal RAM. - 4 bit Waveforms are freely allocatable, and its length is variables; its can be stores many short waveforms or few long waveforms in RAM. - - But waveforms are needs to squash, reallocate to avoid conflict with channel register area, each channel register size is 8 bytes per channels. - - Sound output is time division multiplexed, it's can be captured only single channels output at once. in reason, More activated channels are less sound quality. - - Sound register layout - - Address Bit Description - 7654 3210 - - 78-7f Channel 0 - 78 xxxx xxxx Channel 0 Pitch input bit 0-7 - 79 xxxx xxxx Channel 0 Accumulator bit 0-7* - 7a xxxx xxxx Channel 0 Pitch input bit 8-15 - 7b xxxx xxxx Channel 0 Accumulator bit 8-15* - 7c xxxx xx-- Channel 0 Waveform length, 256 - (x * 4) - ---- --xx Channel 0 Pitch input bit 16-17 - 7d xxxx xxxx Channel 0 Accumulator bit 16-23* - 7e xxxx xxxx Channel 0 Waveform base offset - xxxx xxx- RAM byte (0 to 127) - ---- ---x RAM nibble - ---- ---0 Low nibble - ---- ---1 High nibble - 7f ---- xxxx Channel 0 Volume - - 7f Number of active channels - 7f -xxx ---- Number of active channels - -000 ---- Channel 0 activated - -001 ---- Channel 1 activated - -010 ---- Channel 2 activated - ... - -110 ---- Channel 6 activated - -111 ---- Channel 7 activated - - 70-77 Channel 1 (Optional if activated) - 68-6f Channel 2 (Optional if activated) - ... - 48-4f Channel 6 (Optional if activated) - 40-47 Channel 7 (Optional if activated) - - Rest of RAM area are for 4 bit Waveform and/or scratchpad. - Each waveform byte has 2 nibbles packed, fetches LSB first, MSB next. - ---- xxxx 4 bit waveform, LSB - xxxx ---- Same as above, MSB - - Waveform address: Waveform base offset + Bit 16 to 23 of Accumulator, 1 LSB of result is nibble select, 7 MSB of result is Byte address in RAM. - - Frequency formula: - Frequency: Pitch input * ((Input clock * 15 * Number of activated voices) / 65536) - - There's to way for reduce N163 noises: reduce channel limit and demultiplex - - Channel limit is runtime changeable and it makes some usable effects. - - Demultiplex is used for "non-ear destroyable" emulators, but less hardware accurate. (when LPF and RF filter is not considered) - This core is support both, You can choose output behavior - -*/ - -#include "n163.hpp" -#include - -void n163_core::tick() -{ - if (m_multiplex) - m_out = 0; - // 0xe000-0xe7ff Disable sound bits (bit 6, bit 0 to 5 are CPU ROM Bank 0x8000-0x9fff select.) - if (m_disable) - { - if (!m_multiplex) - m_out = 0; - return; - } - - // tick per each clock - const u32 freq = m_ram[m_voice_cycle + 0] | (u32(m_ram[m_voice_cycle + 2]) << 8) | (bitfield(m_ram[m_voice_cycle + 4], 0, 2) << 16); // 18 bit frequency - u32 accum = m_ram[m_voice_cycle + 1] | (u32(m_ram[m_voice_cycle + 3]) << 8) | ( u32(m_ram[m_voice_cycle + 5]) << 16); // 24 bit accumulator - const u16 length = 256 - (m_ram[m_voice_cycle + 4] & 0xfc); - const u8 addr = m_ram[m_voice_cycle + 6] + bitfield(accum, 16, 8); - const s16 wave = (bitfield(m_ram[bitfield(addr, 1, 7)], bitfield(addr, 0) << 2, 4) - 8); - const s16 volume = bitfield(m_ram[m_voice_cycle + 7], 0, 4); - - // accumulate address - accum = bitfield(accum + freq, 0, 24); - if (bitfield(accum, 16, 8) >= length) - accum = bitfield(accum, 0, 18); - - // writeback to register - m_ram[m_voice_cycle + 1] = bitfield(accum, 0, 8); - m_ram[m_voice_cycle + 3] = bitfield(accum, 8, 8); - m_ram[m_voice_cycle + 5] = bitfield(accum, 16, 8); - - const u8 prev_voice_cycle = m_voice_cycle; - - // update voice cycle - bool flush = m_multiplex ? true : false; - m_voice_cycle -= 0x8; - if (m_voice_cycle < (0x78 - (bitfield(m_ram[0x7f], 4, 3) << 3))) - { - if (!m_multiplex) - flush = true; - m_voice_cycle = 0x78; - } - - // output 4 bit waveform and volume, multiplexed - const u8 chan_index = ((0x78-prev_voice_cycle)>>3)&7; - m_ch_out[chan_index]=wave * volume; - m_acc += m_ch_out[chan_index]; - if (flush) - { - m_out = m_acc / (m_multiplex ? 1 : (bitfield(m_ram[0x7f], 4, 3) + 1)); - m_acc = 0; - } -} - -void n163_core::reset() -{ - // reset this chip - m_disable = false; - m_multiplex = true; - memset(m_ram,0,sizeof(m_ram)); - m_voice_cycle = 0x78; - m_addr_latch.reset(); - m_out = 0; - m_acc = 0; - memset(m_ch_out,0,sizeof(m_ch_out)); -} - -// accessor -void n163_core::addr_w(u8 data) -{ - // 0xf800-0xffff Sound address, increment - m_addr_latch.addr = bitfield(data, 0, 7); - m_addr_latch.incr = bitfield(data, 7); -} - -void n163_core::data_w(u8 data, bool cpu_access) -{ - // 0x4800-0x4fff Sound data write - m_ram[m_addr_latch.addr] = data; - - // address latch increment - if (cpu_access && m_addr_latch.incr) - m_addr_latch.addr = bitfield(m_addr_latch.addr + 1, 0, 7); -} - -u8 n163_core::data_r(bool cpu_access) -{ - // 0x4800-0x4fff Sound data read - const u8 ret = m_ram[m_addr_latch.addr]; - - // address latch increment - if (cpu_access && m_addr_latch.incr) - m_addr_latch.addr = bitfield(m_addr_latch.addr + 1, 0, 7); - - return ret; -} diff --git a/src/engine/platform/sound/n163/n163.hpp b/src/engine/platform/sound/n163/n163.hpp deleted file mode 100644 index 800d1ea13..000000000 --- a/src/engine/platform/sound/n163/n163.hpp +++ /dev/null @@ -1,90 +0,0 @@ -/* - License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details - - Copyright holder(s): cam900 - Modifiers and Contributors for Furnace: cam900, tildearrow - Namco 163 Sound emulation core -*/ - -#include -#include - -#ifndef _VGSOUND_EMU_N163_HPP -#define _VGSOUND_EMU_N163_HPP - -#pragma once - -namespace n163 -{ - typedef unsigned char u8; - typedef unsigned short u16; - typedef unsigned int u32; - typedef signed short s16; - - // get bitfield, bitfield(input, position, len) - template T bitfield(T in, u8 pos, u8 len = 1) - { - return (in >> pos) & (len ? (T(1 << len) - 1) : 1); - } -}; - -using namespace n163; -class n163_core -{ -public: - // accessors, getters, setters - void addr_w(u8 data); - void data_w(u8 data, bool cpu_access = false); - u8 data_r(bool cpu_access = false); - - void set_disable(bool disable) { m_disable = disable; } - - // internal state - void reset(); - void tick(); - - // sound output pin - s16 out() { return m_out; } - - // get channel output - s16 chan_out(u8 ch) { return m_ch_out[ch]; } - - // get voice cycle - u8 voice_cycle() { return m_voice_cycle; } - - // register pool - u8 reg(u8 addr) { return m_ram[addr & 0x7f]; } - void set_multiplex(bool multiplex = true) { m_multiplex = multiplex; } - -private: - // Address latch - struct addr_latch_t - { - addr_latch_t() - : addr(0) - , incr(0) - { } - - void reset() - { - addr = 0; - incr = 0; - } - - u8 addr : 7; - u8 incr : 1; - }; - - bool m_disable = false; - u8 m_ram[0x80] = {0}; // internal 128 byte RAM - u8 m_voice_cycle = 0x78; // Voice cycle for processing - addr_latch_t m_addr_latch; // address latch - s16 m_out = 0; // output - s16 m_ch_out[8] = {0}; // per channel output - // demultiplex related - bool m_multiplex = true; // multiplex flag, but less noisy = inaccurate! - s16 m_acc = 0; // accumulated output -}; - -#endif diff --git a/src/engine/platform/sound/oki/msm6295.cpp b/src/engine/platform/sound/oki/msm6295.cpp deleted file mode 100644 index 1b7fe568a..000000000 --- a/src/engine/platform/sound/oki/msm6295.cpp +++ /dev/null @@ -1,215 +0,0 @@ -/* - License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details - - Copyright holder(s): cam900 - Modifiers and Contributors for Furnace: tildearrow - OKI MSM6295 emulation core - - It is 4 channel ADPCM playback chip from OKI semiconductor. - It was becomes de facto standard for ADPCM playback in arcade machine, due to cost performance. - - The chip itself is pretty barebone: there is no "register" in chip. - It can't control volume and pitch in currently playing channels, only stopable them. - And volume is must be determined at playback start command. - - Command format: - - Playback command (2 byte): - - Byte Bit Description - 76543210 - 0 1xxxxxxx Phrase select (Header stored in ROM) - 1 x000---- Play channel 4 - 0x00---- Play channel 3 - 00x0---- Play channel 2 - 000x---- Play channel 1 - ----xxxx Volume - ----0000 0.0dB - ----0001 -3.2dB - ----0010 -6.0dB - ----0011 -9.2dB - ----0100 -12.0dB - ----0101 -14.5dB - ----0110 -18.0dB - ----0111 -20.5dB - ----1000 -24.0dB - - Suspend command (1 byte): - - Byte Bit Description - 76543210 - 0 0x------ Suspend channel 4 - 0-x----- Suspend channel 3 - 0--x---- Suspend channel 2 - 0---x--- Suspend channel 1 - - Frequency calculation: - if (SS) then - Frequency = Input clock / 165 - else then - Frequency = Input clock / 132 - -*/ - -#include "msm6295.hpp" - -#define CORE_DIVIDER 3 - -#define CHANNEL_DELAY (15/CORE_DIVIDER) -#define MASTER_DELAY (33/CORE_DIVIDER) - -void msm6295_core::tick() -{ - // command handler - if (m_command_pending) - { - if (bitfield(m_command, 7)) // play voice - { - if ((++m_clock) >= ((CHANNEL_DELAY * (m_ss ? 5 : 4)))) - { - m_clock = 0; - if (bitfield(m_next_command, 4, 4) != 0) - { - for (int i = 0; i < 4; i++) - { - if (bitfield(m_next_command, 4 + i)) - { - if (!m_voice[i].m_busy) - { - m_voice[i].m_command = m_command; - m_voice[i].m_volume = (bitfield(m_next_command, 0, 4) < 9) ? m_volume_table[std::min(8, bitfield(m_next_command, 0, 4))] : 0; - } - break; // voices aren't be playable simultaneously at once - } - } - } - m_command = 0; - m_command_pending = false; - } - } - else if (bitfield(m_next_command, 7)) // select phrase - { - if ((++m_clock) >= ((CHANNEL_DELAY * (m_ss ? 5 : 4)))) - { - m_clock = 0; - m_command = m_next_command; - m_command_pending = false; - } - } - else - { - if (bitfield(m_next_command, 3, 4) != 0) // suspend voices - { - for (int i = 0; i < 4; i++) - { - if (bitfield(m_next_command, 3 + i)) - { - if (m_voice[i].m_busy) - m_voice[i].m_command = m_next_command; - } - } - m_next_command &= ~0x78; - } - m_command_pending = false; - } - } - m_out = 0; - for (int i = 0; i < 4; i++) - { - m_voice[i].tick(); - if (!m_voice[i].m_muted) m_out += m_voice[i].m_out; - } -} - -void msm6295_core::reset() -{ - for (auto & elem : m_voice) - elem.reset(); - - m_command = 0; - m_next_command = 0; - m_command_pending = false; - m_clock = 0; - m_out = 0; -} - -void msm6295_core::voice_t::tick() -{ - if (!m_busy) - { - if (bitfield(m_command, 7)) - { - // get phrase header (stored in data memory) - const u32 phrase = bitfield(m_command, 0, 7) << 3; - // Start address - m_addr = (bitfield(m_host.m_intf.read_byte(phrase | 0), 0, 2) << 16) - | (m_host.m_intf.read_byte(phrase | 1) << 8) - | (m_host.m_intf.read_byte(phrase | 2) << 0); - // End address - m_end = (bitfield(m_host.m_intf.read_byte(phrase | 3), 0, 2) << 16) - | (m_host.m_intf.read_byte(phrase | 4) << 8) - | (m_host.m_intf.read_byte(phrase | 5) << 0); - m_nibble = 4; // MSB first, LSB second - m_command = 0; - m_busy = true; - vox_decoder_t::reset(); - } - m_out = 0; - } - else - { - // playback - if ((++m_clock) >= ((MASTER_DELAY * (m_host.m_ss ? 5 : 4)))) - { - m_clock = 0; - bool is_end = (m_command != 0); - m_curr.decode(bitfield(m_host.m_intf.read_byte(m_addr), m_nibble, 4)); - if (m_nibble <= 0) - { - m_nibble = 4; - if (++m_addr > m_end) - is_end = true; - } - else - m_nibble -= 4; - if (is_end) - { - m_command = 0; - m_busy = false; - } - m_out = (out() * m_volume) >> 7; // scale out to 12 bit output - } - } -} - -void msm6295_core::voice_t::reset() -{ - vox_decoder_t::reset(); - m_clock = 0; - m_busy = false; - m_command = 0; - m_addr = 0; - m_nibble = 0; - m_end = 0; - m_volume = 0; - m_out = 0; -} - -// accessors -u8 msm6295_core::busy_r() -{ - return (m_voice[0].m_busy ? 0x01 : 0x00) - | (m_voice[1].m_busy ? 0x02 : 0x00) - | (m_voice[2].m_busy ? 0x04 : 0x00) - | (m_voice[3].m_busy ? 0x08 : 0x00); -} - -void msm6295_core::command_w(u8 data) -{ - if (!m_command_pending) - { - m_next_command = data; - m_command_pending = true; - } -} diff --git a/src/engine/platform/sound/oki/msm6295.hpp b/src/engine/platform/sound/oki/msm6295.hpp deleted file mode 100644 index 5203f4340..000000000 --- a/src/engine/platform/sound/oki/msm6295.hpp +++ /dev/null @@ -1,95 +0,0 @@ -/* - License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details - - Copyright holder(s): cam900 - Modifiers and Contributors for Furnace: tildearrow - OKI MSM6295 emulation core - - See msm6295.cpp for more info. -*/ - -#include "util.hpp" -#include "vox.hpp" -#include -#include - -#ifndef _VGSOUND_EMU_MSM6295_HPP -#define _VGSOUND_EMU_MSM6295_HPP - -#pragma once - -class msm6295_core : public vox_core -{ - friend class vgsound_emu_mem_intf; // common memory interface -public: - // constructor - msm6295_core(vgsound_emu_mem_intf &intf) - : m_voice{{*this,*this},{*this,*this},{*this,*this},{*this,*this}} - , m_intf(intf) - { - } - // accessors, getters, setters - u8 busy_r(); - void command_w(u8 data); - void ss_w(bool ss) { m_ss = ss; } // SS pin - - // internal state - void reset(); - void tick(); - - s32 out() { return m_out; } // built in 12 bit DAC - -private: - // Internal volume table, 9 step - const s32 m_volume_table[9] = { - 32/* 0.0dB */, - 22/* -3.2dB */, - 16/* -6.0dB */, - 11/* -9.2dB */, - 8/* -12.0dB */, - 6/* -14.5dB */, - 4/* -18.0dB */, - 3/* -20.5dB */, - 2/* -24.0dB */ }; // scale out to 5 bit for optimization - -public: - // msm6295 voice structs - struct voice_t : vox_decoder_t - { - // constructor - voice_t(vox_core &vox, msm6295_core &host) - : vox_decoder_t(vox) - , m_host(host) - {}; - - // internal state - virtual void reset() override; - void tick(); - - // accessors, getters, setters - // registers - msm6295_core &m_host; - u16 m_clock = 0; // clock counter - bool m_busy = false; // busy status - bool m_muted = false; // muted - u8 m_command = 0; // current command - u32 m_addr = 0; // current address - s8 m_nibble = 0; // current nibble - u32 m_end = 0; // end address - s32 m_volume = 0; // volume - s32 m_out = 0; // output - }; - voice_t m_voice[4]; -private: - vgsound_emu_mem_intf &m_intf; // common memory interface - - bool m_ss = false; // SS pin controls divider, input clock / 33 * (SS ? 5 : 4) - u8 m_command = 0; // Command byte - u8 m_next_command = 0; // Next command - bool m_command_pending = false; // command pending flag - u16 m_clock = 0; // clock counter - s32 m_out = 0; // 12 bit output -}; - -#endif diff --git a/src/engine/platform/sound/oki/util.hpp b/src/engine/platform/sound/oki/util.hpp deleted file mode 100644 index 731772acc..000000000 --- a/src/engine/platform/sound/oki/util.hpp +++ /dev/null @@ -1,159 +0,0 @@ -/* - License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details - - Copyright holder(s): cam900 - Modifiers and Contributors for Furnace: tildearrow - Various core utilities for vgsound_emu -*/ - -#include -#include -#include - -#ifndef _VGSOUND_EMU_CORE_UTIL_HPP -#define _VGSOUND_EMU_CORE_UTIL_HPP - -#pragma once - -typedef unsigned char u8; -typedef unsigned short u16; -typedef unsigned int u32; -typedef unsigned long long u64; -typedef signed char s8; -typedef signed short s16; -typedef signed int s32; -typedef signed long long s64; -typedef float f32; -typedef double f64; - -const f64 PI = 3.1415926535897932384626433832795; - -// get bitfield, bitfield(input, position, len) -template T bitfield(T in, u8 pos, u8 len = 1) -{ - return (in >> pos) & (len ? (T(1 << len) - 1) : 1); -} - -// get sign extended value, sign_ext(input, len) -template T sign_ext(T in, u8 len) -{ - len = std::max(0, (8 * sizeof(T)) - len); - return T(T(in) << len) >> len; -} - -// convert attenuation decibel value to gain -inline f32 dB_to_gain(f32 attenuation) -{ - return powf(10.0f, attenuation / 20.0f); -} - -class vgsound_emu_mem_intf -{ -public: - virtual u8 read_byte(u32 address) { return 0; } - virtual u16 read_word(u32 address) { return 0; } - virtual u32 read_dword(u32 address) { return 0; } - virtual u64 read_qword(u32 address) { return 0; } - virtual void write_byte(u32 address, u8 data) { } - virtual void write_word(u32 address, u16 data) { } - virtual void write_dword(u32 address, u32 data) { } - virtual void write_qword(u32 address, u64 data) { } -}; - -template -struct clock_pulse_t -{ - void reset(T init = InitWidth) - { - m_edge.reset(); - m_width = m_width_latch = m_counter = init; - m_cycle = 0; - } - - bool tick(T width = 0) - { - bool carry = ((--m_counter) <= 0); - if (carry) - { - if (!width) - m_width = m_width_latch; - else - m_width = width; // reset width - m_counter = m_width; - m_cycle = 0; - } - else - m_cycle++; - - m_edge.tick(carry); - return carry; - } - - void set_width(T width) { m_width = width; } - void set_width_latch(T width) { m_width_latch = width; } - - // Accessors - bool current_edge() { return m_edge.m_current; } - bool rising_edge() { return m_edge.m_rising; } - bool falling_edge() { return m_edge.m_rising; } - T cycle() { return m_cycle; } - - struct edge_t - { - edge_t() - : m_current(InitEdge ^ 1) - , m_previous(InitEdge) - , m_rising(0) - , m_falling(0) - , m_changed(0) - { - set(InitEdge); - } - - void tick(bool toggle) - { - u8 current = m_current; - if (toggle) - current ^= 1; - set(current); - } - - void set(u8 edge) - { - edge &= 1; - m_rising = m_falling = m_changed = 0; - if (m_current != edge) - { - m_changed = 1; - if (m_current && (!edge)) - m_falling = 1; - else if ((!m_current) && edge) - m_rising = 1; - m_current = edge; - } - m_previous = m_current; - } - - void reset() - { - m_previous = InitEdge; - m_current = InitEdge ^ 1; - set(InitEdge); - } - - u8 m_current : 1; // current edge - u8 m_previous : 1; // previous edge - u8 m_rising : 1; // rising edge - u8 m_falling : 1; // falling edge - u8 m_changed : 1; // changed flag - }; - - edge_t m_edge; - T m_width = InitWidth; // clock pulse width - T m_width_latch = InitWidth; // clock pulse width latch - T m_counter = InitWidth; // clock counter - T m_cycle = 0; // clock cycle -}; - -#endif diff --git a/src/engine/platform/sound/oki/vox.hpp b/src/engine/platform/sound/oki/vox.hpp deleted file mode 100644 index c085c0b7c..000000000 --- a/src/engine/platform/sound/oki/vox.hpp +++ /dev/null @@ -1,116 +0,0 @@ -/* - License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details - - Copyright holder(s): cam900 - Modifiers and Contributors for Furnace: tildearrow - Dialogic ADPCM core -*/ - -#include "util.hpp" -#include -#include - -#ifndef _VGSOUND_EMU_CORE_VOX_HPP -#define _VGSOUND_EMU_CORE_VOX_HPP - -#pragma once - -#define MODIFIED_CLAMP(x,xMin,xMax) (std::min(std::max((x),(xMin)),(xMax))) - -class vox_core -{ -protected: - struct vox_decoder_t - { - vox_decoder_t(vox_core &vox) - : m_curr(vox) - , m_loop(vox) - { }; - - virtual void reset() - { - m_curr.reset(); - m_loop.reset(); - m_loop_saved = false; - } - - void save() - { - if (!m_loop_saved) - { - m_loop.copy(m_curr); - m_loop_saved = true; - } - } - - void restore() - { - if (m_loop_saved) - m_curr.copy(m_loop); - } - - s32 out() { return m_curr.m_step; } - - struct decoder_state_t - { - decoder_state_t(vox_core &vox) - : m_vox(vox) - { }; - - void reset() - { - m_index = 0; - m_step = 16; - } - - void copy(decoder_state_t src) - { - m_index = src.m_index; - m_step = src.m_step; - } - - void decode(u8 nibble) - { - const u8 delta = bitfield(nibble, 0, 3); - s16 ss = m_vox.m_step_table[m_index]; // ss(n) - - // d(n) = (ss(n) * B2) + ((ss(n) / 2) * B1) + ((ss(n) / 4) * B0) + (ss(n) / 8) - s16 d = ss >> 3; - if (bitfield(delta, 2)) - d += ss; - if (bitfield(delta, 1)) - d += (ss >> 1); - if (bitfield(delta, 0)) - d += (ss >> 2); - - // if (B3 = 1) then d(n) = d(n) * (-1) X(n) = X(n-1) * d(n) - if (bitfield(nibble, 3)) - m_step = std::max(m_step - d, -2048); - else - m_step = std::min(m_step + d, 2047); - - // adjust step index - m_index = MODIFIED_CLAMP(m_index + m_vox.m_index_table[delta], 0, 48); - } - - vox_core &m_vox; - s8 m_index = 0; - s32 m_step = 16; - }; - - decoder_state_t m_curr; - decoder_state_t m_loop; - bool m_loop_saved = false; - }; - - s8 m_index_table[8] = {-1, -1, -1, -1, 2, 4, 6, 8}; - s32 m_step_table[49] = { - 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, - 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, - 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, - 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552 - }; -}; - -#endif diff --git a/src/engine/platform/sound/scc/scc.cpp b/src/engine/platform/sound/scc/scc.cpp deleted file mode 100644 index 3e230447a..000000000 --- a/src/engine/platform/sound/scc/scc.cpp +++ /dev/null @@ -1,617 +0,0 @@ -/* - License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details - - Copyright holder(s): cam900 - Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst - Modifiers and Contributors for Furnace: Natt Akuma, tildearrow, Grauw - Konami SCC emulation core - - Konami SCC means "Sound Creative Chip", it's actually MSX MegaROM/RAM Mapper with 5 channel Wavetable sound generator. - - It was first appeared at 1987, F-1 Spirit and Nemesis 2/Gradius 2 for MSX. then several MSX cartridges used that until 1990, Metal Gear 2: Solid Snake. - Even after MSX is discontinued, it was still used at some low-end arcade and amusement hardwares. - and some Third-party MSX utilities still support this due to its market shares. - - There's 2 SCC types: - - K051649 (or simply known as SCC) - This chip is used for MSX MegaROM Mapper, some arcade machines. - Channel 4 and 5 must be share waveform, other channels has its own waveforms. - - K052539 (also known as SCC+) - This chip is used for MSX MegaRAM Mapper (Konami Sound Cartridges for Snatcher/SD Snatcher). - All channels can be has its own waveforms, and also has backward compatibility mode with K051649. - - Based on: - https://www.msx.org/wiki/MegaROM_Mappers - https://www.msx.org/wiki/Konami_051649 - https://www.msx.org/wiki/Konami_052539 - http://bifi.msxnet.org/msxnet/tech/scc - http://bifi.msxnet.org/msxnet/tech/soundcartridge - - K051649 Register Layout - - -------------------------------------------------------------------- - - 4000-bfff MegaROM Mapper - - -------------------------------------------------------------------- - - Address Bit R/W Description - 7654 3210 - - 4000-5fff xxxx xxxx R Bank page 0 - c000-dfff mirror of 4000-5fff - - 6000-7fff xxxx xxxx R Bank page 1 - e000-ffff mirror of 6000-7fff - - 8000-9fff xxxx xxxx R Bank page 2 - 0000-1fff mirror of 8000-9fff - - a000-bfff xxxx xxxx R Bank page 3 - 2000-3fff mirror of a000-bfff - - -------------------------------------------------------------------- - - 5000-57ff, 7000-77ff, 9000-97ff, b000-b7ff Bank select - - -------------------------------------------------------------------- - - Address Bit R/W Description - 7654 3210 - - 5000 --xx xxxx W Bank select, Page 0 - 5001-57ff Mirror of 5000 - - 7000 --xx xxxx W Bank select, Page 1 - 7001-77ff Mirror of 7000 - - 9000 --xx xxxx W Bank select, Page 2 - --11 1111 W SCC Enable - 9001-97ff Mirror of 9000 - - b000 --xx xxxx W Bank select, Page 3 - b001-b7ff Mirror of b000 - - -------------------------------------------------------------------- - - 9800-9fff SCC register - - -------------------------------------------------------------------- - - 9800-987f Waveform - - Address Bit R/W Description - 7654 3210 - - 9800-981f xxxx xxxx R/W Channel 0 Waveform (32 byte, 8 bit signed) - 9820-983f xxxx xxxx R/W Channel 1 "" - 9840-985f xxxx xxxx R/W Channel 2 "" - 9860-987f xxxx xxxx R/W Channel 3/4 "" - - 9880-9889 Pitch - - 9880 xxxx xxxx W Channel 0 Pitch LSB - 9881 ---- xxxx W Channel 0 Pitch MSB - 9882 xxxx xxxx W Channel 1 Pitch LSB - 9883 ---- xxxx W Channel 1 Pitch MSB - 9884 xxxx xxxx W Channel 2 Pitch LSB - 9885 ---- xxxx W Channel 2 Pitch MSB - 9886 xxxx xxxx W Channel 3 Pitch LSB - 9887 ---- xxxx W Channel 3 Pitch MSB - 9888 xxxx xxxx W Channel 4 Pitch LSB - 9889 ---- xxxx W Channel 4 Pitch MSB - - 9888-988e Volume - - 988a ---- xxxx W Channel 0 Volume - 988b ---- xxxx W Channel 1 Volume - 988c ---- xxxx W Channel 2 Volume - 988d ---- xxxx W Channel 3 Volume - 988e ---- xxxx W Channel 4 Volume - - 988f ---x ---- W Channel 4 Output enable/disable flag - ---- x--- W Channel 3 Output enable/disable flag - ---- -x-- W Channel 2 Output enable/disable flag - ---- --x- W Channel 1 Output enable/disable flag - ---- ---x W Channel 0 Output enable/disable flag - - 9890-989f Mirror of 9880-988f - - 98a0-98bf xxxx xxxx R Channel 4 Waveform - - 98e0 x--- ---- W Waveform rotate flag for channel 4 - -x-- ---- W Waveform rotate flag for all channels - --x- ---- W Reset waveform position after pitch writes - ---- --x- W 8 bit frequency - ---- --0x W 4 bit frequency - - 98e1-98ff Mirror of 98e0 - - 9900-9fff Mirror of 9800-98ff - - -------------------------------------------------------------------- - - K052539 Register Layout - - -------------------------------------------------------------------- - - 4000-bfff MegaRAM Mapper - - -------------------------------------------------------------------- - - Address Bit R/W Description - 7654 3210 - - 4000-5fff xxxx xxxx R/W Bank page 0 - c000-dfff xxxx xxxx R/W "" - - 6000-7fff xxxx xxxx R/W Bank page 1 - e000-ffff xxxx xxxx R/W "" - - 8000-9fff xxxx xxxx R/W Bank page 2 - 0000-1fff xxxx xxxx R/W "" - - a000-bfff xxxx xxxx R/W Bank page 3 - 2000-3fff xxxx xxxx R/W "" - - -------------------------------------------------------------------- - - 5000-57ff, 7000-77ff, 9000-97ff, b000-b7ff Bank select - - -------------------------------------------------------------------- - - Address Bit R/W Description - 7654 3210 - - 5000 xxxx xxxx W Bank select, Page 0 - 5001-57ff Mirror of 5000 - - 7000 xxxx xxxx W Bank select, Page 1 - 7001-77ff Mirror of 7000 - - 9000 xxxx xxxx W Bank select, Page 2 - --11 1111 W SCC Enable (SCC Compatible mode) - 9001-97ff Mirror of 9000 - - b000 xxxx xxxx W Bank select, Page 3 - 1--- ---- W SCC+ Enable (SCC+ mode) - b001-b7ff Mirror of b000 - - -------------------------------------------------------------------- - - bffe-bfff Mapper configuration - - -------------------------------------------------------------------- - - Address Bit R/W Description - 7654 3210 - - bffe --x- ---- W SCC operation mode - --0- ---- W SCC Compatible mode - --1- ---- W SCC+ mode - ---x ---- W RAM write/Bank select toggle for all Bank pages - ---0 ---- W Bank select enable - ---1 ---- W RAM write enable - ---0 -x-- W RAM write/Bank select toggle for Bank page 2 - ---0 --x- W RAM write/Bank select toggle for Bank page 1 - ---0 ---x W RAM write/Bank select toggle for Bank page 0 - bfff Mirror of bffe - - -------------------------------------------------------------------- - - 9800-9fff SCC Compatible mode register - - -------------------------------------------------------------------- - - 9800-987f Waveform - - Address Bit R/W Description - 7654 3210 - - 9800-981f xxxx xxxx R/W Channel 0 Waveform (32 byte, 8 bit signed) - 9820-983f xxxx xxxx R/W Channel 1 "" - 9840-985f xxxx xxxx R/W Channel 2 "" - 9860-987f xxxx xxxx R/W Channel 3/4 "" - - 9880-9889 Pitch - - 9880 xxxx xxxx W Channel 0 Pitch LSB - 9881 ---- xxxx W Channel 0 Pitch MSB - 9882 xxxx xxxx W Channel 1 Pitch LSB - 9883 ---- xxxx W Channel 1 Pitch MSB - 9884 xxxx xxxx W Channel 2 Pitch LSB - 9885 ---- xxxx W Channel 2 Pitch MSB - 9886 xxxx xxxx W Channel 3 Pitch LSB - 9887 ---- xxxx W Channel 3 Pitch MSB - 9888 xxxx xxxx W Channel 4 Pitch LSB - 9889 ---- xxxx W Channel 4 Pitch MSB - - 9888-988e Volume - - 988a ---- xxxx W Channel 0 Volume - 988b ---- xxxx W Channel 1 Volume - 988c ---- xxxx W Channel 2 Volume - 988d ---- xxxx W Channel 3 Volume - 988e ---- xxxx W Channel 4 Volume - - 988f ---x ---- W Channel 4 Output enable/disable flag - ---- x--- W Channel 3 Output enable/disable flag - ---- -x-- W Channel 2 Output enable/disable flag - ---- --x- W Channel 1 Output enable/disable flag - ---- ---x W Channel 0 Output enable/disable flag - - 9890-989f Mirror of 9880-988f - - 98a0-98bf xxxx xxxx R Channel 4 Waveform - - 98c0 -x-- ---- W Waveform rotate flag for all channels - --x- ---- W Reset waveform position after pitch writes - ---- --x- W 8 bit frequency - ---- --0x W 4 bit frequency - - 98c1-98df Mirror of 98c0 - - 9900-9fff Mirror of 9800-98ff - - -------------------------------------------------------------------- - - b800-bfff SCC+ mode register - - -------------------------------------------------------------------- - - b800-b89f Waveform - - Address Bit R/W Description - 7654 3210 - - b800-b81f xxxx xxxx R/W Channel 0 Waveform (32 byte, 8 bit signed) - b820-b83f xxxx xxxx R/W Channel 1 "" - b840-b85f xxxx xxxx R/W Channel 2 "" - b860-b87f xxxx xxxx R/W Channel 3 "" - b880-b89f xxxx xxxx R/W Channel 3 "" - - b8a0-b8a9 Pitch - - b8a0 xxxx xxxx W Channel 0 Pitch LSB - b8a1 ---- xxxx W Channel 0 Pitch MSB - b8a2 xxxx xxxx W Channel 1 Pitch LSB - b8a3 ---- xxxx W Channel 1 Pitch MSB - b8a4 xxxx xxxx W Channel 2 Pitch LSB - b8a5 ---- xxxx W Channel 2 Pitch MSB - b8a6 xxxx xxxx W Channel 3 Pitch LSB - b8a7 ---- xxxx W Channel 3 Pitch MSB - b8a8 xxxx xxxx W Channel 4 Pitch LSB - b8a9 ---- xxxx W Channel 4 Pitch MSB - - b8a8-b8ae Volume - - b8aa ---- xxxx W Channel 0 Volume - b8ab ---- xxxx W Channel 1 Volume - b8ac ---- xxxx W Channel 2 Volume - b8ad ---- xxxx W Channel 3 Volume - b8ae ---- xxxx W Channel 4 Volume - - b8af ---x ---- W Channel 4 Output enable/disable flag - ---- x--- W Channel 3 Output enable/disable flag - ---- -x-- W Channel 2 Output enable/disable flag - ---- --x- W Channel 1 Output enable/disable flag - ---- ---x W Channel 0 Output enable/disable flag - - b8b0-b8bf Mirror of b8a0-b8af - - b8c0 -x-- ---- W Waveform rotate flag for all channels - --x- ---- W Reset waveform position after pitch writes - ---- --x- W 8 bit frequency - ---- --0x W 4 bit frequency - - b8c1-b8df Mirror of b8c0 - - b900-bfff Mirror of b800-b8ff - - -------------------------------------------------------------------- - - SCC Frequency calculation: - if 8 bit frequency then - Frequency = Input clock / ((bit 0 to 7 of Pitch input) + 1) - else if 4 bit frequency then - Frequency = Input clock / ((bit 8 to 11 of Pitch input) + 1) - else - Frequency = Input clock / (Pitch input + 1) - -*/ - -#include "scc.hpp" -#include - -// shared SCC features -void scc_core::tick() -{ - m_out = 0; - for (auto & elem : m_voice) - { - elem.tick(); - m_out += elem.out; - } -} - -void scc_core::voice_t::tick() -{ - if (pitch >= 9) // or voice is halted - { - // update counter - Post decrement - u16 temp = counter; - if (m_host.m_test.freq_4bit) // 4 bit frequency mode - { - counter = (counter & ~0x0ff) | (bitfield(bitfield(counter, 0, 8) - 1, 0, 8) << 0); - counter = (counter & ~0xf00) | (bitfield(bitfield(counter, 8, 4) - 1, 0, 4) << 8); - } - else - counter = bitfield(counter - 1, 0, 12); - - // handle counter carry - bool carry = m_host.m_test.freq_8bit ? (bitfield(temp, 0, 8) == 0) : - (m_host.m_test.freq_4bit ? (bitfield(temp, 8, 4) == 0) : - (bitfield(temp, 0, 12) == 0)); - if (carry) - { - addr = bitfield(addr + 1, 0, 5); - counter = pitch; - } - } - // get output - if (enable) - out = (wave[addr] * volume) >> 4; // scale to 11 bit digital output - else - out = 0; -} - -void scc_core::reset() -{ - for (auto & elem : m_voice) - elem.reset(); - - m_test.reset(); - m_out = 0; - memset(m_reg,0,sizeof(m_reg)); -} - -void scc_core::voice_t::reset() -{ - memset(wave,0,sizeof(wave)); - enable = false; - pitch = 0; - volume = 0; - addr = 0; - counter = 0; - out = 0; -} - -// SCC accessors -u8 scc_core::wave_r(bool is_sccplus, u8 address) -{ - u8 ret = 0xff; - const u8 voice = bitfield(address, 5, 3); - if (voice > 4) - return ret; - - u8 wave_addr = bitfield(address, 0, 5); - - if (m_test.rotate) // rotate flag - wave_addr = bitfield(wave_addr + m_voice[voice].addr, 0, 5); - - if (!is_sccplus) - { - if (voice == 3) // rotate voice 3~4 flag - { - if (m_test.rotate4 || m_test.rotate) // rotate flag - wave_addr = bitfield(bitfield(address, 0, 5) + m_voice[3 + m_test.rotate].addr, 0, 5); - } - } - ret = m_voice[voice].wave[wave_addr]; - - return ret; -} - -void scc_core::wave_w(bool is_sccplus, u8 address, u8 data) -{ - if (m_test.rotate) // write protected - return; - - const u8 voice = bitfield(address, 5, 3); - if (voice > 4) - return; - - const u8 wave_addr = bitfield(address, 0, 5); - - if (!is_sccplus) - { - if (((voice >= 3) && m_test.rotate4) || (voice >= 4)) // Ignore if write protected, or voice 4 - return; - if (voice >= 3) // voice 3, 4 shares waveform - { - m_voice[3].wave[wave_addr] = data; - m_voice[4].wave[wave_addr] = data; - } - else - m_voice[voice].wave[wave_addr] = data; - } - else - m_voice[voice].wave[wave_addr] = data; -} - -void scc_core::freq_vol_enable_w(u8 address, u8 data) -{ - const u8 voice_freq = bitfield(address, 1, 3); - const u8 voice_reg = bitfield(address, 0, 4); - // *0-*f Pitch, Volume, Enable - switch (voice_reg) - { - case 0x0: // 0x*0 Voice 0 Pitch LSB - case 0x2: // 0x*2 Voice 1 Pitch LSB - case 0x4: // 0x*4 Voice 2 Pitch LSB - case 0x6: // 0x*6 Voice 3 Pitch LSB - case 0x8: // 0x*8 Voice 4 Pitch LSB - if (m_test.resetpos) // Reset address - m_voice[voice_freq].addr = 0; - m_voice[voice_freq].pitch = (m_voice[voice_freq].pitch & ~0x0ff) | data; - m_voice[voice_freq].counter = m_voice[voice_freq].pitch; - break; - case 0x1: // 0x*1 Voice 0 Pitch MSB - case 0x3: // 0x*3 Voice 1 Pitch MSB - case 0x5: // 0x*5 Voice 2 Pitch MSB - case 0x7: // 0x*7 Voice 3 Pitch MSB - case 0x9: // 0x*9 Voice 4 Pitch MSB - if (m_test.resetpos) // Reset address - m_voice[voice_freq].addr = 0; - m_voice[voice_freq].pitch = (m_voice[voice_freq].pitch & ~0xf00) | (u16(bitfield(data, 0, 4)) << 8); - m_voice[voice_freq].counter = m_voice[voice_freq].pitch; - break; - case 0xa: // 0x*a Voice 0 Volume - case 0xb: // 0x*b Voice 1 Volume - case 0xc: // 0x*c Voice 2 Volume - case 0xd: // 0x*d Voice 3 Volume - case 0xe: // 0x*e Voice 4 Volume - m_voice[voice_reg - 0xa].volume = bitfield(data, 0, 4); - break; - case 0xf: // 0x*f Enable/Disable flag - m_voice[0].enable = bitfield(data, 0); - m_voice[1].enable = bitfield(data, 1); - m_voice[2].enable = bitfield(data, 2); - m_voice[3].enable = bitfield(data, 3); - m_voice[4].enable = bitfield(data, 4); - break; - } -} - -void k051649_scc_core::scc_w(bool is_sccplus, u8 address, u8 data) -{ - const u8 voice = bitfield(address, 5, 3); - switch (voice) - { - case 0b000: // 0x00-0x1f Voice 0 Waveform - case 0b001: // 0x20-0x3f Voice 1 Waveform - case 0b010: // 0x40-0x5f Voice 2 Waveform - case 0b011: // 0x60-0x7f Voice 3/4 Waveform - wave_w(false, address, data); - break; - case 0b100: // 0x80-0x9f Pitch, Volume, Enable - freq_vol_enable_w(address, data); - break; - case 0b111: // 0xe0-0xff Test register - m_test.freq_4bit = bitfield(data, 0); - m_test.freq_8bit = bitfield(data, 1); - m_test.resetpos = bitfield(data, 5); - m_test.rotate = bitfield(data, 6); - m_test.rotate4 = bitfield(data, 7); - break; - } - m_reg[address] = data; -} - -void k052539_scc_core::scc_w(bool is_sccplus, u8 address, u8 data) -{ - const u8 voice = bitfield(address, 5, 3); - if (is_sccplus) - { - switch (voice) - { - case 0b000: // 0x00-0x1f Voice 0 Waveform - case 0b001: // 0x20-0x3f Voice 1 Waveform - case 0b010: // 0x40-0x5f Voice 2 Waveform - case 0b011: // 0x60-0x7f Voice 3 Waveform - case 0b100: // 0x80-0x9f Voice 4 Waveform - wave_w(true, address, data); - break; - case 0b101: // 0xa0-0xbf Pitch, Volume, Enable - freq_vol_enable_w(address, data); - break; - case 0b110: // 0xc0-0xdf Test register - m_test.freq_4bit = bitfield(data, 0); - m_test.freq_8bit = bitfield(data, 1); - m_test.resetpos = bitfield(data, 5); - m_test.rotate = bitfield(data, 6); - break; - default: - break; - } - } - else - { - switch (voice) - { - case 0b000: // 0x00-0x1f Voice 0 Waveform - case 0b001: // 0x20-0x3f Voice 1 Waveform - case 0b010: // 0x40-0x5f Voice 2 Waveform - case 0b011: // 0x60-0x7f Voice 3/4 Waveform - wave_w(false, address, data); - break; - case 0b100: // 0x80-0x9f Pitch, Volume, Enable - freq_vol_enable_w(address, data); - break; - case 0b110: // 0xc0-0xdf Test register - m_test.freq_4bit = bitfield(data, 0); - m_test.freq_8bit = bitfield(data, 1); - m_test.resetpos = bitfield(data, 5); - m_test.rotate = bitfield(data, 6); - break; - default: - break; - } - } - m_reg[address] = data; -} - -u8 k051649_scc_core::scc_r(bool is_sccplus, u8 address) -{ - const u8 voice = bitfield(address, 5, 3); - const u8 wave = bitfield(address, 0, 5); - u8 ret = 0xff; - switch (voice) - { - case 0b000: // 0x00-0x1f Voice 0 Waveform - case 0b001: // 0x20-0x3f Voice 1 Waveform - case 0b010: // 0x40-0x5f Voice 2 Waveform - case 0b011: // 0x60-0x7f Voice 3 Waveform - case 0b101: // 0xa0-0xbf Voice 4 Waveform - ret = wave_r(false, (std::min(4, voice) << 5) | wave); - break; - } - return ret; -} - -u8 k052539_scc_core::scc_r(bool is_sccplus, u8 address) -{ - const u8 voice = bitfield(address, 5, 3); - const u8 wave = bitfield(address, 0, 5); - u8 ret = 0xff; - if (is_sccplus) - { - switch (voice) - { - case 0b000: // 0x00-0x1f Voice 0 Waveform - case 0b001: // 0x20-0x3f Voice 1 Waveform - case 0b010: // 0x40-0x5f Voice 2 Waveform - case 0b011: // 0x60-0x7f Voice 3 Waveform - case 0b100: // 0x80-0x9f Voice 4 Waveform - ret = wave_r(true, address); - break; - } - } - else - { - switch (voice) - { - case 0b000: // 0x00-0x1f Voice 0 Waveform - case 0b001: // 0x20-0x3f Voice 1 Waveform - case 0b010: // 0x40-0x5f Voice 2 Waveform - case 0b011: // 0x60-0x7f Voice 3 Waveform - case 0b101: // 0xa0-0xbf Voice 4 Waveform - ret = wave_r(false, (std::min(4, voice) << 5) | wave); - break; - } - } - return ret; -} diff --git a/src/engine/platform/sound/scc/scc.hpp b/src/engine/platform/sound/scc/scc.hpp deleted file mode 100644 index db99ec877..000000000 --- a/src/engine/platform/sound/scc/scc.hpp +++ /dev/null @@ -1,139 +0,0 @@ -/* - License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details - - Copyright holder(s): cam900 - Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst - Modifiers and Contributors for Furnace: Natt Akuma, tildearrow, Grauw - Konami SCC emulation core - - See scc.cpp for more info. -*/ - -#include -#include - -#ifndef _VGSOUND_EMU_SCC_HPP -#define _VGSOUND_EMU_SCC_HPP - -#pragma once - -namespace scc -{ - typedef unsigned char u8; - typedef signed char s8; - typedef unsigned short u16; - typedef signed short s16; - typedef unsigned int u32; - typedef signed int s32; - - // get bitfield, bitfield(input, position, len) - template T bitfield(T in, u8 pos, u8 len = 1) - { - return (in >> pos) & (len ? (T(1 << len) - 1) : 1); - } -} - -using namespace scc; -// shared for SCCs -class scc_core -{ -public: - // constructor - scc_core() - : m_voice{*this,*this,*this,*this,*this} - {}; - virtual ~scc_core(){}; - - // accessors - virtual u8 scc_r(bool is_sccplus, u8 address) = 0; - virtual void scc_w(bool is_sccplus, u8 address, u8 data) = 0; - - // internal state - virtual void reset(); - void tick(); - - // getters - s32 out() { return m_out; } // output to DA0...DA10 pin - s32 chan_out(u8 ch) { return m_voice[ch].out; } - u8 reg(u8 address) { return m_reg[address]; } - -protected: - // voice structs - struct voice_t - { - // constructor - voice_t(scc_core &host) : m_host(host) {}; - - // internal state - void reset(); - void tick(); - - // registers - scc_core &m_host; - s8 wave[32] = {0}; // internal waveform - bool enable = false; // output enable flag - u16 pitch = 0; // pitch - u8 volume = 0; // volume - u8 addr = 0; // waveform pointer - u16 counter = 0; // frequency counter - s32 out = 0; // current output - }; - voice_t m_voice[5]; // 5 voices - - // accessor - u8 wave_r(bool is_sccplus, u8 address); - void wave_w(bool is_sccplus, u8 address, u8 data); - void freq_vol_enable_w(u8 address, u8 data); - - struct test_t - { - // constructor - test_t() - : freq_4bit(0) - , freq_8bit(0) - , resetpos(0) - , rotate(0) - , rotate4(0) - { }; - - void reset() - { - freq_4bit = 0; - freq_8bit = 0; - resetpos = 0; - rotate = 0; - rotate4 = 0; - } - - u8 freq_4bit : 1; // 4 bit frequency - u8 freq_8bit : 1; // 8 bit frequency - u8 resetpos : 1; // reset counter after pitch writes - u8 rotate : 1; // rotate and write protect waveform for all channels - u8 rotate4 : 1; // same as above but for channel 4 only - }; - - test_t m_test; // test register - s32 m_out = 0; // output to DA0...10 - - u8 m_reg[256] = {0}; // register pool -}; - -// SCC core -class k051649_scc_core : public scc_core -{ -public: - // accessors - virtual u8 scc_r(bool is_sccplus, u8 address) override; - virtual void scc_w(bool is_sccplus, u8 address, u8 data) override; -}; - -class k052539_scc_core : public k051649_scc_core -{ -public: - // accessors - virtual u8 scc_r(bool is_sccplus, u8 address) override; - virtual void scc_w(bool is_sccplus, u8 address, u8 data) override; -}; - -#endif diff --git a/src/engine/platform/sound/snes/SPC_DSP.cpp b/src/engine/platform/sound/snes/SPC_DSP.cpp new file mode 100644 index 000000000..6510f460a --- /dev/null +++ b/src/engine/platform/sound/snes/SPC_DSP.cpp @@ -0,0 +1,1032 @@ +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +#include "SPC_DSP.h" + +#include "blargg_endian.h" +#include + +/* Copyright (C) 2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +#if INT_MAX < 0x7FFFFFFF + #error "Requires that int type have at least 32 bits" +#endif + +// TODO: add to blargg_endian.h +#define GET_LE16SA( addr ) ((BOOST::int16_t) GET_LE16( addr )) +#define GET_LE16A( addr ) GET_LE16( addr ) +#define SET_LE16A( addr, data ) SET_LE16( addr, data ) + +static BOOST::uint8_t const initial_regs [SPC_DSP::register_count] = +{ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + +// 0x45,0x8B,0x5A,0x9A,0xE4,0x82,0x1B,0x78,0x00,0x00,0xAA,0x96,0x89,0x0E,0xE0,0x80, +// 0x2A,0x49,0x3D,0xBA,0x14,0xA0,0xAC,0xC5,0x00,0x00,0x51,0xBB,0x9C,0x4E,0x7B,0xFF, +// 0xF4,0xFD,0x57,0x32,0x37,0xD9,0x42,0x22,0x00,0x00,0x5B,0x3C,0x9F,0x1B,0x87,0x9A, +// 0x6F,0x27,0xAF,0x7B,0xE5,0x68,0x0A,0xD9,0x00,0x00,0x9A,0xC5,0x9C,0x4E,0x7B,0xFF, +// 0xEA,0x21,0x78,0x4F,0xDD,0xED,0x24,0x14,0x00,0x00,0x77,0xB1,0xD1,0x36,0xC1,0x67, +// 0x52,0x57,0x46,0x3D,0x59,0xF4,0x87,0xA4,0x00,0x00,0x7E,0x44,0x9C,0x4E,0x7B,0xFF, +// 0x75,0xF5,0x06,0x97,0x10,0xC3,0x24,0xBB,0x00,0x00,0x7B,0x7A,0xE0,0x60,0x12,0x0F, +// 0xF7,0x74,0x1C,0xE5,0x39,0x3D,0x73,0xC1,0x00,0x00,0x7A,0xB3,0xFF,0x4E,0x7B,0xFF +}; + +// if ( io < -32768 ) io = -32768; +// if ( io > 32767 ) io = 32767; +#define CLAMP16( io )\ +{\ + if ( (int16_t) io != io )\ + io = (io >> 31) ^ 0x7FFF;\ +} + +// Access global DSP register +#define REG(n) m.regs [r_##n] + +// Access voice DSP register +#define VREG(r,n) r [v_##n] + +#define WRITE_SAMPLES( l, r, out ) \ +{\ + out [0] = l;\ + out [1] = r;\ + out += 2;\ + if ( out >= m.out_end )\ + {\ + check( out == m.out_end );\ + check( m.out_end != &m.extra [extra_size] || \ + (m.extra <= m.out_begin && m.extra < &m.extra [extra_size]) );\ + out = m.extra;\ + m.out_end = &m.extra [extra_size];\ + }\ +}\ + +void SPC_DSP::set_output( sample_t* out, int size ) +{ + require( (size & 1) == 0 ); // must be even + if ( !out ) + { + out = m.extra; + size = extra_size; + } + m.out_begin = out; + m.out = out; + m.out_end = out + size; +} + +// Volume registers and efb are signed! Easy to forget int8_t cast. +// Prefixes are to avoid accidental use of locals with same names. + +// Gaussian interpolation + +static short const gauss [512] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, + 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, + 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, + 11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, + 18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27, 27, + 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 36, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 58, 59, 60, 61, 62, 64, 65, 66, 67, 69, 70, 71, 73, 74, 76, 77, + 78, 80, 81, 83, 84, 86, 87, 89, 90, 92, 94, 95, 97, 99, 100, 102, + 104, 106, 107, 109, 111, 113, 115, 117, 118, 120, 122, 124, 126, 128, 130, 132, + 134, 137, 139, 141, 143, 145, 147, 150, 152, 154, 156, 159, 161, 163, 166, 168, + 171, 173, 175, 178, 180, 183, 186, 188, 191, 193, 196, 199, 201, 204, 207, 210, + 212, 215, 218, 221, 224, 227, 230, 233, 236, 239, 242, 245, 248, 251, 254, 257, + 260, 263, 267, 270, 273, 276, 280, 283, 286, 290, 293, 297, 300, 304, 307, 311, + 314, 318, 321, 325, 328, 332, 336, 339, 343, 347, 351, 354, 358, 362, 366, 370, + 374, 378, 381, 385, 389, 393, 397, 401, 405, 410, 414, 418, 422, 426, 430, 434, + 439, 443, 447, 451, 456, 460, 464, 469, 473, 477, 482, 486, 491, 495, 499, 504, + 508, 513, 517, 522, 527, 531, 536, 540, 545, 550, 554, 559, 563, 568, 573, 577, + 582, 587, 592, 596, 601, 606, 611, 615, 620, 625, 630, 635, 640, 644, 649, 654, + 659, 664, 669, 674, 678, 683, 688, 693, 698, 703, 708, 713, 718, 723, 728, 732, + 737, 742, 747, 752, 757, 762, 767, 772, 777, 782, 787, 792, 797, 802, 806, 811, + 816, 821, 826, 831, 836, 841, 846, 851, 855, 860, 865, 870, 875, 880, 884, 889, + 894, 899, 904, 908, 913, 918, 923, 927, 932, 937, 941, 946, 951, 955, 960, 965, + 969, 974, 978, 983, 988, 992, 997,1001,1005,1010,1014,1019,1023,1027,1032,1036, +1040,1045,1049,1053,1057,1061,1066,1070,1074,1078,1082,1086,1090,1094,1098,1102, +1106,1109,1113,1117,1121,1125,1128,1132,1136,1139,1143,1146,1150,1153,1157,1160, +1164,1167,1170,1174,1177,1180,1183,1186,1190,1193,1196,1199,1202,1205,1207,1210, +1213,1216,1219,1221,1224,1227,1229,1232,1234,1237,1239,1241,1244,1246,1248,1251, +1253,1255,1257,1259,1261,1263,1265,1267,1269,1270,1272,1274,1275,1277,1279,1280, +1282,1283,1284,1286,1287,1288,1290,1291,1292,1293,1294,1295,1296,1297,1297,1298, +1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305, +}; + +inline int SPC_DSP::interpolate( voice_t const* v ) +{ + // Make pointers into gaussian based on fractional position between samples + int offset = v->interp_pos >> 4 & 0xFF; + short const* fwd = gauss + 255 - offset; + short const* rev = gauss + offset; // mirror left half of gaussian + + int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos]; + int out; + out = (fwd [ 0] * in [0]) >> 11; + out += (fwd [256] * in [1]) >> 11; + out += (rev [256] * in [2]) >> 11; + out = (int16_t) out; + out += (rev [ 0] * in [3]) >> 11; + + CLAMP16( out ); + out &= ~1; + return out; +} + + +//// Counters + +int const simple_counter_range = 2048 * 5 * 3; // 30720 + +static unsigned const counter_rates [32] = +{ + simple_counter_range + 1, // never fires + 2048, 1536, + 1280, 1024, 768, + 640, 512, 384, + 320, 256, 192, + 160, 128, 96, + 80, 64, 48, + 40, 32, 24, + 20, 16, 12, + 10, 8, 6, + 5, 4, 3, + 2, + 1 +}; + +static unsigned const counter_offsets [32] = +{ + 1, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 0, + 0 +}; + +inline void SPC_DSP::init_counter() +{ + m.counter = 0; +} + +inline void SPC_DSP::run_counters() +{ + if ( --m.counter < 0 ) + m.counter = simple_counter_range - 1; +} + +inline unsigned SPC_DSP::read_counter( int rate ) +{ + return ((unsigned) m.counter + counter_offsets [rate]) % counter_rates [rate]; +} + + +//// Envelope + +inline void SPC_DSP::run_envelope( voice_t* const v ) +{ + int env = v->env; + if ( v->env_mode == env_release ) // 60% + { + if ( (env -= 0x8) < 0 ) + env = 0; + v->env = env; + } + else + { + int rate; + int env_data = VREG(v->regs,adsr1); + if ( m.t_adsr0 & 0x80 ) // 99% ADSR + { + if ( v->env_mode >= env_decay ) // 99% + { + env--; + env -= env >> 8; + rate = env_data & 0x1F; + if ( v->env_mode == env_decay ) // 1% + rate = (m.t_adsr0 >> 3 & 0x0E) + 0x10; + } + else // env_attack + { + rate = (m.t_adsr0 & 0x0F) * 2 + 1; + env += rate < 31 ? 0x20 : 0x400; + } + } + else // GAIN + { + int mode; + env_data = VREG(v->regs,gain); + mode = env_data >> 5; + if ( mode < 4 ) // direct + { + env = env_data * 0x10; + rate = 31; + } + else + { + rate = env_data & 0x1F; + if ( mode == 4 ) // 4: linear decrease + { + env -= 0x20; + } + else if ( mode < 6 ) // 5: exponential decrease + { + env--; + env -= env >> 8; + } + else // 6,7: linear increase + { + env += 0x20; + if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 ) + env += 0x8 - 0x20; // 7: two-slope linear increase + } + } + } + + // Sustain level + if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay ) + v->env_mode = env_sustain; + + v->hidden_env = env; + + // unsigned cast because linear decrease going negative also triggers this + if ( (unsigned) env > 0x7FF ) + { + env = (env < 0 ? 0 : 0x7FF); + if ( v->env_mode == env_attack ) + v->env_mode = env_decay; + } + + if ( !read_counter( rate ) ) + v->env = env; // nothing else is controlled by the counter + } +} + + +//// BRR Decoding + +inline void SPC_DSP::decode_brr( voice_t* v ) +{ + // Arrange the four input nybbles in 0xABCD order for easy decoding + int nybbles = m.t_brr_byte * 0x100 + m.ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF]; + + int const header = m.t_brr_header; + + // Write to next four samples in circular buffer + int* pos = &v->buf [v->buf_pos]; + int* end; + if ( (v->buf_pos += 4) >= brr_buf_size ) + v->buf_pos = 0; + + // Decode four samples + for ( end = pos + 4; pos < end; pos++, nybbles <<= 4 ) + { + // Extract nybble and sign-extend + int s = (int16_t) nybbles >> 12; + + // Shift sample based on header + int const shift = header >> 4; + s = (s << shift) >> 1; + if ( shift >= 0xD ) // handle invalid range + s = (s >> 25) << 11; // same as: s = (s < 0 ? -0x800 : 0) + + // Apply IIR filter (8 is the most commonly used) + int const filter = header & 0x0C; + int const p1 = pos [brr_buf_size - 1]; + int const p2 = pos [brr_buf_size - 2] >> 1; + if ( filter >= 8 ) + { + s += p1; + s -= p2; + if ( filter == 8 ) // s += p1 * 0.953125 - p2 * 0.46875 + { + s += p2 >> 4; + s += (p1 * -3) >> 6; + } + else // s += p1 * 0.8984375 - p2 * 0.40625 + { + s += (p1 * -13) >> 7; + s += (p2 * 3) >> 4; + } + } + else if ( filter ) // s += p1 * 0.46875 + { + s += p1 >> 1; + s += (-p1) >> 5; + } + + // Adjust and write sample + CLAMP16( s ); + s = (int16_t) (s * 2); + pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around + } +} + + +//// Misc + +#define MISC_CLOCK( n ) inline void SPC_DSP::misc_##n() + +MISC_CLOCK( 27 ) +{ + m.t_pmon = REG(pmon) & 0xFE; // voice 0 doesn't support PMON +} +MISC_CLOCK( 28 ) +{ + m.t_non = REG(non); + m.t_eon = REG(eon); + m.t_dir = REG(dir); +} +MISC_CLOCK( 29 ) +{ + if ( (m.every_other_sample ^= 1) != 0 ) + m.new_kon &= ~m.kon; // clears KON 63 clocks after it was last read +} +MISC_CLOCK( 30 ) +{ + if ( m.every_other_sample ) + { + m.kon = m.new_kon; + m.t_koff = REG(koff) | m.mute_mask; + } + + run_counters(); + + // Noise + if ( !read_counter( REG(flg) & 0x1F ) ) + { + int feedback = (m.noise << 13) ^ (m.noise << 14); + m.noise = (feedback & 0x4000) ^ (m.noise >> 1); + } +} + + +//// Voices + +#define VOICE_CLOCK( n ) void SPC_DSP::voice_##n( voice_t* const v ) + +inline VOICE_CLOCK( V1 ) +{ + m.t_dir_addr = (m.t_dir * 0x100 + m.t_srcn * 4) & 0xffff; + m.t_srcn = VREG(v->regs,srcn); +} +inline VOICE_CLOCK( V2 ) +{ + // Read sample pointer (ignored if not needed) + uint8_t const* entry = &m.ram [m.t_dir_addr]; + if ( !v->kon_delay ) + entry += 2; + m.t_brr_next_addr = GET_LE16A( entry ); + + m.t_adsr0 = VREG(v->regs,adsr0); + + // Read pitch, spread over two clocks + m.t_pitch = VREG(v->regs,pitchl); +} +inline VOICE_CLOCK( V3a ) +{ + m.t_pitch += (VREG(v->regs,pitchh) & 0x3F) << 8; +} +inline VOICE_CLOCK( V3b ) +{ + // Read BRR header and byte + m.t_brr_byte = m.ram [(v->brr_addr + v->brr_offset) & 0xFFFF]; + m.t_brr_header = m.ram [v->brr_addr]; // brr_addr doesn't need masking +} +VOICE_CLOCK( V3c ) +{ + // Pitch modulation using previous voice's output + if ( m.t_pmon & v->vbit ) + m.t_pitch += ((m.t_output >> 5) * m.t_pitch) >> 10; + + if ( v->kon_delay ) + { + // Get ready to start BRR decoding on next sample + if ( v->kon_delay == 5 ) + { + v->brr_addr = m.t_brr_next_addr; + v->brr_offset = 1; + v->buf_pos = 0; + m.t_brr_header = 0; // header is ignored on this sample + m.kon_check = true; + } + + // Envelope is never run during KON + v->env = 0; + v->hidden_env = 0; + + // Disable BRR decoding until last three samples + v->interp_pos = 0; + if ( --v->kon_delay & 3 ) + v->interp_pos = 0x4000; + + // Pitch is never added during KON + m.t_pitch = 0; + } + + // Gaussian interpolation + { + int output = interpolate( v ); + + // Noise + if ( m.t_non & v->vbit ) + output = (int16_t) (m.noise * 2); + + // Apply envelope + m.t_output = (output * v->env) >> 11 & ~1; + v->t_envx_out = (uint8_t) (v->env >> 4); + } + + // Immediate silence due to end of sample or soft reset + if ( REG(flg) & 0x80 || (m.t_brr_header & 3) == 1 ) + { + v->env_mode = env_release; + v->env = 0; + } + + if ( m.every_other_sample ) + { + // KOFF + if ( m.t_koff & v->vbit ) + v->env_mode = env_release; + + // KON + if ( m.kon & v->vbit ) + { + v->kon_delay = 5; + v->env_mode = env_attack; + } + } + + // Run envelope for next sample + if ( !v->kon_delay ) + run_envelope( v ); +} +inline void SPC_DSP::voice_output( voice_t* const v, int ch ) +{ + // Apply left/right volume + int amp = (m.t_output * (int8_t) VREG(v->regs,voll + ch)) >> 7; + v->out [ch] = (sample_t) amp; // Furnace addition + + // Add to output total + m.t_main_out [ch] += amp; + CLAMP16( m.t_main_out [ch] ); + + // Optionally add to echo total + if ( m.t_eon & v->vbit ) + { + m.t_echo_out [ch] += amp; + CLAMP16( m.t_echo_out [ch] ); + } +} +VOICE_CLOCK( V4 ) +{ + // Decode BRR + m.t_looped = 0; + if ( v->interp_pos >= 0x4000 ) + { + decode_brr( v ); + + if ( (v->brr_offset += 2) >= brr_block_size ) + { + // Start decoding next BRR block + assert( v->brr_offset == brr_block_size ); + v->brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF; + if ( m.t_brr_header & 1 ) + { + v->brr_addr = m.t_brr_next_addr; + m.t_looped = v->vbit; + } + v->brr_offset = 1; + } + } + + // Apply pitch + v->interp_pos = (v->interp_pos & 0x3FFF) + m.t_pitch; + + // Keep from getting too far ahead (when using pitch modulation) + if ( v->interp_pos > 0x7FFF ) + v->interp_pos = 0x7FFF; + + // Output left + voice_output( v, 0 ); +} +inline VOICE_CLOCK( V5 ) +{ + // Output right + voice_output( v, 1 ); + + // ENDX, OUTX, and ENVX won't update if you wrote to them 1-2 clocks earlier + int endx_buf = REG(endx) | m.t_looped; + + // Clear bit in ENDX if KON just began + if ( v->kon_delay == 5 ) + endx_buf &= ~v->vbit; + m.endx_buf = (uint8_t) endx_buf; +} +inline VOICE_CLOCK( V6 ) +{ + (void) v; // avoid compiler warning about unused v + m.outx_buf = (uint8_t) (m.t_output >> 8); +} +inline VOICE_CLOCK( V7 ) +{ + // Update ENDX + REG(endx) = m.endx_buf; + + m.envx_buf = v->t_envx_out; +} +inline VOICE_CLOCK( V8 ) +{ + // Update OUTX + VREG(v->regs,outx) = m.outx_buf; +} +inline VOICE_CLOCK( V9 ) +{ + // Update ENVX + VREG(v->regs,envx) = m.envx_buf; +} + +// Most voices do all these in one clock, so make a handy composite +inline VOICE_CLOCK( V3 ) +{ + voice_V3a( v ); + voice_V3b( v ); + voice_V3c( v ); +} + +// Common combinations of voice steps on different voices. This greatly reduces +// code size and allows everything to be inlined in these functions. +VOICE_CLOCK(V7_V4_V1) { voice_V7(v); voice_V1(v+3); voice_V4(v+1); } +VOICE_CLOCK(V8_V5_V2) { voice_V8(v); voice_V5(v+1); voice_V2(v+2); } +VOICE_CLOCK(V9_V6_V3) { voice_V9(v); voice_V6(v+1); voice_V3(v+2); } + + +//// Echo + +// Current echo buffer pointer for left/right channel +#define ECHO_PTR( ch ) (&m.ram [m.t_echo_ptr + ch * 2]) + +// Sample in echo history buffer, where 0 is the oldest +#define ECHO_FIR( i ) (m.echo_hist_pos [i]) + +// Calculate FIR point for left/right channel +#define CALC_FIR( i, ch ) ((ECHO_FIR( i + 1 ) [ch] * (int8_t) REG(fir + i * 0x10)) >> 6) + +#define ECHO_CLOCK( n ) inline void SPC_DSP::echo_##n() + +inline void SPC_DSP::echo_read( int ch ) +{ + int s = GET_LE16SA( ECHO_PTR( ch ) ); + // second copy simplifies wrap-around handling + ECHO_FIR( 0 ) [ch] = ECHO_FIR( 8 ) [ch] = s >> 1; +} + +ECHO_CLOCK( 22 ) +{ + // History + if ( ++m.echo_hist_pos >= &m.echo_hist [echo_hist_size] ) + m.echo_hist_pos = m.echo_hist; + + m.t_echo_ptr = (m.t_esa * 0x100 + m.echo_offset) & 0xFFFF; + echo_read( 0 ); + + // FIR (using l and r temporaries below helps compiler optimize) + int l = CALC_FIR( 0, 0 ); + int r = CALC_FIR( 0, 1 ); + + m.t_echo_in [0] = l; + m.t_echo_in [1] = r; +} +ECHO_CLOCK( 23 ) +{ + int l = CALC_FIR( 1, 0 ) + CALC_FIR( 2, 0 ); + int r = CALC_FIR( 1, 1 ) + CALC_FIR( 2, 1 ); + + m.t_echo_in [0] += l; + m.t_echo_in [1] += r; + + echo_read( 1 ); +} +ECHO_CLOCK( 24 ) +{ + int l = CALC_FIR( 3, 0 ) + CALC_FIR( 4, 0 ) + CALC_FIR( 5, 0 ); + int r = CALC_FIR( 3, 1 ) + CALC_FIR( 4, 1 ) + CALC_FIR( 5, 1 ); + + m.t_echo_in [0] += l; + m.t_echo_in [1] += r; +} +ECHO_CLOCK( 25 ) +{ + int l = m.t_echo_in [0] + CALC_FIR( 6, 0 ); + int r = m.t_echo_in [1] + CALC_FIR( 6, 1 ); + + l = (int16_t) l; + r = (int16_t) r; + + l += (int16_t) CALC_FIR( 7, 0 ); + r += (int16_t) CALC_FIR( 7, 1 ); + + CLAMP16( l ); + CLAMP16( r ); + + m.t_echo_in [0] = l & ~1; + m.t_echo_in [1] = r & ~1; +} +inline int SPC_DSP::echo_output( int ch ) +{ + int out = (int16_t) ((m.t_main_out [ch] * (int8_t) REG(mvoll + ch * 0x10)) >> 7) + + (int16_t) ((m.t_echo_in [ch] * (int8_t) REG(evoll + ch * 0x10)) >> 7); + CLAMP16( out ); + return out; +} +ECHO_CLOCK( 26 ) +{ + // Left output volumes + // (save sample for next clock so we can output both together) + m.t_main_out [0] = echo_output( 0 ); + + // Echo feedback + int l = m.t_echo_out [0] + (int16_t) ((m.t_echo_in [0] * (int8_t) REG(efb)) >> 7); + int r = m.t_echo_out [1] + (int16_t) ((m.t_echo_in [1] * (int8_t) REG(efb)) >> 7); + + CLAMP16( l ); + CLAMP16( r ); + + m.t_echo_out [0] = l & ~1; + m.t_echo_out [1] = r & ~1; +} +ECHO_CLOCK( 27 ) +{ + // Output + int l = m.t_main_out [0]; + int r = echo_output( 1 ); + m.t_main_out [0] = 0; + m.t_main_out [1] = 0; + + // TODO: global muting isn't this simple (turns DAC on and off + // or something, causing small ~37-sample pulse when first muted) + if ( REG(flg) & 0x40 ) + { + l = 0; + r = 0; + } + + // Output sample to DAC + #ifdef SPC_DSP_OUT_HOOK + SPC_DSP_OUT_HOOK( l, r ); + #else + sample_t* out = m.out; + WRITE_SAMPLES( l, r, out ); + m.out = out; + #endif +} +ECHO_CLOCK( 28 ) +{ + m.t_echo_enabled = REG(flg); +} +inline void SPC_DSP::echo_write( int ch ) +{ + if ( !(m.t_echo_enabled & 0x20) ) + SET_LE16A( ECHO_PTR( ch ), m.t_echo_out [ch] ); + m.t_echo_out [ch] = 0; +} +ECHO_CLOCK( 29 ) +{ + m.t_esa = REG(esa); + + if ( !m.echo_offset ) + m.echo_length = (REG(edl) & 0x0F) * 0x800; + + m.echo_offset += 4; + if ( m.echo_offset >= m.echo_length ) + m.echo_offset = 0; + + // Write left echo + echo_write( 0 ); + + m.t_echo_enabled = REG(flg); +} +ECHO_CLOCK( 30 ) +{ + // Write right echo + echo_write( 1 ); +} + + +//// Timing + +// Execute clock for a particular voice +#define V( clock, voice ) voice_##clock( &m.voices [voice] ); + +/* The most common sequence of clocks uses composite operations +for efficiency. For example, the following are equivalent to the +individual steps on the right: + +V(V7_V4_V1,2) -> V(V7,2) V(V4,3) V(V1,5) +V(V8_V5_V2,2) -> V(V8,2) V(V5,3) V(V2,4) +V(V9_V6_V3,2) -> V(V9,2) V(V6,3) V(V3,4) */ + +// Voice 0 1 2 3 4 5 6 7 +#define GEN_DSP_TIMING \ +PHASE( 0) V(V5,0)V(V2,1)\ +PHASE( 1) V(V6,0)V(V3,1)\ +PHASE( 2) V(V7_V4_V1,0)\ +PHASE( 3) V(V8_V5_V2,0)\ +PHASE( 4) V(V9_V6_V3,0)\ +PHASE( 5) V(V7_V4_V1,1)\ +PHASE( 6) V(V8_V5_V2,1)\ +PHASE( 7) V(V9_V6_V3,1)\ +PHASE( 8) V(V7_V4_V1,2)\ +PHASE( 9) V(V8_V5_V2,2)\ +PHASE(10) V(V9_V6_V3,2)\ +PHASE(11) V(V7_V4_V1,3)\ +PHASE(12) V(V8_V5_V2,3)\ +PHASE(13) V(V9_V6_V3,3)\ +PHASE(14) V(V7_V4_V1,4)\ +PHASE(15) V(V8_V5_V2,4)\ +PHASE(16) V(V9_V6_V3,4)\ +PHASE(17) V(V1,0) V(V7,5)V(V4,6)\ +PHASE(18) V(V8_V5_V2,5)\ +PHASE(19) V(V9_V6_V3,5)\ +PHASE(20) V(V1,1) V(V7,6)V(V4,7)\ +PHASE(21) V(V8,6)V(V5,7) V(V2,0) /* t_brr_next_addr order dependency */\ +PHASE(22) V(V3a,0) V(V9,6)V(V6,7) echo_22();\ +PHASE(23) V(V7,7) echo_23();\ +PHASE(24) V(V8,7) echo_24();\ +PHASE(25) V(V3b,0) V(V9,7) echo_25();\ +PHASE(26) echo_26();\ +PHASE(27) misc_27(); echo_27();\ +PHASE(28) misc_28(); echo_28();\ +PHASE(29) misc_29(); echo_29();\ +PHASE(30) misc_30();V(V3c,0) echo_30();\ +PHASE(31) V(V4,0) V(V1,2)\ + +#if !SPC_DSP_CUSTOM_RUN + +void SPC_DSP::run( int clocks_remain ) +{ + require( clocks_remain > 0 ); + + int const phase = m.phase; + m.phase = (phase + clocks_remain) & 31; + switch ( phase ) + { + loop: + // GCC, why +#ifdef __GNUC__ + #define PHASE( n ) if ( n && !--clocks_remain ) break; __attribute__ ((fallthrough)); case n: +#else + #define PHASE( n ) if ( n && !--clocks_remain ) break; case n: +#endif + GEN_DSP_TIMING + #undef PHASE + + if ( --clocks_remain ) + goto loop; + } +} + +#endif + + +//// Setup + +void SPC_DSP::init( void* ram_64k ) +{ + m.ram = (uint8_t*) ram_64k; + mute_voices( 0 ); + disable_surround( false ); + set_output( 0, 0 ); + reset(); + + #ifndef NDEBUG + // be sure this sign-extends + assert( (int16_t) 0x8000 == -0x8000 ); + + // be sure right shift preserves sign + assert( (-1 >> 1) == -1 ); + + // check clamp macro + int i; + i = +0x8000; CLAMP16( i ); assert( i == +0x7FFF ); + i = -0x8001; CLAMP16( i ); assert( i == -0x8000 ); + + blargg_verify_byte_order(); + #endif +} + +void SPC_DSP::soft_reset_common() +{ + require( m.ram ); // init() must have been called already + + m.noise = 0x4000; + m.echo_hist_pos = m.echo_hist; + m.every_other_sample = 1; + m.echo_offset = 0; + m.phase = 0; + + init_counter(); +} + +void SPC_DSP::soft_reset() +{ + REG(flg) = 0xE0; + soft_reset_common(); +} + +void SPC_DSP::load( uint8_t const regs [register_count] ) +{ + memcpy( m.regs, regs, sizeof m.regs ); + memset( &m.regs [register_count], 0, offsetof (state_t,ram) - register_count ); + + // Internal state + for ( int i = voice_count; --i >= 0; ) + { + voice_t* v = &m.voices [i]; + v->brr_offset = 1; + v->vbit = 1 << i; + v->regs = &m.regs [i * 0x10]; + } + m.new_kon = REG(kon); + m.t_dir = REG(dir); + m.t_esa = REG(esa); + + soft_reset_common(); +} + +void SPC_DSP::reset() { load( initial_regs ); } + + +//// State save/load + +#if !SPC_NO_COPY_STATE_FUNCS + +void SPC_State_Copier::copy( void* state, size_t size ) +{ + func( buf, state, size ); +} + +int SPC_State_Copier::copy_int( int state, int size ) +{ + BOOST::uint8_t s [2]; + SET_LE16( s, state ); + func( buf, &s, size ); + return GET_LE16( s ); +} + +void SPC_State_Copier::skip( int count ) +{ + if ( count > 0 ) + { + char temp [64]; + memset( temp, 0, sizeof temp ); + do + { + int n = sizeof temp; + if ( n > count ) + n = count; + count -= n; + func( buf, temp, n ); + } + while ( count ); + } +} + +void SPC_State_Copier::extra() +{ + int n = 0; + SPC_State_Copier& copier = *this; + SPC_COPY( uint8_t, n ); + skip( n ); +} + +void SPC_DSP::copy_state( unsigned char** io, copy_func_t copy ) +{ + SPC_State_Copier copier( io, copy ); + + // DSP registers + copier.copy( m.regs, register_count ); + + // Internal state + + // Voices + int i; + for ( i = 0; i < voice_count; i++ ) + { + voice_t* v = &m.voices [i]; + + // BRR buffer + int i; + for ( i = 0; i < brr_buf_size; i++ ) + { + int s = v->buf [i]; + SPC_COPY( int16_t, s ); + v->buf [i] = v->buf [i + brr_buf_size] = s; + } + + SPC_COPY( uint16_t, v->interp_pos ); + SPC_COPY( uint16_t, v->brr_addr ); + SPC_COPY( uint16_t, v->env ); + SPC_COPY( int16_t, v->hidden_env ); + SPC_COPY( uint8_t, v->buf_pos ); + SPC_COPY( uint8_t, v->brr_offset ); + SPC_COPY( uint8_t, v->kon_delay ); + { + int m = v->env_mode; + SPC_COPY( uint8_t, m ); + v->env_mode = (enum env_mode_t) m; + } + SPC_COPY( uint8_t, v->t_envx_out ); + + copier.extra(); + } + + // Echo history + for ( i = 0; i < echo_hist_size; i++ ) + { + int j; + for ( j = 0; j < 2; j++ ) + { + int s = m.echo_hist_pos [i] [j]; + SPC_COPY( int16_t, s ); + m.echo_hist [i] [j] = s; // write back at offset 0 + } + } + m.echo_hist_pos = m.echo_hist; + memcpy( &m.echo_hist [echo_hist_size], m.echo_hist, echo_hist_size * sizeof m.echo_hist [0] ); + + // Misc + SPC_COPY( uint8_t, m.every_other_sample ); + SPC_COPY( uint8_t, m.kon ); + + SPC_COPY( uint16_t, m.noise ); + SPC_COPY( uint16_t, m.counter ); + SPC_COPY( uint16_t, m.echo_offset ); + SPC_COPY( uint16_t, m.echo_length ); + SPC_COPY( uint8_t, m.phase ); + + SPC_COPY( uint8_t, m.new_kon ); + SPC_COPY( uint8_t, m.endx_buf ); + SPC_COPY( uint8_t, m.envx_buf ); + SPC_COPY( uint8_t, m.outx_buf ); + + SPC_COPY( uint8_t, m.t_pmon ); + SPC_COPY( uint8_t, m.t_non ); + SPC_COPY( uint8_t, m.t_eon ); + SPC_COPY( uint8_t, m.t_dir ); + SPC_COPY( uint8_t, m.t_koff ); + + SPC_COPY( uint16_t, m.t_brr_next_addr ); + SPC_COPY( uint8_t, m.t_adsr0 ); + SPC_COPY( uint8_t, m.t_brr_header ); + SPC_COPY( uint8_t, m.t_brr_byte ); + SPC_COPY( uint8_t, m.t_srcn ); + SPC_COPY( uint8_t, m.t_esa ); + SPC_COPY( uint8_t, m.t_echo_enabled ); + + SPC_COPY( int16_t, m.t_main_out [0] ); + SPC_COPY( int16_t, m.t_main_out [1] ); + SPC_COPY( int16_t, m.t_echo_out [0] ); + SPC_COPY( int16_t, m.t_echo_out [1] ); + SPC_COPY( int16_t, m.t_echo_in [0] ); + SPC_COPY( int16_t, m.t_echo_in [1] ); + + SPC_COPY( uint16_t, m.t_dir_addr ); + SPC_COPY( uint16_t, m.t_pitch ); + SPC_COPY( int16_t, m.t_output ); + SPC_COPY( uint16_t, m.t_echo_ptr ); + SPC_COPY( uint8_t, m.t_looped ); + + copier.extra(); +} +#endif diff --git a/src/engine/platform/sound/snes/SPC_DSP.h b/src/engine/platform/sound/snes/SPC_DSP.h new file mode 100644 index 000000000..879ee703d --- /dev/null +++ b/src/engine/platform/sound/snes/SPC_DSP.h @@ -0,0 +1,322 @@ +// Highly accurate SNES SPC-700 DSP emulator + +// snes_spc 0.9.0 +#ifndef SPC_DSP_H +#define SPC_DSP_H + +#include "blargg_common.h" + +extern "C" { typedef void (*dsp_copy_func_t)( unsigned char** io, void* state, size_t ); } + +class SPC_DSP { +public: + typedef BOOST::uint8_t uint8_t; + +// Setup + + // Initializes DSP and has it use the 64K RAM provided + void init( void* ram_64k ); + + // Sets destination for output samples. If out is NULL or out_size is 0, + // doesn't generate any. + typedef short sample_t; + void set_output( sample_t* out, int out_size ); + + // Number of samples written to output since it was last set, always + // a multiple of 2. Undefined if more samples were generated than + // output buffer could hold. + int sample_count() const; + +// Emulation + + // Resets DSP to power-on state + void reset(); + + // Emulates pressing reset switch on SNES + void soft_reset(); + + // Reads/writes DSP registers. For accuracy, you must first call run() + // to catch the DSP up to present. + int read ( int addr ) const; + void write( int addr, int data ); + + // Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks + // a pair of samples is be generated. + void run( int clock_count ); + +// Sound control + + // Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events). + // Reduces emulation accuracy. + enum { voice_count = 8 }; + void mute_voices( int mask ); + +// State + + // Resets DSP and uses supplied values to initialize registers + enum { register_count = 128 }; + void load( uint8_t const regs [register_count] ); + + // Saves/loads exact emulator state + enum { state_size = 640 }; // maximum space needed when saving + typedef dsp_copy_func_t copy_func_t; + void copy_state( unsigned char** io, copy_func_t ); + + // Returns non-zero if new key-on events occurred since last call + bool check_kon(); + + // Furnace addition, gets all current voice outputs to an array of samples + void get_voice_outputs( sample_t* outs ); + +// DSP register addresses + + // Global registers + enum { + r_mvoll = 0x0C, r_mvolr = 0x1C, + r_evoll = 0x2C, r_evolr = 0x3C, + r_kon = 0x4C, r_koff = 0x5C, + r_flg = 0x6C, r_endx = 0x7C, + r_efb = 0x0D, r_pmon = 0x2D, + r_non = 0x3D, r_eon = 0x4D, + r_dir = 0x5D, r_esa = 0x6D, + r_edl = 0x7D, + r_fir = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F + }; + + // Voice registers + enum { + v_voll = 0x00, v_volr = 0x01, + v_pitchl = 0x02, v_pitchh = 0x03, + v_srcn = 0x04, v_adsr0 = 0x05, + v_adsr1 = 0x06, v_gain = 0x07, + v_envx = 0x08, v_outx = 0x09 + }; + +public: + enum { extra_size = 16 }; + sample_t* extra() { return m.extra; } + sample_t const* out_pos() const { return m.out; } + void disable_surround( bool ) { } // not supported +public: + BLARGG_DISABLE_NOTHROW + + typedef BOOST::int8_t int8_t; + typedef BOOST::int16_t int16_t; + + enum { echo_hist_size = 8 }; + + enum env_mode_t { env_release, env_attack, env_decay, env_sustain }; + enum { brr_buf_size = 12 }; + struct voice_t + { + int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling) + int buf_pos; // place in buffer where next samples will be decoded + int interp_pos; // relative fractional position in sample (0x1000 = 1.0) + int brr_addr; // address of current BRR block + int brr_offset; // current decoding offset in BRR block + uint8_t* regs; // pointer to voice's DSP registers + int vbit; // bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc. + int kon_delay; // KON delay/current setup phase + env_mode_t env_mode; + int env; // current envelope level + int hidden_env; // used by GAIN mode 7, very obscure quirk + uint8_t t_envx_out; + sample_t out[2]; // Furnace addition, for per-channel oscilloscope + }; +private: + enum { brr_block_size = 9 }; + + struct state_t + { + uint8_t regs [register_count]; + + // Echo history keeps most recent 8 samples (twice the size to simplify wrap handling) + int echo_hist [echo_hist_size * 2] [2]; + int (*echo_hist_pos) [2]; // &echo_hist [0 to 7] + + int every_other_sample; // toggles every sample + int kon; // KON value when last checked + int noise; + int counter; + int echo_offset; // offset from ESA in echo buffer + int echo_length; // number of bytes that echo_offset will stop at + int phase; // next clock cycle to run (0-31) + bool kon_check; // set when a new KON occurs + + // Hidden registers also written to when main register is written to + int new_kon; + uint8_t endx_buf; + uint8_t envx_buf; + uint8_t outx_buf; + + // Temporary state between clocks + + // read once per sample + int t_pmon; + int t_non; + int t_eon; + int t_dir; + int t_koff; + + // read a few clocks ahead then used + int t_brr_next_addr; + int t_adsr0; + int t_brr_header; + int t_brr_byte; + int t_srcn; + int t_esa; + int t_echo_enabled; + + // internal state that is recalculated every sample + int t_dir_addr; + int t_pitch; + int t_output; + int t_looped; + int t_echo_ptr; + + // left/right sums + int t_main_out [2]; + int t_echo_out [2]; + int t_echo_in [2]; + + voice_t voices [voice_count]; + + // non-emulation state + uint8_t* ram; // 64K shared RAM between DSP and SMP + int mute_mask; + sample_t* out; + sample_t* out_end; + sample_t* out_begin; + sample_t extra [extra_size]; + }; + state_t m; + + void init_counter(); + void run_counters(); + unsigned read_counter( int rate ); + + int interpolate( voice_t const* v ); + void run_envelope( voice_t* const v ); + void decode_brr( voice_t* v ); + + void misc_27(); + void misc_28(); + void misc_29(); + void misc_30(); + + void voice_output( voice_t* const v, int ch ); + void voice_V1( voice_t* const ); + void voice_V2( voice_t* const ); + void voice_V3( voice_t* const ); + void voice_V3a( voice_t* const ); + void voice_V3b( voice_t* const ); + void voice_V3c( voice_t* const ); + void voice_V4( voice_t* const ); + void voice_V5( voice_t* const ); + void voice_V6( voice_t* const ); + void voice_V7( voice_t* const ); + void voice_V8( voice_t* const ); + void voice_V9( voice_t* const ); + void voice_V7_V4_V1( voice_t* const ); + void voice_V8_V5_V2( voice_t* const ); + void voice_V9_V6_V3( voice_t* const ); + + void echo_read( int ch ); + int echo_output( int ch ); + void echo_write( int ch ); + void echo_22(); + void echo_23(); + void echo_24(); + void echo_25(); + void echo_26(); + void echo_27(); + void echo_28(); + void echo_29(); + void echo_30(); + + void soft_reset_common(); + +public: + bool mute() { return m.regs[r_flg] & 0x40; } +}; + +#include + +inline int SPC_DSP::sample_count() const { return m.out - m.out_begin; } + +inline int SPC_DSP::read( int addr ) const +{ + assert( (unsigned) addr < register_count ); + return m.regs [addr]; +} + +inline void SPC_DSP::write( int addr, int data ) +{ + assert( (unsigned) addr < register_count ); + + m.regs [addr] = (uint8_t) data; + switch ( addr & 0x0F ) + { + case v_envx: + m.envx_buf = (uint8_t) data; + break; + + case v_outx: + m.outx_buf = (uint8_t) data; + break; + + case 0x0C: + if ( addr == r_kon ) + m.new_kon = (uint8_t) data; + + if ( addr == r_endx ) // always cleared, regardless of data written + { + m.endx_buf = 0; + m.regs [r_endx] = 0; + } + break; + } +} + +inline void SPC_DSP::mute_voices( int mask ) { m.mute_mask = mask; } + +inline bool SPC_DSP::check_kon() +{ + bool old = m.kon_check; + m.kon_check = 0; + return old; +} + +inline void SPC_DSP::get_voice_outputs( sample_t* outs ) +{ + int i; + for ( i = 0; i < voice_count; i++ ) + { + voice_t* v = &m.voices [i]; + outs [i * 2] = v->out [0]; + outs [i * 2 + 1] = v->out [1]; + } +} + +#if !SPC_NO_COPY_STATE_FUNCS + +class SPC_State_Copier { + SPC_DSP::copy_func_t func; + unsigned char** buf; +public: + SPC_State_Copier( unsigned char** p, SPC_DSP::copy_func_t f ) { func = f; buf = p; } + void copy( void* state, size_t size ); + int copy_int( int state, int size ); + void skip( int count ); + void extra(); +}; + +#define SPC_COPY( type, state )\ +{\ + state = (BOOST::type) copier.copy_int( state, sizeof (BOOST::type) );\ + assert( (BOOST::type) state == state );\ +} + +#endif + +#endif diff --git a/src/engine/platform/sound/snes/blargg_common.h b/src/engine/platform/sound/snes/blargg_common.h new file mode 100644 index 000000000..75edff391 --- /dev/null +++ b/src/engine/platform/sound/snes/blargg_common.h @@ -0,0 +1,186 @@ +// Sets up common environment for Shay Green's libraries. +// To change configuration options, modify blargg_config.h, not this file. + +// snes_spc 0.9.0 +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +#include +#include +#include +#include + +#undef BLARGG_COMMON_H +// allow blargg_config.h to #include blargg_common.h +#include "blargg_config.h" +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +// BLARGG_RESTRICT: equivalent to restrict, where supported +#if defined (__GNUC__) || _MSC_VER >= 1100 + #define BLARGG_RESTRICT __restrict +#else + #define BLARGG_RESTRICT +#endif + +// STATIC_CAST(T,expr): Used in place of static_cast (expr) +#ifndef STATIC_CAST + #define STATIC_CAST(T,expr) ((T) (expr)) +#endif + +// blargg_err_t (0 on success, otherwise error string) +#ifndef blargg_err_t + typedef const char* blargg_err_t; +#endif + +// blargg_vector - very lightweight vector of POD types (no constructor/destructor) +template +class blargg_vector { + T* begin_; + size_t size_; +public: + blargg_vector() : begin_( 0 ), size_( 0 ) { } + ~blargg_vector() { free( begin_ ); } + size_t size() const { return size_; } + T* begin() const { return begin_; } + T* end() const { return begin_ + size_; } + blargg_err_t resize( size_t n ) + { + // TODO: blargg_common.cpp to hold this as an outline function, ugh + void* p = realloc( begin_, n * sizeof (T) ); + if ( p ) + begin_ = (T*) p; + else if ( n > size_ ) // realloc failure only a problem if expanding + return "Out of memory"; + size_ = n; + return 0; + } + void clear() { void* p = begin_; begin_ = 0; size_ = 0; free( p ); } + T& operator [] ( size_t n ) const + { + assert( n <= size_ ); // <= to allow past-the-end value + return begin_ [n]; + } +}; + +#ifndef BLARGG_DISABLE_NOTHROW + // throw spec mandatory in ISO C++ if operator new can return NULL + #if __cplusplus >= 199711 || defined (__GNUC__) + #define BLARGG_THROWS( spec ) throw spec + #else + #define BLARGG_THROWS( spec ) + #endif + #define BLARGG_DISABLE_NOTHROW \ + void* operator new ( size_t s ) BLARGG_THROWS(()) { return malloc( s ); }\ + void operator delete ( void* p ) { free( p ); } + #define BLARGG_NEW new +#else + #include + #define BLARGG_NEW new (std::nothrow) +#endif + +// BLARGG_4CHAR('a','b','c','d') = 'abcd' (four character integer constant) +#define BLARGG_4CHAR( a, b, c, d ) \ + ((a&0xFF)*0x1000000L + (b&0xFF)*0x10000L + (c&0xFF)*0x100L + (d&0xFF)) + +// BOOST_STATIC_ASSERT( expr ): Generates compile error if expr is 0. +#ifndef BOOST_STATIC_ASSERT + #ifdef _MSC_VER + // MSVC6 (_MSC_VER < 1300) fails for use of __LINE__ when /Zl is specified + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / (int) !!(expr) - 1] ) + #else + // Some other compilers fail when declaring same function multiple times in class, + // so differentiate them by line + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / !!(expr) - 1] [__LINE__] ) + #endif +#endif + +// BLARGG_COMPILER_HAS_BOOL: If 0, provides bool support for old compiler. If 1, +// compiler is assumed to support bool. If undefined, availability is determined. +#ifndef BLARGG_COMPILER_HAS_BOOL + #if defined (__MWERKS__) + #if !__option(bool) + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #elif defined (_MSC_VER) + #if _MSC_VER < 1100 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #elif defined (__GNUC__) + // supports bool + #elif __cplusplus < 199711 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif +#endif +#if defined (BLARGG_COMPILER_HAS_BOOL) && !BLARGG_COMPILER_HAS_BOOL + // If you get errors here, modify your blargg_config.h file + typedef int bool; + const bool true = 1; + const bool false = 0; +#endif + +// blargg_long/blargg_ulong = at least 32 bits, int if it's big enough + +#if INT_MAX < 0x7FFFFFFF || LONG_MAX == 0x7FFFFFFF + typedef long blargg_long; +#else + typedef int blargg_long; +#endif + +#if UINT_MAX < 0xFFFFFFFF || ULONG_MAX == 0xFFFFFFFF + typedef unsigned long blargg_ulong; +#else + typedef unsigned blargg_ulong; +#endif + +// BOOST::int8_t etc. + +// HAVE_STDINT_H: If defined, use for int8_t etc. +#if defined (HAVE_STDINT_H) + #include + #define BOOST + +// HAVE_INTTYPES_H: If defined, use for int8_t etc. +#elif defined (HAVE_INTTYPES_H) + #include + #define BOOST + +#else + struct BOOST + { + #if UCHAR_MAX == 0xFF && SCHAR_MAX == 0x7F + typedef signed char int8_t; + typedef unsigned char uint8_t; + #else + // No suitable 8-bit type available + typedef struct see_blargg_common_h int8_t; + typedef struct see_blargg_common_h uint8_t; + #endif + + #if USHRT_MAX == 0xFFFF + typedef short int16_t; + typedef unsigned short uint16_t; + #else + // No suitable 16-bit type available + typedef struct see_blargg_common_h int16_t; + typedef struct see_blargg_common_h uint16_t; + #endif + + #if ULONG_MAX == 0xFFFFFFFF + typedef long int32_t; + typedef unsigned long uint32_t; + #elif UINT_MAX == 0xFFFFFFFF + typedef int int32_t; + typedef unsigned int uint32_t; + #else + // No suitable 32-bit type available + typedef struct see_blargg_common_h int32_t; + typedef struct see_blargg_common_h uint32_t; + #endif + }; +#endif + +#endif +#endif diff --git a/src/engine/platform/sound/snes/blargg_config.h b/src/engine/platform/sound/snes/blargg_config.h new file mode 100644 index 000000000..94570ca0b --- /dev/null +++ b/src/engine/platform/sound/snes/blargg_config.h @@ -0,0 +1,26 @@ +// snes_spc 0.9.0 user configuration file. Don't replace when updating library. + +// snes_spc 0.9.0 +#ifndef BLARGG_CONFIG_H +#define BLARGG_CONFIG_H + +// Uncomment to disable debugging checks +#ifndef NDEBUG + #define NDEBUG 1 +#endif + +// Uncomment to enable platform-specific (and possibly non-portable) optimizations +//#define BLARGG_NONPORTABLE 1 + +// Uncomment if automatic byte-order determination doesn't work +//#define BLARGG_BIG_ENDIAN 1 + +// Uncomment if you get errors in the bool section of blargg_common.h +//#define BLARGG_COMPILER_HAS_BOOL 1 + +// Use standard config.h if present +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#endif diff --git a/src/engine/platform/sound/snes/blargg_endian.h b/src/engine/platform/sound/snes/blargg_endian.h new file mode 100644 index 000000000..f2daca641 --- /dev/null +++ b/src/engine/platform/sound/snes/blargg_endian.h @@ -0,0 +1,185 @@ +// CPU Byte Order Utilities + +// snes_spc 0.9.0 +#ifndef BLARGG_ENDIAN +#define BLARGG_ENDIAN + +#include "blargg_common.h" + +// BLARGG_CPU_CISC: Defined if CPU has very few general-purpose registers (< 16) +#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \ + defined (__x86_64__) || defined (__ia64__) || defined (__i386__) + #define BLARGG_CPU_X86 1 + #define BLARGG_CPU_CISC 1 +#endif + +#if defined (__powerpc__) || defined (__ppc__) || defined (__POWERPC__) || defined (__powerc) + #define BLARGG_CPU_POWERPC 1 + #define BLARGG_CPU_RISC 1 +#endif + +// BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only +// one may be #defined to 1. Only needed if something actually depends on byte order. +#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN) +#ifdef __GLIBC__ + // GCC handles this for us + #include + #if __BYTE_ORDER == __LITTLE_ENDIAN + #define BLARGG_LITTLE_ENDIAN 1 + #elif __BYTE_ORDER == __BIG_ENDIAN + #define BLARGG_BIG_ENDIAN 1 + #endif +#else + +#if defined (LSB_FIRST) || defined (__LITTLE_ENDIAN__) || BLARGG_CPU_X86 || \ + (defined (LITTLE_ENDIAN) && LITTLE_ENDIAN+0 != 1234) + #define BLARGG_LITTLE_ENDIAN 1 +#endif + +#if defined (MSB_FIRST) || defined (__BIG_ENDIAN__) || defined (WORDS_BIGENDIAN) || \ + defined (__sparc__) || BLARGG_CPU_POWERPC || \ + (defined (BIG_ENDIAN) && BIG_ENDIAN+0 != 4321) + #define BLARGG_BIG_ENDIAN 1 +#elif !defined (__mips__) + // No endian specified; assume little-endian, since it's most common + #define BLARGG_LITTLE_ENDIAN 1 +#endif +#endif +#endif + +#if BLARGG_LITTLE_ENDIAN && BLARGG_BIG_ENDIAN + #undef BLARGG_LITTLE_ENDIAN + #undef BLARGG_BIG_ENDIAN +#endif + +inline void blargg_verify_byte_order() +{ + #ifndef NDEBUG + #if BLARGG_BIG_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i == 0 ); + #elif BLARGG_LITTLE_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i != 0 ); + #endif + #endif +} + +inline unsigned get_le16( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [1] << 8 | + (unsigned) ((unsigned char const*) p) [0]; +} + +inline unsigned get_be16( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [0] << 8 | + (unsigned) ((unsigned char const*) p) [1]; +} + +inline blargg_ulong get_le32( void const* p ) +{ + return (blargg_ulong) ((unsigned char const*) p) [3] << 24 | + (blargg_ulong) ((unsigned char const*) p) [2] << 16 | + (blargg_ulong) ((unsigned char const*) p) [1] << 8 | + (blargg_ulong) ((unsigned char const*) p) [0]; +} + +inline blargg_ulong get_be32( void const* p ) +{ + return (blargg_ulong) ((unsigned char const*) p) [0] << 24 | + (blargg_ulong) ((unsigned char const*) p) [1] << 16 | + (blargg_ulong) ((unsigned char const*) p) [2] << 8 | + (blargg_ulong) ((unsigned char const*) p) [3]; +} + +inline void set_le16( void* p, unsigned n ) +{ + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [0] = (unsigned char) n; +} + +inline void set_be16( void* p, unsigned n ) +{ + ((unsigned char*) p) [0] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) n; +} + +inline void set_le32( void* p, blargg_ulong n ) +{ + ((unsigned char*) p) [0] = (unsigned char) n; + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [2] = (unsigned char) (n >> 16); + ((unsigned char*) p) [3] = (unsigned char) (n >> 24); +} + +inline void set_be32( void* p, blargg_ulong n ) +{ + ((unsigned char*) p) [3] = (unsigned char) n; + ((unsigned char*) p) [2] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) (n >> 16); + ((unsigned char*) p) [0] = (unsigned char) (n >> 24); +} + +#if BLARGG_NONPORTABLE + // Optimized implementation if byte order is known + #if BLARGG_LITTLE_ENDIAN + #define GET_LE16( addr ) (*(BOOST::uint16_t*) (addr)) + #define GET_LE32( addr ) (*(BOOST::uint32_t*) (addr)) + #define SET_LE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_LE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + #elif BLARGG_BIG_ENDIAN + #define GET_BE16( addr ) (*(BOOST::uint16_t*) (addr)) + #define GET_BE32( addr ) (*(BOOST::uint32_t*) (addr)) + #define SET_BE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_BE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + + #if BLARGG_CPU_POWERPC + // PowerPC has special byte-reversed instructions + #if defined (__MWERKS__) + #define GET_LE16( addr ) (__lhbrx( addr, 0 )) + #define GET_LE32( addr ) (__lwbrx( addr, 0 )) + #define SET_LE16( addr, in ) (__sthbrx( in, addr, 0 )) + #define SET_LE32( addr, in ) (__stwbrx( in, addr, 0 )) + #elif defined (__GNUC__) + #define GET_LE16( addr ) ({unsigned ppc_lhbrx_; asm( "lhbrx %0,0,%1" : "=r" (ppc_lhbrx_) : "r" (addr), "0" (ppc_lhbrx_) ); ppc_lhbrx_;}) + #define GET_LE32( addr ) ({unsigned ppc_lwbrx_; asm( "lwbrx %0,0,%1" : "=r" (ppc_lwbrx_) : "r" (addr), "0" (ppc_lwbrx_) ); ppc_lwbrx_;}) + #define SET_LE16( addr, in ) ({asm( "sthbrx %0,0,%1" : : "r" (in), "r" (addr) );}) + #define SET_LE32( addr, in ) ({asm( "stwbrx %0,0,%1" : : "r" (in), "r" (addr) );}) + #endif + #endif + #endif +#endif + +#ifndef GET_LE16 + #define GET_LE16( addr ) get_le16( addr ) + #define SET_LE16( addr, data ) set_le16( addr, data ) +#endif + +#ifndef GET_LE32 + #define GET_LE32( addr ) get_le32( addr ) + #define SET_LE32( addr, data ) set_le32( addr, data ) +#endif + +#ifndef GET_BE16 + #define GET_BE16( addr ) get_be16( addr ) + #define SET_BE16( addr, data ) set_be16( addr, data ) +#endif + +#ifndef GET_BE32 + #define GET_BE32( addr ) get_be32( addr ) + #define SET_BE32( addr, data ) set_be32( addr, data ) +#endif + +// auto-selecting versions + +inline void set_le( BOOST::uint16_t* p, unsigned n ) { SET_LE16( p, n ); } +inline void set_le( BOOST::uint32_t* p, blargg_ulong n ) { SET_LE32( p, n ); } +inline void set_be( BOOST::uint16_t* p, unsigned n ) { SET_BE16( p, n ); } +inline void set_be( BOOST::uint32_t* p, blargg_ulong n ) { SET_BE32( p, n ); } +inline unsigned get_le( BOOST::uint16_t* p ) { return GET_LE16( p ); } +inline blargg_ulong get_le( BOOST::uint32_t* p ) { return GET_LE32( p ); } +inline unsigned get_be( BOOST::uint16_t* p ) { return GET_BE16( p ); } +inline blargg_ulong get_be( BOOST::uint32_t* p ) { return GET_BE32( p ); } + +#endif diff --git a/src/engine/platform/sound/snes/blargg_source.h b/src/engine/platform/sound/snes/blargg_source.h new file mode 100644 index 000000000..5e45c4fb4 --- /dev/null +++ b/src/engine/platform/sound/snes/blargg_source.h @@ -0,0 +1,100 @@ +/* Included at the beginning of library source files, after all other #include lines. +Sets up helpful macros and services used in my source code. They don't need +module an annoying module prefix on their names since they are defined after +all other #include lines. */ + +// snes_spc 0.9.0 +#ifndef BLARGG_SOURCE_H +#define BLARGG_SOURCE_H + +// If debugging is enabled, abort program if expr is false. Meant for checking +// internal state and consistency. A failed assertion indicates a bug in the module. +// void assert( bool expr ); +#include + +// If debugging is enabled and expr is false, abort program. Meant for checking +// caller-supplied parameters and operations that are outside the control of the +// module. A failed requirement indicates a bug outside the module. +// void require( bool expr ); +#undef require +#define require( expr ) assert( expr ) + +// Like printf() except output goes to debug log file. Might be defined to do +// nothing (not even evaluate its arguments). +// void dprintf( const char* format, ... ); +static inline void blargg_dprintf_( const char*, ... ) { } +#undef dprintf +#define dprintf (1) ? (void) 0 : blargg_dprintf_ + +// If enabled, evaluate expr and if false, make debug log entry with source file +// and line. Meant for finding situations that should be examined further, but that +// don't indicate a problem. In all cases, execution continues normally. +#undef check +#define check( expr ) ((void) 0) + +// If expr yields error string, return it from current function, otherwise continue. +#undef RETURN_ERR +#define RETURN_ERR( expr ) do { \ + blargg_err_t blargg_return_err_ = (expr); \ + if ( blargg_return_err_ ) return blargg_return_err_; \ + } while ( 0 ) + +// If ptr is 0, return out of memory error string. +#undef CHECK_ALLOC +#define CHECK_ALLOC( ptr ) do { if ( (ptr) == 0 ) return "Out of memory"; } while ( 0 ) + +// Avoid any macros which evaluate their arguments multiple times +#undef min +#undef max + +#define DEF_MIN_MAX( type ) \ + static inline type min( type x, type y ) { if ( x < y ) return x; return y; }\ + static inline type max( type x, type y ) { if ( y < x ) return x; return y; } + +DEF_MIN_MAX( int ) +DEF_MIN_MAX( unsigned ) +DEF_MIN_MAX( long ) +DEF_MIN_MAX( unsigned long ) +DEF_MIN_MAX( float ) +DEF_MIN_MAX( double ) + +#undef DEF_MIN_MAX + +/* +// using const references generates crappy code, and I am currenly only using these +// for built-in types, so they take arguments by value + +// TODO: remove +inline int min( int x, int y ) +template +inline T min( T x, T y ) +{ + if ( x < y ) + return x; + return y; +} + +template +inline T max( T x, T y ) +{ + if ( x < y ) + return y; + return x; +} +*/ + +// TODO: good idea? bad idea? +#undef byte +#define byte byte_ +typedef unsigned char byte; + +// deprecated +#define BLARGG_CHECK_ALLOC CHECK_ALLOC +#define BLARGG_RETURN_ERR RETURN_ERR + +// BLARGG_SOURCE_BEGIN: If defined, #included, allowing redefition of dprintf and check +#ifdef BLARGG_SOURCE_BEGIN + #include BLARGG_SOURCE_BEGIN +#endif + +#endif diff --git a/src/engine/platform/sound/su.cpp b/src/engine/platform/sound/su.cpp index b7fa731a6..a87205457 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 3152e8568..546acfc9c 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 000000000..662bd7fd5 --- /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 000000000..4a782bb38 --- /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 000000000..c780c172c --- /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 000000000..942e101ab --- /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 e2d0568c4..000000000 --- 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 78459426f..000000000 --- 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 782a24de9..000000000 --- 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/vrcvi/vrcvi.cpp b/src/engine/platform/sound/vrcvi/vrcvi.cpp deleted file mode 100644 index 87ff05d7c..000000000 --- a/src/engine/platform/sound/vrcvi/vrcvi.cpp +++ /dev/null @@ -1,324 +0,0 @@ -/* - License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details - - Copyright holder(s): cam900 - Modifiers and Contributors for Furnace: cam900, tildearrow - Konami VRC VI sound emulation core - - It's one of NES mapper with built-in sound chip, and also one of 2 Konami VRCs with this feature. (rest one has OPLL derivatives.) - - It's also DACless like other sound chip and mapper-with-sound manufactured by konami, - the Chips 6 bit digital sound output is needs converted to analog sound output when you it want to make some sounds, or send to sound mixer. - - Its are used for Akumajou Densetsu (Japan release of Castlevania III), Madara, Esper Dream 2. - - The chip is installed in 351951 PCB and 351949A PCB. - - 351951 PCB is used exclusivly for Akumajou Densetsu, Small board has VRC VI, PRG and CHR ROM. - - It's configuration also calls VRC6a, iNES mapper 024. - - 351949A PCB is for Last 2 titles with VRC VI, Bigger board has VRC VI, PRG and CHR ROM, and Battery Backed 8K x 8 bit SRAM. - - Additionally, It's PRG A0 and A1 bit to VRC VI input is swapped, compare to above. - - It's configuration also calls VRC6b, iNES mapper 026. - - The chip itself has 053328, 053329, 053330 Revision, but Its difference between revision is unknown. - - Like other mappers for NES, It has internal timer - Its timer can be sync with scanline like other Konami mapper in this era. - - Register layout (Sound and Timer only; 351951 PCB case, 351949A swaps xxx1 and xxx2): - - Address Bits Description - 7654 3210 - - 9000-9002 Pulse 1 - - 9000 x--- ---- Pulse 1 Duty ignore - -xxx ---- Pulse 1 Duty cycle - ---- xxxx Pulse 1 Volume - 9001 xxxx xxxx Pulse 1 Pitch bit 0-7 - 9002 x--- ---- Pulse 1 Enable - ---- xxxx Pulse 1 Pitch bit 8-11 - - 9003 Sound control - - 9003 ---- -x-- 4 bit Frequency mode - ---- -0x- 8 bit Frequency mode - ---- ---x Halt - - a000-a002 Pulse 2 - - a000 x--- ---- Pulse 2 Duty ignore - -xxx ---- Pulse 2 Duty cycle - ---- xxxx Pulse 2 Volume - a001 xxxx xxxx Pulse 2 Pitch bit 0-7 - a002 x--- ---- Pulse 2 Enable - ---- xxxx Pulse 2 Pitch bit 8-11 - - b000-b002 Sawtooth - - b000 --xx xxxx Sawtooth Accumulate Rate - b001 xxxx xxxx Sawtooth Pitch bit 0-7 - b002 x--- ---- Sawtooth Enable - ---- xxxx Sawtooth Pitch bit 8-11 - - f000-f002 IRQ Timer - - f000 xxxx xxxx IRQ Timer latch - f001 ---- -0-- Sync with scanline - ---- --x- Enable timer - ---- ---x Enable timer after IRQ Acknowledge - f002 ---- ---- IRQ Acknowledge - - Frequency calculations: - - if 4 bit Frequency Mode then - Frequency: Input clock / (bit 8 to 11 of Pitch + 1) - end else if 8 bit Frequency Mode then - Frequency: Input clock / (bit 4 to 11 of Pitch + 1) - end else then - Frequency: Input clock / (Pitch + 1) - end -*/ - -#include "vrcvi.hpp" -#include - -void vrcvi_core::tick() -{ - m_out = 0; - if (!m_control.m_halt) // Halt flag - { - // tick per each clock - int elemIndex=0; - for (auto & elem : m_pulse) - { - if (elem.tick()) { - m_out += elem.m_control.m_volume; // add 4 bit pulse output - m_ch_out[elemIndex]=elem.m_control.m_volume; - } else { - m_ch_out[elemIndex]=0; - } - elemIndex++; - } - if (m_sawtooth.tick()) { - m_out += bitfield(m_sawtooth.m_accum, 3, 5); // add 5 bit sawtooth output - m_ch_out[2]=bitfield(m_sawtooth.m_accum, 3, 5); - } else { - m_ch_out[2]=0; - } - } - if (m_timer.tick()) - m_timer.counter_tick(); -} - -void vrcvi_core::reset() -{ - for (auto & elem : m_pulse) - elem.reset(); - - m_sawtooth.reset(); - m_timer.reset(); - m_control.reset(); - m_out = 0; - memset(m_ch_out,0,sizeof(m_ch_out)); -} - -bool vrcvi_core::alu_t::tick() -{ - if (m_divider.m_enable) - { - const u16 temp = m_counter; - // post decrement - if (bitfield(m_host.m_control.m_shift, 1)) - { - m_counter = (m_counter & 0x0ff) | (bitfield(bitfield(m_counter, 8, 4) - 1, 0, 4) << 8); - m_counter = (m_counter & 0xf00) | (bitfield(bitfield(m_counter, 0, 8) - 1, 0, 8) << 0); - } - else if (bitfield(m_host.m_control.m_shift, 0)) - { - m_counter = (m_counter & 0x00f) | (bitfield(bitfield(m_counter, 4, 8) - 1, 0, 8) << 4); - m_counter = (m_counter & 0xff0) | (bitfield(bitfield(m_counter, 0, 4) - 1, 0, 4) << 0); - } - else - m_counter = bitfield(bitfield(m_counter, 0, 12) - 1, 0, 12); - - // carry handling - bool carry = bitfield(m_host.m_control.m_shift, 1) ? (bitfield(temp, 8, 4) == 0) : - (bitfield(m_host.m_control.m_shift, 0) ? (bitfield(temp, 4, 8) == 0) : - (bitfield(temp, 0, 12) == 0)); - if (carry) - m_counter = m_divider.m_divider; - - return carry; - } - return false; -} - -bool vrcvi_core::pulse_t::tick() -{ - if (!m_divider.m_enable) - return false; - - if (vrcvi_core::alu_t::tick()) - m_cycle = bitfield(m_cycle + 1, 0, 4); - - return m_control.m_mode ? true : ((m_cycle > m_control.m_duty) ? true : false); -} - -bool vrcvi_core::sawtooth_t::tick() -{ - if (!m_divider.m_enable) - return false; - - if (vrcvi_core::alu_t::tick()) - { - if (bitfield(m_cycle++, 0)) // Even step only - m_accum += m_rate; - if (m_cycle >= 14) // Reset accumulator at every 14 cycles - { - m_accum = 0; - m_cycle = 0; - } - } - return (m_accum == 0) ? false : true; -} - -void vrcvi_core::alu_t::reset() -{ - m_divider.reset(); - m_counter = 0; - m_cycle = 0; -} - -void vrcvi_core::pulse_t::reset() -{ - vrcvi_core::alu_t::reset(); - m_control.reset(); -} - -void vrcvi_core::sawtooth_t::reset() -{ - vrcvi_core::alu_t::reset(); - m_rate = 0; - m_accum = 0; -} - -bool vrcvi_core::timer_t::tick() -{ - if (m_timer_control.m_enable) - { - if (!m_timer_control.m_sync) // scanline sync mode - { - m_prescaler -= 3; - if (m_prescaler <= 0) - { - m_prescaler += 341; - return true; - } - } - } - return (m_timer_control.m_enable && m_timer_control.m_sync) ? true : false; -} - -void vrcvi_core::timer_t::counter_tick() -{ - if (bitfield(++m_counter, 0, 8) == 0) - { - m_counter = m_counter_latch; - irq_set(); - } -} - -void vrcvi_core::timer_t::reset() -{ - m_timer_control.reset(); - m_prescaler = 341; - m_counter = m_counter_latch = 0; - irq_clear(); -} - -// Accessors - -void vrcvi_core::alu_t::divider_t::write(bool msb, u8 data) -{ - if (msb) - { - m_divider = (m_divider & ~0xf00) | (bitfield(data, 0, 4) << 8); - m_enable = bitfield(data, 7); - } - else - m_divider = (m_divider & ~0x0ff) | data; -} - - -void vrcvi_core::pulse_w(u8 voice, u8 address, u8 data) -{ - pulse_t &v = m_pulse[voice]; - switch (address) - { - case 0x00: // Control - 0x9000 (Pulse 1), 0xa000 (Pulse 2) - v.m_control.m_mode = bitfield(data, 7); - v.m_control.m_duty = bitfield(data, 4, 3); - v.m_control.m_volume = bitfield(data, 0, 4); - break; - case 0x01: // Pitch LSB - 0x9001/0x9002 (Pulse 1), 0xa001/0xa002 (Pulse 2) - v.m_divider.write(false, data); - break; - case 0x02: // Pitch MSB, Enable/Disable - 0x9002/0x9001 (Pulse 1), 0xa002/0xa001 (Pulse 2) - v.m_divider.write(true, data); - if (!v.m_divider.m_enable) // Reset duty cycle - v.m_cycle = 0; - break; - } -} - -void vrcvi_core::saw_w(u8 address, u8 data) -{ - switch (address) - { - case 0x00: // Sawtooth Accumulate - 0xb000 - m_sawtooth.m_rate = bitfield(data, 0, 6); - break; - case 0x01: // Pitch LSB - 0xb001/0xb002 (Sawtooth) - m_sawtooth.m_divider.write(false, data); - break; - case 0x02: // Pitch MSB, Enable/Disable - 0xb002/0xb001 (Sawtooth) - m_sawtooth.m_divider.write(true, data); - if (!m_sawtooth.m_divider.m_enable) // Reset accumulator - m_sawtooth.m_accum = 0; - break; - } -} - -void vrcvi_core::timer_w(u8 address, u8 data) -{ - switch (address) - { - case 0x00: // Timer latch - 0xf000 - m_timer.m_counter_latch = data; - break; - case 0x01: // Timer control - 0xf001/0xf002 - m_timer.m_timer_control.m_sync = bitfield(data, 2); - m_timer.m_timer_control.m_enable = bitfield(data, 1); - m_timer.m_timer_control.m_enable_ack = bitfield(data, 0); - if (m_timer.m_timer_control.m_enable) - { - m_timer.m_counter = m_timer.m_counter_latch; - m_timer.m_prescaler = 341; - } - m_timer.irq_clear(); - break; - case 0x02: // IRQ Acknowledge - 0xf002/0xf001 - m_timer.irq_clear(); - m_timer.m_timer_control.m_enable = m_timer.m_timer_control.m_enable_ack; - break; - } -} - -void vrcvi_core::control_w(u8 data) -{ - // Global control - 0x9003 - m_control.m_halt = bitfield(data, 0); - m_control.m_shift = bitfield(data, 1, 2); -} diff --git a/src/engine/platform/sound/vrcvi/vrcvi.hpp b/src/engine/platform/sound/vrcvi/vrcvi.hpp deleted file mode 100644 index 790061c82..000000000 --- a/src/engine/platform/sound/vrcvi/vrcvi.hpp +++ /dev/null @@ -1,242 +0,0 @@ -/* - License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details - - Copyright holder(s): cam900 - Modifiers and Contributors for Furnace: cam900, tildearrow - Konami VRC VI sound emulation core - - See vrcvi.cpp to more infos. -*/ - -#include -#include - -#ifndef _VGSOUND_EMU_VRCVI_HPP -#define _VGSOUND_EMU_VRCVI_HPP - -#pragma once - -namespace vrcvi -{ - typedef unsigned char u8; - typedef unsigned short u16; - typedef unsigned int u32; - typedef signed char s8; - typedef signed short s16; - - // get bitfield, bitfield(input, position, len) - template T bitfield(T in, u8 pos, u8 len = 1) - { - return (in >> pos) & (len ? (T(1 << len) - 1) : 1); - } -}; - -class vrcvi_intf -{ -public: - virtual void irq_w(bool irq) { } -}; - -using namespace vrcvi; -class vrcvi_core -{ -public: - friend class vrcvi_intf; - // constructor - vrcvi_core(vrcvi_intf &intf) - : m_pulse{*this,*this} - , m_sawtooth(*this) - , m_timer(*this) - , m_intf(intf) - { - } - // accessors, getters, setters - void pulse_w(u8 voice, u8 address, u8 data); - void saw_w(u8 address, u8 data); - void timer_w(u8 address, u8 data); - void control_w(u8 data); - - // internal state - void reset(); - void tick(); - - // 6 bit output - s8 out() { return m_out; } - // channel output - s16 chan_out(u8 ch) { return m_ch_out[ch]; } -private: - // Common ALU for sound channels - struct alu_t - { - alu_t(vrcvi_core &host) - : m_host(host) - { }; - - - virtual void reset(); - virtual bool tick(); - - struct divider_t - { - divider_t() - : m_divider(0) - , m_enable(0) - { }; - - void reset() - { - m_divider = 0; - m_enable = 0; - } - - void write(bool msb, u8 data); - - u16 m_divider : 12; // divider (pitch) - u16 m_enable : 1; // channel enable flag - }; - - vrcvi_core &m_host; - divider_t m_divider; - u16 m_counter = 0; // clock counter - u8 m_cycle = 0; // clock cycle - }; - - // 2 Pulse channels - struct pulse_t : alu_t - { - pulse_t(vrcvi_core &host) - : alu_t(host) - { }; - - virtual void reset() override; - virtual bool tick() override; - - // Control bits - struct pulse_control_t - { - pulse_control_t() - : m_mode(0) - , m_duty(0) - , m_volume(0) - { }; - - void reset() - { - m_mode = 0; - m_duty = 0; - m_volume = 0; - } - - u8 m_mode : 1; // duty toggle flag - u8 m_duty : 3; // 3 bit duty cycle - u8 m_volume : 4; // 4 bit volume - }; - - pulse_control_t m_control; - }; - - // 1 Sawtooth channel - struct sawtooth_t : alu_t - { - sawtooth_t(vrcvi_core &host) - : alu_t(host) - { }; - - virtual void reset() override; - virtual bool tick() override; - - u8 m_rate = 0; // sawtooth accumulate rate - u8 m_accum = 0; // sawtooth accumulator, high 5 bit is accumulated to output - }; - - // Internal timer - struct timer_t - { - timer_t(vrcvi_core &host) - : m_host(host) - { }; - - void reset(); - bool tick(); - void counter_tick(); - - // IRQ update - void update() { m_host.m_intf.irq_w(m_timer_control.m_irq_trigger); } - void irq_set() - { - if (!m_timer_control.m_irq_trigger) - { - m_timer_control.m_irq_trigger = 1; - update(); - } - } - void irq_clear() - { - if (m_timer_control.m_irq_trigger) - { - m_timer_control.m_irq_trigger = 0; - update(); - } - } - - // Control bits - struct timer_control_t - { - timer_control_t() - : m_irq_trigger(0) - , m_enable_ack(0) - , m_enable(0) - , m_sync(0) - { }; - - void reset() - { - m_irq_trigger = 0; - m_enable_ack = 0; - m_enable = 0; - m_sync = 0; - } - - u8 m_irq_trigger : 1; - u8 m_enable_ack : 1; - u8 m_enable : 1; - u8 m_sync : 1; - }; - - vrcvi_core &m_host; // host core - timer_control_t m_timer_control; // timer control bits - s16 m_prescaler = 341; // prescaler - u8 m_counter = 0; // clock counter - u8 m_counter_latch = 0; // clock counter latch - }; - - struct global_control_t - { - global_control_t() - : m_halt(0) - , m_shift(0) - { }; - - void reset() - { - m_halt = 0; - m_shift = 0; - } - - u8 m_halt : 1; // halt sound - u8 m_shift : 2; // 4/8 bit right shift - }; - - pulse_t m_pulse[2]; // 2 pulse channels - sawtooth_t m_sawtooth; // sawtooth channel - timer_t m_timer; // internal timer - global_control_t m_control; // control - - vrcvi_intf &m_intf; - - s8 m_out = 0; // 6 bit output - s8 m_ch_out[3] = {0}; // per-channel output -}; - -#endif diff --git a/src/engine/platform/sound/x1_010/x1_010.cpp b/src/engine/platform/sound/x1_010/x1_010.cpp deleted file mode 100644 index c047b854a..000000000 --- a/src/engine/platform/sound/x1_010/x1_010.cpp +++ /dev/null @@ -1,225 +0,0 @@ -/* - License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details - - Copyright holder(s): cam900 - Modifiers and Contributors for Furnace: cam900, tildearrow - Seta/Allumer X1-010 Emulation core - - the chip has 16 voices, all voices can be switchable to Wavetable or PCM sample playback mode. - It has also 2 output channels, but no known hardware using this feature for stereo sound. - - Wavetable needs to paired with envelope, it's always enabled and similar as AY PSG's one - but its shape is stored at RAM. - - PCM volume is stored by each register. - - Both volume is 4bit per output. - - Everything except PCM sample is stored at paired 8 bit RAM. - - RAM layout (common case: Address bit 12 is swapped when RAM is shared with CPU) - - ----------------------------- - 0000...007f Voice Registers - - 0000...0007 Voice 0 Register - - Address Bits Description - 7654 3210 - 0 x--- ---- Frequency divider* - ---- -x-- Envelope one-shot mode - ---- --x- Sound format - ---- --0- PCM - ---- --1- Wavetable - ---- ---x Keyon/off - PCM case: - 1 xxxx xxxx Volume (Each nibble is for each output) - - 2 xxxx xxxx Frequency* - - 4 xxxx xxxx Start address / 4096 - - 5 xxxx xxxx 0x100 - (End address / 4096) - Wavetable case: - 1 ---x xxxx Wavetable data select - - 2 xxxx xxxx Frequency LSB* - 3 xxxx xxxx "" MSB - - 4 xxxx xxxx Envelope period (.10 fixed point, Low 8 bit) - - 5 ---x xxxx Envelope shape select (!= 0 : Reserved for Voice registers) - - 0008...000f Voice 1 Register - ... - 0078...007f Voice 15 Register - ----------------------------- - 0080...0fff Envelope shape data (Same as volume; Each nibble is for each output) - - 0080...00ff Envelope shape data 1 - 0100...017f Envelope shape data 2 - ... - 0f80...0fff Envelope shape data 31 - ----------------------------- - 1000...1fff Wavetable data - - 1000...107f Wavetable data 0 - 1080...10ff Wavetable data 1 - ... - 1f80...1fff Wavetable data 31 - ----------------------------- - - * Frequency is 4.4 fixed point for PCM, - 6.10 for Wavetable. - Frequency divider is higher precision or just right shift? - needs verification. -*/ - -#include "x1_010.hpp" - -void x1_010_core::tick() -{ - // reset output - m_out[0] = m_out[1] = 0; - for (int i = 0; i < 16; i++) - { - voice_t &v = m_voice[i]; - v.tick(); - m_out[0] += v.data * v.vol_out[0]; - m_out[1] += v.data * v.vol_out[1]; - } -} - -void x1_010_core::voice_t::tick() -{ - data = vol_out[0] = vol_out[1] = 0; - if (flag.keyon) - { - if (flag.wavetable) // Wavetable - { - // envelope, each nibble is for each output - u8 vol = m_host.m_envelope[(bitfield(end_envshape, 0, 5) << 7) | bitfield(env_acc, 10, 7)]; - vol_out[0] = bitfield(vol, 4, 4); - vol_out[1] = bitfield(vol, 0, 4); - env_acc += start_envfreq; - if (flag.env_oneshot && bitfield(env_acc, 17)) - flag.keyon = false; - else - env_acc = bitfield(env_acc, 0, 17); - // get wavetable data - data = m_host.m_wave[(bitfield(vol_wave, 0, 5) << 7) | bitfield(acc, 10, 7)]; - acc = bitfield(acc + (freq >> flag.div), 0, 17); - } - else // PCM sample - { - // volume register, each nibble is for each output - vol_out[0] = bitfield(vol_wave, 4, 4); - vol_out[1] = bitfield(vol_wave, 0, 4); - // get PCM sample - data = m_host.m_intf.read_byte(bitfield(acc, 4, 20)); - acc += bitfield(freq, 0, 8) >> flag.div; - if ((acc >> 16) > (0xff ^ end_envshape)) - flag.keyon = false; - } - } -} - -u8 x1_010_core::ram_r(u16 offset) -{ - if (offset & 0x1000) // wavetable data - return m_wave[offset & 0xfff]; - else if (offset & 0xf80) // envelope shape data - return m_envelope[offset & 0xfff]; - else // channel register - return m_voice[bitfield(offset, 3, 4)].reg_r(offset & 0x7); -} - -void x1_010_core::ram_w(u16 offset, u8 data) -{ - if (offset & 0x1000) // wavetable data - m_wave[offset & 0xfff] = data; - else if (offset & 0xf80) // envelope shape data - m_envelope[offset & 0xfff] = data; - else // channel register - m_voice[bitfield(offset, 3, 4)].reg_w(offset & 0x7, data); -} - -u8 x1_010_core::voice_t::reg_r(u8 offset) -{ - switch (offset & 0x7) - { - case 0x00: return (flag.div << 7) - | (flag.env_oneshot << 2) - | (flag.wavetable << 1) - | (flag.keyon << 0); - case 0x01: return vol_wave; - case 0x02: return bitfield(freq, 0, 8); - case 0x03: return bitfield(freq, 8, 8); - case 0x04: return start_envfreq; - case 0x05: return end_envshape; - default: break; - } - return 0; -} - -void x1_010_core::voice_t::reg_w(u8 offset, u8 data) -{ - switch (offset & 0x7) - { - case 0x00: - { - const bool prev_keyon = flag.keyon; - flag.div = bitfield(data, 7); - flag.env_oneshot = bitfield(data, 2); - flag.wavetable = bitfield(data, 1); - flag.keyon = bitfield(data, 0); - if (!prev_keyon && flag.keyon) // Key on - { - acc = flag.wavetable ? 0 : (u32(start_envfreq) << 16); - env_acc = 0; - } - break; - } - case 0x01: - vol_wave = data; - break; - case 0x02: - freq = (freq & 0xff00) | data; - break; - case 0x03: - freq = (freq & 0x00ff) | (u16(data) << 8); - break; - case 0x04: - start_envfreq = data; - break; - case 0x05: - end_envshape = data; - break; - default: - break; - } -} - -void x1_010_core::voice_t::reset() -{ - flag.reset(); - vol_wave = 0; - freq = 0; - start_envfreq = 0; - end_envshape = 0; - acc = 0; - env_acc = 0; - data = 0; - vol_out[0] = vol_out[1] = 0; -} - -void x1_010_core::reset() -{ - for (auto & elem : m_voice) - elem.reset(); - - std::fill_n(&m_envelope[0], 0x1000, 0); - std::fill_n(&m_wave[0], 0x1000, 0); - m_out[0] = m_out[1] = 0; -} diff --git a/src/engine/platform/sound/x1_010/x1_010.hpp b/src/engine/platform/sound/x1_010/x1_010.hpp deleted file mode 100644 index 3f5d9d4e2..000000000 --- a/src/engine/platform/sound/x1_010/x1_010.hpp +++ /dev/null @@ -1,134 +0,0 @@ -/* - License: BSD-3-Clause - see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details - - Copyright holder(s): cam900 - Modifiers and Contributors for Furnace: cam900, tildearrow - Seta/Allumer X1-010 Emulation core - - See x1_010.cpp for more info. -*/ - -#include -#include - -#ifndef _VGSOUND_EMU_X1_010_HPP -#define _VGSOUND_EMU_X1_010_HPP - -#pragma once - -namespace x1_010 -{ - typedef unsigned char u8; - typedef unsigned short u16; - typedef unsigned int u32; - typedef signed char s8; - typedef signed int s32; - - template T bitfield(T in, u8 pos, u8 len = 1) - { - return (in >> pos) & (len ? (T(1 << len) - 1) : 1); - } -} - -using namespace x1_010; -class x1_010_mem_intf -{ -public: - virtual u8 read_byte(u32 address) { return 0; } -}; - -using namespace x1_010; -class x1_010_core -{ - friend class x1_010_mem_intf; -public: - // constructor - x1_010_core(x1_010_mem_intf &intf) - : m_voice{*this,*this,*this,*this, - *this,*this,*this,*this, - *this,*this,*this,*this, - *this,*this,*this,*this} - , m_intf(intf) - { - m_envelope = std::make_unique(0x1000); - m_wave = std::make_unique(0x1000); - - std::fill_n(&m_envelope[0], 0x1000, 0); - std::fill_n(&m_wave[0], 0x1000, 0); - } - - // register accessor - u8 ram_r(u16 offset); - void ram_w(u16 offset, u8 data); - - // getters - s32 output(u8 channel) { return m_out[channel & 1]; } - s32 chan_out(u8 channel) { return (m_voice[channel].data * (m_voice[channel].vol_out[0]+m_voice[channel].vol_out[1]))<<2; } - - // internal state - void reset(); - void tick(); - -private: - // 16 voices in chip - struct voice_t - { - // constructor - voice_t(x1_010_core &host) : m_host(host) {} - - // internal state - void reset(); - void tick(); - - // register accessor - u8 reg_r(u8 offset); - void reg_w(u8 offset, u8 data); - - // registers - x1_010_core &m_host; - struct flag_t - { - u8 div : 1; - u8 env_oneshot : 1; - u8 wavetable : 1; - u8 keyon : 1; - void reset() - { - div = 0; - env_oneshot = 0; - wavetable = 0; - keyon = 0; - } - flag_t() - : div(0) - , env_oneshot(0) - , wavetable(0) - , keyon(0) - { } - }; - flag_t flag; - u8 vol_wave = 0; - u16 freq = 0; - u8 start_envfreq = 0; - u8 end_envshape = 0; - - // internal registers - u32 acc = 0; - u32 env_acc = 0; - s8 data = 0; - u8 vol_out[2] = {0}; - }; - voice_t m_voice[16]; - - // RAM - std::unique_ptr m_envelope = nullptr; - std::unique_ptr m_wave = nullptr; - - // output data - s32 m_out[2] = {0}; - - x1_010_mem_intf &m_intf; -}; - -#endif diff --git a/src/engine/platform/sound/ymfm/ymfm_opz.cpp b/src/engine/platform/sound/ymfm/ymfm_opz.cpp index 94123bf62..37c6a5fce 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 e62c4ed84..e36b424f4 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 "1Exx: Set phase reset period low byte"; - break; - case 0x1f: - return "1Fxx: 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->isLoopable()?sample->loopEnd:sample->samples); + unsigned int sampleEnd=sample->offSU+(sample->getLoopEndPosition()); unsigned int off=sample->offSU+chan[i].hasOffset; chan[i].hasOffset=0; if (sampleEnd>=getSampleMemCapacity(0)) sampleEnd=getSampleMemCapacity(0)-1; @@ -222,7 +172,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) { chWrite(i,0x0c,sampleEnd&0xff); chWrite(i,0x0d,sampleEnd>>8); if (sample->isLoopable()) { - unsigned int sampleLoop=sample->offSU+sample->loopStart; + unsigned int sampleLoop=sample->offSU+sample->getLoopStartPosition(); if (sampleLoop>=getSampleMemCapacity(0)) sampleLoop=getSampleMemCapacity(0)-1; chWrite(i,0x0e,sampleLoop&0xff); chWrite(i,0x0f,sampleLoop>>8); @@ -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->amiga.useSample)) { + chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->amiga.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->amiga.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,7 +551,8 @@ void DivPlatformSoundUnit::renderSamples() { size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; - int paddedLen=s->samples; + if (s->data8==NULL) continue; + int paddedLen=s->length8; if (memPos>=getSampleMemCapacity(0)) { logW("out of PCM memory for sample %d!",i); break; @@ -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 1d39854f2..d94df8eac 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,9 +109,11 @@ 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); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: void acquire(short* bufL, short* bufR, size_t start, size_t len); @@ -127,7 +134,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 0f9397e1b..9db46d14e 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 (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || dacPos>=s->samples) { - if (s->isLoopable()) { - dacPos=s->loopStart; - } else { - dacSample=-1; - } + if (s->isLoopable() && dacPos>=(unsigned int)s->loopEnd) { + 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()) { @@ -253,7 +222,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SWAN); if (c.chan==1) { - if (ins->type==DIV_INS_AMIGA) { + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { pcm=true; } else if (furnaceDac) { pcm=false; @@ -262,7 +231,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { if (skipRegisterWrites) break; dacPos=0; dacPeriod=0; - if (ins->type==DIV_INS_AMIGA) { + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { dacSample=ins->amiga.getSample(c.value); if (dacSample<0 || dacSample>=parent->song.sampleLen) { dacSample=-1; @@ -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 aafb17ace..473667a93 100644 --- a/src/engine/platform/swan.h +++ b/src/engine/platform/swan.h @@ -74,6 +74,7 @@ class DivPlatformSwan: public DivDispatch { std::queue writes; WSwan* ws; void updateWave(int ch); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: void acquire(short* bufL, short* bufR, size_t start, size_t len); @@ -93,7 +94,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 da3472446..796c94481 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 cabe91533..3eb32b97a 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,8 +42,11 @@ 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 putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); unsigned char dealWithFreq(unsigned char shape, int base, int pitch); @@ -61,13 +64,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 123d1193c..fa29cda60 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -55,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]; @@ -245,7 +112,7 @@ void DivPlatformTX81Z::tick(bool sysTick) { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -256,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) { @@ -321,7 +179,7 @@ void DivPlatformTX81Z::tick(bool sysTick) { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -374,7 +232,7 @@ void DivPlatformTX81Z::tick(bool sysTick) { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -466,7 +324,7 @@ void DivPlatformTX81Z::muteChannel(int ch, bool mute) { if (isMuted[ch]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[ch].state.alg][i]) { + if (KVS(ch,i)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[ch].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -495,7 +353,7 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { if (isMuted[c.chan]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[c.chan].state.alg][i]) { + if (KVS(c.chan,i)) { if (!chan[c.chan].active || chan[c.chan].insChanged) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } @@ -561,7 +419,7 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { if (isMuted[c.chan]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[c.chan].state.alg][i]) { + if (KVS(c.chan,i)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -661,7 +519,7 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { if (isMuted[c.chan]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[c.chan].state.alg][c.value]) { + if (KVS(c.chan,c.value)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -933,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: @@ -952,7 +811,7 @@ void DivPlatformTX81Z::forceIns() { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -1047,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 e867416cf..b1d09db90 100644 --- a/src/engine/platform/tx81z.h +++ b/src/engine/platform/tx81z.h @@ -88,6 +88,7 @@ class DivPlatformTX81Z: public DivPlatformOPM { int octave(int freq); int toFreq(int freq); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: @@ -108,7 +109,6 @@ class DivPlatformTX81Z: public DivPlatformOPM { 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 aa7810256..d0e9e00f1 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -27,7 +27,9 @@ extern "C" { #include "sound/vera_pcm.h" } -#define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d); psg_writereg(psg,((c)*4+(a)),(d));} +//if (dumpWrites) {addWrite(((c)*4+(a)),(d));} +//#define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d); psg_writereg(psg,((c)*4+(a)),(d));} +#define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d); psg_writereg(psg,((c)*4+(a)),(d));if (dumpWrites) {addWrite(((c)*4+(a)),(d));}} #define rWriteLo(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0x3f))|((d)&0x3f)) #define rWriteHi(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0xc0))|(((d)<<6)&0xc0)) #define rWritePCMCtrl(d) {regPool[64]=(d); pcm_write_ctrl(pcm,d);} @@ -51,18 +53,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 +86,11 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len rWritePCMData(tmp_r&0xff); } chan[16].pcm.pos++; - if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[16].pcm.pos>=s->loopEnd) || (chan[16].pcm.pos>=s->samples)) { - if (s->isLoopable()) { - chan[16].pcm.pos=s->loopStart; - } else { - chan[16].pcm.sample=-1; - break; - } + if (s->isLoopable() && chan[16].pcm.pos>=(unsigned int)s->loopEnd) { + chan[16].pcm.pos=s->loopStart; + } else if (chan[16].pcm.pos>=s->samples) { + chan[16].pcm.sample=-1; + break; } } } else { @@ -173,18 +161,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 +202,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; @@ -359,6 +329,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 612b4354b..9cda01203 100644 --- a/src/engine/platform/vera.h +++ b/src/engine/platform/vera.h @@ -60,6 +60,7 @@ class DivPlatformVERA: public DivDispatch { struct VERA_PCM* pcm; int calcNoteFreq(int ch, int note); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: @@ -79,7 +80,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 8475b0e53..93c119484 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) { @@ -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 d23f27be8..834051ddb 100644 --- a/src/engine/platform/vic20.h +++ b/src/engine/platform/vic20.h @@ -63,6 +63,7 @@ class DivPlatformVIC20: public DivDispatch { unsigned char regPool[16]; sound_vic20_t* vic; void updateWave(int ch); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: void acquire(short* bufL, short* bufR, size_t start, size_t len); @@ -82,7 +83,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 ad1cdaa75..1fb7cbb18 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; iloopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[i].dacPos>=s->loopEnd) || (chan[i].dacPos>=s->samples)) { - if (s->isLoopable()) { - chan[i].dacPos=s->loopStart; - } else { - chan[i].dacSample=-1; - chWrite(i,0,0); - } + if (s->isLoopable() && chan[i].dacPos>=(unsigned int)s->loopEnd) { + chan[i].dacPos=s->loopStart; + } else if (chan[i].dacPos>=s->samples) { + chan[i].dacSample=-1; + chWrite(i,0,0); } chan[i].dacPeriod-=rate; } @@ -100,9 +86,10 @@ void DivPlatformVRC6::acquire(short* bufL, short* bufR, size_t start, size_t len // Oscilloscope buffer part if (++writeOscBuf>=32) { writeOscBuf=0; - for (int i=0; i<3; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=vrc6.chan_out(i)<<10; + for (int i=0; i<2; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=vrc6.pulse_out(i)<<10; } + oscBuf[2]->data[oscBuf[2]->needle++]=vrc6.sawtooth_out()<<10; } // Command part @@ -167,18 +154,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; @@ -195,6 +173,25 @@ void DivPlatformVRC6::tick(bool sysTick) { } chan[i].freqChanged=true; } + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val && chan[i].active) { + if ((i!=2) && (!chan[i].pcm)) { + if (dumpWrites) addWrite(0xffff0002+(i<<8),0); + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_VRC6); + chan[i].dacSample=ins->amiga.getSample(chan[i].note); + if (chan[i].dacSample<0 || chan[i].dacSample>=parent->song.sampleLen) { + if (dumpWrites) { + chWrite(i,2,0x80); + chWrite(i,0,isMuted[i]?0:0x80); + addWrite(0xffff0000+(i<<8),chan[i].dacSample); + } + chan[i].dacPos=0; + chan[i].dacPeriod=0; + chan[i].keyOn=true; + } + } + } + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (i==2) { // sawtooth chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,14)-1; @@ -218,7 +215,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)); } @@ -235,14 +232,14 @@ int DivPlatformVRC6::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: if (c.chan!=2) { // pulse wave DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_VRC6); - if (ins->type==DIV_INS_AMIGA) { + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { chan[c.chan].pcm=true; } else if (chan[c.chan].furnaceDac) { chan[c.chan].pcm=false; } if (chan[c.chan].pcm) { if (skipRegisterWrites) break; - if (ins->type==DIV_INS_AMIGA) { + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { chan[c.chan].dacSample=ins->amiga.getSample(c.value); if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) { chan[c.chan].dacSample=-1; @@ -399,6 +396,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 450e09b98..4c56bc797 100644 --- a/src/engine/platform/vrc6.h +++ b/src/engine/platform/vrc6.h @@ -23,10 +23,10 @@ #include #include "../dispatch.h" #include "../macroInt.h" -#include "sound/vrcvi/vrcvi.hpp" +#include "vgsound_emu/src/vrcvi/vrcvi.hpp" -class DivPlatformVRC6: public DivDispatch { +class DivPlatformVRC6: public DivDispatch, public vrcvi_intf { struct Channel { int freq, baseFreq, pitch, pitch2, note; int dacPeriod, dacRate, dacOut; @@ -75,10 +75,10 @@ class DivPlatformVRC6: public DivDispatch { std::queue writes; unsigned char sampleBank; unsigned char writeOscBuf; - vrcvi_intf intf; vrcvi_core vrc6; unsigned char regPool[13]; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: @@ -99,10 +99,9 @@ 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) {}; + DivPlatformVRC6() : vrc6(*this) {}; ~DivPlatformVRC6(); }; diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index dc5c7ed22..1c04267bd 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -23,9 +23,9 @@ #include //#define rWrite(a,v) pendingWrites[a]=v; -#define rWrite(a,v) if (!skipRegisterWrites) { x1_010->ram_w(a,v); if (dumpWrites) { addWrite(a,v); } } +#define rWrite(a,v) if (!skipRegisterWrites) { x1_010.ram_w(a,v); if (dumpWrites) { addWrite(a,v); } } -#define chRead(c,a) x1_010->ram_r((c<<3)|(a&7)) +#define chRead(c,a) x1_010.ram_r((c<<3)|(a&7)) #define chWrite(c,a,v) rWrite((c<<3)|(a&7),v) #define waveWrite(c,a,v) rWrite(0x1000|(chan[c].waveBank<<11)|(c<<7)|(a&0x7f),(v-128)&0xff) #define envFill(c,a) rWrite(0x800|(c<<7)|(a&0x7f),(chan[c].lvol<<4)|chan[c].rvol) @@ -205,45 +205,12 @@ 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(); + x1_010.tick(); - signed int tempL=x1_010->output(0); - signed int tempR=x1_010->output(1); + signed int tempL=x1_010.output(0); + signed int tempR=x1_010.output(1); if (tempL<-32768) tempL=-32768; if (tempL>32767) tempL=32767; @@ -255,11 +222,23 @@ void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t l bufR[h]=stereo?tempR:bufL[h]; for (int i=0; i<16; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=x1_010->chan_out(i); + oscBuf[i]->data[oscBuf[i]->needle++]=(x1_010.voice_out(i,0)+x1_010.voice_out(i,1))>>1; } } } +u8 DivPlatformX1_010::read_byte(u32 address) { + if ((sampleMem!=NULL) && (address>17)&7]<<17)|(address&0x1ffff))&0xffffff; + } else { + address&=0xfffff; + } + return sampleMem[address]; + } + return 0; +} + double DivPlatformX1_010::NoteX1_010(int ch, int note) { if (chan[ch].pcm) { // PCM note double off=8192.0; @@ -345,7 +324,7 @@ void DivPlatformX1_010::tick(bool sysTick) { for (int i=0; i<16; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { - signed char macroVol=((chan[i].vol&15)*MIN(chan[i].furnacePCM?64:15,chan[i].std.vol.val))/(chan[i].furnacePCM?64:15); + signed char macroVol=((chan[i].vol&15)*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/(chan[i].macroVolMul); if ((!isMuted[i]) && (macroVol!=chan[i].outVol)) { chan[i].outVol=macroVol; chan[i].envChanged=true; @@ -354,18 +333,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) { @@ -475,6 +445,12 @@ void DivPlatformX1_010::tick(bool sysTick) { if (!chan[i].std.ex3.will) chan[i].autoEnvNum=1; } } + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val && chan[i].active && chan[i].pcm) { + chWrite(i,0,0); + refreshControl(i); + } + } if (chan[i].active) { if (chan[i].ws.tick()) { updateWave(i); @@ -549,8 +525,9 @@ int DivPlatformX1_010::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: { chWrite(c.chan,0,0); // reset previous note DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_X1_010); - if ((ins->type==DIV_INS_AMIGA) || chan[c.chan].pcm) { - if (ins->type==DIV_INS_AMIGA) { + chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:15; + if ((ins->type==DIV_INS_AMIGA || ins->amiga.useSample) || chan[c.chan].pcm) { + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { chan[c.chan].furnacePCM=true; } else { chan[c.chan].furnacePCM=false; @@ -562,9 +539,18 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].sample=ins->amiga.getSample(c.value); if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); - chWrite(c.chan,4,(s->offX1_010>>12)&0xff); - int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded - chWrite(c.chan,5,(0x100-(end>>12))&0xff); + if (isBanked) { + chan[c.chan].bankSlot=ins->x1_010.bankSlot; + bankSlot[chan[c.chan].bankSlot]=s->offX1_010>>17; + unsigned int bankedOffs=(chan[c.chan].bankSlot<<17)|(s->offX1_010&0x1ffff); + chWrite(c.chan,4,(bankedOffs>>12)&0xff); + int end=(bankedOffs+MIN(s->length8,0x1ffff)+0xfff)&~0xfff; // padded + chWrite(c.chan,5,(0x100-(end>>12))&0xff); + } else { + chWrite(c.chan,4,(s->offX1_010>>12)&0xff); + int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded + chWrite(c.chan,5,(0x100-(end>>12))&0xff); + } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note); @@ -594,9 +580,17 @@ int DivPlatformX1_010::dispatch(DivCommand c) { break; } DivSample* s=parent->getSample(12*sampleBank+c.value%12); - chWrite(c.chan,4,(s->offX1_010>>12)&0xff); - int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded - chWrite(c.chan,5,(0x100-(end>>12))&0xff); + if (isBanked) { + bankSlot[chan[c.chan].bankSlot]=s->offX1_010>>17; + unsigned int bankedOffs=(chan[c.chan].bankSlot<<17)|(s->offX1_010&0x1ffff); + chWrite(c.chan,4,(bankedOffs>>12)&0xff); + int end=(bankedOffs+MIN(s->length8,0x1ffff)+0xfff)&~0xfff; // padded + chWrite(c.chan,5,(0x100-(end>>12))&0xff); + } else { + chWrite(c.chan,4,(s->offX1_010>>12)&0xff); + int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded + chWrite(c.chan,5,(0x100-(end>>12))&0xff); + } chan[c.chan].baseFreq=(((unsigned int)s->rate)<<4)/(chipClock/512); chan[c.chan].freqChanged=true; } @@ -732,6 +726,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: @@ -812,6 +807,9 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].autoEnvDen=c.value&15; chan[c.chan].freqChanged=true; break; + case DIV_CMD_X1_010_SAMPLE_BANK_SLOT: + chan[c.chan].bankSlot=c.value&7; + break; case DIV_CMD_GET_VOLMAX: return 15; break; @@ -852,7 +850,7 @@ DivDispatchOscBuffer* DivPlatformX1_010::getOscBuffer(int ch) { unsigned char* DivPlatformX1_010::getRegisterPool() { for (int i=0; i<0x2000; i++) { - regPool[i]=x1_010->ram_r(i); + regPool[i]=x1_010.ram_r(i); } return regPool; } @@ -870,12 +868,16 @@ void DivPlatformX1_010::reset() { chan[i].ws.setEngine(parent); chan[i].ws.init(NULL,128,255,false); } - x1_010->reset(); + x1_010.reset(); sampleBank=0; // set per-channel initial panning for (int i=0; i<16; i++) { chWrite(i,0,0); } + // set initial bank + for (int b=0; b<8; b++) { + bankSlot[b]=b; + } } bool DivPlatformX1_010::isStereo() { @@ -930,15 +932,15 @@ void DivPlatformX1_010::poke(std::vector& wlist) { } const void* DivPlatformX1_010::getSampleMem(int index) { - return index == 0 ? sampleMem : 0; + return index >= 0 ? sampleMem : 0; } size_t DivPlatformX1_010::getSampleMemCapacity(int index) { - return index == 0 ? 1048576 : 0; + return index == 0 ? (isBanked?16777216:1048576):0; } size_t DivPlatformX1_010::getSampleMemUsage(int index) { - return index == 0 ? sampleMemLen : 0; + return index >= 0 ? sampleMemLen : 0; } void DivPlatformX1_010::renderSamples() { @@ -948,12 +950,14 @@ void DivPlatformX1_010::renderSamples() { for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; int paddedLen=(s->length8+4095)&(~0xfff); + if (isBanked) { // fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!) - if (paddedLen>131072) { - paddedLen=131072; - } - if ((memPos&0xfe0000)!=((memPos+paddedLen)&0xfe0000)) { - memPos=(memPos+0x1ffff)&0xfe0000; + if (paddedLen>131072) { + paddedLen=131072; + } + if ((memPos&0xfe0000)!=((memPos+paddedLen)&0xfe0000)) { + memPos=(memPos+0x1ffff)&0xfe0000; + } } if (memPos>=getSampleMemCapacity()) { logW("out of X1-010 memory for sample %d!",i); @@ -971,6 +975,10 @@ void DivPlatformX1_010::renderSamples() { sampleMemLen=memPos+256; } +void DivPlatformX1_010::setBanked(bool banked) { + isBanked=banked; +} + int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; @@ -983,9 +991,7 @@ int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned in setFlags(flags); sampleMem=new unsigned char[getSampleMemCapacity()]; sampleMemLen=0; - intf.memory=sampleMem; - x1_010=new x1_010_core(intf); - x1_010->reset(); + x1_010.reset(); reset(); return 16; } @@ -994,7 +1000,6 @@ void DivPlatformX1_010::quit() { for (int i=0; i<16; i++) { delete oscBuf[i]; } - delete x1_010; delete[] sampleMem; } diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index 7a85b6336..19fb91350 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -24,20 +24,9 @@ #include "../engine.h" #include "../macroInt.h" #include "../waveSynth.h" -#include "sound/x1_010/x1_010.hpp" +#include "vgsound_emu/src/x1_010/x1_010.hpp" -class DivX1_010Interface: public x1_010_mem_intf { - public: - unsigned char* memory; - int sampleBank; - virtual u8 read_byte(u32 address) override { - if (memory==NULL) return 0; - return memory[address & 0xfffff]; - } - DivX1_010Interface(): memory(NULL), sampleBank(0) {} -}; - -class DivPlatformX1_010: public DivDispatch { +class DivPlatformX1_010: public DivDispatch, public vgsound_emu_mem_intf { struct Channel { struct Envelope { struct EnvFlag { @@ -84,7 +73,9 @@ class DivPlatformX1_010: public DivDispatch { unsigned char pan, autoEnvNum, autoEnvDen; bool active, insChanged, envChanged, freqChanged, keyOn, keyOff, inPorta, furnacePCM, pcm; int vol, outVol, lvol, rvol; + int macroVolMul; unsigned char waveBank; + unsigned int bankSlot; Envelope env; DivMacroInt std; DivWaveSynth ws; @@ -109,7 +100,9 @@ class DivPlatformX1_010: public DivDispatch { pan(255), autoEnvNum(0), autoEnvDen(0), active(false), insChanged(true), envChanged(true), freqChanged(false), keyOn(false), keyOff(false), inPorta(false), furnacePCM(false), pcm(false), vol(15), outVol(15), lvol(15), rvol(15), - waveBank(0) {} + macroVolMul(15), + waveBank(0), + bankSlot(0) {} }; Channel chan[16]; DivDispatchOscBuffer* oscBuf[16]; @@ -118,14 +111,19 @@ class DivPlatformX1_010: public DivDispatch { unsigned char* sampleMem; size_t sampleMemLen; unsigned char sampleBank; - DivX1_010Interface intf; - x1_010_core* x1_010; + x1_010_core x1_010; + + bool isBanked=false; + unsigned int bankSlot[8]; + unsigned char regPool[0x2000]; double NoteX1_010(int ch, int note); void updateWave(int ch); void updateEnvelope(int ch); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: + u8 read_byte(u32 address); void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); @@ -149,9 +147,13 @@ class DivPlatformX1_010: public DivDispatch { size_t getSampleMemUsage(int index = 0); void renderSamples(); const char** getRegisterSheet(); - const char* getEffectName(unsigned char effect); + void setBanked(bool banked); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); + DivPlatformX1_010(): + DivDispatch(), + vgsound_emu_mem_intf(), + x1_010(*this) {} ~DivPlatformX1_010(); }; diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 83c93c58e..13fc014d8 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -156,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; @@ -337,7 +220,7 @@ void DivPlatformYM2203::tick(bool sysTick) { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -348,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) { @@ -387,7 +261,7 @@ void DivPlatformYM2203::tick(bool sysTick) { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -399,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]; @@ -432,7 +310,7 @@ void DivPlatformYM2203::tick(bool sysTick) { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -511,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; } } @@ -535,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++) { @@ -543,7 +427,7 @@ int DivPlatformYM2203::dispatch(DivCommand c) { if (isMuted[c.chan]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[c.chan].state.alg][i]) { + if (KVS(c.chan,i)) { if (!chan[c.chan].active || chan[c.chan].insChanged) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } @@ -603,7 +487,7 @@ int DivPlatformYM2203::dispatch(DivCommand c) { if (isMuted[c.chan]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[c.chan].state.alg][i]) { + if (KVS(c.chan,i)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -688,7 +572,7 @@ int DivPlatformYM2203::dispatch(DivCommand c) { if (isMuted[c.chan]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[c.chan].state.alg][c.value]) { + if (KVS(c.chan,c.value)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -880,7 +764,7 @@ void DivPlatformYM2203::muteChannel(int ch, bool mute) { if (isMuted[ch]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[ch].state.alg][j]) { + if (KVS(ch,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[ch].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -897,7 +781,7 @@ void DivPlatformYM2203::forceIns() { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); diff --git a/src/engine/platform/ym2203.h b/src/engine/platform/ym2203.h index d406e2f28..7a4aa7975 100644 --- a/src/engine/platform/ym2203.h +++ b/src/engine/platform/ym2203.h @@ -43,9 +43,9 @@ class DivPlatformYM2203: public DivPlatformOPN { DivInstrumentFM state; unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; - unsigned char psgMode, autoEnvNum, autoEnvDen; + unsigned char psgMode, autoEnvNum, autoEnvDen, opMask; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged; int vol, outVol; int sample; DivMacroInt std; @@ -66,6 +66,7 @@ class DivPlatformYM2203: public DivPlatformOPN { psgMode(1), autoEnvNum(0), autoEnvDen(0), + opMask(15), active(false), insChanged(true), freqChanged(false), @@ -75,6 +76,7 @@ class DivPlatformYM2203: public DivPlatformOPN { inPorta(false), furnacePCM(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(15), sample(-1) {} @@ -92,6 +94,7 @@ class DivPlatformYM2203: public DivPlatformOPN { bool extMode; unsigned char prescale; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: @@ -114,7 +117,6 @@ class DivPlatformYM2203: public DivPlatformOPN { 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(); diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index 3ff24eb74..f3b279c97 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -59,10 +59,10 @@ 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)); - rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); } opChan[ch].insChanged=false; @@ -102,22 +102,6 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) { } opChan[ch].ins=c.value; break; - case DIV_CMD_PANNING: { - if (c.value==0 && c.value2==0) { - opChan[ch].pan=3; - } else { - opChan[ch].pan=(c.value2>0)|((c.value>0)<<1); - } - DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); - if (parent->song.sharedExtStat) { - for (int i=0; i<4; i++) { - if (ch==i) continue; - opChan[i].pan=opChan[ch].pan; - } - } - rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); - break; - } case DIV_CMD_PITCH: { opChan[ch].pitch=c.value; opChan[ch].freqChanged=true; @@ -358,7 +342,7 @@ void DivPlatformYM2203Ext::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -395,10 +379,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; } } @@ -439,7 +425,7 @@ void DivPlatformYM2203Ext::forceIns() { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); diff --git a/src/engine/platform/ym2203ext.h b/src/engine/platform/ym2203ext.h index 1a398d1a6..9bc460af4 100644 --- a/src/engine/platform/ym2203ext.h +++ b/src/engine/platform/ym2203ext.h @@ -27,15 +27,31 @@ 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) {} + 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), + mask(true), + vol(0) {} }; OpChannel opChan[4]; bool isOpMuted[4]; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: int dispatch(DivCommand c); diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index c3cf52a06..82a130ac0 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -279,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); @@ -478,14 +358,6 @@ void DivPlatformYM2608::acquire(short* bufL, short* bufR, size_t start, size_t l } void DivPlatformYM2608::tick(bool sysTick) { - // PSG - ay->tick(sysTick); - ay->flushWrites(); - for (DivRegWrite& i: ay->getRegisterWrites()) { - immWrite(i.addr&15,i.val); - } - ay->getRegisterWrites().clear(); - // FM for (int i=0; i<6; i++) { if (i==2 && extMode) continue; @@ -496,7 +368,7 @@ void DivPlatformYM2608::tick(bool sysTick) { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -506,18 +378,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) { @@ -550,7 +413,7 @@ void DivPlatformYM2608::tick(bool sysTick) { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -570,6 +433,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]; @@ -600,7 +467,7 @@ void DivPlatformYM2608::tick(bool sysTick) { } if (m.tl.had) { op.tl=127-m.tl.val; - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -647,47 +514,6 @@ void DivPlatformYM2608::tick(bool sysTick) { chan[i].keyOff=false; } } - // ADPCM-B - if (chan[15].furnacePCM) { - chan[15].std.next(); - - if (chan[15].std.vol.had) { - chan[15].outVol=(chan[15].vol*MIN(64,chan[15].std.vol.val))/64; - immWrite(0x10b,chan[15].outVol); - } - - 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].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) { - if (chan[15].sample>=0 && chan[15].samplesong.sampleLen) { - double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; - chan[15].freq=parent->calcFreq(chan[15].baseFreq,chan[15].pitch,false,4,chan[15].pitch2,(double)chipClock/144,off); - } else { - chan[15].freq=0; - } - immWrite(0x109,chan[15].freq&0xff); - immWrite(0x10a,(chan[15].freq>>8)&0xff); - chan[15].freqChanged=false; - } - - if (writeRSSOff) { - immWrite(0x10,0x80|writeRSSOff); - writeRSSOff=0; - } for (int i=16; i<512; i++) { if (pendingWrites[i]!=oldWrites[i]) { @@ -719,16 +545,118 @@ 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; } } + // RSS + for (int i=9; i<15; i++) { + if (chan[i].furnacePCM) { + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=(chan[i].vol*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul; + } + if (chan[i].std.duty.had) { + if (globalRSSVolume!=(chan[i].std.duty.val&0x3f)) { + globalRSSVolume=chan[i].std.duty.val&0x3f; + immWrite(0x11,globalRSSVolume); + } + } + if (chan[i].std.panL.had) { + chan[i].pan=chan[i].std.panL.val&3; + } + if (chan[i].std.phaseReset.had) { + if ((chan[i].std.phaseReset.val==1) && chan[i].active) { + chan[i].keyOn=true; + } + } + if (!isMuted[i] && (chan[i].std.vol.had || chan[i].std.panL.had)) { + immWrite(0x18+(i-9),isMuted[i]?0:((chan[i].pan<<6)|chan[i].outVol)); + } + } + if (chan[i].keyOff) { + writeRSSOff|=(1<<(i-9)); + chan[i].keyOff=false; + } + if (chan[i].keyOn) { + writeRSSOn|=(1<<(i-9)); + chan[i].keyOn=false; + } + } + // ADPCM-B + if (chan[15].furnacePCM) { + chan[15].std.next(); + + if (chan[15].std.vol.had) { + chan[15].outVol=(chan[15].vol*MIN(chan[15].macroVolMul,chan[15].std.vol.val))/chan[15].macroVolMul; + immWrite(0x10b,chan[15].outVol); + } + + if (chan[15].std.arp.had) { + if (!chan[15].inPorta) { + chan[15].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[15].note,chan[15].std.arp.val)); + } + chan[15].freqChanged=true; + } + if (chan[15].std.panL.had) { + if (chan[15].pan!=(chan[15].std.panL.val&3)) { + chan[15].pan=chan[15].std.panL.val&3; + if (!isMuted[15]) { + immWrite(0x101,(isMuted[15]?0:(chan[15].pan<<6))|2); + } + } + } + if (chan[15].std.phaseReset.had) { + if ((chan[15].std.phaseReset.val==1) && chan[15].active) { + chan[15].keyOn=true; + } + } + } + if (chan[15].freqChanged || chan[15].keyOn || chan[15].keyOff) { + if (chan[15].furnacePCM) { + if (chan[15].sample>=0 && chan[15].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; + chan[15].freq=parent->calcFreq(chan[15].baseFreq,chan[15].pitch,false,4,chan[15].pitch2,(double)chipClock/144,off); + } else { + chan[15].freq=0; + } + } + immWrite(0x109,chan[15].freq&0xff); + immWrite(0x10a,(chan[15].freq>>8)&0xff); + if (chan[15].keyOn || chan[15].keyOff) { + immWrite(0x100,0x01); // reset + if (chan[15].active && chan[15].keyOn && !chan[15].keyOff) { + if (chan[15].sample>=0 && chan[15].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[15].sample); + immWrite(0x100,(s->isLoopable())?0xb0:0xa0); // start/repeat + } + } + chan[15].keyOn=false; + chan[15].keyOff=false; + } + chan[15].freqChanged=false; + } + + if (writeRSSOff) { + immWrite(0x10,0x80|writeRSSOff); + writeRSSOff=0; + } + if (writeRSSOn) { immWrite(0x10,writeRSSOn); writeRSSOn=0; } + + // PSG + ay->tick(sysTick); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); } int DivPlatformYM2608::dispatch(DivCommand c) { @@ -740,7 +668,8 @@ int DivPlatformYM2608::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: { if (c.chan>14) { // ADPCM-B DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); - if (ins->type==DIV_INS_AMIGA) { + chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:255; + if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_ADPCMB) { chan[c.chan].furnacePCM=true; } else { chan[c.chan].furnacePCM=false; @@ -761,7 +690,6 @@ 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->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); @@ -782,6 +710,24 @@ int DivPlatformYM2608::dispatch(DivCommand c) { chan[c.chan].macroInit(NULL); chan[c.chan].outVol=chan[c.chan].vol; if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + break; + } + chan[c.chan].sample=12*sampleBank+c.value%12; + if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[c.chan].sample); + immWrite(0x102,(s->offB>>5)&0xff); + immWrite(0x103,(s->offB>>13)&0xff); + int end=s->offB+s->lengthB-1; + immWrite(0x104,(end>>5)&0xff); + immWrite(0x105,(end>>13)&0xff); + immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); + int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0); + immWrite(0x109,freq&0xff); + immWrite(0x10a,(freq>>8)&0xff); + immWrite(0x10b,chan[c.chan].outVol); + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + } else { immWrite(0x100,0x01); // reset immWrite(0x102,0); immWrite(0x103,0); @@ -789,26 +735,31 @@ int DivPlatformYM2608::dispatch(DivCommand c) { immWrite(0x105,0); break; } - DivSample* s=parent->getSample(12*sampleBank+c.value%12); - immWrite(0x102,(s->offB>>5)&0xff); - immWrite(0x103,(s->offB>>13)&0xff); - int end=s->offB+s->lengthB-1; - 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->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); } break; } if (c.chan>8) { // RSS - if (skipRegisterWrites) break; - if (!isMuted[c.chan]) { - writeRSSOn|=(1<<(c.chan-9)); + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); + chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:31; + if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_ADPCMA) { + chan[c.chan].furnacePCM=true; + } else { + chan[c.chan].furnacePCM=false; } - immWrite(0x18+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + if (skipRegisterWrites) break; + if (chan[c.chan].furnacePCM) { + chan[c.chan].macroInit(ins); + if (!chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + immWrite(0x18+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol)); + } + } else { + chan[c.chan].macroInit(NULL); + chan[c.chan].outVol=chan[c.chan].vol; + immWrite(0x18+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol)); + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; break; } DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); @@ -821,12 +772,17 @@ 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++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; - if (isOutput[chan[c.chan].state.alg][i]) { + if (KVS(c.chan,i)) { if (!chan[c.chan].active || chan[c.chan].insChanged) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } @@ -861,28 +817,12 @@ int DivPlatformYM2608::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_OFF: - if (c.chan>14) { - immWrite(0x100,0x01); // reset - break; - } - if (c.chan>8) { - writeRSSOff|=1<<(c.chan-9); - break; - } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: - if (c.chan>14) { - immWrite(0x100,0x01); // reset - break; - } - if (c.chan>8) { - writeRSSOff|=1<<(c.chan-9); - break; - } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; @@ -901,13 +841,13 @@ int DivPlatformYM2608::dispatch(DivCommand c) { break; } if (c.chan>8) { // ADPCM-A - immWrite(0x18+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + immWrite(0x18+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol)); break; } for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; - if (isOutput[chan[c.chan].state.alg][i]) { + if (KVS(c.chan,i)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -915,6 +855,13 @@ int DivPlatformYM2608::dispatch(DivCommand c) { } break; } + case DIV_CMD_ADPCMA_GLOBAL_VOLUME: { + if (globalRSSVolume!=(c.value&0x3f)) { + globalRSSVolume=c.value&0x3f; + immWrite(0x11,globalRSSVolume&0x3f); + } + break; + } case DIV_CMD_GET_VOLUME: { return chan[c.chan].vol; break; @@ -936,7 +883,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { break; } if (c.chan>8) { - immWrite(0x18+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + immWrite(0x18+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol)); break; } rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); @@ -1018,7 +965,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; op.tl=c.value2; - if (isOutput[chan[c.chan].state.alg][c.value]) { + if (KVS(c.chan,c.value)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -1205,7 +1152,7 @@ void DivPlatformYM2608::muteChannel(int ch, bool mute) { immWrite(0x101,(isMuted[ch]?0:(chan[ch].pan<<6))|2); } if (ch>8) { // ADPCM-A - immWrite(0x18+(ch-9),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].vol)); + immWrite(0x18+(ch-9),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].outVol)); return; } if (ch>5) { // PSG @@ -1221,7 +1168,7 @@ void DivPlatformYM2608::forceIns() { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -1242,6 +1189,11 @@ void DivPlatformYM2608::forceIns() { } for (int i=9; i<16; i++) { chan[i].insChanged=true; + if (i>14) { // ADPCM-B + immWrite(0x10b,chan[i].outVol); + } else { + immWrite(0x18+(i-9),isMuted[i]?0:((chan[i].pan<<6)|chan[i].outVol)); + } } ay->forceIns(); @@ -1313,6 +1265,7 @@ void DivPlatformYM2608::reset() { sampleBank=0; writeRSSOff=0; writeRSSOn=0; + globalRSSVolume=0x3f; delay=0; @@ -1322,7 +1275,7 @@ void DivPlatformYM2608::reset() { immWrite(0x22,0x08); // PCM volume - immWrite(0x11,0x3f); // A + immWrite(0x11,globalRSSVolume); // A immWrite(0x10b,0xff); // B // ADPCM limit diff --git a/src/engine/platform/ym2608.h b/src/engine/platform/ym2608.h index ac38a8c08..073e381d8 100644 --- a/src/engine/platform/ym2608.h +++ b/src/engine/platform/ym2608.h @@ -48,12 +48,13 @@ class DivPlatformYM2608: public DivPlatformOPN { DivInstrumentFM state; unsigned char freqH, freqL; int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; - unsigned char psgMode, autoEnvNum, autoEnvDen; + unsigned char psgMode, autoEnvNum, autoEnvDen, opMask; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged; int vol, outVol; int sample; unsigned char pan; + int macroVolMul; DivMacroInt std; void macroInit(DivInstrument* which) { std.init(which); @@ -72,6 +73,7 @@ class DivPlatformYM2608: public DivPlatformOPN { psgMode(1), autoEnvNum(0), autoEnvDen(0), + opMask(15), active(false), insChanged(true), freqChanged(false), @@ -81,10 +83,12 @@ class DivPlatformYM2608: public DivPlatformOPN { inPorta(false), furnacePCM(false), hardReset(false), + opMaskChanged(false), vol(0), outVol(15), sample(-1), - pan(3) {} + pan(3), + macroVolMul(255) {} }; Channel chan[16]; DivDispatchOscBuffer* oscBuf[16]; @@ -99,12 +103,14 @@ class DivPlatformYM2608: public DivPlatformOPN { DivPlatformAY8910* ay; unsigned char sampleBank; unsigned char writeRSSOff, writeRSSOn; + int globalRSSVolume; bool extMode; unsigned char prescale; double NOTE_OPNB(int ch, int note); double NOTE_ADPCMB(int note); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: @@ -127,7 +133,6 @@ class DivPlatformYM2608: public DivPlatformOPN { 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/ym2608ext.cpp b/src/engine/platform/ym2608ext.cpp index 63503ccc3..66a4d252c 100644 --- a/src/engine/platform/ym2608ext.cpp +++ b/src/engine/platform/ym2608ext.cpp @@ -59,6 +59,7 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) { rWrite(baseAddr+0x70,op.d2r&31); rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); rWrite(baseAddr+0x90,op.ssgEnv&15); + opChan[ch].mask=op.enable; } if (opChan[ch].insChanged) { // TODO how does this work? rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); @@ -358,7 +359,7 @@ void DivPlatformYM2608Ext::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -395,10 +396,12 @@ void DivPlatformYM2608Ext::tick(bool sysTick) { immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); } - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn) { writeNoteOn=true; - writeMask|=1<<(4+i); + if (opChan[i].mask) { + writeMask|=1<<(4+i); + } opChan[i].keyOn=false; } } @@ -439,7 +442,7 @@ void DivPlatformYM2608Ext::forceIns() { if (i==2) { // extended channel if (isOpMuted[j]) { rWrite(baseAddr+0x40,127); - } else if (isOutput[chan[i].state.alg][j]) { + } else if (KVS(i,j)) { rWrite(baseAddr+0x40,127-VOL_SCALE_LOG(127-op.tl,opChan[j].vol&0x7f,127)); } else { rWrite(baseAddr+0x40,op.tl); @@ -448,7 +451,7 @@ void DivPlatformYM2608Ext::forceIns() { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -469,8 +472,13 @@ void DivPlatformYM2608Ext::forceIns() { chan[i].freqChanged=true; } } - for (int i=6; i<16; i++) { + for (int i=9; i<16; i++) { chan[i].insChanged=true; + if (i>14) { // ADPCM-B + immWrite(0x10b,chan[i].outVol); + } else { + immWrite(0x18+(i-9),isMuted[i]?0:((chan[i].pan<<6)|chan[i].outVol)); + } } ay->forceIns(); ay->flushWrites(); diff --git a/src/engine/platform/ym2608ext.h b/src/engine/platform/ym2608ext.h index bc3d4f991..4792323cc 100644 --- a/src/engine/platform/ym2608ext.h +++ b/src/engine/platform/ym2608ext.h @@ -27,15 +27,33 @@ 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) {} + 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), + mask(true), + vol(0), + pan(3) {} }; OpChannel opChan[4]; bool isOpMuted[4]; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: int dispatch(DivCommand c); diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 5a90280c4..3d7109055 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -18,15 +18,8 @@ */ #include "ym2610.h" -#include "sound/ymfm/ymfm.h" -#include "../engine.h" -#include "../../ta-log.h" -#include #include -#define CHIP_FREQBASE fmFreqBase -#define CHIP_DIVIDER fmDivBase - const char* regCheatSheetYM2610[]={ // SSG "SSG_FreqL_A", "000", @@ -235,227 +228,10 @@ const char* regCheatSheetYM2610[]={ NULL }; -const void* DivPlatformYM2610Base::getSampleMem(int index) { - return index == 0 ? adpcmAMem : index == 1 ? adpcmBMem : NULL; -} - -size_t DivPlatformYM2610Base::getSampleMemCapacity(int index) { - return index == 0 ? 16777216 : index == 1 ? 16777216 : 0; -} - -size_t DivPlatformYM2610Base::getSampleMemUsage(int index) { - return index == 0 ? adpcmAMemLen : index == 1 ? adpcmBMemLen : 0; -} - -void DivPlatformYM2610Base::renderSamples() { - memset(adpcmAMem,0,getSampleMemCapacity(0)); - - size_t memPos=0; - for (int i=0; isong.sampleLen; i++) { - DivSample* s=parent->song.sample[i]; - int paddedLen=(s->lengthA+255)&(~0xff); - if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) { - memPos=(memPos+0xfffff)&0xf00000; - } - if (memPos>=getSampleMemCapacity(0)) { - logW("out of ADPCM-A memory for sample %d!",i); - break; - } - if (memPos+paddedLen>=getSampleMemCapacity(0)) { - memcpy(adpcmAMem+memPos,s->dataA,getSampleMemCapacity(0)-memPos); - logW("out of ADPCM-A memory for sample %d!",i); - } else { - memcpy(adpcmAMem+memPos,s->dataA,paddedLen); - } - s->offA=memPos; - memPos+=paddedLen; - } - adpcmAMemLen=memPos+256; - - memset(adpcmBMem,0,getSampleMemCapacity(1)); - - memPos=0; - for (int i=0; isong.sampleLen; i++) { - DivSample* s=parent->song.sample[i]; - int paddedLen=(s->lengthB+255)&(~0xff); - if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) { - memPos=(memPos+0xfffff)&0xf00000; - } - if (memPos>=getSampleMemCapacity(1)) { - logW("out of ADPCM-B memory for sample %d!",i); - break; - } - if (memPos+paddedLen>=getSampleMemCapacity(1)) { - memcpy(adpcmBMem+memPos,s->dataB,getSampleMemCapacity(1)-memPos); - logW("out of ADPCM-B memory for sample %d!",i); - } else { - memcpy(adpcmBMem+memPos,s->dataB,paddedLen); - } - s->offB=memPos; - memPos+=paddedLen; - } - adpcmBMemLen=memPos+256; -} - -int DivPlatformYM2610Base::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { - parent=p; - adpcmAMem=new unsigned char[getSampleMemCapacity(0)]; - adpcmAMemLen=0; - adpcmBMem=new unsigned char[getSampleMemCapacity(1)]; - adpcmBMemLen=0; - iface.adpcmAMem=adpcmAMem; - iface.adpcmBMem=adpcmBMem; - iface.sampleBank=0; - return 0; -} - -void DivPlatformYM2610Base::quit() { - delete[] adpcmAMem; - delete[] adpcmBMem; -} - 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); - } else if (ch>3) { // PSG - return NOTE_PERIODIC(note); - } - // FM - return NOTE_FNUM_BLOCK(note,11); -} - -double DivPlatformYM2610::NOTE_ADPCMB(int note) { - if (chan[13].sample>=0 && chan[13].samplesong.sampleLen) { - double off=65535.0*(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0; - return parent->calcBaseFreq((double)chipClock/144,off,note,false); - } - return 0; -} - void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; @@ -466,7 +242,7 @@ void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t l ymfm::ssg_engine::output_data ssgOut; - ymfm::fm_channel>* fmChan[6]; + ymfm::fm_channel>* fmChan[4]; ymfm::adpcm_a_channel* adpcmAChan[6]; for (int i=0; i<4; i++) { fmChan[i]=fme->debug_channel(bchOffs[i]); @@ -501,34 +277,26 @@ void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t l bufL[h]=os[0]; bufR[h]=os[1]; - for (int i=0; i<4; i++) { + for (int i=0; idata[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); } ssge->get_last_out(ssgOut); - for (int i=4; i<7; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-4]; + for (int i=psgChanOffs; idata[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]; } - for (int i=7; i<13; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-7]->get_last_out(0)+adpcmAChan[i-7]->get_last_out(1); + for (int i=adpcmAChanOffs; idata[oscBuf[i]->needle++]=adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1); } - oscBuf[13]->data[oscBuf[13]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); } } -void DivPlatformYM2610::tick(bool sysTick) { - // PSG - ay->tick(sysTick); - ay->flushWrites(); - for (DivRegWrite& i: ay->getRegisterWrites()) { - immWrite(i.addr&15,i.val); - } - ay->getRegisterWrites().clear(); - +void DivPlatformYM2610::tick(bool sysTick) { // FM - for (int i=0; i<4; i++) { + for (int i=0; icalcArp(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) { @@ -591,7 +350,7 @@ void DivPlatformYM2610::tick(bool sysTick) { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -611,6 +370,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]; @@ -641,7 +404,7 @@ void DivPlatformYM2610::tick(bool sysTick) { } if (m.tl.had) { op.tl=127-m.tl.val; - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -689,43 +452,6 @@ void DivPlatformYM2610::tick(bool sysTick) { } } - // ADPCM-B - if (chan[13].furnacePCM) { - chan[13].std.next(); - - if (chan[13].std.vol.had) { - chan[13].outVol=(chan[13].vol*MIN(64,chan[13].std.vol.val))/64; - immWrite(0x1b,chan[13].outVol); - } - - 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].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) { - if (chan[13].sample>=0 && chan[13].samplesong.sampleLen) { - double off=65535.0*(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0; - chan[13].freq=parent->calcFreq(chan[13].baseFreq,chan[13].pitch,false,4,chan[13].pitch2,(double)chipClock/144,off); - } else { - chan[13].freq=0; - } - immWrite(0x19,chan[13].freq&0xff); - immWrite(0x1a,(chan[13].freq>>8)&0xff); - chan[13].freqChanged=false; - } - for (int i=16; i<512; i++) { if (pendingWrites[i]!=oldWrites[i]) { immWrite(i,pendingWrites[i]&0xff); @@ -733,7 +459,7 @@ void DivPlatformYM2610::tick(bool sysTick) { } } - for (int i=0; i<4; i++) { + for (int i=0; isong.linearPitch==2) { @@ -756,23 +482,133 @@ 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; } } + + // ADPCM-A + for (int i=adpcmAChanOffs; i=0 && chan[i].samplesong.sampleLen) { + writeADPCMAOn|=(1<<(i-adpcmAChanOffs)); + } + chan[i].keyOn=false; + } + } + // ADPCM-B + if (chan[adpcmBChanOffs].furnacePCM) { + chan[adpcmBChanOffs].std.next(); + + if (chan[adpcmBChanOffs].std.vol.had) { + chan[adpcmBChanOffs].outVol=(chan[adpcmBChanOffs].vol*MIN(chan[adpcmBChanOffs].macroVolMul,chan[adpcmBChanOffs].std.vol.val))/chan[adpcmBChanOffs].macroVolMul; + immWrite(0x1b,chan[adpcmBChanOffs].outVol); + } + + if (chan[adpcmBChanOffs].std.arp.had) { + if (!chan[adpcmBChanOffs].inPorta) { + chan[adpcmBChanOffs].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[adpcmBChanOffs].note,chan[adpcmBChanOffs].std.arp.val)); + } + chan[adpcmBChanOffs].freqChanged=true; + } + if (chan[adpcmBChanOffs].std.panL.had) { + if (chan[adpcmBChanOffs].pan!=(chan[adpcmBChanOffs].std.panL.val&3)) { + chan[adpcmBChanOffs].pan=chan[adpcmBChanOffs].std.panL.val&3; + if (!isMuted[adpcmBChanOffs]) { + immWrite(0x11,(isMuted[adpcmBChanOffs]?0:(chan[adpcmBChanOffs].pan<<6))); + } + } + } + if (chan[adpcmBChanOffs].std.phaseReset.had) { + if ((chan[adpcmBChanOffs].std.phaseReset.val==1) && chan[adpcmBChanOffs].active) { + chan[adpcmBChanOffs].keyOn=true; + } + } + } + if (chan[adpcmBChanOffs].freqChanged || chan[adpcmBChanOffs].keyOn || chan[adpcmBChanOffs].keyOff) { + if (chan[adpcmBChanOffs].furnacePCM) { + if (chan[adpcmBChanOffs].sample>=0 && chan[adpcmBChanOffs].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[adpcmBChanOffs].sample)->centerRate)/8363.0; + chan[adpcmBChanOffs].freq=parent->calcFreq(chan[adpcmBChanOffs].baseFreq,chan[adpcmBChanOffs].pitch,false,4,chan[adpcmBChanOffs].pitch2,(double)chipClock/144,off); + } else { + chan[adpcmBChanOffs].freq=0; + } + immWrite(0x19,chan[adpcmBChanOffs].freq&0xff); + immWrite(0x1a,(chan[adpcmBChanOffs].freq>>8)&0xff); + } + if (chan[adpcmBChanOffs].keyOn || chan[adpcmBChanOffs].keyOff) { + immWrite(0x10,0x01); // reset + if (chan[adpcmBChanOffs].active && chan[adpcmBChanOffs].keyOn && !chan[adpcmBChanOffs].keyOff) { + if (chan[adpcmBChanOffs].sample>=0 && chan[adpcmBChanOffs].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[adpcmBChanOffs].sample); + immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat + } + } + chan[adpcmBChanOffs].keyOn=false; + chan[adpcmBChanOffs].keyOff=false; + } + chan[adpcmBChanOffs].freqChanged=false; + } + + if (writeADPCMAOff) { + immWrite(0x100,0x80|writeADPCMAOff); + writeADPCMAOff=0; + } + + if (writeADPCMAOn) { + immWrite(0x100,writeADPCMAOn); + writeADPCMAOn=0; + } + + // PSG + ay->tick(sysTick); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); } int DivPlatformYM2610::dispatch(DivCommand c) { - if (c.chan>3 && c.chan<7) { - c.chan-=4; + if (c.chan>=psgChanOffs && c.chan<7) { + c.chan-=psgChanOffs; return ay->dispatch(c); } switch (c.cmd) { case DIV_CMD_NOTE_ON: { - if (c.chan>12) { // ADPCM-B + if (c.chan>=adpcmBChanOffs) { // ADPCM-B DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); - if (ins->type==DIV_INS_AMIGA) { + chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:255; + if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_ADPCMB) { chan[c.chan].furnacePCM=true; } else { chan[c.chan].furnacePCM=false; @@ -793,7 +629,6 @@ 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->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && (s->loopStart>=0))?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); @@ -814,50 +649,104 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].macroInit(NULL); chan[c.chan].outVol=chan[c.chan].vol; if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { - immWrite(0x10,0x01); // reset - immWrite(0x12,0); - immWrite(0x13,0); - immWrite(0x14,0); - immWrite(0x15,0); break; } - DivSample* s=parent->getSample(12*sampleBank+c.value%12); - immWrite(0x12,(s->offB>>8)&0xff); - immWrite(0x13,s->offB>>16); - int end=((s->offB+s->lengthB+0xff)&~0xff)-1; - immWrite(0x14,(end>>8)&0xff); - immWrite(0x15,end>>16); - immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - 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); + chan[c.chan].sample=12*sampleBank+c.value%12; + if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + DivSample* s=parent->getSample(12*sampleBank+c.value%12); + immWrite(0x12,(s->offB>>8)&0xff); + immWrite(0x13,s->offB>>16); + int end=s->offB+s->lengthB-1; + immWrite(0x14,(end>>8)&0xff); + immWrite(0x15,end>>16); + immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0); + immWrite(0x19,freq&0xff); + immWrite(0x1a,(freq>>8)&0xff); + immWrite(0x1b,chan[c.chan].outVol); + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + } else { + immWrite(0x10,0x01); // reset + immWrite(0x12,0); + immWrite(0x13,0); + immWrite(0x14,0); + immWrite(0x15,0); + break; + } } break; } - if (c.chan>6) { // ADPCM-A - if (skipRegisterWrites) break; - if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { - immWrite(0x100,0x80|(1<<(c.chan-7))); - immWrite(0x110+c.chan-7,0); - immWrite(0x118+c.chan-7,0); - immWrite(0x120+c.chan-7,0); - immWrite(0x128+c.chan-7,0); - break; + if (c.chan>=adpcmAChanOffs) { // ADPCM-A + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); + chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:31; + if (!parent->song.disableSampleMacro && (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_ADPCMA)) { + chan[c.chan].furnacePCM=true; + } else { + chan[c.chan].furnacePCM=false; + } + if (skipRegisterWrites) break; + if (chan[c.chan].furnacePCM) { + chan[c.chan].macroInit(ins); + if (!chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + chan[c.chan].sample=ins->amiga.getSample(c.value); + if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[c.chan].sample); + immWrite(0x110+c.chan-adpcmAChanOffs,(s->offA>>8)&0xff); + immWrite(0x118+c.chan-adpcmAChanOffs,s->offA>>16); + int end=s->offA+s->lengthA-1; + immWrite(0x120+c.chan-adpcmAChanOffs,(end>>8)&0xff); + immWrite(0x128+c.chan-adpcmAChanOffs,end>>16); + immWrite(0x108+c.chan-adpcmAChanOffs,isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol)); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); + chan[c.chan].freqChanged=true; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + } else { + writeADPCMAOff|=(1<<(c.chan-adpcmAChanOffs)); + immWrite(0x110+c.chan-adpcmAChanOffs,0); + immWrite(0x118+c.chan-adpcmAChanOffs,0); + immWrite(0x120+c.chan-adpcmAChanOffs,0); + immWrite(0x128+c.chan-adpcmAChanOffs,0); + break; + } + } else { + chan[c.chan].sample=-1; + chan[c.chan].macroInit(NULL); + chan[c.chan].outVol=chan[c.chan].vol; + if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + break; + } + chan[c.chan].sample=12*sampleBank+c.value%12; + if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + DivSample* s=parent->getSample(12*sampleBank+c.value%12); + immWrite(0x110+c.chan-adpcmAChanOffs,(s->offA>>8)&0xff); + immWrite(0x118+c.chan-adpcmAChanOffs,s->offA>>16); + int end=s->offA+s->lengthA-1; + immWrite(0x120+c.chan-adpcmAChanOffs,(end>>8)&0xff); + immWrite(0x128+c.chan-adpcmAChanOffs,end>>16); + immWrite(0x108+c.chan-adpcmAChanOffs,isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol)); + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + } else { + writeADPCMAOff|=(1<<(c.chan-adpcmAChanOffs)); + immWrite(0x110+c.chan-adpcmAChanOffs,0); + immWrite(0x118+c.chan-adpcmAChanOffs,0); + immWrite(0x120+c.chan-adpcmAChanOffs,0); + immWrite(0x128+c.chan-adpcmAChanOffs,0); + break; + } } - DivSample* s=parent->getSample(12*sampleBank+c.value%12); - immWrite(0x110+c.chan-7,(s->offA>>8)&0xff); - immWrite(0x118+c.chan-7,s->offA>>16); - int end=s->offA+s->lengthA-1; - immWrite(0x120+c.chan-7,(end>>8)&0xff); - immWrite(0x128+c.chan-7,end>>16); - immWrite(0x108+(c.chan-7),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); - immWrite(0x100,0x00|(1<<(c.chan-7))); break; } DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); chan[c.chan].macroInit(ins); - if (c.chan<4) { + if (c.chanfm; + 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++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; - if (isOutput[chan[c.chan].state.alg][i]) { + if (KVS(c.chan,i)) { if (!chan[c.chan].active || chan[c.chan].insChanged) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } @@ -905,28 +799,12 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_OFF: - if (c.chan>12) { - immWrite(0x10,0x01); // reset - break; - } - if (c.chan>6) { - immWrite(0x100,0x80|(1<<(c.chan-7))); - break; - } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: - if (c.chan>12) { - immWrite(0x10,0x01); // reset - break; - } - if (c.chan>6) { - immWrite(0x100,0x80|(1<<(c.chan-7))); - break; - } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; @@ -940,18 +818,18 @@ int DivPlatformYM2610::dispatch(DivCommand c) { if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } - if (c.chan>12) { // ADPCM-B + if (c.chan>=adpcmBChanOffs) { // ADPCM-B immWrite(0x1b,chan[c.chan].outVol); break; } - if (c.chan>6) { // ADPCM-A - immWrite(0x108+(c.chan-7),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + if (c.chan>=adpcmAChanOffs) { // ADPCM-A + immWrite(0x108+(c.chan-adpcmAChanOffs),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol)); break; } for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; - if (isOutput[chan[c.chan].state.alg][i]) { + if (KVS(c.chan,i)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -959,6 +837,13 @@ int DivPlatformYM2610::dispatch(DivCommand c) { } break; } + case DIV_CMD_ADPCMA_GLOBAL_VOLUME: { + if (globalADPCMAVolume!=(c.value&0x3f)) { + globalADPCMAVolume=c.value&0x3f; + immWrite(0x101,globalADPCMAVolume&0x3f); + } + break; + } case DIV_CMD_GET_VOLUME: { return chan[c.chan].vol; break; @@ -975,25 +860,25 @@ int DivPlatformYM2610::dispatch(DivCommand c) { } else { chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1); } - if (c.chan>12) { + if (c.chan>=adpcmBChanOffs) { immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); break; } - if (c.chan>6) { - immWrite(0x108+(c.chan-7),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + if (c.chan>=adpcmAChanOffs) { + immWrite(0x108+(c.chan-adpcmAChanOffs),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol)); break; } rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); break; } case DIV_CMD_PITCH: { - if (c.chan==13 && !chan[c.chan].furnacePCM) break; + if (c.chan==adpcmBChanOffs && !chan[c.chan].furnacePCM) break; chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; break; } case DIV_CMD_NOTE_PORTA: { - if (c.chan>3 || parent->song.linearPitch==2) { // PSG, ADPCM-B + if (c.chan>=psgChanOffs || parent->song.linearPitch==2) { // PSG, ADPCM-B int destFreq=NOTE_OPNB(c.chan,c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { @@ -1027,7 +912,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { iface.sampleBank=sampleBank; break; case DIV_CMD_LEGATO: { - if (c.chan==13 && !chan[c.chan].furnacePCM) break; + if (c.chan==adpcmBChanOffs && !chan[c.chan].furnacePCM) break; chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value); chan[c.chan].freqChanged=true; break; @@ -1044,13 +929,13 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } case DIV_CMD_FM_FB: { - if (c.chan>3) break; + if (c.chan>=psgChanOffs) break; chan[c.chan].state.fb=c.value&7; rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); break; } case DIV_CMD_FM_MULT: { - if (c.chan>3) break; + if (c.chan>=psgChanOffs) break; unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; op.mult=c.value2&15; @@ -1058,11 +943,11 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } case DIV_CMD_FM_TL: { - if (c.chan>3) break; + if (c.chan>=psgChanOffs) break; unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; op.tl=c.value2; - if (isOutput[chan[c.chan].state.alg][c.value]) { + if (KVS(c.chan,c.value)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -1070,7 +955,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } case DIV_CMD_FM_AR: { - if (c.chan>3) break; + if (c.chan>=psgChanOffs) break; if (c.value<0) { for (int i=0; i<4; i++) { DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; @@ -1221,13 +1106,13 @@ int DivPlatformYM2610::dispatch(DivCommand c) { return 0; break; case DIV_CMD_GET_VOLMAX: - if (c.chan>12) return 255; - if (c.chan>6) return 31; - if (c.chan>3) return 15; + if (c.chan>=adpcmBChanOffs) return 255; + if (c.chan>=adpcmAChanOffs) return 31; + if (c.chan>=psgChanOffs) return 15; return 127; break; case DIV_CMD_PRE_PORTA: - if (c.chan>3) { + if (c.chan>=psgChanOffs) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FM)); } @@ -1245,15 +1130,8 @@ int DivPlatformYM2610::dispatch(DivCommand c) { void DivPlatformYM2610::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - if (ch>12) { // ADPCM-B - immWrite(0x11,isMuted[ch]?0:(chan[ch].pan<<6)); - } - if (ch>6) { // ADPCM-A - immWrite(0x108+(ch-7),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].vol)); - return; - } - if (ch>3) { // PSG - ay->muteChannel(ch-4,mute); + if (ch>=psgChanOffs) { // PSG + DivPlatformYM2610Base::muteChannel(ch,mute); return; } // FM @@ -1261,11 +1139,11 @@ void DivPlatformYM2610::muteChannel(int ch, bool mute) { } void DivPlatformYM2610::forceIns() { - for (int i=0; i<4; i++) { + for (int i=0; i=4 && ch<7) return ay->getChanMacroInt(ch-4); + if (ch>=psgChanOffs && chgetChanMacroInt(ch-psgChanOffs); return &chan[ch].std; } @@ -1336,17 +1214,20 @@ void DivPlatformYM2610::reset() { chan[i]=DivPlatformYM2610::Channel(); chan[i].std.setEngine(parent); } - for (int i=0; i<4; i++) { + for (int i=0; ireset(); - ay->getRegisterWrites().clear(); - ay->flushWrites(); } bool DivPlatformYM2610::isStereo() { @@ -1377,11 +1255,11 @@ bool DivPlatformYM2610::isStereo() { } bool DivPlatformYM2610::keyOffAffectsArp(int ch) { - return (ch>3); + return (ch>=psgChanOffs); } void DivPlatformYM2610::notifyInsChange(int ins) { - for (int i=0; i<14; i++) { + for (int i=0; isetSkipRegisterWrites(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; - skipRegisterWrites=false; - for (int i=0; i<14; i++) { - isMuted[i]=false; - oscBuf[i]=new DivDispatchOscBuffer; - } - fm=new ymfm::ym2610(iface); - setFlags(flags); - // YM2149, 2MHz - ay=new DivPlatformAY8910(true,chipClock,32); - ay->init(p,3,sugRate,16); - ay->toggleRegisterDump(true); reset(); return 14; } void DivPlatformYM2610::quit() { - for (int i=0; i<14; i++) { - delete oscBuf[i]; - } - ay->quit(); - delete ay; delete fm; DivPlatformYM2610Base::quit(); } diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index dde7ed105..cfe4a5856 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -19,41 +19,9 @@ #ifndef _YM2610_H #define _YM2610_H -#include "fmshared_OPN.h" -#include "../macroInt.h" -#include "ay.h" -#include "sound/ymfm/ymfm_opn.h" +#include "ym2610shared.h" -class DivYM2610Interface: public ymfm::ymfm_interface { - public: - unsigned char* adpcmAMem; - unsigned char* adpcmBMem; - int sampleBank; - uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address); - void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data); - DivYM2610Interface(): adpcmAMem(NULL), adpcmBMem(NULL), sampleBank(0) {} -}; - -class DivPlatformYM2610Base: public DivPlatformOPN { - protected: - unsigned char* adpcmAMem; - size_t adpcmAMemLen; - unsigned char* adpcmBMem; - size_t adpcmBMemLen; - DivYM2610Interface iface; - - public: - 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(); - DivPlatformYM2610Base(): - DivPlatformOPN(9440540.0, 72, 32) {} -}; - -class DivPlatformYM2610: public DivPlatformYM2610Base { +class DivPlatformYM2610: public DivPlatformYM2610Base<14> { protected: const unsigned short chanOffs[4]={ 0x01, 0x02, 0x101, 0x102 @@ -67,64 +35,9 @@ class DivPlatformYM2610: public DivPlatformYM2610Base { 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; - 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) {} - }; - Channel chan[14]; - DivDispatchOscBuffer* oscBuf[14]; - bool isMuted[14]; - ymfm::ym2610* fm; - ymfm::ym2610::output_data fmout; - - DivPlatformAY8910* ay; - - unsigned char sampleBank; - - bool extMode; - - double NOTE_OPNB(int ch, int note); - double NOTE_ADPCMB(int note); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); - + public: void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); @@ -145,10 +58,10 @@ 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(): + DivPlatformYM2610Base<14>(1,4,7,13) {} ~DivPlatformYM2610(); }; #endif diff --git a/src/engine/platform/ym2610Interface.cpp b/src/engine/platform/ym2610Interface.cpp index d442ce347..1b13b3743 100644 --- a/src/engine/platform/ym2610Interface.cpp +++ b/src/engine/platform/ym2610Interface.cpp @@ -17,9 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "sound/ymfm/ymfm.h" -#include "ym2610.h" -#include "../engine.h" +#include "ym2610shared.h" uint8_t DivYM2610Interface::ymfm_external_read(ymfm::access_class type, uint32_t address) { switch (type) { diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 15c7fcc5f..05b0fc866 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -18,14 +18,8 @@ */ #include "ym2610b.h" -#include "sound/ymfm/ymfm.h" -#include "../engine.h" -#include #include -#define CHIP_FREQBASE fmFreqBase -#define CHIP_DIVIDER fmDivBase - const char* regCheatSheetYM2610B[]={ // SSG "SSG_FreqL_A", "000", @@ -302,144 +296,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); - } else if (ch>5) { // PSG - return NOTE_PERIODIC(note); - } - // FM - return NOTE_FNUM_BLOCK(note,11); -} - -double DivPlatformYM2610B::NOTE_ADPCMB(int note) { - if (chan[15].sample>=0 && chan[15].samplesong.sampleLen) { - double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; - return parent->calcBaseFreq((double)chipClock/144,off,note,false); - } - return 0; -} - void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; @@ -484,34 +340,26 @@ void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t bufR[h]=os[1]; - for (int i=0; i<6; i++) { + for (int i=0; idata[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); } ssge->get_last_out(ssgOut); - for (int i=6; i<9; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-6]; + for (int i=psgChanOffs; idata[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]; } - for (int i=9; i<15; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-9]->get_last_out(0)+adpcmAChan[i-9]->get_last_out(1); + for (int i=adpcmAChanOffs; idata[oscBuf[i]->needle++]=adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1); } - oscBuf[15]->data[oscBuf[15]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); } } void DivPlatformYM2610B::tick(bool sysTick) { - // PSG - ay->tick(sysTick); - ay->flushWrites(); - for (DivRegWrite& i: ay->getRegisterWrites()) { - immWrite(i.addr&15,i.val); - } - ay->getRegisterWrites().clear(); - // FM - for (int i=0; i<6; i++) { + for (int i=0; icalcArp(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) { @@ -574,7 +413,7 @@ void DivPlatformYM2610B::tick(bool sysTick) { if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -594,6 +433,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]; @@ -624,7 +467,7 @@ void DivPlatformYM2610B::tick(bool sysTick) { } if (m.tl.had) { op.tl=127-m.tl.val; - if (isOutput[chan[i].state.alg][j]) { + if (KVS(i,j)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[i].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -671,42 +514,6 @@ void DivPlatformYM2610B::tick(bool sysTick) { chan[i].keyOff=false; } } - // ADPCM-B - if (chan[15].furnacePCM) { - chan[15].std.next(); - - if (chan[15].std.vol.had) { - chan[15].outVol=(chan[15].vol*MIN(64,chan[15].std.vol.val))/64; - immWrite(0x1b,chan[15].outVol); - } - - 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].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) { - if (chan[15].sample>=0 && chan[15].samplesong.sampleLen) { - double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; - chan[15].freq=parent->calcFreq(chan[15].baseFreq,chan[15].pitch,false,4,chan[15].pitch2,(double)chipClock/144,off); - } else { - chan[15].freq=0; - } - immWrite(0x19,chan[15].freq&0xff); - immWrite(0x1a,(chan[15].freq>>8)&0xff); - chan[15].freqChanged=false; - } for (int i=16; i<512; i++) { if (pendingWrites[i]!=oldWrites[i]) { @@ -715,7 +522,7 @@ void DivPlatformYM2610B::tick(bool sysTick) { } } - for (int i=0; i<6; i++) { + for (int i=0; isong.linearPitch==2) { @@ -738,23 +545,133 @@ 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; } } + + // ADPCM-A + for (int i=adpcmAChanOffs; i=0 && chan[i].samplesong.sampleLen) { + writeADPCMAOn|=(1<<(i-adpcmAChanOffs)); + } + chan[i].keyOn=false; + } + } + // ADPCM-B + if (chan[adpcmBChanOffs].furnacePCM) { + chan[adpcmBChanOffs].std.next(); + + if (chan[adpcmBChanOffs].std.vol.had) { + chan[adpcmBChanOffs].outVol=(chan[adpcmBChanOffs].vol*MIN(chan[adpcmBChanOffs].macroVolMul,chan[adpcmBChanOffs].std.vol.val))/chan[adpcmBChanOffs].macroVolMul; + immWrite(0x1b,chan[adpcmBChanOffs].outVol); + } + + if (chan[adpcmBChanOffs].std.arp.had) { + if (!chan[adpcmBChanOffs].inPorta) { + chan[adpcmBChanOffs].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[adpcmBChanOffs].note,chan[adpcmBChanOffs].std.arp.val)); + } + chan[adpcmBChanOffs].freqChanged=true; + } + if (chan[adpcmBChanOffs].std.panL.had) { + if (chan[adpcmBChanOffs].pan!=(chan[adpcmBChanOffs].std.panL.val&3)) { + chan[adpcmBChanOffs].pan=chan[adpcmBChanOffs].std.panL.val&3; + if (!isMuted[adpcmBChanOffs]) { + immWrite(0x11,(isMuted[adpcmBChanOffs]?0:(chan[adpcmBChanOffs].pan<<6))); + } + } + } + if (chan[adpcmBChanOffs].std.phaseReset.had) { + if ((chan[adpcmBChanOffs].std.phaseReset.val==1) && chan[adpcmBChanOffs].active) { + chan[adpcmBChanOffs].keyOn=true; + } + } + } + if (chan[adpcmBChanOffs].freqChanged || chan[adpcmBChanOffs].keyOn || chan[adpcmBChanOffs].keyOff) { + if (chan[adpcmBChanOffs].furnacePCM) { + if (chan[adpcmBChanOffs].sample>=0 && chan[adpcmBChanOffs].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[adpcmBChanOffs].sample)->centerRate)/8363.0; + chan[adpcmBChanOffs].freq=parent->calcFreq(chan[adpcmBChanOffs].baseFreq,chan[adpcmBChanOffs].pitch,false,4,chan[adpcmBChanOffs].pitch2,(double)chipClock/144,off); + } else { + chan[adpcmBChanOffs].freq=0; + } + immWrite(0x19,chan[adpcmBChanOffs].freq&0xff); + immWrite(0x1a,(chan[adpcmBChanOffs].freq>>8)&0xff); + } + if (chan[adpcmBChanOffs].keyOn || chan[adpcmBChanOffs].keyOff) { + immWrite(0x10,0x01); // reset + if (chan[adpcmBChanOffs].active && chan[adpcmBChanOffs].keyOn && !chan[adpcmBChanOffs].keyOff) { + if (chan[adpcmBChanOffs].sample>=0 && chan[adpcmBChanOffs].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[adpcmBChanOffs].sample); + immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat + } + } + chan[adpcmBChanOffs].keyOn=false; + chan[adpcmBChanOffs].keyOff=false; + } + chan[adpcmBChanOffs].freqChanged=false; + } + + if (writeADPCMAOff) { + immWrite(0x100,0x80|writeADPCMAOff); + writeADPCMAOff=0; + } + + if (writeADPCMAOn) { + immWrite(0x100,writeADPCMAOn); + writeADPCMAOn=0; + } + + // PSG + ay->tick(sysTick); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); } int DivPlatformYM2610B::dispatch(DivCommand c) { - if (c.chan>5 && c.chan<9) { - c.chan-=6; + if (c.chan>=psgChanOffs && c.chandispatch(c); } switch (c.cmd) { case DIV_CMD_NOTE_ON: { - if (c.chan>14) { // ADPCM-B + if (c.chan>=adpcmBChanOffs) { // ADPCM-B DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); - if (ins->type==DIV_INS_AMIGA) { + chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:255; + if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_ADPCMB) { chan[c.chan].furnacePCM=true; } else { chan[c.chan].furnacePCM=false; @@ -775,7 +692,6 @@ 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->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && (s->loopStart>=0))?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); @@ -796,45 +712,99 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].macroInit(NULL); chan[c.chan].outVol=chan[c.chan].vol; if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { - immWrite(0x10,0x01); // reset - immWrite(0x12,0); - immWrite(0x13,0); - immWrite(0x14,0); - immWrite(0x15,0); break; } - DivSample* s=parent->getSample(12*sampleBank+c.value%12); - immWrite(0x12,(s->offB>>8)&0xff); - immWrite(0x13,s->offB>>16); - int end=((s->offB+s->lengthB+0xff)&~0xff)-1; - immWrite(0x14,(end>>8)&0xff); - immWrite(0x15,end>>16); - immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - 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); + chan[c.chan].sample=12*sampleBank+c.value%12; + if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + DivSample* s=parent->getSample(12*sampleBank+c.value%12); + immWrite(0x12,(s->offB>>8)&0xff); + immWrite(0x13,s->offB>>16); + int end=s->offB+s->lengthB-1; + immWrite(0x14,(end>>8)&0xff); + immWrite(0x15,end>>16); + immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0); + immWrite(0x19,freq&0xff); + immWrite(0x1a,(freq>>8)&0xff); + immWrite(0x1b,chan[c.chan].outVol); + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + } else { + immWrite(0x10,0x01); // reset + immWrite(0x12,0); + immWrite(0x13,0); + immWrite(0x14,0); + immWrite(0x15,0); + break; + } } break; } - if (c.chan>8) { // ADPCM-A - if (skipRegisterWrites) break; - if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { - immWrite(0x100,0x80|(1<<(c.chan-9))); - immWrite(0x110+c.chan-9,0); - immWrite(0x118+c.chan-9,0); - immWrite(0x120+c.chan-9,0); - immWrite(0x128+c.chan-9,0); - break; + if (c.chan>=adpcmAChanOffs) { // ADPCM-A + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); + chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:31; + if (!parent->song.disableSampleMacro && (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_ADPCMA)) { + chan[c.chan].furnacePCM=true; + } else { + chan[c.chan].furnacePCM=false; + } + if (skipRegisterWrites) break; + if (chan[c.chan].furnacePCM) { + chan[c.chan].macroInit(ins); + if (!chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + chan[c.chan].sample=ins->amiga.getSample(c.value); + if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[c.chan].sample); + immWrite(0x110+c.chan-adpcmAChanOffs,(s->offA>>8)&0xff); + immWrite(0x118+c.chan-adpcmAChanOffs,s->offA>>16); + int end=s->offA+s->lengthA-1; + immWrite(0x120+c.chan-adpcmAChanOffs,(end>>8)&0xff); + immWrite(0x128+c.chan-adpcmAChanOffs,end>>16); + immWrite(0x108+c.chan-adpcmAChanOffs,isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol)); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); + chan[c.chan].freqChanged=true; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + } else { + writeADPCMAOff|=(1<<(c.chan-adpcmAChanOffs)); + immWrite(0x110+c.chan-adpcmAChanOffs,0); + immWrite(0x118+c.chan-adpcmAChanOffs,0); + immWrite(0x120+c.chan-adpcmAChanOffs,0); + immWrite(0x128+c.chan-adpcmAChanOffs,0); + break; + } + } else { + chan[c.chan].sample=-1; + chan[c.chan].macroInit(NULL); + chan[c.chan].outVol=chan[c.chan].vol; + if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + break; + } + chan[c.chan].sample=12*sampleBank+c.value%12; + if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + DivSample* s=parent->getSample(12*sampleBank+c.value%12); + immWrite(0x110+c.chan-adpcmAChanOffs,(s->offA>>8)&0xff); + immWrite(0x118+c.chan-adpcmAChanOffs,s->offA>>16); + int end=s->offA+s->lengthA-1; + immWrite(0x120+c.chan-adpcmAChanOffs,(end>>8)&0xff); + immWrite(0x128+c.chan-adpcmAChanOffs,end>>16); + immWrite(0x108+c.chan-adpcmAChanOffs,isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol)); + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + } else { + writeADPCMAOff|=(1<<(c.chan-adpcmAChanOffs)); + immWrite(0x110+c.chan-adpcmAChanOffs,0); + immWrite(0x118+c.chan-adpcmAChanOffs,0); + immWrite(0x120+c.chan-adpcmAChanOffs,0); + immWrite(0x128+c.chan-adpcmAChanOffs,0); + break; + } } - DivSample* s=parent->getSample(12*sampleBank+c.value%12); - immWrite(0x110+c.chan-9,(s->offA>>8)&0xff); - immWrite(0x118+c.chan-9,s->offA>>16); - int end=s->offA+s->lengthA-1; - immWrite(0x120+c.chan-9,(end>>8)&0xff); - immWrite(0x128+c.chan-9,end>>16); - immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); - immWrite(0x100,0x00|(1<<(c.chan-9))); break; } DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); @@ -847,12 +817,17 @@ 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++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; - if (isOutput[chan[c.chan].state.alg][i]) { + if (KVS(c.chan,i)) { if (!chan[c.chan].active || chan[c.chan].insChanged) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } @@ -887,28 +862,12 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_OFF: - if (c.chan>14) { - immWrite(0x10,0x01); // reset - break; - } - if (c.chan>8) { - immWrite(0x100,0x80|(1<<(c.chan-9))); - break; - } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: - if (c.chan>14) { - immWrite(0x10,0x01); // reset - break; - } - if (c.chan>8) { - immWrite(0x100,0x80|(1<<(c.chan-9))); - break; - } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; @@ -922,18 +881,18 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } - if (c.chan>14) { // ADPCM-B + if (c.chan>=adpcmBChanOffs) { // ADPCM-B immWrite(0x1b,chan[c.chan].outVol); break; } - if (c.chan>8) { // ADPCM-A - immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + if (c.chan>=adpcmAChanOffs) { // ADPCM-A + immWrite(0x108+(c.chan-adpcmAChanOffs),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol)); break; } for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; - if (isOutput[chan[c.chan].state.alg][i]) { + if (KVS(c.chan,i)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -941,6 +900,13 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } break; } + case DIV_CMD_ADPCMA_GLOBAL_VOLUME: { + if (globalADPCMAVolume!=(c.value&0x3f)) { + globalADPCMAVolume=c.value&0x3f; + immWrite(0x101,globalADPCMAVolume&0x3f); + } + break; + } case DIV_CMD_GET_VOLUME: { return chan[c.chan].vol; break; @@ -957,25 +923,25 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } else { chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1); } - if (c.chan>14) { + if (c.chan>=adpcmBChanOffs) { immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); break; } - if (c.chan>8) { - immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + if (c.chan>=adpcmAChanOffs) { + immWrite(0x108+(c.chan-adpcmAChanOffs),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol)); break; } rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); break; } case DIV_CMD_PITCH: { - if (c.chan==15 && !chan[c.chan].furnacePCM) break; + if (c.chan==adpcmBChanOffs && !chan[c.chan].furnacePCM) break; chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; break; } case DIV_CMD_NOTE_PORTA: { - if (c.chan>5 || parent->song.linearPitch==2) { // PSG, ADPCM-B + if (c.chan>=psgChanOffs || parent->song.linearPitch==2) { // PSG, ADPCM-B int destFreq=NOTE_OPNB(c.chan,c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { @@ -1009,7 +975,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { iface.sampleBank=sampleBank; break; case DIV_CMD_LEGATO: { - if (c.chan==15 && !chan[c.chan].furnacePCM) break; + if (c.chan==adpcmBChanOffs && !chan[c.chan].furnacePCM) break; chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value); chan[c.chan].freqChanged=true; break; @@ -1026,13 +992,13 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { break; } case DIV_CMD_FM_FB: { - if (c.chan>5) break; + if (c.chan>=psgChanOffs) break; chan[c.chan].state.fb=c.value&7; rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); break; } case DIV_CMD_FM_MULT: { - if (c.chan>5) break; + if (c.chan>=psgChanOffs) break; unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; op.mult=c.value2&15; @@ -1040,11 +1006,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { break; } case DIV_CMD_FM_TL: { - if (c.chan>5) break; + if (c.chan>=psgChanOffs) break; unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; op.tl=c.value2; - if (isOutput[chan[c.chan].state.alg][c.value]) { + if (KVS(c.chan,c.value)) { rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG(127-op.tl,chan[c.chan].outVol&0x7f,127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); @@ -1052,7 +1018,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { break; } case DIV_CMD_FM_AR: { - if (c.chan>5) break; + if (c.chan>=psgChanOffs) break; if (c.value<0) { for (int i=0; i<4; i++) { DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; @@ -1203,13 +1169,13 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { return 0; break; case DIV_CMD_GET_VOLMAX: - if (c.chan>14) return 255; - if (c.chan>8) return 31; - if (c.chan>5) return 15; + if (c.chan>=adpcmBChanOffs) return 255; + if (c.chan>=adpcmAChanOffs) return 31; + if (c.chan>=psgChanOffs) return 15; return 127; break; case DIV_CMD_PRE_PORTA: - if (c.chan>5) { + if (c.chan>=psgChanOffs) { if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FM)); } @@ -1227,15 +1193,8 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { void DivPlatformYM2610B::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - if (ch>14) { // ADPCM-B - immWrite(0x11,isMuted[ch]?0:(chan[ch].pan<<6)); - } - if (ch>8) { // ADPCM-A - immWrite(0x108+(ch-9),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].vol)); - return; - } - if (ch>5) { // PSG - ay->muteChannel(ch-6,mute); + if (ch>=psgChanOffs) { // PSG + DivPlatformYM2610Base::muteChannel(ch,mute); return; } // FM @@ -1243,11 +1202,11 @@ void DivPlatformYM2610B::muteChannel(int ch, bool mute) { } void DivPlatformYM2610B::forceIns() { - for (int i=0; i<6; i++) { + for (int i=0; i=6 && ch<9) return ay->getChanMacroInt(ch-6); + if (ch>=psgChanOffs && chgetChanMacroInt(ch-psgChanOffs); return &chan[ch].std; } @@ -1318,17 +1277,20 @@ void DivPlatformYM2610B::reset() { chan[i]=DivPlatformYM2610B::Channel(); chan[i].std.setEngine(parent); } - for (int i=0; i<6; i++) { + for (int i=0; i5); + return (ch>=psgChanOffs); } void DivPlatformYM2610B::notifyInsChange(int ins) { - for (int i=0; i<16; i++) { + for (int i=0; isetSkipRegisterWrites(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; - skipRegisterWrites=false; - for (int i=0; i<16; i++) { - isMuted[i]=false; - oscBuf[i]=new DivDispatchOscBuffer; - } - fm=new ymfm::ym2610b(iface); - setFlags(flags); - // YM2149, 2MHz - ay=new DivPlatformAY8910(true,chipClock,32); - ay->init(p,3,sugRate,16); - ay->toggleRegisterDump(true); reset(); return 16; } void DivPlatformYM2610B::quit() { - for (int i=0; i<16; i++) { - delete oscBuf[i]; - } - ay->quit(); - delete ay; delete fm; DivPlatformYM2610Base::quit(); } diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index fefb06929..488fadf9a 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -19,12 +19,9 @@ #ifndef _YM2610B_H #define _YM2610B_H -#include "ym2610.h" -#include "../macroInt.h" -#include "sound/ymfm/ymfm_opn.h" +#include "ym2610shared.h" - -class DivPlatformYM2610B: public DivPlatformYM2610Base { +class DivPlatformYM2610B: public DivPlatformYM2610Base<16> { protected: const unsigned short chanOffs[6]={ 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 @@ -34,65 +31,9 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base { 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; - 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) {} - }; - Channel chan[16]; - DivDispatchOscBuffer* oscBuf[16]; - bool isMuted[16]; - ymfm::ym2610b* fm; - ymfm::ym2610b::output_data fmout; - - DivPlatformAY8910* ay; - unsigned char sampleBank; - - bool extMode; - double fmFreqBase=9440540; - unsigned char ayDiv=32; - - double NOTE_OPNB(int ch, int note); - double NOTE_ADPCMB(int note); + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); - + public: void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); @@ -113,10 +54,10 @@ 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(): + DivPlatformYM2610Base<16>(2,6,9,15) {} ~DivPlatformYM2610B(); }; #endif diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index 7c8247ff8..5c8c2c635 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -18,31 +18,27 @@ */ #include "ym2610bext.h" -#include "../engine.h" #include -#define CHIP_FREQBASE fmFreqBase -#define CHIP_DIVIDER fmDivBase - int DivPlatformYM2610BExt::dispatch(DivCommand c) { - if (c.chan<2) { + if (c.chan5) { + if (c.chan>(extChanOffs+3)) { c.chan-=3; return DivPlatformYM2610B::dispatch(c); } - int ch=c.chan-2; + int ch=c.chan-extChanOffs; int ordch=orderedOps[ch]; if (!extMode) { - c.chan=2; + c.chan=extChanOffs; return DivPlatformYM2610B::dispatch(c); } switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); - unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; // TODO: how does this work?! if (isOpMuted[ch]) { @@ -59,10 +55,11 @@ 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)); - rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + rWrite(chanOffs[extChanOffs]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); + rWrite(chanOffs[extChanOffs]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); } opChan[ch].insChanged=false; @@ -83,7 +80,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { case DIV_CMD_VOLUME: { opChan[ch].vol=c.value; DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); - unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; if (isOpMuted[ch]) { rWrite(baseAddr+0x40,127); @@ -115,7 +112,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { opChan[i].pan=opChan[ch].pan; } } - rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + rWrite(chanOffs[extChanOffs]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); break; } case DIV_CMD_PITCH: { @@ -165,19 +162,19 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { break; } case DIV_CMD_FM_FB: { - chan[2].state.fb=c.value&7; - rWrite(chanOffs[2]+ADDR_FB_ALG,(chan[2].state.alg&7)|(chan[2].state.fb<<3)); + chan[extChanOffs].state.fb=c.value&7; + rWrite(chanOffs[extChanOffs]+ADDR_FB_ALG,(chan[extChanOffs].state.alg&7)|(chan[extChanOffs].state.fb<<3)); break; } case DIV_CMD_FM_MULT: { // TODO - unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4)); break; } case DIV_CMD_FM_TL: { // TODO - unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); if (isOutput[ins->fm.alg][c.value]) { rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127)); @@ -189,15 +186,15 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { case DIV_CMD_FM_AR: { if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[2].state.op[i]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i]; op.ar=c.value2&31; - unsigned short baseAddr=chanOffs[2]|opOffs[i]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6)); } } else { - DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; op.ar=c.value2&31; - unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6)); } break; @@ -205,15 +202,15 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { case DIV_CMD_FM_RS: { if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[2].state.op[i]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i]; op.rs=c.value2&3; - unsigned short baseAddr=chanOffs[2]|opOffs[i]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); } } else if (c.value<4) { - DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; op.rs=c.value2&3; - unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); } break; @@ -221,15 +218,15 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { case DIV_CMD_FM_AM: { if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[2].state.op[i]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i]; op.am=c.value2&1; - unsigned short baseAddr=chanOffs[2]|opOffs[i]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); } } else if (c.value<4) { - DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; op.am=c.value2&1; - unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); } break; @@ -237,15 +234,15 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { case DIV_CMD_FM_DR: { if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[2].state.op[i]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i]; op.dr=c.value2&31; - unsigned short baseAddr=chanOffs[2]|opOffs[i]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); } } else if (c.value<4) { - DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; op.dr=c.value2&31; - unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); } break; @@ -253,15 +250,15 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { case DIV_CMD_FM_SL: { if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[2].state.op[i]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i]; op.sl=c.value2&15; - unsigned short baseAddr=chanOffs[2]|opOffs[i]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } } else if (c.value<4) { - DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; op.sl=c.value2&15; - unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } break; @@ -269,15 +266,15 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { case DIV_CMD_FM_RR: { if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[2].state.op[i]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i]; op.rr=c.value2&15; - unsigned short baseAddr=chanOffs[2]|opOffs[i]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } } else if (c.value<4) { - DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; op.rr=c.value2&15; - unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } break; @@ -285,15 +282,15 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { case DIV_CMD_FM_D2R: { if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[2].state.op[i]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i]; op.d2r=c.value2&31; - unsigned short baseAddr=chanOffs[2]|opOffs[i]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); } } else if (c.value<4) { - DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; op.d2r=c.value2&31; - unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); } break; @@ -301,15 +298,15 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { case DIV_CMD_FM_DT: { if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[2].state.op[i]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i]; op.dt=c.value&7; - unsigned short baseAddr=chanOffs[2]|opOffs[i]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); } } else if (c.value<4) { - DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; op.dt=c.value2&7; - unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); } break; @@ -317,15 +314,15 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { case DIV_CMD_FM_SSG: { if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[2].state.op[i]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i]; op.ssgEnv=8^(c.value2&15); - unsigned short baseAddr=chanOffs[2]|opOffs[i]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } } else if (c.value<4) { - DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; op.ssgEnv=8^(c.value2&15); - unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } break; @@ -358,7 +355,7 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -395,10 +392,12 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); } - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn) { writeNoteOn=true; - writeMask|=1<<(4+i); + if (opChan[i].mask) { + writeMask|=1<<(4+i); + } opChan[i].keyOn=false; } } @@ -408,38 +407,38 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { } void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) { - if (ch<2) { + if (ch5) { + if (ch>(extChanOffs+3)) { DivPlatformYM2610B::muteChannel(ch-3,mute); return; } - isOpMuted[ch-2]=mute; + isOpMuted[ch-extChanOffs]=mute; - int ordch=orderedOps[ch-2]; - DivInstrument* ins=parent->getIns(opChan[ch-2].ins,DIV_INS_FM); - unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; + int ordch=orderedOps[ch-extChanOffs]; + DivInstrument* ins=parent->getIns(opChan[ch-extChanOffs].ins,DIV_INS_FM); + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; - if (isOpMuted[ch-2]) { + if (isOpMuted[ch-extChanOffs]) { rWrite(baseAddr+0x40,127); } else if (isOutput[ins->fm.alg][ordch]) { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG(127-op.tl,opChan[ch-2].vol&0x7f,127)); + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG(127-op.tl,opChan[ch-extChanOffs].vol&0x7f,127)); } else { rWrite(baseAddr+0x40,op.tl); } } void DivPlatformYM2610BExt::forceIns() { - for (int i=0; i<6; i++) { + for (int i=0; iforceIns(); @@ -488,21 +487,21 @@ void DivPlatformYM2610BExt::forceIns() { } void* DivPlatformYM2610BExt::getChanState(int ch) { - if (ch>=6) return &chan[ch-3]; - if (ch>=2) return &opChan[ch-2]; + if (ch>=(extChanOffs+4)) return &chan[ch-3]; + if (ch>=extChanOffs) return &opChan[ch-extChanOffs]; return &chan[ch]; } DivMacroInt* DivPlatformYM2610BExt::getChanMacroInt(int ch) { - if (ch>=9 && ch<12) return ay->getChanMacroInt(ch-9); - if (ch>=6) return &chan[ch-3].std; - if (ch>=2) return NULL; // currently not implemented + if (ch>=(psgChanOffs+3) && ch<(adpcmAChanOffs+3)) return ay->getChanMacroInt(ch-psgChanOffs-3); + if (ch>=(extChanOffs+4)) return &chan[ch-3].std; + if (ch>=extChanOffs) return NULL; // currently not implemented return &chan[ch].std; } DivDispatchOscBuffer* DivPlatformYM2610BExt::getOscBuffer(int ch) { - if (ch>=6) return oscBuf[ch-3]; - if (ch<3) return oscBuf[ch]; + if (ch>=(extChanOffs+4)) return oscBuf[ch-3]; + if (ch<(extChanOffs+1)) return oscBuf[ch]; return NULL; } @@ -520,7 +519,7 @@ void DivPlatformYM2610BExt::reset() { } bool DivPlatformYM2610BExt::keyOffAffectsArp(int ch) { - return (ch>8); + return (ch>=(psgChanOffs+3)); } void DivPlatformYM2610BExt::notifyInsChange(int ins) { diff --git a/src/engine/platform/ym2610bext.h b/src/engine/platform/ym2610bext.h index 732678fe5..fcb5e2b00 100644 --- a/src/engine/platform/ym2610bext.h +++ b/src/engine/platform/ym2610bext.h @@ -22,20 +22,9 @@ #include "ym2610b.h" class DivPlatformYM2610BExt: public DivPlatformYM2610B { - struct OpChannel { - DivMacroInt std; - unsigned char freqH, freqL; - int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; - signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; - 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) {} - }; - OpChannel opChan[4]; + DivPlatformYM2610Base::OpChannel opChan[4]; bool isOpMuted[4]; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: int dispatch(DivCommand c); diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index b2bd06a8c..22c9ebf90 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -18,31 +18,27 @@ */ #include "ym2610ext.h" -#include "../engine.h" #include -#define CHIP_FREQBASE fmFreqBase -#define CHIP_DIVIDER fmDivBase - int DivPlatformYM2610Ext::dispatch(DivCommand c) { - if (c.chan<1) { + if (c.chan4) { + if (c.chan>(extChanOffs+3)) { c.chan-=3; return DivPlatformYM2610::dispatch(c); } - int ch=c.chan-1; + int ch=c.chan-extChanOffs; int ordch=orderedOps[ch]; if (!extMode) { - c.chan=2; + c.chan=extChanOffs; return DivPlatformYM2610::dispatch(c); } switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); - unsigned short baseAddr=chanOffs[1]|opOffs[ordch]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; // TODO: how does this work?! if (isOpMuted[ch]) { @@ -59,10 +55,11 @@ 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)); - rWrite(chanOffs[1]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + rWrite(chanOffs[extChanOffs]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); + rWrite(chanOffs[extChanOffs]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); } opChan[ch].insChanged=false; @@ -83,7 +80,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { case DIV_CMD_VOLUME: { opChan[ch].vol=c.value; DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); - unsigned short baseAddr=chanOffs[1]|opOffs[ordch]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; if (isOpMuted[ch]) { rWrite(baseAddr+0x40,127); @@ -115,7 +112,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { opChan[i].pan=opChan[ch].pan; } } - rWrite(chanOffs[1]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + rWrite(chanOffs[extChanOffs]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); break; } case DIV_CMD_PITCH: { @@ -165,19 +162,19 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { break; } case DIV_CMD_FM_FB: { - chan[1].state.fb=c.value&7; - rWrite(chanOffs[1]+ADDR_FB_ALG,(chan[1].state.alg&7)|(chan[1].state.fb<<3)); + chan[extChanOffs].state.fb=c.value&7; + rWrite(chanOffs[extChanOffs]+ADDR_FB_ALG,(chan[extChanOffs].state.alg&7)|(chan[extChanOffs].state.fb<<3)); break; } case DIV_CMD_FM_MULT: { // TODO - unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4)); break; } case DIV_CMD_FM_TL: { // TODO - unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); if (isOutput[ins->fm.alg][c.value]) { rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127)); @@ -189,15 +186,15 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { case DIV_CMD_FM_AR: { if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[1].state.op[i]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i]; op.ar=c.value2&31; - unsigned short baseAddr=chanOffs[1]|opOffs[i]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6)); } } else { - DivInstrumentFM::Operator& op=chan[1].state.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; op.ar=c.value2&31; - unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6)); } break; @@ -205,15 +202,15 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { case DIV_CMD_FM_RS: { if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[1].state.op[i]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i]; op.rs=c.value2&3; - unsigned short baseAddr=chanOffs[1]|opOffs[i]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); } } else if (c.value<4) { - DivInstrumentFM::Operator& op=chan[1].state.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; op.rs=c.value2&3; - unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); } break; @@ -221,15 +218,15 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { case DIV_CMD_FM_AM: { if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[1].state.op[i]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i]; op.am=c.value2&1; - unsigned short baseAddr=chanOffs[1]|opOffs[i]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); } } else if (c.value<4) { - DivInstrumentFM::Operator& op=chan[1].state.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; op.am=c.value2&1; - unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); } break; @@ -237,15 +234,15 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { case DIV_CMD_FM_DR: { if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[1].state.op[i]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i]; op.dr=c.value2&31; - unsigned short baseAddr=chanOffs[1]|opOffs[i]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); } } else if (c.value<4) { - DivInstrumentFM::Operator& op=chan[1].state.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; op.dr=c.value2&31; - unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); } break; @@ -253,15 +250,15 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { case DIV_CMD_FM_SL: { if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[1].state.op[i]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i]; op.sl=c.value2&15; - unsigned short baseAddr=chanOffs[1]|opOffs[i]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } } else if (c.value<4) { - DivInstrumentFM::Operator& op=chan[1].state.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; op.sl=c.value2&15; - unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } break; @@ -269,15 +266,15 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { case DIV_CMD_FM_RR: { if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[1].state.op[i]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i]; op.rr=c.value2&15; - unsigned short baseAddr=chanOffs[1]|opOffs[i]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } } else if (c.value<4) { - DivInstrumentFM::Operator& op=chan[1].state.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; op.rr=c.value2&15; - unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } break; @@ -285,15 +282,15 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { case DIV_CMD_FM_D2R: { if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[1].state.op[i]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i]; op.d2r=c.value2&31; - unsigned short baseAddr=chanOffs[1]|opOffs[i]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); } } else if (c.value<4) { - DivInstrumentFM::Operator& op=chan[1].state.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; op.d2r=c.value2&31; - unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); } break; @@ -301,15 +298,15 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { case DIV_CMD_FM_DT: { if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[1].state.op[i]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i]; op.dt=c.value&7; - unsigned short baseAddr=chanOffs[1]|opOffs[i]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); } } else if (c.value<4) { - DivInstrumentFM::Operator& op=chan[1].state.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; op.dt=c.value2&7; - unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); } break; @@ -317,15 +314,15 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { case DIV_CMD_FM_SSG: { if (c.value<0) { for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[1].state.op[i]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[i]; op.ssgEnv=8^(c.value2&15); - unsigned short baseAddr=chanOffs[1]|opOffs[i]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } } else if (c.value<4) { - DivInstrumentFM::Operator& op=chan[1].state.op[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]]; op.ssgEnv=8^(c.value2&15); - unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } break; @@ -358,7 +355,7 @@ void DivPlatformYM2610Ext::tick(bool sysTick) { bool writeSomething=false; unsigned char writeMask=2; for (int i=0; i<4; i++) { - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn || opChan[i].keyOff) { writeSomething=true; writeMask&=~(1<<(4+i)); @@ -395,10 +392,12 @@ void DivPlatformYM2610Ext::tick(bool sysTick) { immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); } - writeMask|=opChan[i].active<<(4+i); + writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); if (opChan[i].keyOn) { writeNoteOn=true; - writeMask|=1<<(4+i); + if (opChan[i].mask) { + writeMask|=1<<(4+i); + } opChan[i].keyOn=false; } } @@ -408,38 +407,38 @@ void DivPlatformYM2610Ext::tick(bool sysTick) { } void DivPlatformYM2610Ext::muteChannel(int ch, bool mute) { - if (ch<1) { + if (ch4) { + if (ch>(extChanOffs+3)) { DivPlatformYM2610::muteChannel(ch-3,mute); return; } - isOpMuted[ch-1]=mute; + isOpMuted[ch-extChanOffs]=mute; - int ordch=orderedOps[ch-1]; + int ordch=orderedOps[ch-extChanOffs]; DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); - unsigned short baseAddr=chanOffs[1]|opOffs[ordch]; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; if (isOpMuted[ch]) { rWrite(baseAddr+0x40,127); } else if (isOutput[ins->fm.alg][ordch]) { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG(127-op.tl,opChan[ch].vol&0x7f,127)); + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG(127-op.tl,opChan[ch-extChanOffs].vol&0x7f,127)); } else { rWrite(baseAddr+0x40,op.tl); } } void DivPlatformYM2610Ext::forceIns() { - for (int i=0; i<4; i++) { + for (int i=0; iforceIns(); @@ -488,21 +487,21 @@ void DivPlatformYM2610Ext::forceIns() { } void* DivPlatformYM2610Ext::getChanState(int ch) { - if (ch>=5) return &chan[ch-3]; - if (ch>=1) return &opChan[ch-1]; + if (ch>=(extChanOffs+4)) return &chan[ch-3]; + if (ch>=extChanOffs) return &opChan[ch-extChanOffs]; return &chan[ch]; } DivMacroInt* DivPlatformYM2610Ext::getChanMacroInt(int ch) { - if (ch>=7 && ch<10) return ay->getChanMacroInt(ch-7); - if (ch>=5) return &chan[ch-3].std; - if (ch>=1) return NULL; // currently not implemented + if (ch>=(psgChanOffs+3) && ch<(adpcmAChanOffs+3)) return ay->getChanMacroInt(ch-psgChanOffs-3); + if (ch>=(extChanOffs+4)) return &chan[ch-3].std; + if (ch>=extChanOffs) return NULL; // currently not implemented return &chan[ch].std; } DivDispatchOscBuffer* DivPlatformYM2610Ext::getOscBuffer(int ch) { - if (ch>=5) return oscBuf[ch-3]; - if (ch<2) return oscBuf[ch]; + if (ch>=(extChanOffs+4)) return oscBuf[ch-3]; + if (ch<(extChanOffs+1)) return oscBuf[ch]; return NULL; } @@ -520,7 +519,7 @@ void DivPlatformYM2610Ext::reset() { } bool DivPlatformYM2610Ext::keyOffAffectsArp(int ch) { - return (ch>7); + return (ch>=(psgChanOffs+3)); } void DivPlatformYM2610Ext::notifyInsChange(int ins) { diff --git a/src/engine/platform/ym2610ext.h b/src/engine/platform/ym2610ext.h index 119d63569..5cc4432c5 100644 --- a/src/engine/platform/ym2610ext.h +++ b/src/engine/platform/ym2610ext.h @@ -22,20 +22,9 @@ #include "ym2610.h" class DivPlatformYM2610Ext: public DivPlatformYM2610 { - struct OpChannel { - DivMacroInt std; - unsigned char freqH, freqL; - int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; - signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; - 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) {} - }; - OpChannel opChan[4]; + DivPlatformYM2610Base::OpChannel opChan[4]; bool isOpMuted[4]; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: int dispatch(DivCommand c); diff --git a/src/engine/platform/ym2610shared.h b/src/engine/platform/ym2610shared.h new file mode 100644 index 000000000..e8bbd16f9 --- /dev/null +++ b/src/engine/platform/ym2610shared.h @@ -0,0 +1,317 @@ +/** + * 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 _YM2610SHARED_H +#define _YM2610SHARED_H +#include "fmshared_OPN.h" +#include "../macroInt.h" +#include "../engine.h" +#include "../../ta-log.h" +#include "ay.h" +#include "sound/ymfm/ymfm.h" +#include "sound/ymfm/ymfm_opn.h" +#include + +#define CHIP_FREQBASE fmFreqBase +#define CHIP_DIVIDER fmDivBase + +class DivYM2610Interface: public ymfm::ymfm_interface { + public: + unsigned char* adpcmAMem; + unsigned char* adpcmBMem; + int sampleBank; + uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address); + void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data); + DivYM2610Interface(): + adpcmAMem(NULL), + adpcmBMem(NULL), + sampleBank(0) {} +}; + +template class DivPlatformYM2610Base: public DivPlatformOPN { + protected: + 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, opMaskChanged; + int vol, outVol; + int sample; + unsigned char pan, opMask; + int macroVolMul; + 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), + opMaskChanged(false), + vol(0), + outVol(15), + sample(-1), + pan(3), + opMask(15), + macroVolMul(255) {} + }; + + struct OpChannel { + DivMacroInt std; + unsigned char freqH, freqL; + int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; + signed char konCycles; + 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), + mask(true), + vol(0), + pan(3) {} + }; + Channel chan[ChanNum]; + DivDispatchOscBuffer* oscBuf[ChanNum]; + bool isMuted[ChanNum]; + + ymfm::ym2610b* fm; + ymfm::ym2610b::output_data fmout; + DivPlatformAY8910* ay; + + unsigned char* adpcmAMem; + size_t adpcmAMemLen; + unsigned char* adpcmBMem; + size_t adpcmBMemLen; + DivYM2610Interface iface; + + unsigned char sampleBank; + + bool extMode; + + unsigned char writeADPCMAOff, writeADPCMAOn; + int globalADPCMAVolume; + + const int extChanOffs, psgChanOffs, adpcmAChanOffs, adpcmBChanOffs; + const int chanNum=ChanNum; + + double NOTE_OPNB(int ch, int note) { + if (ch>=adpcmBChanOffs) { // ADPCM + return NOTE_ADPCMB(note); + } else if (ch>=psgChanOffs) { // PSG + return NOTE_PERIODIC(note); + } + // FM + return NOTE_FNUM_BLOCK(note,11); + } + double NOTE_ADPCMB(int note) { + if (chan[adpcmBChanOffs].sample>=0 && chan[adpcmBChanOffs].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[adpcmBChanOffs].sample)->centerRate)/8363.0; + return parent->calcBaseFreq((double)chipClock/144,off,note,false); + } + return 0; + } + + public: + void reset() { + writeADPCMAOff=0; + writeADPCMAOn=0; + globalADPCMAVolume=0x3f; + + ay->reset(); + ay->getRegisterWrites().clear(); + ay->flushWrites(); + } + + void muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (ch>=adpcmBChanOffs) { // ADPCM-B + immWrite(0x11,isMuted[ch]?0:(chan[ch].pan<<6)); + } + if (ch>=adpcmAChanOffs) { // ADPCM-A + immWrite(0x108+(ch-adpcmAChanOffs),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].outVol)); + return; + } + if (ch>=psgChanOffs) { // PSG + ay->muteChannel(ch-psgChanOffs,mute); + return; + } + } + + bool isStereo() { + return true; + } + + const void* getSampleMem(int index) { + return index == 0 ? adpcmAMem : index == 1 ? adpcmBMem : NULL; + } + + size_t getSampleMemCapacity(int index) { + return index == 0 ? 16777216 : index == 1 ? 16777216 : 0; + } + + size_t getSampleMemUsage(int index) { + return index == 0 ? adpcmAMemLen : index == 1 ? adpcmBMemLen : 0; + } + + void renderSamples() { + memset(adpcmAMem,0,getSampleMemCapacity(0)); + + size_t memPos=0; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + int paddedLen=(s->lengthA+255)&(~0xff); + if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) { + memPos=(memPos+0xfffff)&0xf00000; + } + if (memPos>=getSampleMemCapacity(0)) { + logW("out of ADPCM-A memory for sample %d!",i); + break; + } + if (memPos+paddedLen>=getSampleMemCapacity(0)) { + memcpy(adpcmAMem+memPos,s->dataA,getSampleMemCapacity(0)-memPos); + logW("out of ADPCM-A memory for sample %d!",i); + } else { + memcpy(adpcmAMem+memPos,s->dataA,paddedLen); + } + s->offA=memPos; + memPos+=paddedLen; + } + adpcmAMemLen=memPos+256; + + memset(adpcmBMem,0,getSampleMemCapacity(1)); + + memPos=0; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + int paddedLen=(s->lengthB+255)&(~0xff); + if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) { + memPos=(memPos+0xfffff)&0xf00000; + } + if (memPos>=getSampleMemCapacity(1)) { + logW("out of ADPCM-B memory for sample %d!",i); + break; + } + if (memPos+paddedLen>=getSampleMemCapacity(1)) { + memcpy(adpcmBMem+memPos,s->dataB,getSampleMemCapacity(1)-memPos); + logW("out of ADPCM-B memory for sample %d!",i); + } else { + memcpy(adpcmBMem+memPos,s->dataB,paddedLen); + } + s->offB=memPos; + memPos+=paddedLen; + } + adpcmBMemLen=memPos+256; + } + + void 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; irate=rate; + } + } + + int init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; iset_fidelity(ymfm::OPN_FIDELITY_MAX); + setFlags(flags); + // YM2149, 2MHz + ay=new DivPlatformAY8910(true,chipClock,32); + ay->init(p,3,sugRate,16); + ay->toggleRegisterDump(true); + return 0; + } + + void quit() { + for (int i=0; iquit(); + delete ay; + delete[] adpcmAMem; + delete[] adpcmBMem; + } + + DivPlatformYM2610Base(int ext, int psg, int adpcmA, int adpcmB): + DivPlatformOPN(9440540.0, 72, 32), + extChanOffs(ext), + psgChanOffs(psg), + adpcmAChanOffs(adpcmA), + adpcmBChanOffs(adpcmB) {} +}; + +#endif diff --git a/src/engine/platform/ymz280b.cpp b/src/engine/platform/ymz280b.cpp index 287020e82..143124b5d 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]={ @@ -94,23 +90,14 @@ void DivPlatformYMZ280B::tick(bool sysTick) { for (int i=0; i<8; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { - chan[i].outVol=((chan[i].vol&0xff)*chan[i].std.vol.val)>>6; + chan[i].outVol=((chan[i].vol&0xff)*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul; writeOutVol(i); } 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) { @@ -122,9 +109,19 @@ void DivPlatformYMZ280B::tick(bool sysTick) { chan[i].freqChanged=true; } if (chan[i].std.panL.had) { // panning - chan[i].panning=MIN((chan[i].std.panL.val*15/16+15)/2+1,15); + if (chan[i].isNewYMZ) { + chan[i].panning=8+chan[i].std.panL.val; + } else { + chan[i].panning=MIN((chan[i].std.panL.val*15/16+15)/2+1,15); + } rWrite(0x03+i*4,chan[i].panning); } + if (chan[i].std.phaseReset.had) { + if ((chan[i].std.phaseReset.val==1) && chan[i].active) { + chan[i].audPos=0; + chan[i].setPos=true; + } + } if (chan[i].setPos) { // force keyon chan[i].keyOn=true; @@ -142,7 +139,7 @@ void DivPlatformYMZ280B::tick(bool sysTick) { 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 @@ -207,6 +204,8 @@ int DivPlatformYMZ280B::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); + chan[c.chan].isNewYMZ=ins->type==DIV_INS_YMZ280B; + chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255; chan[c.chan].sample=ins->amiga.getSample(c.value); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); @@ -267,14 +266,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; @@ -297,6 +297,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: @@ -330,6 +331,8 @@ void DivPlatformYMZ280B::forceIns() { chan[i].insChanged=true; chan[i].freqChanged=true; chan[i].sample=-1; + + rWrite(0x03+i*4,chan[i].panning); } } diff --git a/src/engine/platform/ymz280b.h b/src/engine/platform/ymz280b.h index 0d254c088..72bf3791c 100644 --- a/src/engine/platform/ymz280b.h +++ b/src/engine/platform/ymz280b.h @@ -32,8 +32,9 @@ class DivPlatformYMZ280B: public DivDispatch { int sample, wave, ins; int note; int panning; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, setPos; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, setPos, isNewYMZ; int vol, outVol; + int macroVolMul; DivMacroInt std; void macroInit(DivInstrument* which) { std.init(which); @@ -56,8 +57,10 @@ class DivPlatformYMZ280B: public DivDispatch { keyOff(false), inPorta(false), setPos(false), + isNewYMZ(false), vol(255), - outVol(255) {} + outVol(255), + macroVolMul(64) {} }; Channel chan[8]; DivDispatchOscBuffer* oscBuf[8]; @@ -68,6 +71,7 @@ class DivPlatformYMZ280B: public DivDispatch { size_t sampleMemLen; ymz280b_device ymz280b; unsigned char regPool[256]; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: @@ -91,7 +95,6 @@ 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); diff --git a/src/engine/platform/zxbeeper.cpp b/src/engine/platform/zxbeeper.cpp index 524d3c117..5fa3840bb 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; 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_FREQUENCY(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.pitch.had) { if (chan[i].std.pitch.mode) { @@ -224,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: diff --git a/src/engine/platform/zxbeeper.h b/src/engine/platform/zxbeeper.h index a9b400cb6..b02b1fb76 100644 --- a/src/engine/platform/zxbeeper.h +++ b/src/engine/platform/zxbeeper.h @@ -72,6 +72,7 @@ class DivPlatformZXBeeper: public DivDispatch { int tempR[32]; unsigned char regPool[128]; bool sampleOut; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: void acquire(short* bufL, short* bufR, size_t start, size_t len); @@ -92,7 +93,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 853955e8b..1890befc8 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -31,7 +31,9 @@ void DivEngine::nextOrder() { curRow=0; if (repeatPattern) return; if (++curOrder>=curSubSong->ordersLen) { + logV("end of orders reached"); endOfSong=true; + memset(walked,0,8192); curOrder=0; } } @@ -57,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", @@ -157,6 +169,7 @@ const char* cmdName[]={ "X1_010_ENVELOPE_PERIOD", "X1_010_ENVELOPE_SLIDE", "X1_010_AUTO_ENVELOPE", + "X1_010_SAMPLE_BANK_SLOT", "WS_SWEEP_TIME", "WS_SWEEP_AMOUNT", @@ -193,6 +206,8 @@ const char* cmdName[]={ "DIV_CMD_SU_SYNC_PERIOD_LOW", "DIV_CMD_SU_SYNC_PERIOD_HIGH", + "ADPCMA_GLOBAL_VOLUME", + "ALWAYS_SET_VOLUME" }; @@ -215,7 +230,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) { @@ -276,13 +311,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) { @@ -306,20 +367,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<(nextSpeed*(curSubSong->timeBase+1))); + if (song.delayBehavior==2) comparison=true; + if (comparison) { chan[i].rowDelay=effectVal+1; chan[i].delayOrder=whatOrder; chan[i].delayRow=whatRow; @@ -330,6 +409,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } returnAfterPre=true; } else { + logV("higher than nextSpeed! %d>%d",effectVal,nextSpeed); chan[i].delayLocked=false; } } @@ -337,6 +417,8 @@ void DivEngine::processRow(int i, bool afterDelay) { } } if (returnAfterPre) return; + } else { + logV("honoring delay at position %d",whatRow); } if (chan[i].delayLocked) return; @@ -344,10 +426,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 @@ -359,11 +447,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; @@ -380,11 +470,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; @@ -401,6 +493,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; @@ -417,6 +510,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)); } } @@ -461,11 +555,13 @@ 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; @@ -481,11 +577,13 @@ 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; @@ -499,6 +597,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 { @@ -512,6 +611,7 @@ void DivEngine::processRow(int i, bool afterDelay) { 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?! @@ -523,6 +623,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 @@ -546,12 +647,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) { @@ -567,14 +670,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; @@ -583,6 +683,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?! @@ -601,6 +702,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?! @@ -618,9 +720,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; @@ -631,6 +735,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; @@ -639,7 +744,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) { @@ -702,6 +811,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 @@ -733,15 +843,18 @@ 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) { 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)); @@ -752,12 +865,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; @@ -781,7 +896,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; } @@ -851,6 +973,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); @@ -876,7 +1005,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; @@ -906,7 +1037,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; @@ -924,13 +1055,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) { @@ -950,7 +1098,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,note.channel)); } } - pendingNotes.pop(); + pendingNotes.pop_front(); } if (!freelance) { @@ -995,15 +1143,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)); } @@ -1032,10 +1184,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)); } } } @@ -1049,11 +1203,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; @@ -1067,6 +1223,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) { @@ -1115,7 +1272,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; @@ -1153,7 +1310,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]); } @@ -1168,13 +1325,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]); } @@ -1210,7 +1367,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi const int loopStart=pBeginVaild?sPreview.pBegin:s->loopStart; const int loopEnd=pEndVaild?sPreview.pEnd:(int)s->loopEnd; for (size_t i=0; i=s->samples || (sPreview.pEnd>=0 && (int)sPreview.pos>=sPreview.pEnd)) { + if (sPreview.pos>=(int)s->samples || (sPreview.pEnd>=0 && sPreview.pos>=sPreview.pEnd)) { samp_temp=0; } else { samp_temp=s->data16[sPreview.pos]; @@ -1223,98 +1380,95 @@ 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.dir) { - if (s->isLoopable() && ((int)sPreview.pos)loopMode) { - case DIV_SAMPLE_LOOPMODE_FORWARD: - sPreview.dir=false; - sPreview.pos=loopStart+1; - break; - case DIV_SAMPLE_LOOPMODE_BACKWARD: - sPreview.dir=true; - sPreview.pos=loopEnd-1; - break; - case DIV_SAMPLE_LOOPMODE_PINGPONG: - sPreview.dir=false; - sPreview.pos=loopStart+1; - break; - case DIV_SAMPLE_LOOPMODE_ONESHOT: - default: - break; + if (sPreview.dir) { // backward + if (sPreview.posloopStart || (sPreview.pBegin>=0 && sPreview.posisLoopable() && sPreview.posloopEnd) { + switch (s->loopMode) { + case DivSampleLoopMode::DIV_SAMPLE_LOOP_FORWARD: + sPreview.pos=s->loopStart; + sPreview.dir=false; + break; + case DivSampleLoopMode::DIV_SAMPLE_LOOP_BACKWARD: + sPreview.pos=s->loopEnd-1; + sPreview.dir=true; + break; + case DivSampleLoopMode::DIV_SAMPLE_LOOP_PINGPONG: + sPreview.pos=s->loopStart; + sPreview.dir=false; + break; + default: + break; + } } } - } else { - if (s->isLoopable() && ((int)sPreview.pos)>=loopEnd) { - switch (s->loopMode) { - case DIV_SAMPLE_LOOPMODE_FORWARD: - sPreview.dir=false; - sPreview.pos=loopStart; - break; - case DIV_SAMPLE_LOOPMODE_BACKWARD: - sPreview.dir=true; - sPreview.pos=loopEnd-1; - break; - case DIV_SAMPLE_LOOPMODE_PINGPONG: - sPreview.dir=true; - sPreview.pos=loopEnd-1; - break; - case DIV_SAMPLE_LOOPMODE_ONESHOT: - default: - break; + } else { // forward + if (sPreview.pos>=s->loopEnd || (sPreview.pEnd>=0 && sPreview.pos>=sPreview.pEnd)) { + if (s->isLoopable() && sPreview.pos>=s->loopStart) { + switch (s->loopMode) { + case DivSampleLoopMode::DIV_SAMPLE_LOOP_FORWARD: + sPreview.pos=s->loopStart; + sPreview.dir=false; + break; + case DivSampleLoopMode::DIV_SAMPLE_LOOP_BACKWARD: + sPreview.pos=s->loopEnd-1; + sPreview.dir=true; + break; + case DivSampleLoopMode::DIV_SAMPLE_LOOP_PINGPONG: + sPreview.pos=s->loopEnd-1; + sPreview.dir=true; + break; + default: + break; + } } } } } - - if (sPreview.dir) { - if ((s->isLoopable() && sPreview.posloopEnd) && ((int)sPreview.pos)loopMode) { - case DIV_SAMPLE_LOOPMODE_FORWARD: - sPreview.dir=false; - sPreview.pos=loopStart+1; - break; - case DIV_SAMPLE_LOOPMODE_BACKWARD: - sPreview.dir=true; - sPreview.pos=loopEnd-1; - break; - case DIV_SAMPLE_LOOPMODE_PINGPONG: - sPreview.dir=false; - sPreview.pos=loopStart+1; - break; - case DIV_SAMPLE_LOOPMODE_ONESHOT: - default: - if (((int)sPreview.pos)<0) { - sPreview.sample=-1; - } - break; + if (sPreview.dir) { // backward + if (sPreview.pos<=s->loopStart || (sPreview.pBegin>=0 && sPreview.pos<=sPreview.pBegin)) { + if (s->isLoopable() && sPreview.pos>=s->loopStart) { + switch (s->loopMode) { + case DivSampleLoopMode::DIV_SAMPLE_LOOP_FORWARD: + sPreview.pos=s->loopStart; + sPreview.dir=false; + break; + case DivSampleLoopMode::DIV_SAMPLE_LOOP_BACKWARD: + sPreview.pos=s->loopEnd-1; + sPreview.dir=true; + break; + case DivSampleLoopMode::DIV_SAMPLE_LOOP_PINGPONG: + sPreview.pos=s->loopStart; + sPreview.dir=false; + break; + default: + break; + } + } else if (sPreview.pos<0) { + sPreview.sample=-1; } - } else if (((int)sPreview.pos)<0) { - sPreview.sample=-1; } - } else { - if ((s->isLoopable() && (int)sPreview.pos>=s->loopStart) && ((int)sPreview.pos)>=loopEnd) { - switch (s->loopMode) { - case DIV_SAMPLE_LOOPMODE_FORWARD: - sPreview.dir=false; - sPreview.pos=loopStart; - break; - case DIV_SAMPLE_LOOPMODE_BACKWARD: - sPreview.dir=true; - sPreview.pos=loopEnd-1; - break; - case DIV_SAMPLE_LOOPMODE_PINGPONG: - sPreview.dir=true; - sPreview.pos=loopEnd-1; - break; - case DIV_SAMPLE_LOOPMODE_ONESHOT: - default: - if (sPreview.pos>=s->samples) { - sPreview.sample=-1; - } - break; + } else { // forward + if (sPreview.pos>=s->loopEnd || (sPreview.pEnd>=0 && sPreview.pos>=sPreview.pEnd)) { + if (s->isLoopable() && sPreview.pos>=s->loopStart) { + switch (s->loopMode) { + case DivSampleLoopMode::DIV_SAMPLE_LOOP_FORWARD: + sPreview.pos=s->loopStart; + sPreview.dir=false; + break; + case DivSampleLoopMode::DIV_SAMPLE_LOOP_BACKWARD: + sPreview.pos=s->loopEnd-1; + sPreview.dir=true; + break; + case DivSampleLoopMode::DIV_SAMPLE_LOOP_PINGPONG: + sPreview.pos=s->loopEnd-1; + sPreview.dir=true; + break; + default: + break; + } + } else if (sPreview.pos>=(int)s->samples) { + sPreview.sample=-1; } - } else if (sPreview.pos>=s->samples) { - sPreview.sample=-1; } } } else if (sPreview.wave>=0 && sPreview.wave<(int)song.wave.size()) { @@ -1325,7 +1479,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi } else { samp_temp=((MIN(wave->data[sPreview.pos],wave->max)<<14)/wave->max)-8192; } - if (++sPreview.pos>=(unsigned int)wave->len) { + if (++sPreview.pos>=wave->len) { sPreview.pos=0; } blip_add_delta(samp_bb,i,samp_temp-samp_prevSample); @@ -1355,25 +1509,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 3cb1f4b1f..76e337a3d 100644 --- a/src/engine/safeReader.cpp +++ b/src/engine/safeReader.cpp @@ -77,6 +77,85 @@ 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); @@ -144,6 +223,7 @@ double SafeReader::readD() { 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 f29800a4a..5af7fa09d 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]; @@ -139,4 +204,4 @@ void SafeWriter::finish() { delete[] buf; buf=NULL; operative=false; -} \ No newline at end of file +} diff --git a/src/engine/safeWriter.h b/src/engine/safeWriter.h index 9072c61d7..414417fd2 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 a4bb5cd36..eb3f32c8b 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -33,13 +33,155 @@ extern "C" { #include "../../extern/adpcm/ymb_codec.h" #include "../../extern/adpcm/ymz_codec.h" } +#include "brrUtils.h" DivSampleHistory::~DivSampleHistory() { if (data!=NULL) delete[] data; } bool DivSample::isLoopable() { - return ((loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && (loopStart>=0 && loopStart<(int)samples) && loopEnd<=samples); + return loop && ((loopStart>=0 && loopStartloopStart && loopEnd<=(int)samples)); +} + +int DivSample::getSampleOffset(int offset, int length, DivSampleDepth depth) { + if ((length==0) || (offset==length)) { + int off=offset; + switch (depth) { + case DIV_SAMPLE_DEPTH_1BIT: + off=(offset+7)/8; + break; + case DIV_SAMPLE_DEPTH_1BIT_DPCM: + off=(offset+7)/8; + break; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: + off=(offset+1)/2; + break; + case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: + off=(offset+1)/2; + break; + case DIV_SAMPLE_DEPTH_ADPCM_A: + off=(offset+1)/2; + break; + case DIV_SAMPLE_DEPTH_ADPCM_B: + off=(offset+1)/2; + break; + case DIV_SAMPLE_DEPTH_8BIT: + off=offset; + break; + case DIV_SAMPLE_DEPTH_BRR: + off=9*((offset+15)/16); + break; + case DIV_SAMPLE_DEPTH_VOX: + off=(offset+1)/2; + break; + case DIV_SAMPLE_DEPTH_16BIT: + off=offset*2; + break; + default: + break; + } + return off; + } else { + int off=offset; + int len=length; + switch (depth) { + case DIV_SAMPLE_DEPTH_1BIT: + off=(offset+7)/8; + len=(length+7)/8; + break; + case DIV_SAMPLE_DEPTH_1BIT_DPCM: + off=(offset+7)/8; + len=(length+7)/8; + break; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: + off=(offset+1)/2; + len=(length+1)/2; + break; + case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: + off=(offset+1)/2; + len=(length+1)/2; + break; + case DIV_SAMPLE_DEPTH_ADPCM_A: + off=(offset+1)/2; + len=(length+1)/2; + break; + case DIV_SAMPLE_DEPTH_ADPCM_B: + off=(offset+1)/2; + len=(length+1)/2; + break; + case DIV_SAMPLE_DEPTH_8BIT: + off=offset; + len=length; + break; + case DIV_SAMPLE_DEPTH_BRR: + off=9*((offset+15)/16); + len=9*((length+15)/16); + break; + case DIV_SAMPLE_DEPTH_VOX: + off=(offset+1)/2; + len=(length+1)/2; + break; + case DIV_SAMPLE_DEPTH_16BIT: + off=offset*2; + len=length*2; + break; + default: + break; + } + return isLoopable()?off:len; + } +} + +int DivSample::getLoopStartPosition(DivSampleDepth depth) { + return getSampleOffset(loopStart,0,depth); +} + +int DivSample::getLoopEndPosition(DivSampleDepth depth) { + return getSampleOffset(loopEnd,samples,depth); +} + +int DivSample::getEndPosition(DivSampleDepth depth) { + int off=samples; + switch (depth) { + case DIV_SAMPLE_DEPTH_1BIT: + off=length1; + break; + case DIV_SAMPLE_DEPTH_1BIT_DPCM: + off=lengthDPCM; + break; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: + off=lengthZ; + break; + case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: + off=lengthQSoundA; + break; + case DIV_SAMPLE_DEPTH_ADPCM_A: + off=lengthA; + break; + case DIV_SAMPLE_DEPTH_ADPCM_B: + off=lengthB; + break; + case DIV_SAMPLE_DEPTH_8BIT: + off=length8; + break; + case DIV_SAMPLE_DEPTH_BRR: + off=lengthBRR; + break; + case DIV_SAMPLE_DEPTH_VOX: + off=lengthVOX; + break; + case DIV_SAMPLE_DEPTH_16BIT: + off=length16; + break; + default: + break; + } + return off; +} + +void DivSample::setSampleCount(unsigned int count) { + samples=count; + if ((!isLoopable()) || loopEnd<0 || loopEnd>(int)samples) loopEnd=samples; } bool DivSample::save(const char* path) { @@ -57,7 +199,7 @@ bool DivSample::save(const char* path) { si.channels=1; si.samplerate=rate; switch (depth) { - case 8: // 8-bit + case DIV_SAMPLE_DEPTH_8BIT: // 8-bit si.format=SF_FORMAT_PCM_U8|SF_FORMAT_WAV; break; default: // 16-bit @@ -83,14 +225,14 @@ bool DivSample::save(const char* path) { if(isLoopable()) { inst.loop_count = 1; - inst.loops[0].mode = SF_LOOP_NONE+loopMode; + inst.loops[0].mode = (int)loopMode+SF_LOOP_FORWARD; inst.loops[0].start = loopStart; inst.loops[0].end = loopEnd; } sf_command(f, SFC_SET_INSTRUMENT, &inst, sizeof(inst)); switch (depth) { - case 8: { + case DIV_SAMPLE_DEPTH_8BIT: { // convert from signed to unsigned unsigned char* buf=new unsigned char[length8]; for (size_t i=0; isamples) { - loopEnd=samples; - } + setSampleCount(count); return true; } @@ -202,7 +341,7 @@ bool DivSample::resize(unsigned int count) { } else { initInternal(DIV_SAMPLE_DEPTH_8BIT,count); } - samples=count; + setSampleCount(count); return true; } else if (depth==DIV_SAMPLE_DEPTH_16BIT) { if (data16!=NULL) { @@ -214,7 +353,7 @@ bool DivSample::resize(unsigned int count) { } else { initInternal(DIV_SAMPLE_DEPTH_16BIT,count); } - samples=count; + setSampleCount(count); return true; } return false; @@ -241,7 +380,7 @@ bool DivSample::strip(unsigned int begin, unsigned int end) { // do nothing return true; } - samples=count; + setSampleCount(count); return true; } else if (depth==DIV_SAMPLE_DEPTH_16BIT) { if (data16!=NULL) { @@ -259,7 +398,7 @@ bool DivSample::strip(unsigned int begin, unsigned int end) { // do nothing return true; } - samples=count; + setSampleCount(count); return true; } return false; @@ -280,7 +419,7 @@ bool DivSample::trim(unsigned int begin, unsigned int end) { // do nothing return true; } - samples=count; + setSampleCount(count); return true; } else if (depth==DIV_SAMPLE_DEPTH_16BIT) { if (data16!=NULL) { @@ -293,7 +432,7 @@ bool DivSample::trim(unsigned int begin, unsigned int end) { // do nothing return true; } - samples=count; + setSampleCount(count); return true; } return false; @@ -316,7 +455,7 @@ bool DivSample::insert(unsigned int pos, unsigned int length) { } else { initInternal(DIV_SAMPLE_DEPTH_8BIT,count); } - samples=count; + setSampleCount(count); return true; } else if (depth==DIV_SAMPLE_DEPTH_16BIT) { if (data16!=NULL) { @@ -333,7 +472,7 @@ bool DivSample::insert(unsigned int pos, unsigned int length) { } else { initInternal(DIV_SAMPLE_DEPTH_16BIT,count); } - samples=count; + setSampleCount(count); return true; } return false; @@ -360,11 +499,10 @@ bool DivSample::insert(unsigned int pos, unsigned int length) { #define RESAMPLE_END \ if (loopStart>=0) loopStart=(double)loopStart*(r/(double)rate); \ - if (loopEnd=0) loopEnd=(double)loopEnd*(r/(double)rate); \ centerRate=(int)((double)centerRate*(r/(double)rate)); \ rate=r; \ samples=finalCount; \ - if (loopEnd>samples) loopEnd=samples; \ if (depth==DIV_SAMPLE_DEPTH_16BIT) { \ delete[] oldData16; \ } else if (depth==DIV_SAMPLE_DEPTH_8BIT) { \ @@ -714,7 +852,7 @@ void DivSample::render() { } break; case DIV_SAMPLE_DEPTH_BRR: // BRR - // TODO! + brrDecode(dataBRR,data16,samples); break; case DIV_SAMPLE_DEPTH_VOX: // VOX oki_decode(dataVOX,data16,samples); @@ -771,7 +909,10 @@ void DivSample::render() { data8[i]=data16[i]>>8; } } - // TODO: BRR! + if (depth!=DIV_SAMPLE_DEPTH_BRR) { // BRR + if (!initInternal(DIV_SAMPLE_DEPTH_BRR,samples)) return; + brrEncode(data16,dataBRR,samples); + } if (depth!=DIV_SAMPLE_DEPTH_VOX) { // VOX if (!initInternal(DIV_SAMPLE_DEPTH_VOX,samples)) return; oki_encode(data16,dataVOX,samples); @@ -801,7 +942,7 @@ void* DivSample::getCurBuf() { case DIV_SAMPLE_DEPTH_16BIT: return data16; default: - break; + return NULL; } return NULL; } @@ -829,7 +970,7 @@ unsigned int DivSample::getCurBufLen() { case DIV_SAMPLE_DEPTH_16BIT: return length16; default: - break; + return 0; } return 0; } @@ -844,9 +985,9 @@ DivSampleHistory* DivSample::prepareUndo(bool data, bool doNotPush) { duplicate=new unsigned char[getCurBufLen()]; memcpy(duplicate,getCurBuf(),getCurBufLen()); } - h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,rate,centerRate,loopStart,loopEnd,loopMode); + h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,rate,centerRate,loopStart,loopEnd,loop,loopMode); } else { - h=new DivSampleHistory(depth,rate,centerRate,loopStart,loopEnd,loopMode); + h=new DivSampleHistory(depth,rate,centerRate,loopStart,loopEnd,loop,loopMode); } if (!doNotPush) { while (!redoHist.empty()) { @@ -878,6 +1019,7 @@ DivSampleHistory* DivSample::prepareUndo(bool data, bool doNotPush) { centerRate=h->centerRate; \ loopStart=h->loopStart; \ loopEnd=h->loopEnd; \ + loop=h->loop; \ loopMode=h->loopMode; diff --git a/src/engine/sample.h b/src/engine/sample.h index cc5bbeb50..e3b7743d8 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -25,14 +25,11 @@ #include "../ta-utils.h" #include -enum DivResampleFilters { - DIV_RESAMPLE_NONE=0, - DIV_RESAMPLE_LINEAR, - DIV_RESAMPLE_CUBIC, - DIV_RESAMPLE_BLEP, - DIV_RESAMPLE_SINC, - DIV_RESAMPLE_BEST, - DIV_RESAMPLE_MAX // for identify boundary +enum DivSampleLoopMode: unsigned char { + DIV_SAMPLE_LOOP_FORWARD=0, + DIV_SAMPLE_LOOP_BACKWARD, + DIV_SAMPLE_LOOP_PINGPONG, + DIV_SAMPLE_LOOP_MAX // boundary for loop mode }; enum DivSampleDepth: unsigned char { @@ -46,26 +43,28 @@ enum DivSampleDepth: unsigned char { DIV_SAMPLE_DEPTH_BRR=9, DIV_SAMPLE_DEPTH_VOX=10, DIV_SAMPLE_DEPTH_16BIT=16, - DIV_SAMPLE_DEPTH_MAX // for identify boundary + DIV_SAMPLE_DEPTH_MAX // boundary for sample depth }; -enum DivSampleLoopMode: unsigned char { - DIV_SAMPLE_LOOPMODE_ONESHOT=0, - DIV_SAMPLE_LOOPMODE_FORWARD, - DIV_SAMPLE_LOOPMODE_BACKWARD, - DIV_SAMPLE_LOOPMODE_PINGPONG, - DIV_SAMPLE_LOOPMODE_MAX // for identify boundary +enum DivResampleFilters { + DIV_RESAMPLE_NONE=0, + DIV_RESAMPLE_LINEAR, + DIV_RESAMPLE_CUBIC, + DIV_RESAMPLE_BLEP, + DIV_RESAMPLE_SINC, + DIV_RESAMPLE_BEST, + DIV_RESAMPLE_MAX // for identify boundary }; struct DivSampleHistory { unsigned char* data; unsigned int length, samples; DivSampleDepth depth; - int rate, centerRate, loopStart; - unsigned int loopEnd; + int rate, centerRate, loopStart, loopEnd; + bool loop; DivSampleLoopMode loopMode; bool hasSample; - DivSampleHistory(void* d, unsigned int l, unsigned int s, DivSampleDepth de, int r, int cr, int ls, unsigned int le, DivSampleLoopMode lm): + DivSampleHistory(void* d, unsigned int l, unsigned int s, DivSampleDepth de, int r, int cr, int ls, int le, bool lp, DivSampleLoopMode lm): data((unsigned char*)d), length(l), samples(s), @@ -74,9 +73,10 @@ struct DivSampleHistory { centerRate(cr), loopStart(ls), loopEnd(le), + loop(lp), loopMode(lm), hasSample(true) {} - DivSampleHistory(DivSampleDepth de, int r, int cr, int ls, unsigned int le, DivSampleLoopMode lm): + DivSampleHistory(DivSampleDepth de, int r, int cr, int ls, int le, bool lp, DivSampleLoopMode lm): data(NULL), length(0), samples(0), @@ -85,6 +85,7 @@ struct DivSampleHistory { centerRate(cr), loopStart(ls), loopEnd(le), + loop(lp), loopMode(lm), hasSample(false) {} ~DivSampleHistory(); @@ -92,14 +93,7 @@ struct DivSampleHistory { struct DivSample { String name; - int rate, centerRate, loopStart, loopOffP; - unsigned int loopEnd; - // valid values are: - // - 0: One Shot (Loop disable) - // - 1: Foward loop - // - 2: Backward loop - // - 3: Pingpong loop - DivSampleLoopMode loopMode; + int rate, centerRate, loopStart, loopEnd, loopOffP; // valid values are: // - 0: ZX Spectrum overlay drum (1-bit) // - 1: 1-bit NES DPCM (1-bit) @@ -112,6 +106,12 @@ struct DivSample { // - 10: VOX ADPCM // - 16: 16-bit PCM DivSampleDepth depth; + bool loop; + // valid values are: + // - 0: Forward loop + // - 1: Backward loop + // - 2: Pingpong loop + DivSampleLoopMode loopMode; // these are the new data structures. signed char* data8; // 8 @@ -127,13 +127,48 @@ struct DivSample { unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthBRR, lengthVOX; unsigned int off8, off16, off1, offDPCM, offZ, offQSoundA, offA, offB, offBRR, offVOX; - unsigned int offSegaPCM, offQSound, offX1_010, offES5506, offSU, offYMZ280B, offRF5C68; + unsigned int offSegaPCM, offQSound, offX1_010, offES5506, offSU, offYMZ280B, offRF5C68, offSNES; unsigned int samples; std::deque undoHist; std::deque redoHist; + /** + * check if sample is loopable. + * @return whether it is loopable. + */ + bool isLoopable(); + + /** + * get sample start position + * @return the samples start position. + */ + int getLoopStartPosition(DivSampleDepth depth=DIV_SAMPLE_DEPTH_MAX); + + /** + * get sample loop end position + * @return the samples loop end position. + */ + int getLoopEndPosition(DivSampleDepth depth=DIV_SAMPLE_DEPTH_MAX); + + /** + * get sample end position + * @return the samples end position. + */ + int getEndPosition(DivSampleDepth depth=DIV_SAMPLE_DEPTH_MAX); + + /** + * get sample offset + * @return the sample offset. + */ + int getSampleOffset(int offset, int length, DivSampleDepth depth=DIV_SAMPLE_DEPTH_MAX); + + /** + * @warning DO NOT USE - internal functions + */ + void setSampleCount(unsigned int count); + /** * @warning DO NOT USE - internal functions */ @@ -258,11 +293,12 @@ struct DivSample { name(""), rate(32000), centerRate(8363), - loopStart(0), + loopStart(-1), + loopEnd(-1), loopOffP(0), - loopEnd(~0), - loopMode(DIV_SAMPLE_LOOPMODE_ONESHOT), depth(DIV_SAMPLE_DEPTH_16BIT), + loop(false), + loopMode(DIV_SAMPLE_LOOP_FORWARD), data8(NULL), data16(NULL), data1(NULL), @@ -300,6 +336,7 @@ struct DivSample { offSU(0), offYMZ280B(0), offRF5C68(0), + offSNES(0), samples(0) {} ~DivSample(); }; diff --git a/src/engine/song.cpp b/src/engine/song.cpp index 216a1bc4d..1adb33ebb 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 b7cc8b60d..128b953c4 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -139,6 +139,8 @@ struct DivSubSong { String chanShortName[DIV_MAX_CHANS]; void clearData(); + void optimizePatterns(); + void rearrangePatterns(); DivSubSong(): hilightA(4), @@ -354,7 +356,7 @@ struct DivSong { // - 1: stereo // - YM2203: // - bit 0-4: clock rate - // - 0: 3.58MHz (MTSC) + // - 0: 3.58MHz (NTSC) // - 1: 3.55MHz (PAL) // - 2: 4MHz // - 3: 3MHz @@ -374,7 +376,7 @@ struct DivSong { // - 2: FM: clock / 48, SSG: clock / 8 // - YM3526, YM3812, Y8950: // - bit 0-7: clock rate - // - 0: 3.58MHz (MTSC) + // - 0: 3.58MHz (NTSC) // - 1: 3.55MHz (PAL) // - 2: 4MHz // - 3: 3MHz @@ -382,7 +384,7 @@ struct DivSong { // - 5: 3.5MHz // - YMF262: // - bit 0-7: clock rate - // - 0: 14.32MHz (MTSC) + // - 0: 14.32MHz (NTSC) // - 1: 14.19MHz (PAL) // - 2: 14MHz // - 3: 16MHz @@ -390,7 +392,7 @@ struct DivSong { // - YMF289B: (TODO) // - bit 0-7: clock rate // - 0: 33.8688MHz - // - 1: 28.64MHz (MTSC) + // - 1: 28.64MHz (NTSC) // - 2: 28.38MHz (PAL) // - MSM6295: // - bit 0-6: clock rate @@ -421,7 +423,7 @@ struct DivSong { // - YMZ280B: // - bit 0-7: clock rate // - 0: 16.9344MHz - // - 1: 14.32MHz (MTSC) + // - 1: 14.32MHz (NTSC) // - 2: 14.19MHz (PAL) // - 3: 16MHz // - 4: 16.67MHz @@ -429,7 +431,7 @@ struct DivSong { 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: @@ -439,7 +441,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; @@ -462,6 +464,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; @@ -498,6 +510,10 @@ struct DivSong { bool volMacroLinger; bool brokenOutVol; bool e1e2StopOnSameNote; + bool brokenPortaArp; + bool snNoLowPeriods; + bool disableSampleMacro; + bool autoSystem; std::vector ins; std::vector wave; @@ -541,6 +557,7 @@ struct DivSong { systemLen(2), name(""), author(""), + systemName(""), carrier(""), composer(""), vendor(""), @@ -560,8 +577,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), @@ -596,7 +615,11 @@ struct DivSong { newVolumeScaling(true), volMacroLinger(true), brokenOutVol(false), - e1e2StopOnSameNote(false) { + e1e2StopOnSameNote(false), + brokenPortaArp(false), + snNoLowPeriods(false), + disableSampleMacro(true), + autoSystem(true) { 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 34f887ba0..ed163e4d8 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,235 @@ 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 effectValShift(unsigned char, unsigned char val) { + return val< 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); +}; + +template int effectValLongShift(unsigned char cmd, unsigned char val) { + return (((((unsigned int)cmd)&((1<<(bits-8))-1))<<8)|((unsigned int)val))< 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 fmOPNAPostEffectHandlerMap(fmOPNPostEffectHandlerMap); + fmOPNAPostEffectHandlerMap.insert({ + {0x31, {DIV_CMD_ADPCMA_GLOBAL_VOLUME, "1Fxx: Set ADPCM-A global volume (0 to 3F)"}}, + }); + + 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 +623,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 +642,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 +659,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"}} } ); @@ -848,29 +674,14 @@ void DivEngine::registerSystems() { {"Pulse 1", "Pulse 2", "Triangle", "Noise", "PCM"}, {"S1", "S2", "TR", "NO", "PCM"}, {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}, + {DIV_INS_NES, DIV_INS_NES, DIV_INS_NES, DIV_INS_NES, 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 +705,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 +717,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,27 +727,16 @@ 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.", {"FM 1", "FM 2", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6"}, {"F1", "F2", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6"}, {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 + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA}, + {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_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + fmEffectHandlerMap, + fmOPNAPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_YM2610_EXT]=new DivSysDef( @@ -945,10 +745,10 @@ void DivEngine::registerSystems() { {"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6"}, {"F1", "O1", "O2", "O3", "O4", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6"}, {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 + {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_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA}, + {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_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + fmEffectHandlerMap, + fmOPNAPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_AY8910]=new DivSysDef( @@ -959,8 +759,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 +771,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"}}, } ); @@ -996,26 +785,12 @@ void DivEngine::registerSystems() { {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8"}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8"}, {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}, + {DIV_INS_OPM, DIV_INS_OPM, DIV_INS_OPM, DIV_INS_OPM, DIV_INS_OPM, DIV_INS_OPM, DIV_INS_OPM, DIV_INS_OPM}, {}, - 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 +799,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 +811,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 +823,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 +839,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 +851,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 +862,7 @@ void DivEngine::registerSystems() { {DIV_CH_PULSE}, {DIV_INS_PET}, {}, - waveOnlyEffectHandler + waveOnlyEffectHandlerMap ); sysDefs[DIV_SYSTEM_SNES]=new DivSysDef( @@ -1138,32 +882,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 +896,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 +908,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"}}, } ); @@ -1217,76 +924,35 @@ void DivEngine::registerSystems() { {"Pulse 1", "Pulse 2", "PCM"}, {"S1", "S2", "PCM"}, {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM}, - {DIV_INS_STD, DIV_INS_STD, DIV_INS_AMIGA}, + {DIV_INS_NES, DIV_INS_NES, 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 +964,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( @@ -1320,22 +986,22 @@ void DivEngine::registerSystems() { {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"}, {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"}, {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 + {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_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMB}, + {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_AMIGA}, + fmEffectHandlerMap, + fmOPNAPostEffectHandlerMap ); 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 + {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_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMB}, + {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_NULL, DIV_INS_NULL, DIV_INS_AMIGA}, + fmEffectHandlerMap, + fmOPNAPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_OPL]=new DivSysDef( @@ -1346,8 +1012,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 +1024,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 +1036,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( @@ -1407,6 +1073,7 @@ void DivEngine::registerSystems() { {"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_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_RF5C68, DIV_INS_RF5C68, DIV_INS_RF5C68, DIV_INS_RF5C68, DIV_INS_RF5C68, DIV_INS_RF5C68, DIV_INS_RF5C68, DIV_INS_RF5C68}, {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA} ); @@ -1418,27 +1085,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 +1102,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( @@ -1469,7 +1114,7 @@ void DivEngine::registerSystems() { {"Square"}, {"SQ"}, {DIV_CH_PULSE}, - {DIV_INS_STD} + {DIV_INS_BEEPER} ); sysDefs[DIV_SYSTEM_SEGAPCM]=new DivSysDef( @@ -1478,10 +1123,10 @@ void DivEngine::registerSystems() { {"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"}, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"}, {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_SEGAPCM, DIV_INS_SEGAPCM, DIV_INS_SEGAPCM, DIV_INS_SEGAPCM, DIV_INS_SEGAPCM, DIV_INS_SEGAPCM, DIV_INS_SEGAPCM, DIV_INS_SEGAPCM, DIV_INS_SEGAPCM, DIV_INS_SEGAPCM, DIV_INS_SEGAPCM, DIV_INS_SEGAPCM, DIV_INS_SEGAPCM, DIV_INS_SEGAPCM, DIV_INS_SEGAPCM, DIV_INS_SEGAPCM}, {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 +1146,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 + {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_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMB}, + {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_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + fmEffectHandlerMap, + fmOPNAPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_SFX_BEEPER]=new DivSysDef( @@ -1525,31 +1170,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 +1196,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 +1207,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 +1219,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 +1231,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( @@ -1619,10 +1241,10 @@ void DivEngine::registerSystems() { {"FM 1", "FM 2", "FM 3", "FM 4", "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", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, {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 + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMB}, + {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_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + fmEffectHandlerMap, + fmOPNAPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_YM2610_FULL_EXT]=new DivSysDef( @@ -1631,10 +1253,10 @@ void DivEngine::registerSystems() { {"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "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", "O1", "O2", "O3", "O4", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, {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 + {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_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMB}, + {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_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + fmEffectHandlerMap, + fmOPNAPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_OPLL_DRUMS]=new DivSysDef( @@ -1645,10 +1267,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,46 +1285,29 @@ 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.", {"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", "ADPCM 1", "ADPCM 2", "ADPCM 3"}, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "A1", "A2", "A3"}, {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_QSOUND, DIV_INS_QSOUND, DIV_INS_QSOUND, DIV_INS_QSOUND, DIV_INS_QSOUND, DIV_INS_QSOUND, DIV_INS_QSOUND, DIV_INS_QSOUND, DIV_INS_QSOUND, DIV_INS_QSOUND, DIV_INS_QSOUND, DIV_INS_QSOUND, DIV_INS_QSOUND, DIV_INS_QSOUND, DIV_INS_QSOUND, DIV_INS_QSOUND, DIV_INS_QSOUND, DIV_INS_QSOUND, DIV_INS_QSOUND}, {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 +1318,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 + {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_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMB}, + {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_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + fmEffectHandlerMap, + fmOPNAPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_SEGAPCM_COMPAT]=new DivSysDef( @@ -1740,10 +1342,10 @@ void DivEngine::registerSystems() { {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"}, {"P1", "P2", "P3", "P4", "P5"}, {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_SEGAPCM, DIV_INS_SEGAPCM, DIV_INS_SEGAPCM, DIV_INS_SEGAPCM, DIV_INS_SEGAPCM}, {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 +1356,19 @@ 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"}}, + {0x12, {DIV_CMD_X1_010_SAMPLE_BANK_SLOT, "12xx: Set sample bank slot (0 to 7)"}}, + {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 +1380,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 +1394,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"}, @@ -1827,115 +1402,71 @@ void DivEngine::registerSystems() { {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, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM} ); + EffectHandlerMap es5506PreEffectHandlerMap={ + {0x10, {DIV_CMD_WAVE, "10xx: Change waveform or sample, transwave index (00 to FF)",effectVal}}, + {0x11, {DIV_CMD_ES5506_FILTER_MODE, "11xx: Set filter mode (00 to 03)",effectValAnd<3>}}, + {0x13, {DIV_CMD_SAMPLE_TRANSWAVE_SLICE_MODE, "130x: Set transwave slice mode (bit 0)",effectValAnd<1>}}, + {0x14, {DIV_CMD_ES5506_FILTER_K1, "14xx: Set filter coefficient K1 low byte (00 to FF)",effectValShift<0>,constVal<0x00ff>}}, + {0x15, {DIV_CMD_ES5506_FILTER_K1, "15xx: Set filter coefficient K1 high byte (00 to FF)",effectValShift<8>,constVal<0xff00>}}, + {0x16, {DIV_CMD_ES5506_FILTER_K2, "16xx: Set filter coefficient K2 low byte (00 to FF)",effectValShift<0>,constVal<0x00ff>}}, + {0x17, {DIV_CMD_ES5506_FILTER_K2, "17xx: Set filter coefficient K2 high byte (00 to FF)",effectValShift<8>,constVal<0xff00>}}, + {0x22, {DIV_CMD_ES5506_ENVELOPE_LVRAMP, "22xx: Set envelope left volume ramp (signed) (00 to FF)",effectVal}}, + {0x23, {DIV_CMD_ES5506_ENVELOPE_RVRAMP, "23xx: Set envelope right volume ramp (signed) (00 to FF)",effectVal}}, + {0x24, {DIV_CMD_ES5506_ENVELOPE_K1RAMP, "24xx: Set envelope filter coefficient k1 ramp (signed) (00 to FF)",effectVal,constVal<0>}}, + {0x25, {DIV_CMD_ES5506_ENVELOPE_K1RAMP, "25xx: Set envelope filter coefficient k1 ramp (signed, slower) (00 to FF)",effectVal,constVal<1>}}, + {0x26, {DIV_CMD_ES5506_ENVELOPE_K2RAMP, "24xx: Set envelope filter coefficient k2 ramp (signed) (00 to FF)",effectVal,constVal<0>}}, + {0x27, {DIV_CMD_ES5506_ENVELOPE_K2RAMP, "25xx: Set envelope filter coefficient k2 ramp (signed, slower) (00 to FF)",effectVal,constVal<1>}} + }; + EffectHandlerMap es5506PostEffectHandlerMap={ + {0x12, {DIV_CMD_ES5506_PAUSE, "120x: Set pause (bit 0)",effectValAnd<1>}}, + {0x18, {DIV_CMD_ES5506_FILTER_K1_SLIDE, "18xx: Set filter coefficient K1 slide up (00 to FF)",effectVal,constVal<0>}}, + {0x19, {DIV_CMD_ES5506_FILTER_K1_SLIDE, "19xx: Set filter coefficient K1 slide down (00 to FF)",effectVal,constVal<1>}}, + {0x1a, {DIV_CMD_ES5506_FILTER_K2_SLIDE, "1axx: Set filter coefficient K2 slide up (00 to FF)",effectVal,constVal<0>}}, + {0x1b, {DIV_CMD_ES5506_FILTER_K2_SLIDE, "1bxx: Set filter coefficient K2 slide down (00 to FF)",effectVal,constVal<1>}}, + }; + const EffectHandler es5506ECountHandler(DIV_CMD_ES5506_ENVELOPE_COUNT, "2xxx: Set envelope count (000 to 1FF)", effectValLong<9>); + const EffectHandler es5506K1Handler(DIV_CMD_ES5506_FILTER_K1, "3xxx: Set filter coefficient K1 (000 to FFF)", effectValLongShift<12,4>); + const EffectHandler es5506K2Handler(DIV_CMD_ES5506_FILTER_K2, "4xxx: Set filter coefficient K2 (000 to FFF)", effectValLongShift<12,4>); + const EffectHandler transWaveSlicePositionHandler(DIV_CMD_SAMPLE_TRANSWAVE_SLICE_POS, "5xxx: Set transwave slice point (000 to FFF)", effectValLong<12>); + for (int i=0; i<2; i++) es5506PreEffectHandlerMap.emplace(0x20+i,es5506ECountHandler); + for (int i=0; i<16; i++) es5506PreEffectHandlerMap.emplace(0x30+i, es5506K1Handler); + for (int i=0; i<16; i++) es5506PreEffectHandlerMap.emplace(0x40+i, es5506K2Handler); + for (int i=0; i<16; i++) es5506PreEffectHandlerMap.emplace(0x50+i, transWaveSlicePositionHandler); + sysDefs[DIV_SYSTEM_ES5506]=new DivSysDef( "Ensoniq ES5506", NULL, 0xb1, 0, 32, false, true, 0/*0x171*/, false, - "a sample chip used in the Ensoniq's unique transwave synthesizers, \nand SoundScape series PC ISA soundcards...\nthat's just yet another (partially)SB compatible one with emulated OPL3 and MIDI ROMpler.", + "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}, {DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506}, {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, 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: // filter mode - dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_MODE,ch,effectVal&3)); - break; - case 0x12: // pause - dispatchCmd(DivCommand(DIV_CMD_ES5506_PAUSE,ch,effectVal&1)); - break; - case 0x13: // slice enable - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_TRANSWAVE_SLICE_MODE,ch,effectVal&1)); - break; - case 0x14: // filter coefficient K1, 8 bit LSB - dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K1,ch,effectVal&0xff,0x00ff)); - break; - case 0x15: // filter coefficient K1, 8 bit MSB - dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K1,ch,(effectVal&0xff)<<8,0xff00)); - break; - case 0x16: // filter coefficient K2, 8 bit LSB - dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K2,ch,effectVal&0xff,0x00ff)); - break; - case 0x17: // filter coefficient K2, 8 bit MSB - dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K2,ch,(effectVal&0xff)<<8,0xff00)); - break; - case 0x20: - case 0x21: // envelope ECOUNT - dispatchCmd(DivCommand(DIV_CMD_ES5506_ENVELOPE_COUNT,ch,((effect&0x01)<<8)|effectVal)); - break; - case 0x22: // envelope LVRAMP - dispatchCmd(DivCommand(DIV_CMD_ES5506_ENVELOPE_LVRAMP,ch,effectVal)); - break; - case 0x23: // envelope RVRAMP - dispatchCmd(DivCommand(DIV_CMD_ES5506_ENVELOPE_RVRAMP,ch,effectVal)); - break; - case 0x24: - case 0x25: // envelope K1RAMP - dispatchCmd(DivCommand(DIV_CMD_ES5506_ENVELOPE_K1RAMP,ch,effectVal,effect&0x01)); - break; - case 0x26: - case 0x27: // envelope K2RAMP - dispatchCmd(DivCommand(DIV_CMD_ES5506_ENVELOPE_K2RAMP,ch,effectVal,effect&0x01)); - break; - default: - if ((effect&0xf0)==0x30) { // filter coefficient K1, 12 bit MSB - dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K1,ch,((effect&0x0f)<<12)|((effectVal&0xff)<<4),0xfff0)); - } else if ((effect&0xf0)==0x40) { // filter coefficient K2, 12 bit MSB - dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K2,ch,((effect&0x0f)<<12)|((effectVal&0xff)<<4),0xfff0)); - } else if ((effect&0xf0)==0x50) { // slice position - dispatchCmd(DivCommand(DIV_CMD_SAMPLE_TRANSWAVE_SLICE_POS,ch,((effect&0x0f)<<8)|(effectVal&0xff))); - } else { - return false; - } - break; - } - return true; - }, - [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { - switch (effect) { - case 0x12: // pause - dispatchCmd(DivCommand(DIV_CMD_ES5506_PAUSE,ch,effectVal&1)); - break; - case 0x18: // filter coefficient K1 slide up - case 0x19: // filter coefficient K1 slide down - dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K1_SLIDE,ch,effectVal,effect&0x01)); - break; - case 0x1a: // filter coefficient K2 slide up - case 0x1b: // filter coefficient K2 slide down - dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K2_SLIDE,ch,effectVal,effect&0x01)); - break; - default: - return false; - break; - } - return true; - } + es5506PreEffectHandlerMap, + es5506PostEffectHandlerMap ); 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 + {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_ADPCMB}, + {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_AMIGA}, + 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 + {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_ADPCMB}, + {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_AMIGA}, + fmEffectHandlerMap, + fmOPLPostEffectHandlerMap ); sysDefs[DIV_SYSTEM_SCC_PLUS]=new DivSysDef( @@ -1946,9 +1477,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!", @@ -1957,74 +1513,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( @@ -2033,17 +1523,10 @@ void DivEngine::registerSystems() { {"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, {"CH1", "CH2", "CH3", "CH4"}, {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_MSM6295, DIV_INS_MSM6295, DIV_INS_MSM6295, DIV_INS_MSM6295}, {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)"}}, } ); @@ -2053,20 +1536,11 @@ void DivEngine::registerSystems() { {"Sample"}, {"PCM"}, {DIV_CH_PCM}, + {DIV_INS_MSM6258}, {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)"}}, } ); @@ -2076,21 +1550,13 @@ void DivEngine::registerSystems() { {"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8"}, {"1", "2", "3", "4", "5", "6", "7", "8"}, {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_YMZ280B, DIV_INS_YMZ280B, DIV_INS_YMZ280B, DIV_INS_YMZ280B, DIV_INS_YMZ280B, DIV_INS_YMZ280B, DIV_INS_YMZ280B, DIV_INS_YMZ280B}, {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( @@ -2101,7 +1567,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( @@ -2112,7 +1578,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( @@ -2123,7 +1589,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 @@ -2144,20 +1610,20 @@ 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( @@ -2168,15 +1634,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/variable; y: thin pulse/noise)"}} } ); diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index a88869b5f..868a56216 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -545,9 +545,9 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(0x95); 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]) { - loopTimer[streamID]=(double)sample->loopEnd; + w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0)|(sampleDir[streamID]?0x10:0)); // flags + if (sample->isLoopable() && !sampleDir[streamID]) { + loopTimer[streamID]=sample->length8; loopSample[streamID]=write.val; } } @@ -841,7 +841,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; @@ -1603,7 +1603,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->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)+0xff)&(~0xff); if (alignedSize>65536) alignedSize=65536; if ((memPos&0xff0000)!=((memPos+alignedSize)&0xff0000)) { memPos=(memPos+0xffff)&0xff0000; @@ -1613,9 +1613,9 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { sample->offSegaPCM=memPos; unsigned int readPos=0; for (unsigned int j=0; jloopMode != DIV_SAMPLE_LOOPMODE_ONESHOT) && readPos>=sample->loopEnd) || readPos>=sample->length8) { + if (readPos>=(unsigned int)sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)) { if (sample->isLoopable()) { - readPos=sample->loopStart; + readPos=sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT); pcmMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80); } else { pcmMem[memPos++]=0x80; @@ -1626,7 +1626,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { readPos++; if (memPos>=16777216) break; } - sample->loopOffP=readPos-sample->loopStart; + sample->loopOffP=readPos-sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT); if (memPos>=16777216) break; } @@ -1862,6 +1862,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->loopEnd) { + if (sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)) { w->writeC(0x93); w->writeC(nextToTouch); - w->writeI(sample->off8+sample->loopStart); + w->writeI(sample->off8+sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)); w->writeC(0x81); - w->writeI(sample->loopEnd-sample->loopStart); + w->writeI(sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)-sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)); } } loopSample[nextToTouch]=-1; @@ -1987,24 +2013,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 953400ee1..9de8544fe 100644 --- a/src/engine/wavetable.cpp +++ b/src/engine/wavetable.cpp @@ -59,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 616b048cf..0f518ab53 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/engine/zsm.cpp b/src/engine/zsm.cpp new file mode 100644 index 000000000..89eb01496 --- /dev/null +++ b/src/engine/zsm.cpp @@ -0,0 +1,218 @@ +/** + * 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 "zsm.h" +#include "../ta-log.h" +#include "../utfutils.h" +#include "song.h" + +DivZSM::DivZSM() { + w = NULL; + init(); +} + +DivZSM::~DivZSM() { +} + +void DivZSM::init(unsigned int rate) { + if (w != NULL) delete w; + w = new SafeWriter; + w->init(); + // write default ZSM data header + w->write("zm",2); // magic header + w->writeC(ZSM_VERSION); + // no loop offset + w->writeS(0); + w->writeC(0); + // no PCM + w->writeS(0x00); + w->writeC(0x00); + // FM channel mask + w->writeC(0x00); + // PSG channel mask + w->writeS(0x00); + w->writeS((unsigned short)rate); + // 2 reserved bytes (set to zero) + w->writeS(0x00); + tickRate = rate; + loopOffset=-1; + numWrites=0; + memset(&ymState,-1,sizeof(ymState)); + memset(&psgState,-1,sizeof(psgState)); + ticks=0; +} + +int DivZSM::getoffset() { + return w->tell(); +} + +void DivZSM::writeYM(unsigned char a, unsigned char v) { + int lastMask = ymMask; + if (a==0x19 && v>=0x80) a=0x1a; // AMD/PSD use same reg addr. store PMD as 0x1a + if (a==0x08 && (v&0xf8)) ymMask |= (1 << (v & 0x07)); // mark chan as in-use if keyDN + if (a!=0x08) ymState[ym_NEW][a] = v; // cache the newly-written value + bool writeit=false; // used to suppress spurious writes to unused channels + if (a < 0x20) { + if (a == 0x08) { + // write keyUPDN messages if channel is active. + writeit = (ymMask & (1 << (v & 0x07))) > 0; + } + else { + // do not suppress global registers + writeit = true; + } + } else { + writeit = (ymMask & (1 << (a & 0x07))) > 0; // a&0x07 = chan ID for regs >=0x20 + } + if (lastMask != ymMask) { + // if the ymMask just changed, then the channel has become active. + // This can only happen on a KeyDN event, so voice = v & 0x07 + // insert a keyUP just to be safe. + ymwrites.push_back(DivRegWrite(0x08,v&0x07)); + numWrites++; + // flush the ym_NEW cached states for this channel into the ZSM.... + for ( int i=0x20 + (v&0x07); i <= 0xff ; i+=8) { + if (ymState[ym_NEW][i] != ymState[ym_PREV][i]) { + ymwrites.push_back(DivRegWrite(i,ymState[ym_NEW][i])); + numWrites++; + // ...and update the shadow + ymState[ym_PREV][i] = ymState[ym_NEW][i]; + } + } + } + // Handle the current write if channel is active + if (writeit && ((ymState[ym_NEW][a] != ymState[ym_PREV][a])||a==0x08) ) { + // update YM shadow if not the KeyUPDN register. + if (a!=0x008) ymState[ym_PREV][a] = ymState[ym_NEW][a]; + // if reg = PMD, then change back to real register 0x19 + if (a==0x1a) a=0x19; + ymwrites.push_back(DivRegWrite(a,v)); + numWrites++; + } +} + +void DivZSM::writePSG(unsigned char a, unsigned char v) { + // TODO: suppress writes to PSG voice that is not audible (volume=0) + if (a >= 64) { + logD ("ZSM: ignoring VERA PSG write a=%02x v=%02x",a,v); + return; + } + if(psgState[psg_PREV][a] == v) { + if (psgState[psg_NEW][a] != v) + // NEW value is being reset to the same as PREV value + // so it is no longer a new write. + numWrites--; + } else { + if (psgState[psg_PREV][a] == psgState[psg_NEW][a]) + // if this write changes the NEW cached value to something other + // than the PREV value, then this is a new write. + numWrites++; + } + psgState[psg_NEW][a] = v; + // mark channel as used in the psgMask if volume is set > 0. + if ((a % 4 == 2) && (v & 0x3f)) psgMask |= (1 << (a>>2)); +} + +void DivZSM::writePCM(unsigned char a, unsigned char v) { + // ZSM standard for PCM playback has not been established yet. +} + +void DivZSM::tick(int numticks) { + flushWrites(); + ticks += numticks; +} + +void DivZSM::setLoopPoint() { + tick(0); // flush any ticks+writes + flushTicks(); // flush ticks incase no writes were pending + logI("ZSM: loop at file offset %d bytes",w->tell()); + loopOffset=w->tell(); + // update the ZSM header's loop offset value + w->seek(0x03,SEEK_SET); + w->writeS((short)(loopOffset&0xffff)); + w->writeC((unsigned char)((loopOffset>>16)&0xff)); + w->seek(loopOffset,SEEK_SET); + // reset the PSG shadow and write cache + memset(&psgState,-1,sizeof(psgState)); + // reset the YM shadow.... + memset(&ymState[ym_PREV],-1,sizeof(ymState[ym_PREV])); + // ... and cache (except for unused channels) + memset(&ymState[ym_NEW],-1,0x20); + for (int chan=0; chan<8 ; chan++) { + // do not clear state for as-yet-unused channels + if (!(ymMask & (1<writeC(ZSM_EOF); + // update channel use masks. + w->seek(0x09,SEEK_SET); + w->writeC((unsigned char)(ymMask & 0xff)); + w->writeS((short)(psgMask & 0xffff)); + // todo: put PCM offset/data writes here once defined in ZSM standard. + return w; +} + +void DivZSM::flushWrites() { + logD("ZSM: flushWrites.... numwrites=%d ticks=%d ymwrites=%d",numWrites,ticks,ymwrites.size()); + if (numWrites==0) return; + flushTicks(); // only flush ticks if there are writes pending. + for (unsigned char i=0;i<64;i++) { + if (psgState[psg_NEW][i] == psgState[psg_PREV][i]) continue; + psgState[psg_PREV][i]=psgState[psg_NEW][i]; + w->writeC(i); + w->writeC(psgState[psg_NEW][i]); + } + int n=0; // n = completed YM writes. used to determine when to write the CMD byte... + for (DivRegWrite& write: ymwrites) { + if (n%ZSM_YM_MAX_WRITES == 0) { + if(ymwrites.size()-n > ZSM_YM_MAX_WRITES) { + w->writeC((unsigned char)(ZSM_YM_CMD+ZSM_YM_MAX_WRITES)); + logD("ZSM: YM-write: %d (%02x) [max]",ZSM_YM_MAX_WRITES,ZSM_YM_MAX_WRITES+ZSM_YM_CMD); + } else { + w->writeC((unsigned char)(ZSM_YM_CMD+ymwrites.size()-n)); + logD("ZSM: YM-write: %d (%02x)",ymwrites.size()-n,ZSM_YM_CMD+ymwrites.size()-n); + } + } + n++; + w->writeC(write.addr); + w->writeC(write.val); + } + ymwrites.clear(); + numWrites=0; +} + +void DivZSM::flushTicks() { + while (ticks > ZSM_DELAY_MAX) { + logD("ZSM: write delay %d (max)",ZSM_DELAY_MAX); + w->writeC((unsigned char)(ZSM_DELAY_CMD+ZSM_DELAY_MAX)); + ticks -= ZSM_DELAY_MAX; + } + if (ticks>0) { + logD("ZSM: write delay %d",ticks); + w->writeC(ZSM_DELAY_CMD+ticks); + } + ticks=0; +} diff --git a/src/engine/zsm.h b/src/engine/zsm.h new file mode 100644 index 000000000..af0979e7c --- /dev/null +++ b/src/engine/zsm.h @@ -0,0 +1,67 @@ +/** + * 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 _ZSM_H +#define _ZSM_H + +//#include "engine.h" +#include "safeWriter.h" +#include "dispatch.h" +#include + +#define ZSM_HEADER_SIZE 16 +#define ZSM_VERSION 1 +#define ZSM_YM_CMD 0x40 +#define ZSM_DELAY_CMD 0x80 +#define ZSM_YM_MAX_WRITES 63 +#define ZSM_DELAY_MAX 127 +#define ZSM_EOF ZSM_DELAY_CMD + +enum YM_STATE { ym_PREV, ym_NEW, ym_STATES }; +enum PSG_STATE { psg_PREV, psg_NEW, psg_STATES }; + +class DivZSM { + private: + SafeWriter* w; + int ymState[ym_STATES][256]; + int psgState[psg_STATES][64]; + std::vector ymwrites; + int loopOffset; + int numWrites; + int ticks; + int tickRate; + int ymMask = 0; + int psgMask = 0; + public: + DivZSM(); + ~DivZSM(); + void init(unsigned int rate = 60); + int getoffset(); + void writeYM(unsigned char a, unsigned char v); + void writePSG(unsigned char a, unsigned char v); + void writePCM(unsigned char a, unsigned char v); + void tick(int numticks = 1); + void setLoopPoint(); + SafeWriter* finish(); + private: + void flushWrites(); + void flushTicks(); +}; + +#endif diff --git a/src/engine/zsmOps.cpp b/src/engine/zsmOps.cpp new file mode 100644 index 000000000..47e819329 --- /dev/null +++ b/src/engine/zsmOps.cpp @@ -0,0 +1,175 @@ +/** + * 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 "engine.h" +#include "../ta-log.h" +#include "../utfutils.h" +#include "song.h" +#include "zsm.h" + +constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0; +constexpr int MASTER_CLOCK_MASK=(sizeof(void*)==8)?0xff:0; + +SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) { + + int VERA = -1; + int YM = -1; + int IGNORED = 0; + + //loop = false; + // find indexes for YM and VERA. Ignore other systems. + for (int i=0; i= 0) { IGNORED++;break; } + VERA = i; + logD("VERA detected as chip id %d",i); + break; + case DIV_SYSTEM_YM2151: + if (YM >= 0) { IGNORED++;break; } + YM = i; + logD("YM detected as chip id %d",i); + break; + default: + IGNORED++; + logD("Ignoring chip %d systemID %d",i,song.system[i]); + } + } + if (VERA < 0 && YM < 0) { + logE("No supported systems for ZSM"); + return NULL; + } + if (IGNORED > 0) + logW("ZSM export ignoring %d unsupported system%c",IGNORED,IGNORED>1?'s':' '); + + stop(); + repeatPattern=false; + setOrder(0); + BUSY_BEGIN_SOFT; + + double origRate=got.rate; + got.rate=zsmrate & 0xffff; + + // determine loop point + int loopOrder=0; + int loopRow=0; + int loopEnd=0; + walkSong(loopOrder,loopRow,loopEnd); + logI("loop point: %d %d",loopOrder,loopRow); + warnings=""; + + DivZSM zsm; + zsm.init(zsmrate); + + // reset the playback state + curOrder=0; + freelance=false; + playing=false; + extValuePresent=false; + remainingLoops=-1; + + // Prepare to write song data + playSub(false); + size_t tickCount=0; + bool done=false; + int loopPos=-1; + int writeCount=0; + int fracWait=0; // accumulates fractional ticks + if (VERA >= 0) disCont[VERA].dispatch->toggleRegisterDump(true); + if (YM >= 0) { + disCont[YM].dispatch->toggleRegisterDump(true); + zsm.writeYM(0x18,0); // initialize the LFO freq to 0 + // note - I think there's a bug where Furnace writes AMD/PMD=max + // that shouldn't be there, requiring this initialization that shouldn't + // be there for ZSM. + } + + while (!done) { + if (loopPos==-1) { + if (loopOrder==curOrder && loopRow==curRow && ticks==1 && loop) { + loopPos=zsm.getoffset(); + zsm.setLoopPoint(); + } + } + if (nextTick() || !playing) { + done=true; + if (!loop) { + for (int i=0; igetRegisterWrites().clear(); + } + break; + } + if (!playing) { + loopPos=-1; + } + } + // get register dumps + for (int j=0; j<2; j++) { + int i=0; + // dump YM writes first + if (j==0) { + if (YM < 0) + continue; + else + i=YM; + } + // dump VERA writes second + if (j==1) { + if (VERA < 0) + continue; + else { + i=VERA; + } + } + std::vector& writes=disCont[i].dispatch->getRegisterWrites(); + if (writes.size() > 0) + logD("zsmOps: Writing %d messages to chip %d",writes.size(), i); + for (DivRegWrite& write: writes) { + if (i==YM) zsm.writeYM(write.addr&0xff, write.val); + if (i==VERA) zsm.writePSG(write.addr&0xff, write.val); + writeCount++; + } + writes.clear(); + } + + // write wait + int totalWait=cycles>>MASTER_CLOCK_PREC; + fracWait += cycles & MASTER_CLOCK_MASK; + totalWait += fracWait>>MASTER_CLOCK_PREC; + fracWait &= MASTER_CLOCK_MASK; + if (totalWait>0) { + zsm.tick(totalWait); + tickCount+=totalWait; + } + } + // end of song + + // done - close out. + got.rate = origRate; + if (VERA >= 0) disCont[VERA].dispatch->toggleRegisterDump(false); + if (YM >= 0) disCont[YM].dispatch->toggleRegisterDump(false); + + remainingLoops=-1; + playing=false; + freelance=false; + extValuePresent=false; + + BUSY_END; + return zsm.finish(); +} diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 70e09e727..7e5dfdf26 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -23,16 +23,17 @@ const char* aboutLine[]={ "tildearrow", - "is proud to present", + "is not so happy to present", "", ("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,", - "time and dedication.", + "what a mess of a versioning scheme we have...", + "I mean it! these pre-releases are like normal releases", + "by now but only because I promised you to have SNES in", + "0.6pre2 I am doing this whole mess...", "", "> CREDITS <", "", @@ -49,6 +50,7 @@ const char* aboutLine[]={ "tildearrow", "BlastBrothers", "Mahbod Karamoozian", + "nicco1690", "Raijin", "", "-- documentation --", @@ -67,6 +69,7 @@ const char* aboutLine[]={ "AURORA*FIELDS", "BlueElectric05", "breakthetargets", + "brickblock369", "CaptainMalware", "DeMOSic", "DevEd", @@ -78,12 +81,17 @@ const char* aboutLine[]={ "kleeder", "jaezu", "Laggy", + "LovelyA72", "LunaMoth", + "Lunathir", + "LVintageNerd", "Mahbod Karamoozian", "Miker", "nicco1690", "NikonTeen", - "SnugglyValeria", + "psxdominator", + "Raijin", + "SnugglyBun", "SuperJet Spade", "TheDuccinator", "theloredev", @@ -96,6 +104,8 @@ const char* aboutLine[]={ "-- additional feedback/fixes --", "fd", "GENATARi", + "host12prog", + "Lunathir", "plane", "TheEssem", "", @@ -127,12 +137,14 @@ const char* aboutLine[]={ "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 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 C163 emulator by cam900", + "Namco 163 emulator by cam900", "Seta X1-010 emulator by cam900", "Konami VRC6 emulator by cam900", "Konami SCC emulator by cam900", diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index 547e59a17..89b89801b 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -63,12 +63,45 @@ float FurnaceGUI::computeGradPos(int type, int chan) { return chanOscBright[chan]; break; case GUI_OSCREF_NOTE_TRIGGER: - return keyHit[chan]*5.0f; + return keyHit1[chan]; break; } return 0.0f; } +void FurnaceGUI::calcChanOsc() { + std::vector oscBufs; + std::vector oscFFTs; + std::vector oscChans; + + int chans=e->getTotalChannelCount(); + + for (int i=0; igetOscBuffer(i); + if (buf!=NULL && e->curSubSong->chanShow[i]) { + // 30ms should be enough + int displaySize=(float)(buf->rate)*0.03f; + if (e->isRunning()) { + float minLevel=1.0f; + float maxLevel=-1.0f; + unsigned short needlePos=buf->needle; + 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 (maxLevel1.0f) estimate=1.0f; + chanOscVol[i]=MAX(chanOscVol[i]*0.87f,estimate); + } + } else { + chanOscVol[i]=MAX(chanOscVol[i]*0.87f,0.0f); + } + if (chanOscVol[i]<0.00001f) chanOscVol[i]=0.0f; + } +} + void FurnaceGUI::drawChanOsc() { if (nextWindow==GUI_WINDOW_CHAN_OSC) { chanOscOpen=true; @@ -361,7 +394,6 @@ void FurnaceGUI::drawChanOsc() { if (maxLeveldata[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f; diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index 8e5d75172..fcea0fa06 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -139,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)) { @@ -189,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."); @@ -207,31 +251,39 @@ 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"); + } + ImGui::Checkbox("Disable new sample features",&e->song.disableSampleMacro); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.6pre2"); } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_COMPAT_FLAGS; diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 62b93479b..2a1c7a66a 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]); @@ -317,6 +338,46 @@ void FurnaceGUI::drawInsList() { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPL_DRUMS]); name=fmt::sprintf(ICON_FA_COFFEE " %.2X: %s##_INS%d",i,ins->name,i); break; + case DIV_INS_OPM: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPM]); + name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_NES: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_NES]); + name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_MSM6258: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MSM6258]); + name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_MSM6295: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MSM6295]); + name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_ADPCMA: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_ADPCMA]); + name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_ADPCMB: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_ADPCMB]); + name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_SEGAPCM: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SEGAPCM]); + name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_QSOUND: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_QSOUND]); + name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_YMZ280B: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_YMZ280B]); + name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_RF5C68: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_RF5C68]); + name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + break; default: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d",i,ins->name,i); @@ -358,6 +419,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 +455,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 +474,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 +493,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 +532,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 +551,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 +570,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 +614,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 991fc7f7d..d082f30c9 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -27,6 +27,11 @@ #include "../engine/platform/nes.h" #include "../engine/platform/c64.h" #include "../engine/platform/arcade.h" +#include "../engine/platform/segapcm.h" +#include "../engine/platform/ym2203.h" +#include "../engine/platform/ym2203ext.h" +#include "../engine/platform/ym2608.h" +#include "../engine/platform/ym2608ext.h" #include "../engine/platform/ym2610.h" #include "../engine/platform/ym2610ext.h" #include "../engine/platform/ym2610b.h" @@ -36,24 +41,111 @@ #include "../engine/platform/tia.h" #include "../engine/platform/saa.h" #include "../engine/platform/amiga.h" +#include "../engine/platform/qsound.h" #include "../engine/platform/x1_010.h" #include "../engine/platform/n163.h" #include "../engine/platform/vrc6.h" #include "../engine/platform/es5506.h" +#include "../engine/platform/lynx.h" +#include "../engine/platform/pcmdac.h" #include "../engine/platform/dummy.h" -#define GENESIS_DEBUG \ +#define COMMON_CHIP_DEBUG \ + ImGui::Text("- rate: %d",ch->rate); \ + ImGui::Text("- chipClock: %d",ch->chipClock); + +#define FM_CHIP_DEBUG \ + COMMON_CHIP_DEBUG; \ + ImGui::Text("- lastBusy: %d",ch->lastBusy); \ + ImGui::Text("- delay: %d",ch->delay); + +#define FM_OPN_CHIP_DEBUG \ + FM_CHIP_DEBUG; \ + ImGui::Text("- fmFreqBase: %.f",ch->fmFreqBase); \ + ImGui::Text("- fmDivBase: %d",ch->fmDivBase); \ + ImGui::Text("- ayDiv: %d",ch->ayDiv); + +#define COMMON_CHIP_DEBUG_BOOL \ + ImGui::TextColored(ch->skipRegisterWrites?colorOn:colorOff,">> SkipRegisterWrites"); \ + ImGui::TextColored(ch->dumpWrites?colorOn:colorOff,">> DumpWrites"); + +#define FM_CHIP_DEBUG_BOOL \ + COMMON_CHIP_DEBUG_BOOL; \ + ImGui::TextColored(ch->lastBusy?colorOn:colorOff,">> LastBusy"); \ + +#define FM_OPN_CHIP_DEBUG_BOOL \ + FM_CHIP_DEBUG_BOOL; \ + ImGui::TextColored(ch->extSys?colorOn:colorOff,">> ExtSys"); \ + +#define GENESIS_CHIP_DEBUG \ + DivPlatformGenesis* ch=(DivPlatformGenesis*)data; \ + ImGui::Text("> YM2612"); \ + FM_OPN_CHIP_DEBUG; \ + ImGui::Text("- lfoValue: %d",ch->lfoValue); \ + ImGui::Text("- softPCMTimer: %d",ch->softPCMTimer); \ + FM_OPN_CHIP_DEBUG_BOOL; \ + ImGui::TextColored(ch->extMode?colorOn:colorOff,">> ExtMode"); \ + ImGui::TextColored(ch->softPCM?colorOn:colorOff,">> SoftPCM"); \ + ImGui::TextColored(ch->useYMFM?colorOn:colorOff,">> UseYMFM"); \ + ImGui::TextColored(ch->ladder?colorOn:colorOff,">> Ladder"); + +#define OPNB_CHIP_DEBUG \ + FM_OPN_CHIP_DEBUG; \ + ImGui::Text("- sampleBank: %d",ch->sampleBank); \ + ImGui::Text("- writeADPCMAOff: %d",ch->writeADPCMAOff); \ + ImGui::Text("- writeADPCMAOn: %d",ch->writeADPCMAOn); \ + ImGui::Text("- globalADPCMAVolume: %d",ch->globalADPCMAVolume); \ + ImGui::Text("- extChanOffs: %d",ch->extChanOffs); \ + ImGui::Text("- psgChanOffs: %d",ch->psgChanOffs); \ + ImGui::Text("- adpcmAChanOffs: %d",ch->adpcmAChanOffs); \ + ImGui::Text("- adpcmBChanOffs: %d",ch->adpcmBChanOffs); \ + ImGui::Text("- chanNum: %d",ch->chanNum); \ + FM_OPN_CHIP_DEBUG_BOOL; \ + ImGui::TextColored(ch->extMode?colorOn:colorOff,">> ExtMode"); + +#define SMS_CHIP_DEBUG \ + DivPlatformSMS* sms=(DivPlatformSMS*)data; \ + ImGui::Text("> SMS"); \ + ImGui::Text("- rate: %d",sms->rate); \ + ImGui::Text("- chipClock: %d",sms->chipClock); \ + ImGui::Text("- lastPan: %d",sms->lastPan); \ + ImGui::Text("- oldValue: %d",sms->oldValue); \ + ImGui::Text("- snNoiseMode: %d",sms->snNoiseMode); \ + ImGui::Text("- divider: %d",sms->divider); \ + ImGui::Text("- toneDivider: %.f",sms->toneDivider); \ + ImGui::Text("- noiseDivider: %.f",sms->noiseDivider); \ + ImGui::TextColored(sms->skipRegisterWrites?colorOn:colorOff,">> SkipRegisterWrites"); \ + ImGui::TextColored(sms->dumpWrites?colorOn:colorOff,">> DumpWrites"); \ + ImGui::TextColored(sms->updateSNMode?colorOn:colorOff,">> UpdateSNMode"); \ + ImGui::TextColored(sms->resetPhase?colorOn:colorOff,">> ResetPhase"); \ + ImGui::TextColored(sms->isRealSN?colorOn:colorOff,">> IsRealSN"); \ + ImGui::TextColored(sms->stereo?colorOn:colorOff,">> Stereo"); \ + ImGui::TextColored(sms->nuked?colorOn:colorOff,">> Nuked"); + + +#define GENESIS_CHAN_DEBUG \ DivPlatformGenesis::Channel* ch=(DivPlatformGenesis::Channel*)data; \ ImGui::Text("> YM2612"); \ ImGui::Text("- freqHL: %.2x%.2x",ch->freqH,ch->freqL); \ ImGui::Text("* freq: %d",ch->freq); \ ImGui::Text(" - base: %d",ch->baseFreq); \ ImGui::Text(" - pitch: %d",ch->pitch); \ + ImGui::Text(" - pitch2: %d",ch->pitch2); \ + ImGui::Text("- portaPauseFreq: %d",ch->portaPauseFreq); \ + ImGui::Text("* DAC:"); \ + ImGui::Text(" - period: %d",ch->dacPeriod); \ + ImGui::Text(" - rate: %d",ch->dacRate); \ + ImGui::Text(" - pos: %d",ch->dacPos); \ + ImGui::Text(" - sample: %d",ch->dacSample); \ + ImGui::Text(" - delay: %d",ch->dacDelay); \ + ImGui::Text(" - output: %d",ch->dacOutput); \ ImGui::Text("- note: %d",ch->note); \ ImGui::Text("- ins: %d",ch->ins); \ ImGui::Text("- vol: %.2x",ch->vol); \ ImGui::Text("- outVol: %.2x",ch->outVol); \ ImGui::Text("- pan: %x",ch->pan); \ + ImGui::Text("- opMask: %x",ch->opMask); \ + ImGui::Text("- sampleBank: %d",ch->sampleBank); \ ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); \ ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); \ ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); \ @@ -61,14 +153,41 @@ ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); \ ImGui::TextColored(ch->portaPause?colorOn:colorOff,">> PortaPause"); \ ImGui::TextColored(ch->furnaceDac?colorOn:colorOff,">> FurnaceDAC"); \ - ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); \ + ImGui::TextColored(ch->hardReset?colorOn:colorOff,">> hardReset"); \ + ImGui::TextColored(ch->opMaskChanged?colorOn:colorOff,">> opMaskChanged"); \ + ImGui::TextColored(ch->dacMode?colorOn:colorOff,">> DACMode"); \ + ImGui::TextColored(ch->dacReady?colorOn:colorOff,">> DACReady"); \ + ImGui::TextColored(ch->dacDirection?colorOn:colorOff,">> DACDirection"); -#define SMS_DEBUG \ +#define GENESIS_OPCHAN_DEBUG \ + DivPlatformGenesisExt::OpChannel* ch=(DivPlatformGenesisExt::OpChannel*)data; \ + ImGui::Text("> YM2612 (per operator)"); \ + ImGui::Text("- freqHL: %.2x%.2x",ch->freqH,ch->freqL); \ + ImGui::Text("* freq: %d",ch->freq); \ + ImGui::Text(" - base: %d",ch->baseFreq); \ + ImGui::Text(" - pitch: %d",ch->pitch); \ + ImGui::Text(" - pitch2: %d",ch->pitch2); \ + ImGui::Text("- portaPauseFreq: %d",ch->portaPauseFreq); \ + ImGui::Text("- ins: %d",ch->ins); \ + ImGui::Text("- vol: %.2x",ch->vol); \ + ImGui::Text("- pan: %x",ch->pan); \ + ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); \ + ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); \ + ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); \ + ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); \ + ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); \ + ImGui::TextColored(ch->portaPause?colorOn:colorOff,">> PortaPause"); \ + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); \ + ImGui::TextColored(ch->mask?colorOn:colorOff,">> Mask"); + +#define SMS_CHAN_DEBUG \ DivPlatformSMS::Channel* ch=(DivPlatformSMS::Channel*)data; \ ImGui::Text("> SMS"); \ ImGui::Text("* freq: %d",ch->freq); \ ImGui::Text(" - base: %d",ch->baseFreq); \ ImGui::Text(" - pitch: %d",ch->pitch); \ + ImGui::Text(" - pitch2: %d",ch->pitch2); \ ImGui::Text("- note: %d",ch->note); \ ImGui::Text("- ins: %d",ch->ins); \ ImGui::Text("- vol: %.2x",ch->vol); \ @@ -79,31 +198,473 @@ ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); \ ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); +#define OPN_CHAN_DEBUG \ + DivPlatformYM2203::Channel* ch=(DivPlatformYM2203::Channel*)data; \ + ImGui::Text("> YM2203"); \ + ImGui::Text("- freqHL: %.2x%.2x",ch->freqH,ch->freqL); \ + ImGui::Text("* freq: %d",ch->freq); \ + ImGui::Text(" - base: %d",ch->baseFreq); \ + ImGui::Text(" - pitch: %d",ch->pitch); \ + ImGui::Text(" - pitch2: %d",ch->pitch2); \ + ImGui::Text("- portaPauseFreq: %d",ch->portaPauseFreq); \ + ImGui::Text("* PSG:"); \ + ImGui::Text(" - psgMode: %d",ch->psgMode); \ + ImGui::Text(" - autoEnvNum: %d",ch->autoEnvNum); \ + ImGui::Text(" - autoEnvDen: %d",ch->autoEnvDen); \ + ImGui::Text("- sample: %d",ch->sample); \ + ImGui::Text("- note: %d",ch->note); \ + ImGui::Text("- ins: %d",ch->ins); \ + ImGui::Text("- vol: %.2x",ch->vol); \ + ImGui::Text("- outVol: %.2x",ch->outVol); \ + ImGui::Text("- opMask: %x",ch->opMask); \ + ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); \ + ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); \ + ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); \ + ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); \ + ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); \ + ImGui::TextColored(ch->portaPause?colorOn:colorOff,">> PortaPause"); \ + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); \ + ImGui::TextColored(ch->hardReset?colorOn:colorOff,">> hardReset"); \ + ImGui::TextColored(ch->opMaskChanged?colorOn:colorOff,">> opMaskChanged"); \ + ImGui::TextColored(ch->furnacePCM?colorOn:colorOff,">> FurnacePCM"); + +#define OPN_OPCHAN_DEBUG \ + DivPlatformYM2203Ext::OpChannel* ch=(DivPlatformYM2203Ext::OpChannel*)data; \ + ImGui::Text("> YM2203 (per operator)"); \ + ImGui::Text("- freqHL: %.2x%.2x",ch->freqH,ch->freqL); \ + ImGui::Text("* freq: %d",ch->freq); \ + ImGui::Text(" - base: %d",ch->baseFreq); \ + ImGui::Text(" - pitch: %d",ch->pitch); \ + ImGui::Text(" - pitch2: %d",ch->pitch2); \ + ImGui::Text("- portaPauseFreq: %d",ch->portaPauseFreq); \ + ImGui::Text("- ins: %d",ch->ins); \ + ImGui::Text("- vol: %.2x",ch->vol); \ + ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); \ + ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); \ + ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); \ + ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); \ + ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); \ + ImGui::TextColored(ch->portaPause?colorOn:colorOff,">> PortaPause"); \ + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); \ + ImGui::TextColored(ch->mask?colorOn:colorOff,">> Mask"); + +#define OPNB_CHAN_DEBUG \ + ImGui::Text("- freqHL: %.2x%.2x",ch->freqH,ch->freqL); \ + ImGui::Text("* freq: %d",ch->freq); \ + ImGui::Text(" - base: %d",ch->baseFreq); \ + ImGui::Text(" - pitch: %d",ch->pitch); \ + ImGui::Text(" - pitch2: %d",ch->pitch2); \ + ImGui::Text("- portaPauseFreq: %d",ch->portaPauseFreq); \ + ImGui::Text("* PSG:"); \ + ImGui::Text(" - psgMode: %d",ch->psgMode); \ + ImGui::Text(" - autoEnvNum: %d",ch->autoEnvNum); \ + ImGui::Text(" - autoEnvDen: %d",ch->autoEnvDen); \ + ImGui::Text("- sample: %d",ch->sample); \ + ImGui::Text("- note: %d",ch->note); \ + ImGui::Text("- ins: %d",ch->ins); \ + ImGui::Text("- vol: %.2x",ch->vol); \ + ImGui::Text("- outVol: %.2x",ch->outVol); \ + ImGui::Text("- pan: %x",ch->pan); \ + ImGui::Text("- opMask: %x",ch->opMask); \ + ImGui::Text("- macroVolMul: %x",ch->macroVolMul); \ + ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); \ + ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); \ + ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); \ + ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); \ + ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); \ + ImGui::TextColored(ch->portaPause?colorOn:colorOff,">> PortaPause"); \ + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); \ + ImGui::TextColored(ch->hardReset?colorOn:colorOff,">> hardReset"); \ + ImGui::TextColored(ch->opMaskChanged?colorOn:colorOff,">> opMaskChanged"); \ + ImGui::TextColored(ch->furnacePCM?colorOn:colorOff,">> FurnacePCM"); + +#define OPNB_OPCHAN_DEBUG \ + ImGui::Text("- freqHL: %.2x%.2x",ch->freqH,ch->freqL); \ + ImGui::Text("* freq: %d",ch->freq); \ + ImGui::Text(" - base: %d",ch->baseFreq); \ + ImGui::Text(" - pitch: %d",ch->pitch); \ + ImGui::Text(" - pitch2: %d",ch->pitch2); \ + ImGui::Text("- portaPauseFreq: %d",ch->portaPauseFreq); \ + ImGui::Text("- ins: %d",ch->ins); \ + ImGui::Text("- vol: %.2x",ch->vol); \ + ImGui::Text("- pan: %x",ch->pan); \ + ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); \ + ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); \ + ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); \ + ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); \ + ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); \ + ImGui::TextColored(ch->portaPause?colorOn:colorOff,">> PortaPause"); \ + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); \ + ImGui::TextColored(ch->mask?colorOn:colorOff,">> Mask"); + +void putDispatchChip(void* data, int type) { + ImVec4 colorOn=ImVec4(1.0f,1.0f,0.0f,1.0f); + ImVec4 colorOff=ImVec4(0.3f,0.3f,0.3f,1.0f); + switch (type) { + case DIV_SYSTEM_YM2612: + case DIV_SYSTEM_YM2612_EXT: + case DIV_SYSTEM_YM2612_FRAC: + case DIV_SYSTEM_YM2612_FRAC_EXT: { + GENESIS_CHIP_DEBUG; + break; + } + case DIV_SYSTEM_GENESIS: + case DIV_SYSTEM_GENESIS_EXT: { + GENESIS_CHIP_DEBUG; + SMS_CHIP_DEBUG; + break; + } + case DIV_SYSTEM_SMS: { + SMS_CHIP_DEBUG; + break; + } + case DIV_SYSTEM_OPN: + case DIV_SYSTEM_OPN_EXT: { + DivPlatformYM2203* ch=(DivPlatformYM2203*)data; + ImGui::Text("> YM2203"); + FM_OPN_CHIP_DEBUG; + ImGui::Text("- sampleBank: %d",ch->sampleBank); + ImGui::Text("- prescale: %d",ch->prescale); + FM_OPN_CHIP_DEBUG_BOOL; + ImGui::TextColored(ch->extMode?colorOn:colorOff,">> ExtMode"); + break; + } + case DIV_SYSTEM_PC98: + case DIV_SYSTEM_PC98_EXT: { + DivPlatformYM2608* ch=(DivPlatformYM2608*)data; + ImGui::Text("> YM2608"); + FM_OPN_CHIP_DEBUG; + ImGui::Text("- sampleBank: %d",ch->sampleBank); + ImGui::Text("- writeRSSOff: %d",ch->writeRSSOff); + ImGui::Text("- writeRSSOn: %d",ch->writeRSSOn); + ImGui::Text("- globalRSSVolume: %d",ch->globalRSSVolume); + ImGui::Text("- prescale: %d",ch->prescale); + FM_OPN_CHIP_DEBUG_BOOL; + ImGui::TextColored(ch->extMode?colorOn:colorOff,">> ExtMode"); + break; + } + case DIV_SYSTEM_YM2610: + case DIV_SYSTEM_YM2610_EXT: + case DIV_SYSTEM_YM2610_FULL: + case DIV_SYSTEM_YM2610_FULL_EXT: { + DivPlatformYM2610* ch=(DivPlatformYM2610*)data; + ImGui::Text("> YM2610"); + OPNB_CHIP_DEBUG; + break; + } + case DIV_SYSTEM_YM2610B: + case DIV_SYSTEM_YM2610B_EXT: { + DivPlatformYM2610B* ch=(DivPlatformYM2610B*)data; + ImGui::Text("> YM2610B"); + OPNB_CHIP_DEBUG; + break; + } + case DIV_SYSTEM_GB: { + DivPlatformGB* ch=(DivPlatformGB*)data; + ImGui::Text("> GameBoy"); + COMMON_CHIP_DEBUG; + ImGui::Text("- lastPan: %d",ch->lastPan); + ImGui::Text("- antiClickPeriodCount: %d",ch->antiClickPeriodCount); + ImGui::Text("- antiClickWavePos: %d",ch->antiClickWavePos); + COMMON_CHIP_DEBUG_BOOL; + ImGui::TextColored(ch->antiClickEnabled?colorOn:colorOff,">> AntiClickEnabled"); + break; + } + case DIV_SYSTEM_PCE: { + DivPlatformPCE* ch=(DivPlatformPCE*)data; + ImGui::Text("> PCEngine"); + COMMON_CHIP_DEBUG; + ImGui::Text("- lastPan: %d",ch->lastPan); + ImGui::Text("- cycles: %d",ch->cycles); + ImGui::Text("- curChan: %d",ch->curChan); + ImGui::Text("- delay: %d",ch->delay); + ImGui::Text("- sampleBank: %d",ch->sampleBank); + ImGui::Text("- lfoMode: %d",ch->lfoMode); + ImGui::Text("- lfoSpeed: %d",ch->lfoSpeed); + COMMON_CHIP_DEBUG_BOOL; + ImGui::TextColored(ch->antiClickEnabled?colorOn:colorOff,">> AntiClickEnabled"); + break; + } + case DIV_SYSTEM_NES: { + DivPlatformNES* ch=(DivPlatformNES*)data; + ImGui::Text("> NES"); + COMMON_CHIP_DEBUG; + ImGui::Text("* DAC:"); + ImGui::Text(" - Period: %d",ch->dacPeriod); + ImGui::Text(" - Rate: %d",ch->dacRate); + ImGui::Text(" - Pos: %d",ch->dacPos); + ImGui::Text(" - AntiClick: %d",ch->dacAntiClick); + ImGui::Text(" - Sample: %d",ch->dacSample); + ImGui::Text("- dpcmBank: %d",ch->dpcmBank); + ImGui::Text("- sampleBank: %d",ch->sampleBank); + ImGui::Text("- writeOscBuf: %d",ch->writeOscBuf); + ImGui::Text("- apuType: %d",ch->apuType); + COMMON_CHIP_DEBUG_BOOL; + ImGui::TextColored(ch->dpcmMode?colorOn:colorOff,">> DPCMMode"); + ImGui::TextColored(ch->dacAntiClickOn?colorOn:colorOff,">> DACAntiClickOn"); + ImGui::TextColored(ch->useNP?colorOn:colorOff,">> UseNP"); + ImGui::TextColored(ch->goingToLoop?colorOn:colorOff,">> GoingToLoop"); + break; + } + case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_8580: { + DivPlatformC64* ch=(DivPlatformC64*)data; + ImGui::Text("> C64"); + COMMON_CHIP_DEBUG; + ImGui::Text("- filtControl: %d",ch->filtControl); + ImGui::Text("- filtRes: %d",ch->filtRes); + ImGui::Text("- vol: %d",ch->vol); + ImGui::Text("- writeOscBuf: %d",ch->writeOscBuf); + ImGui::Text("- filtCut: %d",ch->filtCut); + ImGui::Text("- resetTime: %d",ch->resetTime); + COMMON_CHIP_DEBUG_BOOL; + ImGui::TextColored(ch->isFP?colorOn:colorOff,">> IsFP"); + break; + } + case DIV_SYSTEM_ARCADE: + case DIV_SYSTEM_YM2151: { + DivPlatformArcade* ch=(DivPlatformArcade*)data; + ImGui::Text("> YM2151"); + FM_CHIP_DEBUG; + ImGui::Text("- baseFreqOff: %d",ch->baseFreqOff); + ImGui::Text("- amDepth: %d",ch->amDepth); + ImGui::Text("- pmDepth: %d",ch->pmDepth); + FM_CHIP_DEBUG_BOOL; + ImGui::TextColored(ch->useYMFM?colorOn:colorOff,">> UseYMFM"); + break; + } + case DIV_SYSTEM_SEGAPCM: + case DIV_SYSTEM_SEGAPCM_COMPAT: { + DivPlatformSegaPCM* ch=(DivPlatformSegaPCM*)data; + ImGui::Text("> SegaPCM"); + COMMON_CHIP_DEBUG; + ImGui::Text("- delay: %d",ch->delay); + ImGui::Text("- pcmL: %d",ch->pcmL); + ImGui::Text("- pcmR: %d",ch->pcmR); + ImGui::Text("- pcmCycles: %d",ch->pcmCycles); + ImGui::Text("- sampleBank: %d",ch->sampleBank); + ImGui::Text("- lastBusy: %d",ch->lastBusy); + COMMON_CHIP_DEBUG_BOOL; + break; + } + case DIV_SYSTEM_AY8910: { + DivPlatformAY8910* ch=(DivPlatformAY8910*)data; + ImGui::Text("> AY-3-8910"); + COMMON_CHIP_DEBUG; + ImGui::Text("- lastBusy: %d",ch->lastBusy); + ImGui::Text("- sampleBank: %d",ch->sampleBank); + ImGui::Text("- stereoSep: %d",ch->stereoSep); + ImGui::Text("- delay: %d",ch->delay); + ImGui::Text("- extClock: %d",ch->extClock); + ImGui::Text("- extDiv: %d",ch->extDiv); + ImGui::Text("- portAVal: %d",ch->portAVal); + ImGui::Text("- portBVal: %d",ch->portBVal); + ImGui::Text("* envelope:"); + ImGui::Text(" - mode: %d",ch->ayEnvMode); + ImGui::Text(" - period: %d",ch->ayEnvPeriod); + ImGui::Text(" * slide: %d",ch->ayEnvSlide); + ImGui::Text(" - slideLow: %d",ch->ayEnvSlideLow); + COMMON_CHIP_DEBUG_BOOL; + ImGui::TextColored(ch->extMode?colorOn:colorOff,">> ExtMode"); + ImGui::TextColored(ch->stereo?colorOn:colorOff,">> Stereo"); + ImGui::TextColored(ch->sunsoft?colorOn:colorOff,">> Sunsoft"); + ImGui::TextColored(ch->intellivision?colorOn:colorOff,">> Intellivision"); + ImGui::TextColored(ch->clockSel?colorOn:colorOff,">> ClockSel"); + ImGui::TextColored(ch->ioPortA?colorOn:colorOff,">> IoPortA"); + ImGui::TextColored(ch->ioPortB?colorOn:colorOff,">> IoPortB"); + break; + } + case DIV_SYSTEM_AY8930: { + DivPlatformAY8930* ch=(DivPlatformAY8930*)data; + ImGui::Text("> AY8930"); + COMMON_CHIP_DEBUG; + ImGui::Text("* noise:"); + ImGui::Text(" - and: %d",ch->ayNoiseAnd); + ImGui::Text(" - or: %d",ch->ayNoiseOr); + ImGui::Text("- sampleBank: %d",ch->sampleBank); + ImGui::Text("- stereoSep: %d",ch->stereoSep); + ImGui::Text("- delay: %d",ch->delay); + ImGui::Text("- portAVal: %d",ch->portAVal); + ImGui::Text("- portBVal: %d",ch->portBVal); + COMMON_CHIP_DEBUG_BOOL; + ImGui::TextColored(ch->bank?colorOn:colorOff,">> Bank"); + ImGui::TextColored(ch->extMode?colorOn:colorOff,">> ExtMode"); + ImGui::TextColored(ch->stereo?colorOn:colorOff,">> Stereo"); + ImGui::TextColored(ch->clockSel?colorOn:colorOff,">> ClockSel"); + ImGui::TextColored(ch->ioPortA?colorOn:colorOff,">> IoPortA"); + ImGui::TextColored(ch->ioPortB?colorOn:colorOff,">> IoPortB"); + break; + } + case DIV_SYSTEM_QSOUND: { + DivPlatformQSound* ch=(DivPlatformQSound*)data; + ImGui::Text("> QSound"); + COMMON_CHIP_DEBUG; + ImGui::Text("* echo:"); + ImGui::Text(" - delay: %d",ch->echoDelay); + ImGui::Text(" - feedback: %d",ch->echoFeedback); + COMMON_CHIP_DEBUG_BOOL; + break; + } + case DIV_SYSTEM_X1_010: { + DivPlatformX1_010* ch=(DivPlatformX1_010*)data; + ImGui::Text("> X1-010"); + COMMON_CHIP_DEBUG; + ImGui::Text("- sampleBank: %d",ch->sampleBank); + ImGui::Text("- bankSlot: [%d,%d,%d,%d,%d,%d,%d,%d]",ch->bankSlot[0],ch->bankSlot[1],ch->bankSlot[2],ch->bankSlot[3],ch->bankSlot[4],ch->bankSlot[5],ch->bankSlot[6],ch->bankSlot[7]); + COMMON_CHIP_DEBUG_BOOL; + ImGui::TextColored(ch->stereo?colorOn:colorOff,">> Stereo"); + ImGui::TextColored(ch->isBanked?colorOn:colorOff,">> IsBanked"); + break; + } + case DIV_SYSTEM_N163: { + DivPlatformN163* ch=(DivPlatformN163*)data; + ImGui::Text("> N163"); + COMMON_CHIP_DEBUG; + ImGui::Text("- initChanMax: %d",ch->initChanMax); + ImGui::Text("- chanMax: %d",ch->chanMax); + ImGui::Text("- loadWave: %d",ch->loadWave); + ImGui::Text("- loadPos: %d",ch->loadPos); + ImGui::Text("- loadLen: %d",ch->loadLen); + ImGui::Text("- loadMode: %d",ch->loadMode); + COMMON_CHIP_DEBUG_BOOL; + ImGui::TextColored(ch->multiplex?colorOn:colorOff,">> Multiplex"); + break; + } + case DIV_SYSTEM_VRC6: { + DivPlatformVRC6* ch=(DivPlatformVRC6*)data; + ImGui::Text("> VRC6"); + COMMON_CHIP_DEBUG; + ImGui::Text("- sampleBank: %.2x",ch->sampleBank); + ImGui::Text("- writeOscBuf: %.2x",ch->writeOscBuf); + COMMON_CHIP_DEBUG_BOOL; + break; + } + case DIV_SYSTEM_LYNX: { + DivPlatformLynx* ch=(DivPlatformLynx*)data; + ImGui::Text("> Lynx"); + COMMON_CHIP_DEBUG; + COMMON_CHIP_DEBUG_BOOL; + break; + } + case DIV_SYSTEM_PCM_DAC: { + DivPlatformPCMDAC* ch=(DivPlatformPCMDAC*)data; + ImGui::Text("> PCM DAC"); + COMMON_CHIP_DEBUG; + ImGui::Text("- outDepth: %d",ch->outDepth); + COMMON_CHIP_DEBUG_BOOL; + ImGui::TextColored(ch->outStereo?colorOn:colorOff,">> OutStereo"); + break; + } + default: + ImGui::Text("Unimplemented chip! Help!"); + break; + } +} void putDispatchChan(void* data, int chanNum, int type) { ImVec4 colorOn=ImVec4(1.0f,1.0f,0.0f,1.0f); ImVec4 colorOff=ImVec4(0.3f,0.3f,0.3f,1.0f); switch (type) { - case DIV_SYSTEM_GENESIS: - case DIV_SYSTEM_YM2612: { + case DIV_SYSTEM_GENESIS: { if (chanNum>5) { - SMS_DEBUG; + SMS_CHAN_DEBUG; } else { - GENESIS_DEBUG; + GENESIS_CHAN_DEBUG; } break; } case DIV_SYSTEM_GENESIS_EXT: { if (chanNum>8) { - SMS_DEBUG; + SMS_CHAN_DEBUG; } else if (chanNum>=2 && chanNum<=5) { - // TODO ext ch 3 debug + GENESIS_OPCHAN_DEBUG } else { - GENESIS_DEBUG; + GENESIS_CHAN_DEBUG; + } + break; + } + case DIV_SYSTEM_YM2612: + case DIV_SYSTEM_YM2612_FRAC: { + GENESIS_CHAN_DEBUG; + break; + } + case DIV_SYSTEM_YM2612_EXT: + case DIV_SYSTEM_YM2612_FRAC_EXT: { + if (chanNum>=2 && chanNum<=5) { + GENESIS_OPCHAN_DEBUG + } else { + GENESIS_CHAN_DEBUG; } break; } case DIV_SYSTEM_SMS: { - SMS_DEBUG; + SMS_CHAN_DEBUG; + break; + } + case DIV_SYSTEM_OPN: { + OPN_CHAN_DEBUG; + break; + } + case DIV_SYSTEM_OPN_EXT: { + if (chanNum>=2 && chanNum<=5) { + OPN_OPCHAN_DEBUG; + } else { + OPN_CHAN_DEBUG; + } + break; + } + case DIV_SYSTEM_PC98: { + DivPlatformYM2608::Channel* ch=(DivPlatformYM2608::Channel*)data; + ImGui::Text("> YM2608"); + OPNB_CHAN_DEBUG; + break; + } + case DIV_SYSTEM_PC98_EXT: { + if (chanNum>=2 && chanNum<=5) { + DivPlatformYM2608Ext::OpChannel* ch=(DivPlatformYM2608Ext::OpChannel*)data; + ImGui::Text("> YM2608 (per operator)"); + OPNB_OPCHAN_DEBUG; + } else { + DivPlatformYM2608Ext::Channel* ch=(DivPlatformYM2608Ext::Channel*)data; + ImGui::Text("> YM2608"); + OPNB_CHAN_DEBUG; + } + break; + } + case DIV_SYSTEM_YM2610: + case DIV_SYSTEM_YM2610_FULL: { + DivPlatformYM2610::Channel* ch=(DivPlatformYM2610::Channel*)data; + ImGui::Text("> YM2610"); + OPNB_CHAN_DEBUG; + break; + } + case DIV_SYSTEM_YM2610B: { + DivPlatformYM2610B::Channel* ch=(DivPlatformYM2610B::Channel*)data; + ImGui::Text("> YM2610B"); + OPNB_CHAN_DEBUG; + break; + } + case DIV_SYSTEM_YM2610_EXT: + case DIV_SYSTEM_YM2610_FULL_EXT: { + if (chanNum>=1 && chanNum<=4) { + DivPlatformYM2610Ext::OpChannel* ch=(DivPlatformYM2610Ext::OpChannel*)data; + ImGui::Text("> YM2610 (per operator)"); + OPNB_OPCHAN_DEBUG; + } else { + DivPlatformYM2610Ext::Channel* ch=(DivPlatformYM2610Ext::Channel*)data; + ImGui::Text("> YM2610"); + OPNB_CHAN_DEBUG; + } + break; + } + case DIV_SYSTEM_YM2610B_EXT: { + if (chanNum>=2 && chanNum<=5) { + DivPlatformYM2610BExt::OpChannel* ch=(DivPlatformYM2610BExt::OpChannel*)data; + ImGui::Text("> YM2610B (per operator)"); + OPNB_OPCHAN_DEBUG; + } else { + DivPlatformYM2610BExt::Channel* ch=(DivPlatformYM2610BExt::Channel*)data; + ImGui::Text("> YM2610B"); + OPNB_CHAN_DEBUG; + } break; } case DIV_SYSTEM_GB: { @@ -112,6 +673,7 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::Text("* freq: %d",ch->freq); ImGui::Text(" - base: %d",ch->baseFreq); ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text(" - pitch2: %d",ch->pitch2); ImGui::Text("- note: %d",ch->note); ImGui::Text("- ins: %d",ch->ins); ImGui::Text("- duty: %d",ch->duty); @@ -134,17 +696,20 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::Text("* freq: %d",ch->freq); ImGui::Text(" - base: %d",ch->baseFreq); ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text(" - pitch2: %d",ch->pitch2); ImGui::Text("- note: %d",ch->note); ImGui::Text("* DAC:"); ImGui::Text(" - period: %d",ch->dacPeriod); ImGui::Text(" - rate: %d",ch->dacRate); ImGui::Text(" - pos: %d",ch->dacPos); + ImGui::Text(" - out: %d",ch->dacOut); ImGui::Text(" - sample: %d",ch->dacSample); ImGui::Text("- ins: %d",ch->ins); ImGui::Text("- pan: %.2x",ch->pan); ImGui::Text("- vol: %.2x",ch->vol); ImGui::Text("- outVol: %.2x",ch->outVol); ImGui::Text("- wave: %d",ch->wave); + ImGui::Text("- macroVolMul: %d",ch->macroVolMul); ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); @@ -162,6 +727,7 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::Text("* freq: %d",ch->freq); ImGui::Text(" - base: %d",ch->baseFreq); ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text(" - pitch2: %d",ch->pitch2); ImGui::Text(" - prev: %d",ch->prevFreq); ImGui::Text("- note: %d",ch->note); ImGui::Text("- ins: %d",ch->ins); @@ -186,6 +752,7 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::Text("* freq: %d",ch->freq); ImGui::Text(" - base: %d",ch->baseFreq); ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text(" - pitch2: %d",ch->pitch2); ImGui::Text(" - prev: %d",ch->prevFreq); ImGui::Text("- testWhen: %d",ch->testWhen); ImGui::Text("- note: %d",ch->note); @@ -219,6 +786,7 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::Text("* freq: %d",ch->freq); ImGui::Text(" - base: %d",ch->baseFreq); ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text(" - pitch2: %d",ch->pitch2); ImGui::Text("- note: %d",ch->note); ImGui::Text("- ins: %d",ch->ins); ImGui::Text("- KOnCycles: %d",ch->konCycles); @@ -232,8 +800,140 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); ImGui::TextColored(ch->portaPause?colorOn:colorOff,">> PortaPause"); + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); + break; + } + case DIV_SYSTEM_SEGAPCM: + case DIV_SYSTEM_SEGAPCM_COMPAT: { + DivPlatformSegaPCM::Channel* ch=(DivPlatformSegaPCM::Channel*)data; + ImGui::Text("> SegaPCM"); + ImGui::Text("* freq: %d",ch->freq); + ImGui::Text(" - base: %d",ch->baseFreq); + ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text(" - pitch2: %d",ch->pitch2); + ImGui::Text("- note: %d",ch->note); + ImGui::Text("- ins: %d",ch->ins); + ImGui::Text("* PCM:"); + ImGui::Text(" - sample: %d",ch->pcm.sample); + ImGui::Text(" - pos: %d",ch->pcm.pos); + ImGui::Text(" - len: %d",ch->pcm.len); + ImGui::Text(" - freq: %d",ch->pcm.freq); + ImGui::Text("- vol: %.2x",ch->vol); + ImGui::Text("- outVol: %.2x",ch->outVol); + ImGui::Text("- chVolL: %.2x",ch->chVolL); + ImGui::Text("- chVolR: %.2x",ch->chVolR); + ImGui::Text("- chPanL: %.2x",ch->chPanL); + ImGui::Text("- chPanR: %.2x",ch->chPanR); + ImGui::Text("- macroVolMul: %.2x",ch->macroVolMul); + ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); + ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); + ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); + ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); + ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); + ImGui::TextColored(ch->portaPause?colorOn:colorOff,">> PortaPause"); ImGui::TextColored(ch->furnacePCM?colorOn:colorOff,">> FurnacePCM"); ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); + ImGui::TextColored(ch->isNewSegaPCM?colorOn:colorOff,">> IsNewSegaPCM"); + break; + } + case DIV_SYSTEM_AY8910: { + DivPlatformAY8910::Channel* ch=(DivPlatformAY8910::Channel*)data; + ImGui::Text("> AY-3-8910"); + ImGui::Text("* freq: %d",ch->freq); + ImGui::Text(" - base: %d",ch->baseFreq); + ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text(" - pitch2: %d",ch->pitch2); + ImGui::Text("- note: %d",ch->note); + ImGui::Text("- ins: %d",ch->ins); + ImGui::Text("* psgMode:"); + ImGui::Text(" - tone: %d",ch->psgMode.tone); + ImGui::Text(" - noise: %d",ch->psgMode.noise); + ImGui::Text(" - envelope: %d",ch->psgMode.envelope); + ImGui::Text(" - dac: %d",ch->psgMode.dac); + ImGui::Text("* DAC:"); + ImGui::Text(" - sample: %d",ch->dac.sample); + ImGui::Text(" - rate: %d",ch->dac.rate); + ImGui::Text(" - period: %d",ch->dac.period); + ImGui::Text(" - pos: %d",ch->dac.pos); + ImGui::Text(" - out: %d",ch->dac.out); + ImGui::Text("- autoEnvNum: %.2x",ch->autoEnvNum); + ImGui::Text("- autoEnvDen: %.2x",ch->autoEnvDen); + ImGui::Text("- vol: %.2x",ch->vol); + ImGui::Text("- outVol: %.2x",ch->outVol); + ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); + ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); + ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); + ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); + ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); + ImGui::TextColored(ch->portaPause?colorOn:colorOff,">> PortaPause"); + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); + ImGui::TextColored(ch->dac.furnaceDAC?colorOn:colorOff,">> furnaceDAC"); + break; + } + case DIV_SYSTEM_AY8930: { + DivPlatformAY8930::Channel* ch=(DivPlatformAY8930::Channel*)data; + ImGui::Text("> AY8930"); + ImGui::Text("* freq: %d",ch->freq); + ImGui::Text(" - base: %d",ch->baseFreq); + ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text(" - pitch2: %d",ch->pitch2); + ImGui::Text("- note: %d",ch->note); + ImGui::Text("- ins: %d",ch->ins); + ImGui::Text("- duty: %d",ch->duty); + ImGui::Text("* envelope:"); + ImGui::Text(" - mode: %d",ch->envelope.mode); + ImGui::Text(" - period: %d",ch->envelope.period); + ImGui::Text(" * slide: %d",ch->envelope.slide); + ImGui::Text(" - low: %d",ch->envelope.slideLow); + ImGui::Text("* psgMode:"); + ImGui::Text(" - tone: %d",ch->psgMode.tone); + ImGui::Text(" - noise: %d",ch->psgMode.noise); + ImGui::Text(" - envelope: %d",ch->psgMode.envelope); + ImGui::Text(" - dac: %d",ch->psgMode.dac); + ImGui::Text("* DAC:"); + ImGui::Text(" - sample: %d",ch->dac.sample); + ImGui::Text(" - rate: %d",ch->dac.rate); + ImGui::Text(" - period: %d",ch->dac.period); + ImGui::Text(" - pos: %d",ch->dac.pos); + ImGui::Text(" - out: %d",ch->dac.out); + ImGui::Text("- autoEnvNum: %.2x",ch->autoEnvNum); + ImGui::Text("- autoEnvDen: %.2x",ch->autoEnvDen); + ImGui::Text("- vol: %.2x",ch->vol); + ImGui::Text("- outVol: %.2x",ch->outVol); + ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); + ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); + ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); + ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); + ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); + ImGui::TextColored(ch->portaPause?colorOn:colorOff,">> PortaPause"); + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); + ImGui::TextColored(ch->dac.furnaceDAC?colorOn:colorOff,">> furnaceDAC"); + break; + } + case DIV_SYSTEM_QSOUND: { + DivPlatformQSound::Channel* ch=(DivPlatformQSound::Channel*)data; + ImGui::Text("> QSound"); + ImGui::Text("* freq: %d",ch->freq); + ImGui::Text(" - base: %d",ch->baseFreq); + ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text(" - pitch2: %d",ch->pitch2); + ImGui::Text("- note: %d",ch->note); + ImGui::Text("- ins: %d",ch->ins); + ImGui::Text("- sample: %d",ch->sample); + ImGui::Text("- echo: %d",ch->echo); + ImGui::Text("- panning: %d",ch->panning); + ImGui::Text("- vol: %.2x",ch->vol); + ImGui::Text("- outVol: %.2x",ch->outVol); + ImGui::Text("- resVol: %.2x",ch->resVol); + ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); + ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); + ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); + ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); + ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); + ImGui::TextColored(ch->useWave?colorOn:colorOff,">> UseWave"); + ImGui::TextColored(ch->surround?colorOn:colorOff,">> Surround"); + ImGui::TextColored(ch->isNewQSound?colorOn:colorOff,">> IsNewQSound"); break; } case DIV_SYSTEM_X1_010: { @@ -242,6 +942,7 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::Text("* freq: %.4x",ch->freq); ImGui::Text(" - base: %d",ch->baseFreq); ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text(" - pitch2: %d",ch->pitch2); ImGui::Text("- note: %d",ch->note); ImGui::Text("- wave: %d",ch->wave); ImGui::Text("- sample: %d",ch->sample); @@ -255,6 +956,7 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::Text(" - autoEnvNum: %.2x",ch->autoEnvNum); ImGui::Text(" - autoEnvDen: %.2x",ch->autoEnvDen); ImGui::Text("- WaveBank: %d",ch->waveBank); + ImGui::Text("- bankSlot: %d",ch->bankSlot); ImGui::Text("- vol: %.2x",ch->vol); ImGui::Text("- outVol: %.2x",ch->outVol); ImGui::Text("- Lvol: %.2x",ch->lvol); @@ -283,6 +985,7 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::Text("* freq: %.4x",ch->freq); ImGui::Text(" - base: %d",ch->baseFreq); ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text(" - pitch2: %d",ch->pitch2); ImGui::Text("- note: %d",ch->note); ImGui::Text("- wave: %d",ch->wave); ImGui::Text("- wavepos: %d",ch->wavePos); @@ -313,6 +1016,7 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::Text("* freq: %d",ch->freq); ImGui::Text(" - base: %d",ch->baseFreq); ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text(" - pitch2: %d",ch->pitch2); ImGui::Text("- note: %d",ch->note); ImGui::Text("* DAC:"); ImGui::Text(" - period: %d",ch->dacPeriod); @@ -407,10 +1111,6 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::TextColored(ch->pcmChanged.position?colorOn:colorOff,">> PCMPositionChanged"); ImGui::TextColored(ch->pcmChanged.loopBank?colorOn:colorOff,">> PCMLoopBankChanged"); ImGui::TextColored(ch->pcmChanged.transwaveInd?colorOn:colorOff,">> PCMTranswaveIndexChanged"); - ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); - ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); - ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); - ImGui::TextColored(ch->useWave?colorOn:colorOff,">> UseWave"); ImGui::TextColored(ch->isReverseLoop?colorOn:colorOff,">> IsReverseLoop"); ImGui::TextColored(ch->isTranswave?colorOn:colorOff,">> IsTranswave"); ImGui::TextColored(ch->transwaveIRQ?colorOn:colorOff,">> TranswaveIRQ"); @@ -419,8 +1119,73 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::TextColored(ch->envelope.k2Slow?colorOn:colorOff,">> EnvK2Slow"); break; } + case DIV_SYSTEM_LYNX: { + DivPlatformLynx::Channel* ch=(DivPlatformLynx::Channel*)data; + ImGui::Text("> Lynx"); + ImGui::Text("* freq:"); + ImGui::Text(" - base: %d",ch->baseFreq); + ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text(" - pitch2: %d",ch->pitch2); + ImGui::Text("* FreqDiv:"); + ImGui::Text(" - clockDivider: %d",ch->fd.clockDivider); + ImGui::Text(" - backup: %d",ch->fd.backup); + ImGui::Text("* note: %d",ch->note); + ImGui::Text(" - actualNote: %d",ch->actualNote); + ImGui::Text("* Sample:"); + ImGui::Text(" - sample: %d",ch->sample); + ImGui::Text(" - pos: %d",ch->samplePos); + ImGui::Text(" - accum: %d",ch->sampleAccum); + ImGui::Text(" * freq: %d",ch->sampleFreq); + ImGui::Text(" - base: %d",ch->sampleBaseFreq); + ImGui::Text("- ins: %d",ch->ins); + ImGui::Text("* duty:"); + ImGui::Text(" - int_feedback7: %d",ch->duty.int_feedback7); + ImGui::Text(" - feedback: %d",ch->duty.feedback); + ImGui::Text("- pan: %.2x",ch->pan); + ImGui::Text("- vol: %.2x",ch->vol); + ImGui::Text("- outVol: %.2x",ch->outVol); + ImGui::Text("- macroVolMul: %.2x",ch->macroVolMul); + ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); + ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); + ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); + ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); + ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); + ImGui::TextColored(ch->pcm?colorOn:colorOff,">> DAC"); + break; + } + case DIV_SYSTEM_PCM_DAC: { + DivPlatformPCMDAC::Channel* ch=(DivPlatformPCMDAC::Channel*)data; + ImGui::Text("> PCM DAC"); + ImGui::Text("* freq:"); + ImGui::Text(" - base: %d",ch->baseFreq); + ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text(" - pitch2: %d",ch->pitch2); + ImGui::Text("* note: %d",ch->note); + ImGui::Text("* Sample: %d",ch->sample); + ImGui::Text(" - dir: %d",ch->audDir); + ImGui::Text(" - loc: %d",ch->audLoc); + ImGui::Text(" - len: %d",ch->audLen); + ImGui::Text(" * pos: %d",ch->audPos); + ImGui::Text(" - sub: %d",ch->audSub); + ImGui::Text("- wave: %d",ch->wave); + ImGui::Text("- ins: %d",ch->ins); + ImGui::Text("- panL: %.2x",ch->panL); + ImGui::Text("- panR: %.2x",ch->panR); + ImGui::Text("- vol: %.2x",ch->vol); + ImGui::Text("- envVol: %.2x",ch->envVol); + ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); + ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); + ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); + ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); + ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); + ImGui::TextColored(ch->useWave?colorOn:colorOff,">> UseWave"); + ImGui::TextColored(ch->setPos?colorOn:colorOff,">> SetPos"); + break; + } default: - ImGui::Text("Unknown system! Help!"); + ImGui::Text("Unimplemented chip! Help!"); break; } } diff --git a/src/gui/debug.h b/src/gui/debug.h index 1f22b4855..e3c911a84 100644 --- a/src/gui/debug.h +++ b/src/gui/debug.h @@ -21,5 +21,6 @@ #define _GUI_DEBUG_H #include "../engine/song.h" +void putDispatchChip(void* data, int type); void putDispatchChan(void* data, int chanNum, int type); #endif \ No newline at end of file diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index be39ced0e..61dacf893 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 @@ -65,6 +66,22 @@ void FurnaceGUI::drawDebug() { ImGui::Checkbox("Enable",&bpOn); ImGui::TreePop(); } + if (ImGui::TreeNode("Chip Status")) { + ImGui::Text("for best results set latency to minimum or use the Frame Advance button."); + ImGui::Columns(e->song.systemLen); + for (int i=0; isong.systemLen; i++) { + void* ch=e->getDispatch(i); + ImGui::TextColored(uiColors[GUI_COLOR_ACCENT_PRIMARY],"Chip %d: %s",i,getSystemName(e->song.system[i])); + if (e->song.system[i]==DIV_SYSTEM_NULL) { + ImGui::Text("NULL"); + } else { + putDispatchChip(ch,e->song.system[i]); + } + ImGui::NextColumn(); + } + ImGui::Columns(); + ImGui::TreePop(); + } if (ImGui::TreeNode("Dispatch Status")) { ImGui::Text("for best results set latency to minimum or use the Frame Advance button."); ImGui::Columns(e->getTotalChannelCount()); @@ -154,14 +171,20 @@ 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("loopMode: %d",(int)(sample->loopMode)); + ImGui::Text("loopEnd: %d", sample->loopEnd); ImGui::Text("loopOffP: %d",sample->loopOffP); - ImGui::Text("depth: %d",(unsigned char)sample->depth); + ImGui::Text(sample->loop?"loop: Enabled":"loop: Disabled"); + if (sampleLoopModes[sample->loopMode]!=NULL) { + ImGui::Text("loopMode: %d (%s)",(unsigned char)sample->loopMode,sampleLoopModes[sample->loopMode]); + } else { + ImGui::Text("loopMode: %d ()",(unsigned char)sample->loopMode); + } + 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); @@ -172,6 +195,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); @@ -182,6 +206,8 @@ void FurnaceGUI::drawDebug() { ImGui::Text("offX1_010: %x",sample->offX1_010); ImGui::Text("offES5506: %x",sample->offES5506); 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(); @@ -212,24 +238,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(); } @@ -263,9 +299,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; @@ -326,7 +376,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(); diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 701445de1..41771dd44 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--; @@ -710,6 +736,7 @@ void FurnaceGUI::doAction(int what) { sample->name=prevSample->name; sample->loopStart=prevSample->loopStart; sample->loopEnd=prevSample->loopEnd; + sample->loop=prevSample->loop; sample->loopMode=prevSample->loopMode; sample->depth=prevSample->depth; if (sample->init(prevSample->samples)) { @@ -729,6 +756,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; @@ -1263,11 +1299,9 @@ void FurnaceGUI::doAction(int what) { e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; - if (sample->loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT) { - sample->loopMode=DIV_SAMPLE_LOOPMODE_FORWARD; - } sample->loopStart=start; sample->loopEnd=end; + sample->loop=true; updateSampleTex=true; e->renderSamples(); @@ -1275,6 +1309,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 912bd1c09..1d0e2a250 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 9bd827951..153d70da3 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 @@ -405,7 +405,7 @@ void FurnaceGUI::doCopy(bool cut) { } } -void FurnaceGUI::doPaste(PasteMode mode) { +void FurnaceGUI::doPaste(PasteMode mode, int arg) { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_PASTE); char* clipText=SDL_GetClipboardText(); @@ -431,7 +431,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { int startOff=-1; bool invalidData=false; if (data.size()<2) return; - if (data[0]!=fmt::sprintf("org.tildearrow.furnace - Pattern Data (%d)",DIV_ENGINE_VERSION)) return; + if (data[0].find("org.tildearrow.furnace - Pattern Data")!=0) return; if (sscanf(data[1].c_str(),"%d",&startOff)!=1) return; if (startOff<0) return; @@ -481,14 +481,16 @@ void FurnaceGUI::doPaste(PasteMode mode) { continue; } - if ((mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG) && strcmp(note,"...")==0) { + if ((mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG || + mode==GUI_PASTE_MODE_INS_BG || mode==GUI_PASTE_MODE_INS_FG) && strcmp(note,"...")==0) { // do nothing. } else { - if (mode!=GUI_PASTE_MODE_MIX_BG || (pat->data[j][0]==0 && pat->data[j][1]==0)) { + if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_INS_BG) || (pat->data[j][0]==0 && pat->data[j][1]==0)) { if (!decodeNote(note,pat->data[j][0],pat->data[j][1])) { invalidData=true; break; } + if (mode==GUI_PASTE_MODE_INS_BG || mode==GUI_PASTE_MODE_INS_FG) pat->data[j][2]=arg; } } } else { @@ -505,7 +507,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { note[2]=0; if (iFine==1) { - if (!opMaskPaste.ins) { + if (!opMaskPaste.ins || mode==GUI_PASTE_MODE_INS_BG || mode==GUI_PASTE_MODE_INS_FG) { iFine++; continue; } @@ -527,7 +529,8 @@ void FurnaceGUI::doPaste(PasteMode mode) { } if (strcmp(note,"..")==0) { - if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG)) { + if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG || + mode==GUI_PASTE_MODE_INS_BG || mode==GUI_PASTE_MODE_INS_FG)) { pat->data[j][iFine+1]=-1; } } else { @@ -536,7 +539,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { invalidData=true; break; } - if (mode!=GUI_PASTE_MODE_MIX_BG || pat->data[j][iFine+1]==-1) { + if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_INS_BG) || pat->data[j][iFine+1]==-1) { if (iFine<(3+e->curPat[iCoarse].effectCols*2)) pat->data[j][iFine+1]=val; } } @@ -577,7 +580,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; } } @@ -616,9 +619,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); } } } diff --git a/src/gui/effectList.cpp b/src/gui/effectList.cpp index a71a2fec7..488d4e794 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 f64f72cbb..8d524ab46 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -10,13 +10,14 @@ #ifdef USE_NFD struct NFDState { - bool isSave; + bool isSave, allowMultiple; String header; std::vector filter; String path; FileDialogSelectCallback clickCallback; - NFDState(bool save, String h, std::vector filt, String pa, FileDialogSelectCallback cc): + NFDState(bool save, String h, std::vector filt, String pa, FileDialogSelectCallback cc, bool multi): isSave(save), + allowMultiple(multi), header(h), filter(filt), path(pa), @@ -25,41 +26,55 @@ struct NFDState { }; // TODO: filter -void _nfdThread(const NFDState state, std::atomic* ok, String* result) { +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 { - ret=NFD_OpenDialog(state.filter,state.path.c_str(),&out,state.clickCallback); + 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 (out!=NULL) { - (*result)=out; + 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 { - (*result)=""; + logD("pushing single path"); + if (out!=NULL) { + logD("we have it"); + result->push_back(String(out)); + } } break; case NFD_CANCEL: - (*result)=""; break; case NFD_ERROR: - (*result)=""; logE("NFD error! %s\n",NFD_GetError()); + (*errorOutput)=true; break; default: logE("NFD unknown return code %d!\n",ret); - (*result)=""; break; } (*ok)=true; } #endif -bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale, FileDialogSelectCallback clickCallback) { +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; @@ -68,16 +83,18 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, c #ifdef USE_NFD dialogOK=false; #ifdef NFD_NON_THREADED - _nfdThread(NFDState(false,header,filter,path,clickCallback),&dialogOK,&nfdResult); + _nfdThread(NFDState(false,header,filter,path,clickCallback,allowMultiple),&dialogOK,&nfdResult,&hasError); #else - dialogO=new std::thread(_nfdThread,NFDState(false,header,filter,path,clickCallback),&dialogOK,&nfdResult); + 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); + 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; @@ -92,14 +109,16 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, c #ifdef USE_NFD dialogOK=false; #ifdef NFD_NON_THREADED - _nfdThread(NFDState(true,header,filter,path,NULL),&dialogOK,&nfdResult); + _nfdThread(NFDState(true,header,filter,path,NULL,false),&dialogOK,&nfdResult,&hasError); #else - dialogS=new std::thread(_nfdThread,NFDState(true,header,filter,path,NULL),&dialogOK,&nfdResult); + 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); } @@ -109,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(); } @@ -147,10 +166,15 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { if (sysDialog) { #ifdef USE_NFD if (dialogOK) { + fileName.clear(); fileName=nfdResult; - size_t dsPos=fileName.rfind(DIR_SEPARATOR); - if (dsPos!=String::npos) curPath=fileName.substr(0,dsPos); - logD("returning %s",fileName.c_str()); + 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; } @@ -159,10 +183,11 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { 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; } } @@ -170,13 +195,19 @@ 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; } @@ -193,6 +224,10 @@ bool FurnaceGUIFileDialog::isOpen() { return opened; } +bool FurnaceGUIFileDialog::isError() { + return hasError; +} + String FurnaceGUIFileDialog::getPath() { if (sysDialog) { if (curPath.size()>1) { @@ -207,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 6724eb951..7990b0370 100644 --- a/src/gui/fileDialog.h +++ b/src/gui/fileDialog.h @@ -28,30 +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; - String nfdResult; + 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 78abce7cb..428fce5a4 100644 --- a/src/gui/findReplace.cpp +++ b/src/gui/findReplace.cpp @@ -39,6 +39,7 @@ const char* queryReplaceModes[GUI_QUERY_REPLACE_MAX]={ "set", "add", "add (overflow)", + "scale", "clear" }; @@ -292,6 +293,8 @@ void FurnaceGUI::doReplace() { } } break; + case GUI_QUERY_REPLACE_SCALE: + break; case GUI_QUERY_REPLACE_CLEAR: p->data[i.y][0]=0; p->data[i.y][1]=0; @@ -314,6 +317,13 @@ void FurnaceGUI::doReplace() { 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; @@ -335,6 +345,13 @@ void FurnaceGUI::doReplace() { 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; @@ -402,6 +419,13 @@ void FurnaceGUI::doReplace() { 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; @@ -423,6 +447,13 @@ void FurnaceGUI::doReplace() { 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; @@ -447,7 +478,6 @@ void FurnaceGUI::doReplace() { } if (!us.pat.empty()) { - printf("pusher\n"); undoHist.push_back(us); redoHist.clear(); if (undoHist.size()>settings.maxUndoSteps) undoHist.pop_front(); @@ -563,11 +593,11 @@ void FurnaceGUI::drawFindReplace() { i.note=0; } if (i.note==130) { - snprintf(tempID,1024,"REL"); + snprintf(tempID,1024,"%s##MREL",macroRelLabel); } else if (i.note==129) { - snprintf(tempID,1024,"==="); + snprintf(tempID,1024,"%s##NREL",noteRelLabel); } else if (i.note==128) { - snprintf(tempID,1024,"OFF"); + snprintf(tempID,1024,"%s##NOFF",noteOffLabel); } else if (i.note>=-60 && i.note<120) { snprintf(tempID,1024,"%s",noteNames[i.note+60]); } else { @@ -583,13 +613,13 @@ void FurnaceGUI::drawFindReplace() { } } if (i.noteMode!=GUI_QUERY_RANGE && i.noteMode!=GUI_QUERY_RANGE_NOT) { - if (ImGui::Selectable("OFF",i.note==128)) { + if (ImGui::Selectable(noteOffLabel,i.note==128)) { i.note=128; } - if (ImGui::Selectable("===",i.note==129)) { + if (ImGui::Selectable(noteRelLabel,i.note==129)) { i.note=129; } - if (ImGui::Selectable("REL",i.note==130)) { + if (ImGui::Selectable(macroRelLabel,i.note==130)) { i.note=130; } } @@ -886,11 +916,11 @@ void FurnaceGUI::drawFindReplace() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (queryReplaceNoteMode==GUI_QUERY_REPLACE_SET) { if (queryReplaceNote==130) { - snprintf(tempID,1024,"REL"); + snprintf(tempID,1024,"%s##MREL",macroRelLabel); } else if (queryReplaceNote==129) { - snprintf(tempID,1024,"==="); + snprintf(tempID,1024,"%s##NREL",noteRelLabel); } else if (queryReplaceNote==128) { - snprintf(tempID,1024,"OFF"); + snprintf(tempID,1024,"%s##NOFF",noteOffLabel); } else if (queryReplaceNote>=-60 && queryReplaceNote<120) { snprintf(tempID,1024,"%s",noteNames[queryReplaceNote+60]); } else { @@ -904,13 +934,13 @@ void FurnaceGUI::drawFindReplace() { queryReplaceNote=j-60; } } - if (ImGui::Selectable("OFF",queryReplaceNote==128)) { + if (ImGui::Selectable(noteOffLabel,queryReplaceNote==128)) { queryReplaceNote=128; } - if (ImGui::Selectable("===",queryReplaceNote==129)) { + if (ImGui::Selectable(noteRelLabel,queryReplaceNote==129)) { queryReplaceNote=129; } - if (ImGui::Selectable("REL",queryReplaceNote==130)) { + if (ImGui::Selectable(macroRelLabel,queryReplaceNote==130)) { queryReplaceNote=130; } ImGui::EndCombo(); @@ -920,6 +950,8 @@ void FurnaceGUI::drawFindReplace() { if (queryReplaceNote<-180) queryReplaceNote=-180; if (queryReplaceNote>180) queryReplaceNote=180; } + } else if (queryReplaceNoteMode==GUI_QUERY_REPLACE_SCALE) { + ImGui::Text("INVALID"); } ImGui::EndDisabled(); @@ -942,6 +974,13 @@ void FurnaceGUI::drawFindReplace() { if (queryReplaceIns<-255) queryReplaceIns=-255; if (queryReplaceIns>255) queryReplaceIns=255; } + } else if (queryReplaceInsMode==GUI_QUERY_REPLACE_SCALE) { + if (queryReplaceIns<0) queryReplaceIns=0; + if (queryReplaceIns>400) queryReplaceIns=400; + if (ImGui::InputInt("##IRValue",&queryReplaceIns,1,12)) { + if (queryReplaceIns<0) queryReplaceIns=0; + if (queryReplaceIns>400) queryReplaceIns=400; + } } ImGui::EndDisabled(); @@ -964,6 +1003,13 @@ void FurnaceGUI::drawFindReplace() { if (queryReplaceVol<-255) queryReplaceVol=-255; if (queryReplaceVol>255) queryReplaceVol=255; } + } else if (queryReplaceVolMode==GUI_QUERY_REPLACE_SCALE) { + if (queryReplaceVol<0) queryReplaceVol=0; + if (queryReplaceVol>400) queryReplaceVol=400; + if (ImGui::InputInt("##VRValue",&queryReplaceVol,1,12)) { + if (queryReplaceVol<0) queryReplaceVol=0; + if (queryReplaceVol>400) queryReplaceVol=400; + } } ImGui::EndDisabled(); @@ -988,6 +1034,13 @@ void FurnaceGUI::drawFindReplace() { 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]>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; + } } ImGui::EndDisabled(); @@ -997,23 +1050,29 @@ void FurnaceGUI::drawFindReplace() { ImGui::TableNextColumn(); ImGui::BeginDisabled(!queryReplaceEffectValDo[i]); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::Combo("##ERMode",&queryReplaceEffectValMode[i],queryReplaceModes,GUI_QUERY_REPLACE_MAX); + 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("##ERValueH",ImGuiDataType_S32,&queryReplaceEffectVal[i],&_ONE,&_SIXTEEN,"%.2X",ImGuiInputTextFlags_CharsHexadecimal)) { + 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("##ERValue",&queryReplaceEffectVal[i],1,12)) { + 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(); } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 642572d38..190fdec05 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,19 +576,125 @@ 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()); } +void FurnaceGUI::autoDetectSystem() { + std::map sysCountMap; + for (int i=0; isong.systemLen; i++) { + try { + sysCountMap.at(e->song.system[i])++; + } catch (std::exception& ex) { + sysCountMap[e->song.system[i]]=1; + } + } + + logV("sysCountMap:"); + for (std::pair k: sysCountMap) { + logV("%s: %d",e->getSystemName(k.first),k.second); + } + + bool isMatch=false; + std::map defCountMap; + for (FurnaceGUISysCategory& i: sysCategories) { + for (FurnaceGUISysDef& j: i.systems) { + defCountMap.clear(); + for (size_t k=0; k k: defCountMap) { + logV("- %s: %d",e->getSystemName(k.first),k.second); + } + for (std::pair k: defCountMap) { + try { + if (sysCountMap.at(k.first)!=k.second) { + isMatch=false; + break; + } + } catch (std::exception& ex) { + isMatch=false; + break; + } + } + if (isMatch) { + logV("match found!"); + e->song.systemName=j.name; + break; + } + } + if (isMatch) break; + } + + if (!isMatch) { + bool isFirst=true; + e->song.systemName=""; + for (std::pair k: sysCountMap) { + if (!isFirst) e->song.systemName+=" + "; + if (k.second>1) { + e->song.systemName+=fmt::sprintf("%d×",k.second); + } + if (k.first==DIV_SYSTEM_N163) { + e->song.systemName+=settings.c163Name; + } else { + e->song.systemName+=e->getSystemName(k.first); + } + isFirst=false; + } + } +} + +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\ +Size=1280,776\n\ Collapsed=0\n\ \n\ [Window][Debug##Default]\n\ -Pos=54,0\n\ +Pos=54,19\n\ Size=400,400\n\ Collapsed=0\n\ \n\ @@ -574,9 +705,9 @@ Collapsed=0\n\ \n\ [Window][Song Information]\n\ Pos=978,24\n\ -Size=302,217\n\ +Size=302,179\n\ Collapsed=0\n\ -DockId=0x00000004,0\n\ +DockId=0x0000000F,0\n\ \n\ [Window][Orders]\n\ Pos=0,24\n\ @@ -588,7 +719,7 @@ DockId=0x00000007,0\n\ Pos=653,24\n\ Size=323,217\n\ Collapsed=0\n\ -DockId=0x00000006,2\n\ +DockId=0x00000006,0\n\ \n\ [Window][Wavetables]\n\ Pos=653,24\n\ @@ -600,13 +731,13 @@ DockId=0x00000006,1\n\ Pos=653,24\n\ Size=323,217\n\ Collapsed=0\n\ -DockId=0x00000006,0\n\ +DockId=0x00000006,2\n\ \n\ [Window][Pattern]\n\ Pos=0,243\n\ -Size=1246,512\n\ +Size=1246,557\n\ Collapsed=0\n\ -DockId=0x0000000B,0\n\ +DockId=0x00000013,0\n\ \n\ [Window][Instrument Editor]\n\ Pos=372,102\n\ @@ -615,7 +746,7 @@ Collapsed=0\n\ \n\ [Window][Warning]\n\ Pos=481,338\n\ -Size=346,71\n\ +Size=264,86\n\ Collapsed=0\n\ \n\ [Window][Sample Editor]\n\ @@ -648,8 +779,8 @@ Size=514,71\n\ Collapsed=0\n\ \n\ [Window][Mixer]\n\ -Pos=63,55\n\ -Size=450,215\n\ +Pos=429,198\n\ +Size=453,355\n\ Collapsed=0\n\ \n\ [Window][Oscilloscope]\n\ @@ -660,7 +791,7 @@ DockId=0x0000000E,0\n\ \n\ [Window][Volume Meter]\n\ Pos=1248,243\n\ -Size=32,512\n\ +Size=32,557\n\ Collapsed=0\n\ DockId=0x0000000C,0\n\ \n\ @@ -735,9 +866,10 @@ Size=368,449\n\ Collapsed=0\n\ \n\ [Window][Register View]\n\ -Pos=847,180\n\ -Size=417,393\n\ +Pos=829,243\n\ +Size=417,557\n\ Collapsed=0\n\ +DockId=0x00000014,0\n\ \n\ [Window][New Song]\n\ Pos=267,110\n\ @@ -756,8 +888,40 @@ Size=304,40\n\ Collapsed=0\n\ DockId=0x0000000A,0\n\ \n\ +[Window][Subsongs]\n\ +Pos=978,205\n\ +Size=302,36\n\ +Collapsed=0\n\ +DockId=0x00000010,0\n\ +\n\ +[Window][Oscilloscope (per-channel)]\n\ +Pos=1095,243\n\ +Size=151,557\n\ +Collapsed=0\n\ +DockId=0x00000012,0\n\ +\n\ +[Window][Piano]\n\ +Pos=177,669\n\ +Size=922,118\n\ +Collapsed=0\n\ +\n\ +[Window][Log Viewer]\n\ +Pos=60,60\n\ +Size=541,637\n\ +Collapsed=0\n\ +\n\ +[Window][Pattern Manager]\n\ +Pos=60,60\n\ +Size=1099,366\n\ +Collapsed=0\n\ +\n\ +[Window][Chip Manager]\n\ +Pos=60,60\n\ +Size=490,407\n\ +Collapsed=0\n\ +\n\ [Docking][Data]\n\ -DockSpace ID=0x8B93E3BD Window=0xA787BDB4 Pos=0,24 Size=1280,731 Split=Y Selected=0x6C01C512\n\ +DockSpace ID=0x8B93E3BD Window=0xA787BDB4 Pos=0,24 Size=1280,776 Split=Y Selected=0x6C01C512\n\ DockNode ID=0x00000001 Parent=0x8B93E3BD SizeRef=1280,217 Split=X Selected=0xF3094A52\n\ DockNode ID=0x00000003 Parent=0x00000001 SizeRef=976,231 Split=X Selected=0x65CC51DC\n\ DockNode ID=0x00000007 Parent=0x00000003 SizeRef=345,231 HiddenTabBar=1 Selected=0x8F5BFC9A\n\ @@ -768,10 +932,17 @@ DockSpace ID=0x8B93E3BD Window=0xA787BDB4 Pos=0,24 Size=1280,731 Spl DockNode ID=0x0000000E Parent=0x00000009 SizeRef=292,105 HiddenTabBar=1 Selected=0x6D682373\n\ DockNode ID=0x0000000A Parent=0x00000005 SizeRef=292,40 HiddenTabBar=1 Selected=0x0DE44CFF\n\ DockNode ID=0x00000006 Parent=0x00000008 SizeRef=323,406 Selected=0xD2AD486B\n\ - DockNode ID=0x00000004 Parent=0x00000001 SizeRef=302,231 Selected=0x60B9D088\n\ + DockNode ID=0x00000004 Parent=0x00000001 SizeRef=302,231 Split=Y Selected=0x60B9D088\n\ + DockNode ID=0x0000000F Parent=0x00000004 SizeRef=302,179 Selected=0x60B9D088\n\ + DockNode ID=0x00000010 Parent=0x00000004 SizeRef=302,36 HiddenTabBar=1 Selected=0x723A6369\n\ DockNode ID=0x00000002 Parent=0x8B93E3BD SizeRef=1280,512 Split=X Selected=0x6C01C512\n\ - DockNode ID=0x0000000B Parent=0x00000002 SizeRef=1246,503 CentralNode=1 HiddenTabBar=1 Selected=0xB9ADD0D5\n\ - DockNode ID=0x0000000C Parent=0x00000002 SizeRef=32,503 HiddenTabBar=1 Selected=0x644DA2C1\n\n"; + DockNode ID=0x0000000B Parent=0x00000002 SizeRef=1246,503 Split=X Selected=0xB9ADD0D5\n\ + DockNode ID=0x00000011 Parent=0x0000000B SizeRef=1093,557 Split=X Selected=0xB9ADD0D5\n\ + DockNode ID=0x00000013 Parent=0x00000011 SizeRef=827,557 CentralNode=1 HiddenTabBar=1 Selected=0xB9ADD0D5\n\ + DockNode ID=0x00000014 Parent=0x00000011 SizeRef=417,557 Selected=0x425428FB\n\ + DockNode ID=0x00000012 Parent=0x0000000B SizeRef=151,557 HiddenTabBar=1 Selected=0x4C07BC58\n\ + DockNode ID=0x0000000C Parent=0x00000002 SizeRef=32,503 HiddenTabBar=1 Selected=0x644DA2C1\n"; + void FurnaceGUI::prepareLayout() { FILE* check; @@ -860,7 +1031,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 +1299,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 +1396,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 +1407,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 +1446,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 +1499,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 +1531,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 +1563,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 +1624,27 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; + case GUI_FILE_EXPORT_ZSM: + if (!dirExists(workingDirZSMExport)) workingDirZSMExport=getHomeDir(); + hasOpened=fileDialog->openSave( + "Export ZSM", + {"ZSM file", "*.zsm"}, + "ZSM file{.zsm}", + workingDirZSMExport, + 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 +1741,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 +1788,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 +1879,7 @@ int FurnaceGUI::save(String path, int dmfVersion) { if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } + pushRecentFile(path); return 0; } @@ -1672,9 +1957,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 +1993,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 +2052,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 +2155,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 +2173,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); \ @@ -1906,6 +2230,30 @@ void FurnaceGUI::editOptions(bool topMenu) { if (ImGui::BeginMenu("paste special...")) { if (ImGui::MenuItem("paste mix",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX))) doPaste(GUI_PASTE_MODE_MIX_FG); if (ImGui::MenuItem("paste mix (background)",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX_BG))) doPaste(GUI_PASTE_MODE_MIX_BG); + if (ImGui::BeginMenu("paste with ins (foreground)")) { + if (e->song.ins.empty()) { + ImGui::Text("no instruments available"); + } + for (size_t i=0; isong.ins.size(); i++) { + snprintf(id,4095,"%.2X: %s",(int)i,e->song.ins[i]->name.c_str()); + if (ImGui::MenuItem(id)) { + doPaste(GUI_PASTE_MODE_INS_FG,i); + } + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("paste with ins (background)")) { + if (e->song.ins.empty()) { + ImGui::Text("no instruments available"); + } + for (size_t i=0; isong.ins.size(); i++) { + snprintf(id,4095,"%.2X: %s",(int)i,e->song.ins[i]->name.c_str()); + if (ImGui::MenuItem(id)) { + doPaste(GUI_PASTE_MODE_INS_BG,i); + } + } + ImGui::EndMenu(); + } if (ImGui::MenuItem("paste flood",BIND_FOR(GUI_ACTION_PAT_PASTE_FLOOD))) doPaste(GUI_PASTE_MODE_FLOOD); if (ImGui::MenuItem("paste overflow",BIND_FOR(GUI_ACTION_PAT_PASTE_OVERFLOW))) doPaste(GUI_PASTE_MODE_OVERFLOW); ImGui::EndMenu(); @@ -2027,7 +2375,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 +2448,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 +2575,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 +2770,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 +2799,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 +2807,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 +2950,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 +2968,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 +2991,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 +3032,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 +3062,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 +3089,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 +3245,7 @@ bool FurnaceGUI::loop() { eventTimeEnd=SDL_GetPerformanceCounter(); layoutTimeBegin=SDL_GetPerformanceCounter(); - + ImGui_ImplSDLRenderer_NewFrame(); ImGui_ImplSDL2_NewFrame(sdlWin); ImGui::NewFrame(); @@ -2809,6 +3271,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 +3305,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 +3316,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 +3341,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 +3366,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 +3387,55 @@ 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)) continue; - sysAddOption((DivSystem)availableSystems[j]); + int numZSMCompat=0; + for (int i=0; isong.systemLen; i++) { + if ((e->song.system[i] == DIV_SYSTEM_VERA) || (e->song.system[i] == DIV_SYSTEM_YM2151)) numZSMCompat++; + } + if (numZSMCompat > 0) { + if (ImGui::BeginMenu("export ZSM...")) { + ImGui::Text("Commander X16 Zsound Music File"); + if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) { + if (zsmExportTickRate<1) zsmExportTickRate=1; + if (zsmExportTickRate>44100) zsmExportTickRate=44100; + } + ImGui::Checkbox("loop",&zsmExportLoop); + ImGui::SameLine(); + if (ImGui::Button("Begin Export")) { + openFileDialog(GUI_FILE_EXPORT_ZSM); + ImGui::CloseCurrentPopup(); + } + ImGui::EndMenu(); + } + } + 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(); + if (e->song.autoSystem) { + autoDetectSystem(); + } + 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 +3444,34 @@ 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)) continue; - sysChangeOption(i,(DivSystem)availableSystems[j]); + DivSystem picked=systemPicker(); + if (picked!=DIV_SYSTEM_NULL) { + e->changeSystem(i,picked,preserveChanPos); + if (e->song.autoSystem) { + autoDetectSystem(); + } + 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()+")"); + } + if (e->song.autoSystem) { + autoDetectSystem(); + updateWindowTitle(); } } } @@ -2971,6 +3522,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 +3547,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(); @@ -3007,7 +3565,7 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("log viewer",BIND_FOR(GUI_ACTION_WINDOW_LOG),logOpen)) logOpen=!logOpen; if (ImGui::MenuItem("statistics",BIND_FOR(GUI_ACTION_WINDOW_STATS),statsOpen)) statsOpen=!statsOpen; if (spoilerOpen) if (ImGui::MenuItem("spoiler",NULL,spoilerOpen)) spoilerOpen=!spoilerOpen; - + ImGui::EndMenu(); } if (ImGui::BeginMenu("help")) { @@ -3089,11 +3647,45 @@ bool FurnaceGUI::loop() { ImGui::EndMainMenuBar(); } + calcChanOsc(); + 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); @@ -3125,6 +3717,8 @@ bool FurnaceGUI::loop() { drawPiano(); drawNotes(); drawChannels(); + drawPatManager(); + drawSysManager(); drawRegView(); drawLog(); drawEffectList(); @@ -3134,6 +3728,13 @@ bool FurnaceGUI::loop() { if (firstFrame) { firstFrame=false; +#ifdef IS_MOBILE + SDL_GetWindowSize(sdlWin,&scrW,&scrH); + scrW/=dpiScale; + scrH/=dpiScale; + portrait=(scrWgetPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_INS_OPEN: case GUI_FILE_INS_OPEN_REPLACE: case GUI_FILE_INS_SAVE: + case GUI_FILE_INS_SAVE_DMP: workingDirIns=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_WAVE_OPEN: + case GUI_FILE_WAVE_OPEN_REPLACE: case GUI_FILE_WAVE_SAVE: + case GUI_FILE_WAVE_SAVE_DMW: + case GUI_FILE_WAVE_SAVE_RAW: workingDirWave=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_SAMPLE_OPEN: + 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; @@ -3196,9 +3805,15 @@ 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_ZSM: + workingDirZSMExport=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; @@ -3220,14 +3835,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"); @@ -3241,12 +3873,29 @@ 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_ZSM) { + checkExtension(".zsm"); + } + 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"); } @@ -3265,21 +3914,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) { @@ -3312,6 +3950,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) { @@ -3323,18 +3967,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()); @@ -3398,15 +4091,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) { @@ -3425,9 +4142,58 @@ bool FurnaceGUI::loop() { } break; } + case GUI_FILE_EXPORT_ZSM: { + SafeWriter* w=e->saveZSM(zsmExportTickRate,zsmExportLoop); + 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 ZSM! (%s)",e->getLastError())); + } + break; + } 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; @@ -3461,6 +4227,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; } @@ -3488,6 +4268,11 @@ bool FurnaceGUI::loop() { ImGui::OpenPopup("Select Instrument"); } + if (displayPendingRawSample) { + displayPendingRawSample=false; + ImGui::OpenPopup("Import Raw Sample"); + } + if (displayExporting) { displayExporting=false; ImGui::OpenPopup("Rendering..."); @@ -3829,6 +4614,20 @@ bool FurnaceGUI::loop() { ImGui::CloseCurrentPopup(); } break; + case GUI_WARN_SYSTEM_DEL: + if (ImGui::Button("Yes")) { + e->removeSystem(sysToDelete,preserveChanPos); + if (e->song.autoSystem) { + autoDetectSystem(); + updateWindowTitle(); + } + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("No")) { + ImGui::CloseCurrentPopup(); + } + break; case GUI_WARN_GENERIC: if (ImGui::Button("OK")) { ImGui::CloseCurrentPopup(); @@ -3918,6 +4717,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 @@ -3932,7 +4778,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) { @@ -4007,10 +4853,13 @@ bool FurnaceGUI::init() { workingDirSample=e->getConfString("lastDirSample",workingDir); workingDirAudioExport=e->getConfString("lastDirAudioExport",workingDir); workingDirVGMExport=e->getConfString("lastDirVGMExport",workingDir); + workingDirZSMExport=e->getConfString("lastDirZSMExport",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); @@ -4032,6 +4881,8 @@ 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); @@ -4041,6 +4892,9 @@ bool FurnaceGUI::init() { tempoView=e->getConfBool("tempoView",true); waveHex=e->getConfBool("waveHex",false); + waveSigned=e->getConfBool("waveSigned",false); + waveGenVisible=e->getConfBool("waveGenVisible",false); + waveEditStyle=e->getConfInt("waveEditStyle",0); lockLayout=e->getConfBool("lockLayout",false); #ifdef IS_MOBILE fullScreen=true; @@ -4086,6 +4940,13 @@ bool FurnaceGUI::init() { syncSettings(); + for (int i=0; igetConfString(fmt::sprintf("recentFile%d",i),""); + if (!r.empty()) { + recentFile.push_back(r); + } + } + if (settings.dpiScale>=0.5f) { dpiScale=settings.dpiScale; } @@ -4099,10 +4960,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("lastDirZSMExport",workingDirZSMExport); + 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); @@ -4270,6 +5161,8 @@ 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); @@ -4278,11 +5171,17 @@ bool FurnaceGUI::finish() { 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); @@ -4322,6 +5221,16 @@ bool FurnaceGUI::finish() { e->setConf("chanOscUseGrad",chanOscUseGrad); e->setConf("chanOscGrad",chanOscGrad.toString()); + // commit recent files + for (int i=0; i<30; i++) { + String key=fmt::sprintf("recentFile%d",i); + if (i>=settings.maxRecentFile || i>=(int)recentFile.size()) { + e->setConf(key,""); + } else { + e->setConf(key,recentFile[i]); + } + } + for (int i=0; isong.ins) { \ + _wi->std.waveMacro.vZoom=-1; \ + _wi->std.waveMacro.vScroll=-1; \ + } #define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str() @@ -76,6 +80,7 @@ enum FurnaceGUIColors { GUI_COLOR_FILE_AUDIO, GUI_COLOR_FILE_WAVE, GUI_COLOR_FILE_VGM, + GUI_COLOR_FILE_ZSM, GUI_COLOR_FILE_FONT, GUI_COLOR_FILE_OTHER, @@ -155,8 +160,20 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_SU, GUI_COLOR_INSTR_NAMCO, GUI_COLOR_INSTR_OPL_DRUMS, + GUI_COLOR_INSTR_OPM, + GUI_COLOR_INSTR_NES, + GUI_COLOR_INSTR_MSM6258, + GUI_COLOR_INSTR_MSM6295, + GUI_COLOR_INSTR_ADPCMA, + GUI_COLOR_INSTR_ADPCMB, + GUI_COLOR_INSTR_SEGAPCM, + GUI_COLOR_INSTR_QSOUND, + GUI_COLOR_INSTR_YMZ280B, + GUI_COLOR_INSTR_RF5C68, GUI_COLOR_INSTR_UNKNOWN, + GUI_COLOR_CHANNEL_BG, + GUI_COLOR_CHANNEL_FG, GUI_COLOR_CHANNEL_FM, GUI_COLOR_CHANNEL_PULSE, GUI_COLOR_CHANNEL_NOISE, @@ -200,6 +217,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, @@ -242,6 +266,8 @@ 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, @@ -251,21 +277,43 @@ enum FurnaceGUIWindows { 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_ZSM, + GUI_FILE_EXPORT_CMDSTREAM, GUI_FILE_EXPORT_ROM, GUI_FILE_LOAD_MAIN_FONT, GUI_FILE_LOAD_PAT_FONT, @@ -277,7 +325,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 { @@ -292,6 +344,7 @@ enum FurnaceGUIWarnings { GUI_WARN_CLOSE_SETTINGS, GUI_WARN_CLEAR, GUI_WARN_SUBSONG_DEL, + GUI_WARN_SYSTEM_DEL, GUI_WARN_GENERIC }; @@ -352,6 +405,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, @@ -434,6 +489,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, @@ -446,7 +502,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, @@ -459,6 +518,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, @@ -500,6 +562,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, @@ -545,7 +608,9 @@ enum PasteMode { GUI_PASTE_MODE_MIX_FG, GUI_PASTE_MODE_MIX_BG, GUI_PASTE_MODE_FLOOD, - GUI_PASTE_MODE_OVERFLOW + GUI_PASTE_MODE_OVERFLOW, + GUI_PASTE_MODE_INS_FG, + GUI_PASTE_MODE_INS_BG }; #define FURKMOD_CTRL (1U<<31) @@ -845,10 +910,11 @@ struct FurnaceGUIMacroDesc { const char** modeName; ImVec4 color; unsigned int bitOffset; - bool isBitfield, blockMode, macroMode; - 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, bool mMode=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), @@ -858,8 +924,9 @@ struct FurnaceGUIMacroDesc { bitOffset(bitOff), isBitfield(bitfield), blockMode(block), - macroMode(mMode), - hoverFunc(hf) { + bit30(bit30Special), + hoverFunc(hf), + hoverFuncUser(hfu) { // MSVC -> hell this->min=macroMin; this->max=macroMax; @@ -882,6 +949,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 @@ -943,28 +1011,47 @@ 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, workingDirZSMExport, 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, zsmExportLoop, 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 zsmExportTickRate; int macroPointSize; + int waveEditStyle; + float mobileMenuPos, autoButtonSize; + 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; @@ -996,6 +1083,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; @@ -1005,6 +1098,7 @@ class FurnaceGUI { int snCore; int nesCore; int fdsCore; + int c64Core; int pcSpeakerOutMethod; String yrw801Path; String tg100Path; @@ -1047,6 +1141,8 @@ class FurnaceGUI { int roundedMenus; int loadJapanese; int loadChinese; + int loadChineseTraditional; + int loadKorean; int fmLayout; int sampleLayout; int waveLayout; @@ -1088,12 +1184,31 @@ class FurnaceGUI { 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 channelTextCenter; + 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(): @@ -1107,6 +1222,7 @@ class FurnaceGUI { snCore(0), nesCore(0), fdsCore(0), + c64Core(1), pcSpeakerOutMethod(0), yrw801Path(""), tg100Path(""), @@ -1149,6 +1265,8 @@ class FurnaceGUI { roundedMenus(0), loadJapanese(0), loadChinese(0), + loadChineseTraditional(0), + loadKorean(0), fmLayout(0), sampleLayout(0), waveLayout(0), @@ -1190,12 +1308,30 @@ class FurnaceGUI { blankIns(0), dragMovesSelection(1), unsignedDetune(0), + noThreadedInput(0), + clampSamples(0), + saveUnusedPatterns(0), + channelColors(1), + channelTextColors(0), + channelStyle(1), + channelVolStyle(0), + channelFeedbackStyle(1), + channelFont(1), + channelTextCenter(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]; @@ -1212,16 +1348,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, spoilerOpen; + bool subSongsOpen, findOpen, spoilerOpen, patManagerOpen, sysManagerOpen; SelectionPoint selStart, selEnd, cursor, cursorDrag, dragStart, dragEnd; bool selecting, selectingFull, dragging, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI; - bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, lockLayout, editOptsVisible, latchNibble, nonLatchNibble; + 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; @@ -1326,6 +1463,8 @@ class FurnaceGUI { bool macroDragInitialValueSet; bool macroDragInitialValue; bool macroDragChar; + bool macroDragBit30; + bool macroDragSettingBit30; bool macroDragLineMode; bool macroDragMouseMoved; ImVec2 macroDragLineInitial; @@ -1334,10 +1473,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; @@ -1357,10 +1497,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; @@ -1435,8 +1575,9 @@ class FurnaceGUI { // visualizer float keyHit[DIV_MAX_CHANS]; + float keyHit1[DIV_MAX_CHANS]; int lastIns[DIV_MAX_CHANS]; - + // log window bool followLog; @@ -1452,12 +1593,30 @@ 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); void drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, unsigned char sus, unsigned char egt, unsigned char algOrGlobalSus, float maxTl, float maxArDr, const ImVec2& size, unsigned short instType); void drawGBEnv(unsigned char vol, unsigned char len, unsigned char sLen, bool dir, const ImVec2& size); void drawSysConf(int chan, DivSystem type, unsigned int& flags, bool modifyOnChange); + void kvsConfig(DivInstrument* ins); // these ones offer ctrl-wheel fine value changes. bool CWSliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format=NULL, ImGuiSliderFlags flags=0); @@ -1467,9 +1626,13 @@ class FurnaceGUI { bool CWVSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format="%d", ImGuiSliderFlags flags=0); void updateWindowTitle(); + void autoDetectSystem(); void prepareLayout(); + ImVec4 channelColor(int ch); + ImVec4 channelTextColor(int ch); void readOsc(); + void calcChanOsc(); void pushAccentColors(const ImVec4& one, const ImVec4& two, const ImVec4& border, const ImVec4& borderShadow); void popAccentColors(); @@ -1485,16 +1648,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(); @@ -1505,6 +1671,8 @@ class FurnaceGUI { void drawPiano(); void drawNotes(); void drawChannels(); + void drawPatManager(); + void drawSysManager(); void drawRegView(); void drawAbout(); void drawSettings(); @@ -1556,7 +1724,7 @@ class FurnaceGUI { void doInsert(); void doTranspose(int amount, OperationMask& mask); void doCopy(bool cut); - void doPaste(PasteMode mode=GUI_PASTE_MODE_NORMAL); + void doPaste(PasteMode mode=GUI_PASTE_MODE_NORMAL, int arg=0); void doChangeIns(int ins); void doInterpolate(); void doFade(int p0, int p1, bool mode); @@ -1572,9 +1740,12 @@ class FurnaceGUI { 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(); @@ -1588,9 +1759,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); @@ -1598,9 +1774,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); @@ -1610,6 +1786,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); @@ -1619,6 +1796,7 @@ class FurnaceGUI { void runBackupThread(); void pushPartBlend(); void popPartBlend(); + bool detectOutOfBoundsWindow(); int processEvent(SDL_Event* ev); bool loop(); bool finish(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 7c922afba..eb309a509 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -80,11 +80,11 @@ const int vgmVersions[6]={ }; const char* insTypes[DIV_INS_MAX+1]={ - "Standard (SMS/NES)", - "FM (4-operator)", + "SN76489/Sega PSG", + "FM (OPN)", "Game Boy", "C64", - "Sample", + "Generic Sample", "PC Engine", "AY-3-8910/SSG", "AY8930", @@ -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", @@ -113,9 +113,25 @@ const char* insTypes[DIV_INS_MAX+1]={ "Sound Unit", "Namco WSG", "OPL (drums)", + "FM (OPM)", // 33 + "NES", // 34 + "MSM6258", + "MSM6295", + "ADPCM-A", + "ADPCM-B", + "SegaPCM", + "QSound", + "YMZ280B", + "RF5C68", NULL }; +const char* sampleLoopModes[DIV_SAMPLE_LOOP_MAX]={ + "Forward", + "Backward", + "Ping pong" +}; + const char* sampleDepths[DIV_SAMPLE_DEPTH_MAX]={ "1-bit PCM", "1-bit DPCM", @@ -126,7 +142,7 @@ const char* sampleDepths[DIV_SAMPLE_DEPTH_MAX]={ "ADPCM-B", NULL, "8-bit PCM", - NULL, // "BRR", + "BRR", "VOX", NULL, NULL, @@ -494,11 +510,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), @@ -576,6 +594,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), @@ -588,7 +607,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), @@ -601,6 +623,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), @@ -642,6 +667,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), @@ -690,6 +716,7 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_FILE_AUDIO,"",ImVec4(1.0f,1.0f,0.5f,1.0f)), D(GUI_COLOR_FILE_WAVE,"",ImVec4(1.0f,0.75f,0.5f,1.0f)), D(GUI_COLOR_FILE_VGM,"",ImVec4(1.0f,1.0f,0.5f,1.0f)), + D(GUI_COLOR_FILE_ZSM,"",ImVec4(1.0f,1.0f,0.5f,1.0f)), D(GUI_COLOR_FILE_FONT,"",ImVec4(0.3f,1.0f,0.6f,1.0f)), D(GUI_COLOR_FILE_OTHER,"",ImVec4(0.7f,0.7f,0.7f,1.0f)), @@ -769,8 +796,20 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_INSTR_SU,"",ImVec4(0.95f,0.98f,1.0f,1.0f)), D(GUI_COLOR_INSTR_NAMCO,"",ImVec4(1.0f,1.0f,0.0f,1.0f)), D(GUI_COLOR_INSTR_OPL_DRUMS,"",ImVec4(0.3f,1.0f,0.9f,1.0f)), + D(GUI_COLOR_INSTR_OPM,"",ImVec4(0.2f,0.6f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_NES,"",ImVec4(0.4f,1.0f,0.3f,1.0f)), + D(GUI_COLOR_INSTR_MSM6258,"",ImVec4(1.0f,0.5f,0.7f,1.0f)), + D(GUI_COLOR_INSTR_MSM6295,"",ImVec4(1.0f,0.6f,0.9f,1.0f)), + D(GUI_COLOR_INSTR_ADPCMA,"",ImVec4(1.0f,1.0f,0.5f,1.0f)), + D(GUI_COLOR_INSTR_ADPCMB,"",ImVec4(1.0f,0.75f,0.5f,1.0f)), + D(GUI_COLOR_INSTR_SEGAPCM,"",ImVec4(1.0f,0.9f,0.6f,1.0f)), + D(GUI_COLOR_INSTR_QSOUND,"",ImVec4(1.0f,0.8f,0.3f,1.0f)), + D(GUI_COLOR_INSTR_YMZ280B,"",ImVec4(0.4f,0.5f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_RF5C68,"",ImVec4(1.0f,0.3f,0.3f,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)), @@ -814,6 +853,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)), @@ -833,7 +879,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, @@ -902,5 +950,121 @@ const int availableSystems[]={ DIV_SYSTEM_MSM6258, 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, + DIV_SYSTEM_ES5506, + 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 +}; diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index 217a5cb70..a7ff2bf79 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -40,10 +40,18 @@ extern const char* noteNames[180]; extern const char* noteNamesG[180]; extern const char* pitchLabel[11]; extern const char* insTypes[]; +extern const char* sampleLoopModes[]; extern const char* sampleDepths[]; extern const char* resampleStrats[]; +extern const char* chipCategoryNames[]; extern const char* loopMode[]; 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 86ca8d2a0..ce9d111ae 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]={ @@ -187,6 +255,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 }; @@ -207,6 +279,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 }; @@ -233,48 +313,58 @@ const char* dualWSEffects[9]={ "Phase Modulation" }; -const char* macroAbsoluteMode[3]={ - "Relative", - "Absolute", - NULL +const char* gbHWSeqCmdTypes[6]={ + "Envelope", + "Sweep", + "Wait", + "Wait for Release", + "Loop", + "Loop until Release" }; -const char* macroRelativeMode[3]={ - "Absolute", - "Relative", - NULL +const char* snesGainModes[5]={ + "Direct", + "Decrease (linear)", + "Decrease (logarithmic)", + "Increase (linear)", + "Increase (bent line)" }; -const char* macroQSoundMode[3]={ - "Independent", - "QSound", - NULL -}; +// 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"; -const char* macroFilterMode[4]={ - "Relative", - "Absolute", - "Delta", - NULL -}; - -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 mode=""; - if (val>1) return mode=": Release"; - if (val>0) return mode=": Loop"; - return fmt::sprintf("%d%s",id,mode); +String macroHoverLoop(int id, float val, void* u) { + if (val>1) return "Release"; + if (val>0) return "Loop"; + return ""; } -String macroHoverES5506FilterMode(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: @@ -295,8 +385,7 @@ String macroHoverES5506FilterMode(int id, float val) { return fmt::sprintf("%d: %s",id,mode); } -String macroLFOWaves(int id, float val) { - String mode="???"; +String macroLFOWaves(int id, float val, void* u) { switch (((int)val)&3) { case 0: mode="Saw"; @@ -1140,10 +1229,62 @@ 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::kvsConfig(DivInstrument* ins) { + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("(click to configure TL scaling)"); + } + int opCount=4; + if (ins->type==DIV_INS_OPLL) opCount=2; + if (ins->type==DIV_INS_OPL) opCount=(ins->fm.ops==4)?4:2; + if (ImGui::BeginPopupContextItem("IKVSOpt",ImGuiPopupFlags_MouseButtonLeft)) { + ImGui::Text("operator level changes with volume?"); + if (ImGui::BeginTable("KVSTable",4,ImGuiTableFlags_BordersInner)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch); + for (int i=0; i<4; i++) { + int o=(opCount==4)?orderedOps[i]:i; + if (!(i&1)) ImGui::TableNextRow(); + const char* label="AUTO##OPKVS"; + if (ins->fm.op[o].kvs==0) { + label="NO##OPKVS"; + } else if (ins->fm.op[o].kvs==1) { + label="YES##OPKVS"; + } + ImGui::TableNextColumn(); + ImGui::Text("%d",i+1); + ImGui::TableNextColumn(); + ImGui::PushID(o); + if (ImGui::Button(label,ImVec2(ImGui::GetContentRegionAvail().x,0.0f))) { + if (++ins->fm.op[o].kvs>2) ins->fm.op[o].kvs=0; + PARAMETER; + } + ImGui::PopID(); + } + ImGui::EndTable(); + } + ImGui::EndPopup(); + } +} + 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; @@ -1162,14 +1303,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 @@ -1186,15 +1327,41 @@ 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 (i.macroMode && i.modeName[0]!=NULL) { - for (int m=0; i.modeName[m]!=NULL; m++) { - String modeName=fmt::sprintf("%s##IMacroMode%d",i.modeName[m],m); - if (ImGui::RadioButton(modeName.c_str(),(int)i.macro->mode==m)) { - i.macro->mode=m; - } + 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); + if (ImGui::Checkbox(modeName.c_str(),&modeVal)) { + i.macro->mode=modeVal; } } } @@ -1202,17 +1369,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)); @@ -1232,11 +1401,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(); @@ -1254,6 +1445,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); @@ -1321,6 +1514,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)) { @@ -1337,18 +1551,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(); @@ -1360,9 +1574,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(); } @@ -1411,6 +1625,49 @@ void FurnaceGUI::drawMacros(std::vector& macros) { #define CENTER_TEXT_20(text) \ ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(20.0f*dpiScale-ImGui::CalcTextSize(text).x)); +#define OP_DRAG_POINT \ + if (ImGui::Button(ICON_FA_ARROWS)) { \ + } \ + if (ImGui::BeginDragDropSource()) { \ + opToMove=i; \ + ImGui::SetDragDropPayload("FUR_OP",NULL,0,ImGuiCond_Once); \ + ImGui::Button(ICON_FA_ARROWS "##SysDrag"); \ + ImGui::SameLine(); \ + if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { \ + ImGui::Text("(copying)"); \ + } else { \ + ImGui::Text("(swapping)"); \ + } \ + ImGui::EndDragDropSource(); \ + } else if (ImGui::IsItemHovered()) { \ + ImGui::SetTooltip("- drag to swap operator\n- shift-drag to copy operator"); \ + } \ + if (ImGui::BeginDragDropTarget()) { \ + const ImGuiPayload* dragItem=ImGui::AcceptDragDropPayload("FUR_OP"); \ + if (dragItem!=NULL) { \ + if (dragItem->IsDataType("FUR_OP")) { \ + if (opToMove!=i && opToMove>=0) { \ + int destOp=(opCount==4 && ins->type!=DIV_INS_OPL_DRUMS)?opOrder[i]:i; \ + int sourceOp=(opCount==4 && ins->type!=DIV_INS_OPL_DRUMS)?opOrder[opToMove]:opToMove; \ + if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { \ + e->lockEngine([ins,destOp,sourceOp]() { \ + ins->fm.op[destOp]=ins->fm.op[sourceOp]; \ + }); \ + } else { \ + e->lockEngine([ins,destOp,sourceOp]() { \ + DivInstrumentFM::Operator origOp=ins->fm.op[sourceOp]; \ + ins->fm.op[sourceOp]=ins->fm.op[destOp]; \ + ins->fm.op[destOp]=origOp; \ + }); \ + } \ + PARAMETER; \ + } \ + opToMove=-1; \ + } \ + } \ + ImGui::EndDragDropTarget(); \ + } + void FurnaceGUI::drawInsEdit() { if (nextWindow==GUI_WINDOW_INS_EDIT) { insEditOpen=true; @@ -1418,7 +1675,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"); @@ -1466,6 +1730,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"); @@ -1479,7 +1749,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++) { @@ -1489,7 +1759,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 @@ -1544,11 +1814,12 @@ void FurnaceGUI::drawInsEdit() { if (ImGui::BeginTabBar("insEditTab")) { std::vector macroList; - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPL_DRUMS) { + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPM) { char label[32]; 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 || ins->type==DIV_INS_OPM); if (ImGui::BeginTabItem("FM")) { if (ImGui::BeginTable("fmDetails",3,ImGuiTableFlags_SizingStretchSame)) { @@ -1558,6 +1829,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); switch (ins->type) { case DIV_INS_FM: + case DIV_INS_OPM: ImGui::TableNextColumn(); P(CWSliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); rightClickable P(CWSliderScalar(FM_NAME(FM_FMS),ImGuiDataType_U8,&ins->fm.fms,&_ZERO,&_SEVEN)); rightClickable @@ -1566,6 +1838,7 @@ void FurnaceGUI::drawInsEdit() { P(CWSliderScalar(FM_NAME(FM_AMS),ImGuiDataType_U8,&ins->fm.ams,&_ZERO,&_THREE)); rightClickable ImGui::TableNextColumn(); drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); + kvsConfig(ins); break; case DIV_INS_OPZ: ImGui::TableNextColumn(); @@ -1578,6 +1851,8 @@ void FurnaceGUI::drawInsEdit() { P(CWSliderScalar(FM_NAME(FM_AMS2),ImGuiDataType_U8,&ins->fm.ams2,&_ZERO,&_THREE)); rightClickable ImGui::TableNextColumn(); drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); + kvsConfig(ins); + if (ImGui::Button("Request from TX81Z")) { doAction(GUI_ACTION_TX81Z_REQUEST); } @@ -1610,6 +1885,7 @@ void FurnaceGUI::drawInsEdit() { } ImGui::TableNextColumn(); drawAlgorithm(ins->fm.alg&algMax,fourOp?FM_ALGS_4OP_OPL:FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); + kvsConfig(ins); break; } case DIV_INS_OPLL: { @@ -1634,11 +1910,61 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndDisabled(); 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(); @@ -1688,7 +2014,7 @@ void FurnaceGUI::drawInsEdit() { } if (willDisplayOps) { if (settings.fmLayout==0) { - int numCols=16; + int numCols=15; if (ins->type==DIV_INS_OPL ||ins->type==DIV_INS_OPL_DRUMS) numCols=13; if (ins->type==DIV_INS_OPLL) numCols=12; if (ins->type==DIV_INS_OPZ) numCols=19; @@ -1698,7 +2024,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.05f); // ar ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.05f); // dr ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.05f); // sl - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,0.05f); // d2r } ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthStretch,0.05f); // rr @@ -1715,14 +2041,16 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableSetupColumn("c9z",ImGuiTableColumnFlags_WidthStretch,0.05f); // fine } - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { ImGui::TableSetupColumn("c10",ImGuiTableColumnFlags_WidthStretch,0.05f); // dt + } + if (ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { ImGui::TableSetupColumn("c11",ImGuiTableColumnFlags_WidthStretch,0.05f); // dt2 } ImGui::TableSetupColumn("c15",ImGuiTableColumnFlags_WidthFixed); // am ImGui::TableSetupColumn("c12",ImGuiTableColumnFlags_WidthFixed); // -separator- - if (ins->type!=DIV_INS_OPLL) { + if (ins->type!=DIV_INS_OPLL && ins->type!=DIV_INS_OPM) { ImGui::TableSetupColumn("c13",ImGuiTableColumnFlags_WidthStretch,0.2f); // ssg/waveform } ImGui::TableSetupColumn("c14",ImGuiTableColumnFlags_WidthStretch,0.3f); // env @@ -1742,7 +2070,7 @@ void FurnaceGUI::drawInsEdit() { CENTER_TEXT(FM_SHORT_NAME(FM_SL)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_SL)); } - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_D2R)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_D2R)); @@ -1760,7 +2088,7 @@ void FurnaceGUI::drawInsEdit() { CENTER_TEXT(FM_SHORT_NAME(FM_TL)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_TL)); ImGui::TableNextColumn(); - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { CENTER_TEXT(FM_SHORT_NAME(FM_RS)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_RS)); } else { @@ -1784,15 +2112,17 @@ void FurnaceGUI::drawInsEdit() { ImGui::TextUnformatted(FM_SHORT_NAME(FM_FINE)); } ImGui::TableNextColumn(); - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { CENTER_TEXT(FM_SHORT_NAME(FM_DT)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_DT)); ImGui::TableNextColumn(); + } + if (ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { CENTER_TEXT(FM_SHORT_NAME(FM_DT2)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_DT2)); ImGui::TableNextColumn(); } - if (ins->type==DIV_INS_FM) { + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM) { CENTER_TEXT(FM_SHORT_NAME(FM_AM)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_AM)); } else { @@ -1804,7 +2134,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); CENTER_TEXT(FM_NAME(FM_WS)); ImGui::TextUnformatted(FM_NAME(FM_WS)); - } else if (ins->type!=DIV_INS_OPLL) { + } else if (ins->type!=DIV_INS_OPLL && ins->type!=DIV_INS_OPM) { ImGui::TableNextColumn(); CENTER_TEXT(FM_NAME(FM_SSG)); ImGui::TextUnformatted(FM_NAME(FM_SSG)); @@ -1854,17 +2184,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) { @@ -1877,7 +2221,7 @@ void FurnaceGUI::drawInsEdit() { 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; + int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM)?31:15; bool ssgOn=op.ssgEnv&8; bool ksrOn=op.ksr; bool vibOn=op.vib; @@ -1901,7 +2245,7 @@ void FurnaceGUI::drawInsEdit() { P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); } - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { ImGui::TableNextColumn(); op.d2r&=31; CENTER_VSLIDER; @@ -1930,7 +2274,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); CENTER_VSLIDER; - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { P(CWVSliderScalar("##RS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); } else { P(CWVSliderScalar("##KSL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); @@ -1956,7 +2300,7 @@ void FurnaceGUI::drawInsEdit() { P(CWVSliderScalar("##FINE",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dvb,&_ZERO,&_FIFTEEN)); } - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { int detune=(op.dt&7)-(settings.unsignedDetune?0:3); ImGui::TableNextColumn(); CENTER_VSLIDER; @@ -1964,11 +2308,10 @@ void FurnaceGUI::drawInsEdit() { op.dt=detune+(settings.unsignedDetune?0:3); } - ImGui::TableNextColumn(); - CENTER_VSLIDER; - P(CWVSliderScalar("##DT2",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable - if (ImGui::IsItemHovered() && ins->type==DIV_INS_FM) { - ImGui::SetTooltip("Only on YM2151 (OPM)"); + if (ins->type!=DIV_INS_FM) { + ImGui::TableNextColumn(); + CENTER_VSLIDER; + P(CWVSliderScalar("##DT2",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable } ImGui::TableNextColumn(); @@ -2008,7 +2351,7 @@ void FurnaceGUI::drawInsEdit() { } } - if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPL_DRUMS && ins->type!=DIV_INS_OPZ) { + if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPL_DRUMS && ins->type!=DIV_INS_OPZ && ins->type!=DIV_INS_OPM) { ImGui::TableNextColumn(); ImGui::Dummy(ImVec2(4.0f*dpiScale,2.0f*dpiScale)); ImGui::TableNextColumn(); @@ -2018,11 +2361,6 @@ void FurnaceGUI::drawInsEdit() { if (ImGui::Checkbox("##SSGOn",&ssgOn)) { PARAMETER op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); } - if (ins->type==DIV_INS_FM) { - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Only for OPN family chips"); - } - } ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); @@ -2030,7 +2368,7 @@ void FurnaceGUI::drawInsEdit() { op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); } } - } else { + } else if (ins->type!=DIV_INS_OPM) { ImGui::TableNextColumn(); bool amOn=op.am; ImGui::SetCursorPosY(ImGui::GetCursorPosY()+0.5*(sliderHeight-ImGui::GetFrameHeight()*4.0-ImGui::GetStyle().ItemSpacing.y*3.0)); @@ -2065,7 +2403,7 @@ void FurnaceGUI::drawInsEdit() { 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)"); } - } else if (ins->type==DIV_INS_OPLL) { + } else if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPM) { ImGui::TableNextColumn(); ImGui::Dummy(ImVec2(4.0f*dpiScale,2.0f*dpiScale)); } @@ -2148,12 +2486,24 @@ void FurnaceGUI::drawInsEdit() { } else { snprintf(tempID,1024,"Operator %d",i+1); } - CENTER_TEXT(tempID); - ImGui::TextUnformatted(tempID); + float nextCursorPosX=ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(tempID).x-(opsAreMutable?(ImGui::GetStyle().FramePadding.x*2.0f):0.0f)); + OP_DRAG_POINT; + ImGui::SameLine(); + ImGui::SetCursorPosX(nextCursorPosX); + if (opsAreMutable) { + pushToggleColors(op.enable); + if (ImGui::Button(tempID)) { + op.enable=!op.enable; + PARAMETER; + } + popToggleColors(); + } else { + ImGui::TextUnformatted(tempID); + } float sliderHeight=200.0f*dpiScale; float waveWidth=140.0*dpiScale; - float waveHeight=sliderHeight-ImGui::GetFrameHeightWithSpacing()*(ins->type==DIV_INS_OPLL?4.5f:5.5f); + float waveHeight=sliderHeight-ImGui::GetFrameHeightWithSpacing()*((ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPL)?5.0f:4.5f); int maxTl=127; if (ins->type==DIV_INS_OPLL) { @@ -2166,7 +2516,7 @@ void FurnaceGUI::drawInsEdit() { 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; + int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM)?31:15; bool ssgOn=op.ssgEnv&8; bool ksrOn=op.ksr; @@ -2219,7 +2569,7 @@ void FurnaceGUI::drawInsEdit() { } float textX_D2R=0.0f; - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { ImGui::SameLine(); op.d2r&=31; textX_D2R=ImGui::GetCursorPosX(); @@ -2253,7 +2603,7 @@ void FurnaceGUI::drawInsEdit() { 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) { + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { ImGui::SetCursorPos(ImVec2(textX_D2R,textY)); CENTER_TEXT_20(FM_SHORT_NAME(FM_D2R)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_D2R)); @@ -2271,9 +2621,6 @@ void FurnaceGUI::drawInsEdit() { 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); @@ -2294,17 +2641,35 @@ void FurnaceGUI::drawInsEdit() { op.dt=detune+(settings.unsignedDetune?0:3); } rightClickable + 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_OPM: { + drawWaveform(0,true,ImVec2(waveWidth,waveHeight)); + + // 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: @@ -2504,9 +2869,9 @@ void FurnaceGUI::drawInsEdit() { 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)); + P(CWVSliderScalar("##TL",ImVec2(ImGui::GetFrameHeight(),sliderHeight-((ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM)?(ImGui::GetFrameHeightWithSpacing()+ImGui::CalcTextSize(FM_SHORT_NAME(FM_AM)).y+ImGui::GetStyle().ItemSpacing.y):0.0f)),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); - if (ins->type==DIV_INS_FM) { + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM) { CENTER_TEXT(FM_SHORT_NAME(FM_AM)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_AM)); bool amOn=op.am; @@ -2581,16 +2946,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(); @@ -2611,23 +2989,18 @@ void FurnaceGUI::drawInsEdit() { 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; + int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM)?31:15; bool ssgOn=op.ssgEnv&8; bool ksrOn=op.ksr; bool vibOn=op.vib; bool susOn=op.sus; // don't you make fun of this one unsigned char ssgEnv=op.ssgEnv&7; - if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPL_DRUMS && ins->type!=DIV_INS_OPZ) { + if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPL_DRUMS && ins->type!=DIV_INS_OPZ && ins->type!=DIV_INS_OPM) { ImGui::SameLine(); if (ImGui::Checkbox((ins->type==DIV_INS_OPLL)?FM_NAME(FM_EGS):"SSG On",&ssgOn)) { PARAMETER op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); } - if (ins->type==DIV_INS_FM) { - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Only for OPN family chips"); - } - } } if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS) { @@ -2637,6 +3010,14 @@ void FurnaceGUI::drawInsEdit() { } } + if (ins->type==DIV_INS_OPZ) { + ImGui::SameLine(); + bool fixedOn=op.egt; + if (ImGui::Checkbox("Fixed",&fixedOn)) { PARAMETER + op.egt=fixedOn; + } + } + //52.0 controls vert scaling; default 96 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,52.0*dpiScale),ins->type); //P(CWSliderScalar(FM_NAME(FM_AR),ImGuiDataType_U8,&op.ar,&_ZERO,&_THIRTY_ONE)); rightClickable @@ -2669,7 +3050,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("%s",FM_NAME(FM_SL)); } - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); @@ -2711,7 +3092,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { P(CWSliderScalar("##RS",ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_RS)); @@ -2721,33 +3102,94 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("%s",FM_NAME(FM_KSL)); } - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(CWSliderScalar(FM_NAME(FM_MULT),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); rightClickable - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_MULT)); - - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { - int detune=(op.dt&7)-(settings.unsignedDetune?0:3); + if (ins->type==DIV_INS_OPZ) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (CWSliderInt("##DT",&detune,-3,4)) { PARAMETER - op.dt=detune+(settings.unsignedDetune?0:3); - } rightClickable + P(CWSliderScalar(FM_NAME(FM_EGSHIFT),ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); rightClickable ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_DT)); + ImGui::Text("%s",FM_NAME(FM_EGSHIFT)); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(CWSliderScalar("##DT2",ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable - if (ImGui::IsItemHovered() && ins->type==DIV_INS_FM) { - ImGui::SetTooltip("Only on YM2151 (OPM)"); - } + P(CWSliderScalar(FM_NAME(FM_REV),ImGuiDataType_U8,&op.dam,&_ZERO,&_SEVEN)); rightClickable ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_DT2)); + ImGui::Text("%s",FM_NAME(FM_REV)); + } + + if (ins->type==DIV_INS_OPZ) { + if (op.egt) { + int block=op.dt; + int freqNum=(op.mult<<4)|(op.dvb&15); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderInt(FM_NAME(FM_MULT),&block,0,7)) { PARAMETER + if (block<0) block=0; + if (block>7) block=7; + op.dt=block; + } rightClickable + ImGui::TableNextColumn(); + ImGui::Text("Block"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderInt(FM_NAME(FM_FINE),&freqNum,0,255)) { PARAMETER + if (freqNum<0) freqNum=0; + if (freqNum>255) freqNum=255; + op.mult=freqNum>>4; + op.dvb=freqNum&15; + } rightClickable + ImGui::TableNextColumn(); + ImGui::Text("FreqNum"); + } else { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(CWSliderScalar(FM_NAME(FM_MULT),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_MULT)); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(CWSliderScalar(FM_NAME(FM_FINE),ImGuiDataType_U8,&op.dvb,&_ZERO,&_FIFTEEN)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_FINE)); + } + } else { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(CWSliderScalar(FM_NAME(FM_MULT),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_MULT)); + } + + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { + if (!(ins->type==DIV_INS_OPZ && op.egt)) { + 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+(settings.unsignedDetune?0:3); + } rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_DT)); + } + + if (ins->type!=DIV_INS_FM) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(CWSliderScalar("##DT2",ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_DT2)); + } if (ins->type==DIV_INS_FM) { // OPN only ImGui::TableNextRow(); @@ -2819,12 +3261,19 @@ void FurnaceGUI::drawInsEdit() { } } - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + if (ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { macroList.push_back(FurnaceGUIMacroDesc("AM Depth",&ins->std.ex1Macro,0,127,128,uiColors[GUI_COLOR_MACRO_OTHER])); 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,false,NULL,macroLFOWaves)); - macroList.push_back(FurnaceGUIMacroDesc("OpMask",&ins->std.ex4Macro,0,4,128,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,fmOperatorBits)); + 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 || ins->type==DIV_INS_OPM) { + 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(); @@ -2850,7 +3299,7 @@ void FurnaceGUI::drawInsEdit() { 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; + int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM)?31:15; if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS) { macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_TL),&ins->std.opMacros[ordi].tlMacro,0,maxTl,128,uiColors[GUI_COLOR_MACRO_OTHER])); @@ -2889,8 +3338,10 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_RS),&ins->std.opMacros[ordi].rsMacro,0,3,32,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_MULT),&ins->std.opMacros[ordi].multMacro,0,15,64,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_DT),&ins->std.opMacros[ordi].dtMacro,0,7,64,uiColors[GUI_COLOR_MACRO_OTHER])); - macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_DT2),&ins->std.opMacros[ordi].dt2Macro,0,3,32,uiColors[GUI_COLOR_MACRO_OTHER])); - macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_AM),&ins->std.opMacros[ordi].amMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true)); + if (ins->type==DIV_INS_OPM || ins->type==DIV_INS_OPZ) { + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_DT2),&ins->std.opMacros[ordi].dt2Macro,0,3,32,uiColors[GUI_COLOR_MACRO_OTHER])); + } + macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_AM),&ins->std.opMacros[ordi].amMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); if (ins->type==DIV_INS_FM) { macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_SSG),&ins->std.opMacros[ordi].ssgMacro,0,4,64,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,ssgEnvBits)); @@ -2903,52 +3354,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); @@ -3010,29 +3690,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)); @@ -3040,14 +3720,52 @@ void FurnaceGUI::drawInsEdit() { P(ImGui::Checkbox("Don't test/gate before new note",&ins->c64.noTest)); ImGui::EndTabItem(); } - if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_ES5506) { - if (ImGui::BeginTabItem("Sample")) { + if (ins->type==DIV_INS_PCE || + ins->type==DIV_INS_MSM6258 || + ins->type==DIV_INS_MSM6295 || + ins->type==DIV_INS_ADPCMA || + ins->type==DIV_INS_ADPCMB || + ins->type==DIV_INS_SEGAPCM || + ins->type==DIV_INS_QSOUND || + ins->type==DIV_INS_YMZ280B || + ins->type==DIV_INS_RF5C68 || + ins->type==DIV_INS_AMIGA || + ins->type==DIV_INS_MULTIPCM || + ins->type==DIV_INS_MIKEY || + ins->type==DIV_INS_X1_010 || + ins->type==DIV_INS_SWAN || + ins->type==DIV_INS_AY || + ins->type==DIV_INS_AY8930 || + ins->type==DIV_INS_VRC6 || + 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_PCE || + ins->type==DIV_INS_MIKEY || + ins->type==DIV_INS_X1_010 || + ins->type==DIV_INS_SWAN || + ins->type==DIV_INS_AY || + ins->type==DIV_INS_AY8930 || + ins->type==DIV_INS_VRC6 || + ins->type==DIV_INS_SU) { + P(ImGui::Checkbox("Use sample",&ins->amiga.useSample)); + if (ins->type==DIV_INS_SU) { + P(ImGui::Checkbox("Switch roles of frequency and phase reset timer",&ins->su.switchRoles)); + } + if (ins->type==DIV_INS_X1_010) { + if (ImGui::InputInt("Sample bank slot##BANKSLOT",&ins->x1_010.bankSlot,1,4)) { PARAMETER + if (ins->x1_010.bankSlot<0) ins->x1_010.bankSlot=0; + if (ins->x1_010.bankSlot>=7) ins->x1_010.bankSlot=7; + } + } + } if (ImGui::BeginCombo("Initial Sample",sName.c_str())) { String id; for (int i=0; isong.sampleLen; i++) { @@ -3062,27 +3780,35 @@ void FurnaceGUI::drawInsEdit() { P(ImGui::Checkbox("Reversed playback",&ins->amiga.reversed)); ImGui::EndDisabled(); // Wavetable - ImGui::BeginDisabled(ins->amiga.useNoteMap||ins->amiga.transWave.enable); - 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 || ins->type==DIV_INS_SNES) { + ImGui::BeginDisabled(ins->amiga.useNoteMap||ins->amiga.transWave.enable); + P(ImGui::Checkbox("Use wavetable (Amiga/SNES only)",&ins->amiga.useWave)); + if (ins->amiga.useWave) { + int len=ins->amiga.waveLen+1; + if (ImGui::InputInt("Width",&len,2,16)) { + if (ins->type==DIV_INS_SNES) { + if (len<16) len=16; + if (len>256) len=256; + ins->amiga.waveLen=(len&(~15))-1; + } else { + if (len<2) len=2; + if (len>256) len=256; + ins->amiga.waveLen=(len&(~1))-1; + } + PARAMETER + } } + ImGui::EndDisabled(); } - ImGui::EndDisabled(); // Note map ImGui::BeginDisabled(ins->amiga.useWave||ins->amiga.transWave.enable); P(ImGui::Checkbox("Use sample map (does not work yet!)",&ins->amiga.useNoteMap)); if (ins->amiga.useNoteMap) { - if (ImGui::BeginTable("NoteMap",4,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { + if (ImGui::BeginTable("NoteMap",3/*4*/,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch); + //ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupScrollFreeze(0,1); @@ -3090,8 +3816,8 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::TableNextColumn(); ImGui::Text("Sample"); - ImGui::TableNextColumn(); - ImGui::Text("Frequency"); + /*ImGui::TableNextColumn(); + ImGui::Text("Frequency");*/ ImGui::TableNextColumn(); ImGui::Text("Reversed"); for (int i=0; i<120; i++) { @@ -3121,12 +3847,14 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndCombo(); } + /* ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::InputInt(fmt::sprintf("##SampleMap_Freq_%d",i).c_str(),&ins->amiga.noteMap[i].freq,50,500)) { PARAMETER if (ins->amiga.noteMap[i].freq<0) ins->amiga.noteMap[i].freq=0; if (ins->amiga.noteMap[i].freq>262144) ins->amiga.noteMap[i].freq=262144; } + */ ImGui::TableNextColumn(); if (ImGui::RadioButton(fmt::sprintf("Disable##SampleMap_Reversed_Disable_%d",i).c_str(),ins->amiga.noteMap[i].reversed==0)) { MARK_MODIFIED ins->amiga.noteMap[i].reversed=0; @@ -3284,17 +4012,17 @@ void FurnaceGUI::drawInsEdit() { } } ImGui::TableNextColumn(); - if (ImGui::RadioButton(fmt::sprintf("Forward##TransWaveMap_LoopMode_Forward_%d",i).c_str(),transWaveMap.loopMode==DIV_SAMPLE_LOOPMODE_FORWARD)) { MARK_MODIFIED - transWaveMap.loopMode=DIV_SAMPLE_LOOPMODE_FORWARD; + if (ImGui::RadioButton(fmt::sprintf("Forward##TransWaveMap_LoopMode_Forward_%d",i).c_str(),transWaveMap.loopMode==DIV_SAMPLE_LOOP_FORWARD)) { MARK_MODIFIED + transWaveMap.loopMode=DIV_SAMPLE_LOOP_FORWARD; } - if (ImGui::RadioButton(fmt::sprintf("Backward##TransWaveMap_LoopMode_Backward_%d",i).c_str(),transWaveMap.loopMode==DIV_SAMPLE_LOOPMODE_BACKWARD)) { MARK_MODIFIED - transWaveMap.loopMode=DIV_SAMPLE_LOOPMODE_BACKWARD; + if (ImGui::RadioButton(fmt::sprintf("Backward##TransWaveMap_LoopMode_Backward_%d",i).c_str(),transWaveMap.loopMode==DIV_SAMPLE_LOOP_BACKWARD)) { MARK_MODIFIED + transWaveMap.loopMode=DIV_SAMPLE_LOOP_BACKWARD; } - if (ImGui::RadioButton(fmt::sprintf("Pingpong##TransWaveMap_LoopMode_Pingpong_%d",i).c_str(),transWaveMap.loopMode==DIV_SAMPLE_LOOPMODE_PINGPONG)) { MARK_MODIFIED - transWaveMap.loopMode=DIV_SAMPLE_LOOPMODE_PINGPONG; + if (ImGui::RadioButton(fmt::sprintf("Pingpong##TransWaveMap_LoopMode_Pingpong_%d",i).c_str(),transWaveMap.loopMode==DIV_SAMPLE_LOOP_PINGPONG)) { MARK_MODIFIED + transWaveMap.loopMode=DIV_SAMPLE_LOOP_PINGPONG; } - if (ImGui::RadioButton(fmt::sprintf("Use sample setting##TransWaveMap_LoopMode_Default_%d",i).c_str(),transWaveMap.loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT)) { MARK_MODIFIED - transWaveMap.loopMode=DIV_SAMPLE_LOOPMODE_ONESHOT; + if (ImGui::RadioButton(fmt::sprintf("Use sample setting##TransWaveMap_LoopMode_Default_%d",i).c_str(),transWaveMap.loopMode==DIV_SAMPLE_LOOP_MAX)) { MARK_MODIFIED + transWaveMap.loopMode=DIV_SAMPLE_LOOP_MAX; } ImGui::TableNextColumn(); if (ImGui::RadioButton(fmt::sprintf("Disable##TransWaveMap_Reversed_Disable_%d",i).c_str(),transWaveMap.reversed==0)) { MARK_MODIFIED @@ -3322,9 +4050,11 @@ void FurnaceGUI::drawInsEdit() { drawMacros(macroList); ImGui::EndTabItem(); } + 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; @@ -3369,7 +4099,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; @@ -3393,12 +4123,6 @@ void FurnaceGUI::drawInsEdit() { if (ImGui::BeginTable("ESParams",2,ImGuiTableFlags_SizingStretchSame)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.0); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); - // volume - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - P(CWSliderScalar("Left volume",ImGuiDataType_S32,&ins->es5506.lVol,&_ZERO,&_SIXTY_FIVE_THOUSAND_FIVE_HUNDRED_THIRTY_FIVE)); rightClickable - ImGui::TableNextColumn(); - P(CWSliderScalar("Right volume",ImGuiDataType_S32,&ins->es5506.rVol,&_ZERO,&_SIXTY_FIVE_THOUSAND_FIVE_HUNDRED_THIRTY_FIVE)); rightClickable // filter ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -3433,23 +4157,6 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_MULTIPCM) { if (ImGui::BeginTabItem("MultiPCM")) { - 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 (ImGui::BeginCombo("Initial Sample",sName.c_str())) { - String id; - for (int i=0; isong.sampleLen; i++) { - id=fmt::sprintf("%d: %s",i,e->song.sample[i]->name); - if (ImGui::Selectable(id.c_str(),ins->amiga.initSample==i)) { - ins->amiga.initSample=i; - PARAMETER - } - } - ImGui::EndCombo(); - } ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale); if (ImGui::BeginTable("MultiPCMADSRParams",7,ImGuiTableFlags_NoHostExtendX)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); @@ -3533,14 +4240,110 @@ 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 || + (ins->type==DIV_INS_X1_010 && !ins->amiga.useSample) || ins->type==DIV_INS_N163 || ins->type==DIV_INS_FDS || - ins->type==DIV_INS_SWAN || - ins->type==DIV_INS_PCE || + (ins->type==DIV_INS_SWAN && !ins->amiga.useSample) || + (ins->type==DIV_INS_PCE && !ins->amiga.useSample) || 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)) { @@ -3703,7 +4506,7 @@ void FurnaceGUI::drawInsEdit() { } } } - if ((ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930)) { + if (ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930) { volMax=31; } if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_VERA || ins->type==DIV_INS_VRC6_SAW) { @@ -3712,11 +4515,15 @@ void FurnaceGUI::drawInsEdit() { 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_SEGAPCM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { 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; @@ -3727,6 +4534,21 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_ES5506) { volMax=65535; } + if (ins->type==DIV_INS_MSM6258) { + volMax=0; + } + if (ins->type==DIV_INS_MSM6295) { + volMax=8; + } + if (ins->type==DIV_INS_ADPCMA) { + volMax=31; + } + if (ins->type==DIV_INS_ADPCMB || ins->type==DIV_INS_YMZ280B || ins->type==DIV_INS_RF5C68) { + volMax=255; + } + if (ins->type==DIV_INS_QSOUND) { + volMax=16383; + } const char* dutyLabel="Duty/Noise"; int dutyMin=0; @@ -3740,38 +4562,45 @@ void FurnaceGUI::drawInsEdit() { dutyMax=96; } } - if (ins->type==DIV_INS_FM) { + if (ins->type==DIV_INS_STD) { + dutyLabel="Duty"; + } + if (ins->type==DIV_INS_OPM || ins->type==DIV_INS_OPZ) { dutyMax=32; } - if ((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)) { - dutyMax=31; + if (ins->type==DIV_INS_AY) { + dutyMax=ins->amiga.useSample?0:31; } - if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_FM) { + if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM) { dutyLabel="Noise Freq"; } if (ins->type==DIV_INS_MIKEY) { dutyLabel="Duty/Int"; - dutyMax=10; + dutyMax=ins->amiga.useSample?0:10; } if (ins->type==DIV_INS_BEEPER) { dutyLabel="Pulse Width"; dutyMax=255; } if (ins->type==DIV_INS_AY8930) { - dutyMax=255; + dutyMax=ins->amiga.useSample?0:255; } - if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC || ins->type==DIV_INS_PET || ins->type==DIV_INS_VIC) { + if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC || + ins->type==DIV_INS_PET || ins->type==DIV_INS_VIC || ins->type==DIV_INS_SEGAPCM || + ins->type==DIV_INS_FM) { dutyMax=0; } if (ins->type==DIV_INS_PCE || ins->type==DIV_INS_NAMCO) { dutyLabel="Noise"; - dutyMax=1; + dutyMax=(ins->type==DIV_INS_PCE && !ins->amiga.useSample)?1:0; } if (ins->type==DIV_INS_SWAN) { dutyLabel="Noise"; - dutyMax=8; + dutyMax=ins->amiga.useSample?0:8; } - 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) { + 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 || + ins->type==DIV_INS_SNES) { dutyMax=0; } if (ins->type==DIV_INS_VERA) { @@ -3784,7 +4613,7 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_VRC6) { dutyLabel="Duty"; - dutyMax=7; + dutyMax=ins->amiga.useSample?0:7; } if (ins->type==DIV_INS_ES5506) { dutyLabel="Filter Mode"; @@ -3793,18 +4622,41 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_SU) { dutyMax=127; } + if (ins->type==DIV_INS_ES5506) { + dutyLabel="Filter Mode"; + dutyMax=3; + } + if (ins->type==DIV_INS_MSM6258) { + dutyLabel="Frequency Divider"; + dutyMax=2; + } + if (ins->type==DIV_INS_MSM6295) { + dutyLabel="Frequency Divider"; + dutyMax=1; + } + if (ins->type==DIV_INS_ADPCMA) { + dutyLabel="Global Volume"; + dutyMax=63; + } + if (ins->type==DIV_INS_ADPCMB || ins->type==DIV_INS_YMZ280B || ins->type==DIV_INS_RF5C68) { + dutyMax=0; + } + if (ins->type==DIV_INS_QSOUND) { + dutyLabel="Echo Level"; + dutyMax=32767; + } const char* waveLabel="Waveform"; - int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VERA)?3:255; - bool bitMode=false; - if (ins->type==DIV_INS_C64 || ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) { - bitMode=true; + int waveMax=(ins->type==DIV_INS_VERA)?3:(MAX(1,e->song.waveLen-1)); + bool waveBitMode=false; + if (ins->type==DIV_INS_C64 || ins->type==DIV_INS_SAA1099) { + waveBitMode=true; } - if (ins->type==DIV_INS_STD || ins->type==DIV_INS_VRC6 || ins->type==DIV_INS_VRC6_SAW) waveMax=0; + if (ins->type==DIV_INS_STD || ins->type==DIV_INS_VRC6_SAW || ins->type==DIV_INS_NES) waveMax=0; 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_OPL_DRUMS || 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 || ins->type==DIV_INS_OPM) waveMax=0; if (ins->type==DIV_INS_MIKEY) waveMax=0; if ((ins->type==DIV_INS_AMIGA && !ins->amiga.useWave) || ins->type==DIV_INS_ES5506) { if (ins->amiga.transWave.enable) { @@ -3827,16 +4679,31 @@ void FurnaceGUI::drawInsEdit() { waveMax=MAX(0,(int)(e->song.waveLen)-1); } if (ins->type==DIV_INS_MULTIPCM) waveMax=0; + if (ins->type==DIV_INS_ADPCMA) waveMax=0; + if (ins->type==DIV_INS_ADPCMB) waveMax=0; + if (ins->type==DIV_INS_QSOUND) waveMax=0; + if (ins->type==DIV_INS_YMZ280B) waveMax=0; + if (ins->type==DIV_INS_MSM6258) waveMax=0; + if (ins->type==DIV_INS_MSM6295) waveMax=0; + if (ins->type==DIV_INS_SEGAPCM) waveMax=0; if (ins->type==DIV_INS_SU) waveMax=7; if (ins->type==DIV_INS_PET) { waveMax=8; - bitMode=true; + waveBitMode=true; } - + if (ins->type==DIV_INS_VRC6) { + waveMax=ins->amiga.useSample?(MAX(1,e->song.waveLen-1)):0; + } + if (ins->type==DIV_INS_OPLL) { waveLabel="Patch"; } + if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930) { + waveMax=ins->amiga.useSample?0:3; + waveBitMode=ins->amiga.useSample?false:true; + } + const char** waveNames=NULL; if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) waveNames=ayShapeBits; if (ins->type==DIV_INS_C64) waveNames=c64ShapeBits; @@ -3851,8 +4718,8 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_X1_010) { dutyMax=0; - ex1Max=7; - ex2Max=255; + ex1Max=ins->amiga.useSample?0:7; + ex2Max=ins->amiga.useSample?0:255; ex2Bit=false; } if (ins->type==DIV_INS_N163) { @@ -3867,11 +4734,22 @@ void FurnaceGUI::drawInsEdit() { ex1Max=16383; ex2Max=255; } + if (ins->type==DIV_INS_MSM6258) { + ex1Max=1; + } + if (ins->type==DIV_INS_QSOUND) { + ex1Max=16383; + ex2Max=2725; + } if (ins->type==DIV_INS_SAA1099) ex1Max=8; if (ins->type==DIV_INS_ES5506) { ex1Max=65535; ex2Max=65535; } + if (ins->type==DIV_INS_SNES && !ins->snes.useEnv) { + ex1Max=4; + ex2Max=31; + } int panMin=0; int panMax=0; @@ -3879,28 +4757,39 @@ void FurnaceGUI::drawInsEdit() { bool panSingleNoBit=false; if (ins->type==DIV_INS_STD ||//Game Gear ins->type==DIV_INS_FM || + ins->type==DIV_INS_OPM || 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) { + ins->type==DIV_INS_MSM6258 || + ins->type==DIV_INS_VERA || + ins->type==DIV_INS_ADPCMA || + ins->type==DIV_INS_ADPCMB) { panMax=1; panSingle=true; } - if (ins->type==DIV_INS_AMIGA) { - panMax=127; - } - if (ins->type==DIV_INS_X1_010 || ins->type==DIV_INS_PCE || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_SAA1099 || ins->type==DIV_INS_NAMCO) { + if (ins->type==DIV_INS_X1_010 || ins->type==DIV_INS_PCE || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_SAA1099 || ins->type==DIV_INS_NAMCO || ins->type==DIV_INS_RF5C68) { panMax=15; } - if (ins->type==DIV_INS_ES5506) { - panMax=65535; + if (ins->type==DIV_INS_SEGAPCM) { + panMax=127; } - if (ins->type==DIV_INS_AMIGA && ins->std.panLMacro.mode) { + if (ins->type==DIV_INS_AMIGA) { + if (ins->std.panLMacro.mode) { + panMin=-16; + panMax=16; + } else { + panMin=0; + panMax=127; + } + } + if (ins->type==DIV_INS_QSOUND) { panMin=-16; panMax=16; + panSingleNoBit=true; } - if (ins->type==DIV_INS_MULTIPCM) { + if (ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_YMZ280B) { panMin=-7; panMax=7; panSingleNoBit=true; @@ -3910,31 +4799,37 @@ 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,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,false,NULL,NULL,true,mikeyFeedbackBits)); + 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,false,NULL,¯oHoverES5506FilterMode)); + 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])); } } if (waveMax>0) { - macroList.push_back(FurnaceGUIMacroDesc(waveLabel,&ins->std.waveMacro,0,waveMax,(bitMode && ins->type!=DIV_INS_PET)?64:160,uiColors[GUI_COLOR_MACRO_WAVE],false,false,NULL,NULL,bitMode,waveNames,((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?1:0))); + macroList.push_back(FurnaceGUIMacroDesc(waveLabel,&ins->std.waveMacro,0,waveMax,(waveBitMode && ins->type!=DIV_INS_PET)?64:160,uiColors[GUI_COLOR_MACRO_WAVE],false,NULL,NULL,waveBitMode,waveNames,((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?1:0))); } if (panMax>0) { if (panSingle) { - macroList.push_back(FurnaceGUIMacroDesc("Panning",&ins->std.panLMacro,0,2,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,panBits)); + macroList.push_back(FurnaceGUIMacroDesc("Panning",&ins->std.panLMacro,0,2,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,panBits)); + } else if (ins->type==DIV_INS_QSOUND) { + macroList.push_back(FurnaceGUIMacroDesc("Panning",&ins->std.panLMacro,panMin,panMax,CLAMP(31+panMax-panMin,32,160),uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc("Surround",&ins->std.panRMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); } else { if (panSingleNoBit || (ins->type==DIV_INS_AMIGA && ins->std.panLMacro.mode)) { - 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)?true: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,CLAMP(31+panMax-panMin,32,160),uiColors[GUI_COLOR_MACRO_OTHER],false,(ins->type==DIV_INS_AMIGA)?true: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) { @@ -3947,38 +4842,56 @@ void FurnaceGUI::drawInsEdit() { } macroList.push_back(FurnaceGUIMacroDesc("Pitch",&ins->std.pitchMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,true,macroRelativeMode)); if (ins->type==DIV_INS_FM || + ins->type==DIV_INS_OPM || ins->type==DIV_INS_STD || + ins->type==DIV_INS_NES || 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 || + ins->type==DIV_INS_MSM6258 || + ins->type==DIV_INS_MSM6295 || + ins->type==DIV_INS_ADPCMA || + ins->type==DIV_INS_ADPCMB || + ins->type==DIV_INS_SEGAPCM || + ins->type==DIV_INS_QSOUND || + ins->type==DIV_INS_YMZ280B || + ins->type==DIV_INS_RF5C68 || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SWAN || - ins->type==DIV_INS_ES5506 || ins->type==DIV_INS_MULTIPCM || + (ins->type==DIV_INS_VRC6 && ins->amiga.useSample) || ins->type==DIV_INS_SU || - ins->type==DIV_INS_MIKEY) { - macroList.push_back(FurnaceGUIMacroDesc("Phase Reset",&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true)); + ins->type==DIV_INS_MIKEY || + ins->type==DIV_INS_ES5506 || + (ins->type==DIV_INS_X1_010 && ins->amiga.useSample)) { + macroList.push_back(FurnaceGUIMacroDesc("Phase Reset",&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); } if (ex1Max>0) { if (ins->type==DIV_INS_C64) { macroList.push_back(FurnaceGUIMacroDesc("Filter Mode",&ins->std.ex1Macro,0,ex1Max,64,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,filtModeBits)); } else if (ins->type==DIV_INS_SAA1099) { - macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,saaEnvBits)); - } else if (ins->type==DIV_INS_X1_010) { - macroList.push_back(FurnaceGUIMacroDesc("Envelope Mode",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,x1_010EnvBits)); + macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,saaEnvBits)); + } else if (ins->type==DIV_INS_X1_010 && !ins->amiga.useSample) { + macroList.push_back(FurnaceGUIMacroDesc("Envelope Mode",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,x1_010EnvBits)); } else if (ins->type==DIV_INS_N163) { macroList.push_back(FurnaceGUIMacroDesc("Wave Length",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } else if (ins->type==DIV_INS_FDS) { macroList.push_back(FurnaceGUIMacroDesc("Mod Depth",&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,true,macroFilterMode)); } 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 if (ins->type==DIV_INS_MSM6258) { + macroList.push_back(FurnaceGUIMacroDesc("Clock Divider",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); + } else if (ins->type==DIV_INS_QSOUND) { + macroList.push_back(FurnaceGUIMacroDesc("Echo Feedback",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); + } else if (ins->type==DIV_INS_SNES) { + macroList.push_back(FurnaceGUIMacroDesc("Gain Mode",&ins->std.ex1Macro,0,ex1Max,64,uiColors[GUI_COLOR_MACRO_VOLUME],false,NULL,NULL,false,snesGainModes)); } else { macroList.push_back(FurnaceGUIMacroDesc("Duty",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } @@ -3990,10 +4903,14 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("Wave Update",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,n163UpdateBits)); } else if (ins->type==DIV_INS_FDS) { macroList.push_back(FurnaceGUIMacroDesc("Mod Speed",&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,true,macroFilterMode)); } 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 if (ins->type==DIV_INS_QSOUND) { + macroList.push_back(FurnaceGUIMacroDesc("Echo Length",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); + } else if (ins->type==DIV_INS_SNES) { + macroList.push_back(FurnaceGUIMacroDesc("Gain Rate",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_VOLUME])); } else { macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex2Macro,0,ex2Max,ex2Bit?64:160,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,ex2Bit,ayEnvBits)); } @@ -4002,7 +4919,7 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("Special",&ins->std.ex3Macro,0,2,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,c64SpecialBits)); macroList.push_back(FurnaceGUIMacroDesc("Test/Gate",&ins->std.ex4Macro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true)); } - if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_X1_010) { + if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || (ins->type==DIV_INS_X1_010 && !ins->amiga.useSample)) { macroList.push_back(FurnaceGUIMacroDesc("AutoEnv Num",&ins->std.ex3Macro,0,15,160,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("AutoEnv Den",&ins->std.algMacro,0,15,160,uiColors[GUI_COLOR_MACRO_OTHER])); } @@ -4020,17 +4937,18 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_FDS) { macroList.push_back(FurnaceGUIMacroDesc("Mod Position",&ins->std.ex3Macro,0,127,160,uiColors[GUI_COLOR_MACRO_OTHER])); } + 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,false,NULL,NULL,true,es5506EnvelopeModes)); - macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.algMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,es5506ControlModes)); - } - if (ins->type==DIV_INS_SU) { - macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.ex3Macro,0,4,64,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,suControlBits)); + 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); @@ -4070,8 +4988,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; } @@ -4100,15 +5018,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(); @@ -4143,6 +5061,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/newSong.cpp b/src/gui/newSong.cpp index 0dea772b7..c819ffd3a 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 000000000..22d5b645e --- /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 b7940a9e8..ae194925d 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -17,6 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +// for suck's fake Clang extension! +#include #define _USE_MATH_DEFINES #include "gui.h" #include "../ta-log.h" @@ -24,6 +26,7 @@ #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" #include "guiConst.h" +#include "../utfutils.h" #include inline float randRange(float min, float max) { @@ -56,7 +59,7 @@ void FurnaceGUI::popPartBlend() { // 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(); @@ -114,9 +117,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)) { @@ -151,7 +154,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 { @@ -182,7 +185,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]); @@ -194,7 +197,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) { @@ -221,13 +224,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); @@ -263,15 +266,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]]); } } @@ -297,9 +300,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) { @@ -372,10 +375,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; @@ -393,7 +403,7 @@ void FurnaceGUI::drawPattern() { ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]); ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_SELECTION_HOVER]); ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_SELECTION_ACTIVE]); - if (ImGui::BeginTable("PatternView",displayChans+2,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollX|ImGuiTableFlags_ScrollY|ImGuiTableFlags_NoPadInnerX)) { + if (ImGui::BeginTable("PatternView",displayChans+2,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollX|ImGuiTableFlags_ScrollY|ImGuiTableFlags_NoPadInnerX|ImGuiTableFlags_NoBordersInFrozenArea)) { ImGui::TableSetupColumn("pos",ImGuiTableColumnFlags_WidthFixed); char chanID[2048]; float lineHeight=(ImGui::GetTextLineHeight()+2*dpiScale); @@ -440,41 +450,46 @@ void FurnaceGUI::drawPattern() { if (!e->curSubSong->chanShow[i]) continue; ImGui::TableNextColumn(); bool displayTooltip=false; - if (e->curSubSong->chanCollapse[i]) { - const char* chName=e->getChannelShortName(i); - if (strlen(chName)>3) { - snprintf(chanID,2048,"...##_CH%d",i); - } else { - snprintf(chanID,2048,"%s##_CH%d",chName,i); - } - displayTooltip=true; - } else { - const char* chName=e->getChannelName(i); - size_t chNameLimit=6+4*e->curPat[i].effectCols; - if (strlen(chName)>chNameLimit) { - String shortChName=chName; - shortChName.resize(chNameLimit-3); - shortChName+="..."; - snprintf(chanID,2048," %s##_CH%d",shortChName.c_str(),i); - displayTooltip=true; - } else { - 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; + ImVec4 chanHeadBase=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; + keyHit1[i]=1.0f; + 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; @@ -484,17 +499,210 @@ void FurnaceGUI::drawPattern() { chanHeadActive.x*=0.8; chanHeadActive.y*=0.8; chanHeadActive.z*=0.8; chanHeadHover.x*=0.4+keyHit[i]; chanHeadHover.y*=0.4+keyHit[i]; chanHeadHover.z*=0.4+keyHit[i]; } - keyHit[i]-=0.02*60.0*ImGui::GetIO().DeltaTime; + keyHit[i]-=((settings.channelStyle==0)?0.02:0.01)*60.0*ImGui::GetIO().DeltaTime; if (keyHit[i]<0) keyHit[i]=0; ImGui::PushStyleColor(ImGuiCol_Header,chanHead); ImGui::PushStyleColor(ImGuiCol_HeaderActive,chanHeadActive); ImGui::PushStyleColor(ImGuiCol_HeaderHovered,chanHeadHover); - ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(chanHead)); + ImGui::PushStyleColor(ImGuiCol_Text,ImGui::GetColorU32(channelTextColor(i))); + if (settings.channelStyle==0) ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(chanHead)); if (muted) ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_CHANNEL_MUTED]); - ImGui::Selectable(chanID,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+1.0f*dpiScale)); + if (settings.channelFont==0) ImGui::PushFont(mainFont); + + // TODO: appearance + ImGuiWindow* window=ImGui::GetCurrentWindow(); + ImVec2 size=ImVec2( + 1.0f, + lineHeight+1.0f*dpiScale + ); + ImDrawList* dl=ImGui::GetWindowDrawList(); + + if (settings.channelStyle!=0) { + size.y+=6.0f*dpiScale; + } + + if (settings.channelStyle==2) { + size.y+=6.0f*dpiScale; + } + + ImVec2 minArea=window->DC.CursorPos; + ImVec2 maxArea=ImVec2( + minArea.x+window->WorkRect.Max.x-window->WorkRect.Min.x, + minArea.y+size.y + ); + ImRect rect=ImRect(minArea,maxArea); + float padding=ImGui::CalcTextSize("A").x; + + ImVec2 minLabelArea=minArea; + ImVec2 maxLabelArea=maxArea; + + if (e->curSubSong->chanCollapse[i]) { + const char* chName=e->getChannelShortName(i); + if (strlen(chName)>3) { + snprintf(chanID,2048,"..."); + } else { + snprintf(chanID,2048,"%s",chName); + } + displayTooltip=true; + } else { + minLabelArea.x+=padding; + maxLabelArea.x-=padding; + if (settings.channelStyle==3) { // split button + maxLabelArea.x-=ImGui::GetFrameHeightWithSpacing(); + } + const char* chName=e->getChannelName(i); + float chNameLimit=maxLabelArea.x-minLabelArea.x; + if (ImGui::CalcTextSize(chName).x>chNameLimit) { + String shortChName; + float totalAdvanced=0.0f; + float ellipsisSize=ImGui::CalcTextSize("...").x; + for (const char* j=chName; *j;) { + signed char l; + int ch=decodeUTF8((const unsigned char*)j,l); + + totalAdvanced+=ImGui::GetFont()->GetCharAdvance(ch); + if (totalAdvanced>(chNameLimit-ellipsisSize)) break; + + for (int k=0; kAddRectFilled(rect.Min,rect.Max,col); + dl->AddText(ImVec2(minLabelArea.x,rect.Min.y),ImGui::GetColorU32(channelTextColor(i)),chanID); + } + break; + case 1: { // line + ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID(chanID))) { + bool hovered=ImGui::ItemHoverable(rect,ImGui::GetID(chanID)); + ImU32 fadeCol0=ImGui::GetColorU32(ImVec4( + chanHeadBase.x, + chanHeadBase.y, + chanHeadBase.z, + hovered?0.25f:0.0f + )); + ImU32 fadeCol=ImGui::GetColorU32(ImVec4( + chanHeadBase.x, + chanHeadBase.y, + chanHeadBase.z, + hovered?0.5f:MIN(1.0f,chanHeadBase.w*keyHit[i]*4.0f) + )); + dl->AddRectFilledMultiColor(rect.Min,rect.Max,fadeCol0,fadeCol0,fadeCol,fadeCol); + dl->AddLine(ImVec2(rect.Min.x,rect.Max.y),ImVec2(rect.Max.x,rect.Max.y),ImGui::GetColorU32(chanHeadBase),2.0f*dpiScale); + dl->AddText(ImVec2(minLabelArea.x,rect.Min.y+3.0*dpiScale),ImGui::GetColorU32(channelTextColor(i)),chanID); + } + break; + } + case 2: { // round + ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID(chanID))) { + bool hovered=ImGui::ItemHoverable(rect,ImGui::GetID(chanID)); + ImU32 fadeCol0=ImGui::GetColorU32(ImVec4( + chanHeadBase.x, + chanHeadBase.y, + chanHeadBase.z, + hovered?0.5f:MIN(1.0f,0.3f+chanHeadBase.w*keyHit[i]*1.5f) + )); + ImU32 fadeCol=ImGui::GetColorU32(ImVec4( + chanHeadBase.x, + chanHeadBase.y, + chanHeadBase.z, + hovered?0.3f:MIN(1.0f,0.2f+chanHeadBase.w*keyHit[i]*1.2f) + )); + ImVec2 rMin=rect.Min; + ImVec2 rMax=rect.Max; + rMin.x+=3.0f*dpiScale; + rMin.y+=6.0f*dpiScale; + rMax.x-=3.0f*dpiScale; + rMax.y-=6.0f*dpiScale; + dl->AddRectFilledMultiColor(rMin,rMax,fadeCol0,fadeCol0,fadeCol,fadeCol,4.0f*dpiScale); + dl->AddText(ImVec2(minLabelArea.x,rect.Min.y+6.0*dpiScale),ImGui::GetColorU32(channelTextColor(i)),chanID); + } + break; + } + case 3: // split button + ImGui::Dummy(ImVec2(1.0f,2.0f*dpiScale)); + ImGui::SetCursorPosX(minLabelArea.x); + ImGui::TextUnformatted(chanID); + ImGui::SameLine(); + ImGui::PushFont(mainFont); + ImGui::SmallButton(muted?ICON_FA_VOLUME_OFF:ICON_FA_VOLUME_UP); + ImGui::PopFont(); + break; + case 4: { // square border + ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID(chanID))) { + bool hovered=ImGui::ItemHoverable(rect,ImGui::GetID(chanID)); + ImU32 fadeCol=ImGui::GetColorU32(ImVec4( + chanHeadBase.x, + chanHeadBase.y, + chanHeadBase.z, + hovered?1.0f:MIN(1.0f,0.2f+chanHeadBase.w*keyHit[i]*4.0f) + )); + ImVec2 rMin=rect.Min; + ImVec2 rMax=rect.Max; + rMin.x+=2.0f*dpiScale; + rMin.y+=3.0f*dpiScale; + rMax.x-=3.0f*dpiScale; + rMax.y-=3.0f*dpiScale; + dl->AddRect(rMin,rMax,fadeCol,0.0f,2.0*dpiScale); + dl->AddText(ImVec2(minLabelArea.x,rect.Min.y+3.0*dpiScale),ImGui::GetColorU32(channelTextColor(i)),chanID); + } + break; + } + case 5: { // round border + ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID(chanID))) { + bool hovered=ImGui::ItemHoverable(rect,ImGui::GetID(chanID)); + ImU32 fadeCol=ImGui::GetColorU32(ImVec4( + chanHeadBase.x, + chanHeadBase.y, + chanHeadBase.z, + hovered?1.0f:MIN(1.0f,0.2f+chanHeadBase.w*keyHit[i]*4.0f) + )); + ImVec2 rMin=rect.Min; + ImVec2 rMax=rect.Max; + rMin.x+=2.0f*dpiScale; + rMin.y+=3.0f*dpiScale; + rMax.x-=3.0f*dpiScale; + rMax.y-=3.0f*dpiScale; + dl->AddRect(rMin,rMax,fadeCol,4.0f*dpiScale,ImDrawFlags_RoundCornersAll,2.0*dpiScale); + dl->AddText(ImVec2(minLabelArea.x,rect.Min.y+3.0*dpiScale),ImGui::GetColorU32(channelTextColor(i)),chanID); + } + break; + } + } + ImGui::PopID(); + + if (extraChannelButtons==0 || settings.channelVolStyle!=0) ImGui::PopStyleVar(); + 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); @@ -506,11 +714,72 @@ 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); } + + if (settings.channelStyle==3) { + ImGui::Dummy(ImVec2(1.0f,2.0f*dpiScale)); + } + + // volume bar + if (settings.channelVolStyle!=0) { + ImVec2 sizeV=ImVec2( + 1.0f, + 6.0*dpiScale + ); + ImVec2 minAreaV=window->DC.CursorPos; + ImVec2 maxAreaV=ImVec2( + minAreaV.x+window->WorkRect.Max.x-window->WorkRect.Min.x, + minAreaV.y+sizeV.y + ); + ImRect rectV=ImRect(minAreaV,maxAreaV); + ImGui::ItemSize(sizeV,ImGui::GetStyle().FramePadding.y); + if (ImGui::ItemAdd(rectV,ImGui::GetID(chanID))) { + float xLeft=0.0f; + float xRight=1.0f; + + if (e->keyHit[i]) { + keyHit1[i]=1.0f; + e->keyHit[i]=false; + } + + if (e->isRunning()) { + DivChannelState* cs=e->getChanState(i); + float stereoPan=(float)(e->convertPanSplitToLinearLR(cs->panL,cs->panR,256)-128)/128.0; + switch (settings.channelVolStyle) { + case 1: // simple + xRight=((float)(e->getChanState(i)->volume>>8)/(float)e->getMaxVolumeChan(i))*0.9+(keyHit1[i]*0.1f); + break; + case 2: { // stereo + float amount=((float)(e->getChanState(i)->volume>>8)/(float)e->getMaxVolumeChan(i))*0.4+(keyHit1[i]*0.1f); + xRight=0.5+amount*(1.0+MIN(0.0,stereoPan)); + xLeft=0.5-amount*(1.0-MAX(0.0,stereoPan)); + break; + } + case 3: // real + xRight=chanOscVol[i]; + break; + case 4: // real (stereo) + xRight=0.5+chanOscVol[i]*0.5*(1.0+MIN(0.0,stereoPan)); + xLeft=0.5-chanOscVol[i]*0.5*(1.0-MAX(0.0,stereoPan)); + break; + } + + dl->AddRectFilled( + ImLerp(rectV.Min,rectV.Max,ImVec2(xLeft,0.0f)), + ImLerp(rectV.Min,rectV.Max,ImVec2(xRight,1.0f)), + ImGui::GetColorU32(chanHeadBase) + ); + } + keyHit1[i]-=0.2f; + if (keyHit1[i]<0.0f) keyHit1[i]=0.0f; + } + } + + // extra buttons if (extraChannelButtons==2) { DivPattern* pat=e->curPat[i].getPattern(e->curOrders->ord[i][ord],true); ImGui::PushFont(mainFont); @@ -701,6 +970,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; diff --git a/src/gui/piano.cpp b/src/gui/piano.cpp index 0e0063bb2..082cc65e9 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 64ffc4100..b75bda4f0 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 5862dfadc..48332b339 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 ae24d9cba..ca9fd9b0b 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -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 } @@ -362,7 +362,13 @@ void FurnaceGUI::initSystemPresets() { DIV_SYSTEM_MSM6295, 64, 0, 0, 0 } - )); + )); + cat.systems.push_back(FurnaceGUISysDef( + "SNES", { + DIV_SYSTEM_SNES, 64, 0, 0, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Wavetable","chips which use user-specified waveforms to generate sound."); @@ -415,7 +421,7 @@ void FurnaceGUI::initSystemPresets() { } )); cat.systems.push_back(FurnaceGUISysDef( - "Namco C163", { + "Namco 163", { DIV_SYSTEM_N163, 64, 0, 0, 0 } @@ -595,49 +601,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 @@ -650,6 +656,12 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "SNES", { + DIV_SYSTEM_SNES, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Mattel Intellivision", { DIV_SYSTEM_AY8910, 64, 0, 48, @@ -860,6 +872,41 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + Neotron", { + DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_YM2610_FULL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "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( + "MSX + Neotron (with YM2610B)", { + DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_YM2610B, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "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 @@ -931,11 +978,27 @@ void FurnaceGUI::initSystemPresets() { cat.systems.push_back(FurnaceGUISysDef( "NEC PC-98 (with PC-9801-86)", { // -73 also has OPNA DIV_SYSTEM_PC98, 64, 0, 1, + DIV_SYSTEM_PCM_DAC, 64, 0, 44099|(15<<16), // 2x 16-bit Burr Brown DAC + DIV_SYSTEM_PCM_DAC, 64, 0, 44099|(15<<16), 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, + DIV_SYSTEM_PCM_DAC, 64, 0, 44099|(15<<16), + DIV_SYSTEM_PCM_DAC, 64, 0, 44099|(15<<16), + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with PC-9801-73)", { + DIV_SYSTEM_PC98, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with PC-9801-73; extended channel 3)", { DIV_SYSTEM_PC98_EXT, 64, 0, 1, 0 } @@ -943,6 +1006,7 @@ void FurnaceGUI::initSystemPresets() { 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_PCM_DAC, 64, 0, 44099|(15<<16)|(1<<20), DIV_SYSTEM_OPL3, 64, 0, 0, 0 } @@ -950,6 +1014,7 @@ void FurnaceGUI::initSystemPresets() { 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_PCM_DAC, 64, 0, 44099|(15<<16)|(1<<20), DIV_SYSTEM_OPL3, 64, 0, 0, 0 } @@ -957,6 +1022,7 @@ void FurnaceGUI::initSystemPresets() { 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_PCM_DAC, 64, 0, 44099|(15<<16)|(1<<20), DIV_SYSTEM_OPL3_DRUMS, 64, 0, 2, 0 } @@ -964,6 +1030,7 @@ void FurnaceGUI::initSystemPresets() { 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_PCM_DAC, 64, 0, 44099|(15<<16)|(1<<20), DIV_SYSTEM_OPL3_DRUMS, 64, 0, 2, 0 } @@ -1063,6 +1130,20 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Atari ST", { + DIV_SYSTEM_AY8910, 64, 0, 19, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Atari STE", { + DIV_SYSTEM_AY8910, 64, 0, 19, + DIV_SYSTEM_PCM_DAC, 64, 0, 50667|(7<<16), + DIV_SYSTEM_PCM_DAC, 64, 0, 50667|(7<<16), + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "SAM Coupé", { DIV_SYSTEM_SAA1099, 64, 0, 0, @@ -1117,17 +1198,33 @@ void FurnaceGUI::initSystemPresets() { 0 } )); - cat.systems.push_back(FurnaceGUISysDef( - "PC + AdLib/Sound Blaster", { + cat.systems.push_back(FurnaceGUISysDef( + "PC + AdLib", { DIV_SYSTEM_OPL2, 64, 0, 0, DIV_SYSTEM_PCSPKR, 64, 0, 0, 0 } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + AdLib (drums mode)", { + DIV_SYSTEM_OPL2, 64, 0, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster", { + DIV_SYSTEM_OPL2, 64, 0, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + DIV_SYSTEM_PCM_DAC, 64, 0, 22049|(7<<16), + 0 + } )); cat.systems.push_back(FurnaceGUISysDef( "PC + AdLib/Sound Blaster (drums mode)", { DIV_SYSTEM_OPL2_DRUMS, 64, 0, 0, DIV_SYSTEM_PCSPKR, 64, 0, 0, + DIV_SYSTEM_PCM_DAC, 64, 0, 22049|(7<<16), 0 } )); @@ -1136,6 +1233,7 @@ void FurnaceGUI::initSystemPresets() { DIV_SYSTEM_OPL2, 64, 0, 0, DIV_SYSTEM_SAA1099, 64, 0, 1, DIV_SYSTEM_SAA1099, 64, 0, 1, + DIV_SYSTEM_PCM_DAC, 64, 0, 22049|(7<<16), DIV_SYSTEM_PCSPKR, 64, 0, 0, 0 } @@ -1145,6 +1243,7 @@ void FurnaceGUI::initSystemPresets() { DIV_SYSTEM_OPL2_DRUMS, 64, 0, 0, DIV_SYSTEM_SAA1099, 64, 0, 1, DIV_SYSTEM_SAA1099, 64, 0, 1, + DIV_SYSTEM_PCM_DAC, 64, 0, 22049|(7<<16), DIV_SYSTEM_PCSPKR, 64, 0, 0, 0 } @@ -1153,6 +1252,7 @@ void FurnaceGUI::initSystemPresets() { "PC + Sound Blaster Pro", { DIV_SYSTEM_OPL2, 64, -127, 0, DIV_SYSTEM_OPL2, 64, 127, 0, + DIV_SYSTEM_PCM_DAC, 64, 0, 22049|(7<<16)|(1<<20), //alternatively 44.1 khz mono DIV_SYSTEM_PCSPKR, 64, 0, 0, 0 } @@ -1161,6 +1261,7 @@ void FurnaceGUI::initSystemPresets() { "PC + Sound Blaster Pro (drums mode)", { DIV_SYSTEM_OPL2_DRUMS, 64, -127, 0, DIV_SYSTEM_OPL2_DRUMS, 64, 127, 0, + DIV_SYSTEM_PCM_DAC, 64, 0, 22049|(7<<16)|(1<<20), //alternatively 44.1 khz mono DIV_SYSTEM_PCSPKR, 64, 0, 0, 0 } @@ -1168,6 +1269,7 @@ void FurnaceGUI::initSystemPresets() { cat.systems.push_back(FurnaceGUISysDef( "PC + Sound Blaster Pro 2", { DIV_SYSTEM_OPL3, 64, 0, 0, + DIV_SYSTEM_PCM_DAC, 64, 0, 44099|(15<<16)|(1<<20), DIV_SYSTEM_PCSPKR, 64, 0, 0, 0 } @@ -1175,6 +1277,7 @@ void FurnaceGUI::initSystemPresets() { cat.systems.push_back(FurnaceGUISysDef( "PC + Sound Blaster Pro 2 (drums mode)", { DIV_SYSTEM_OPL3_DRUMS, 64, 0, 0, + DIV_SYSTEM_PCM_DAC, 64, 0, 44099|(15<<16)|(1<<20), DIV_SYSTEM_PCSPKR, 64, 0, 0, 0 } @@ -1247,6 +1350,15 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "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, @@ -1267,6 +1379,34 @@ void FurnaceGUI::initSystemPresets() { 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) @@ -1332,6 +1472,13 @@ void FurnaceGUI::initSystemPresets() { 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 @@ -1869,7 +2016,7 @@ void FurnaceGUI::initSystemPresets() { "Alpha denshi Alpha-68K", { DIV_SYSTEM_OPN, 64, 0, 3, // 3MHz DIV_SYSTEM_OPLL, 64, 0, 0, // 3.58MHz - // software controlled 8 bit DAC + DIV_SYSTEM_PCM_DAC, 64, 0, 7613|(7<<16), // software controlled 8 bit DAC 0 } )); @@ -1877,7 +2024,7 @@ void FurnaceGUI::initSystemPresets() { "Alpha denshi Alpha-68K (extended channel 3)", { DIV_SYSTEM_OPN_EXT, 64, 0, 3, // 3MHz DIV_SYSTEM_OPLL, 64, 0, 0, // 3.58MHz - // software controlled 8 bit DAC + DIV_SYSTEM_PCM_DAC, 64, 0, 7613|(7<<16), // software controlled 8 bit DAC 0 } )); @@ -1885,7 +2032,7 @@ void FurnaceGUI::initSystemPresets() { "Alpha denshi Alpha-68K (drums mode)", { DIV_SYSTEM_OPN, 64, 0, 3, // 3MHz DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, // 3.58MHz - // software controlled 8 bit DAC + DIV_SYSTEM_PCM_DAC, 64, 0, 7613|(7<<16), // software controlled 8 bit DAC 0 } )); @@ -1893,7 +2040,7 @@ void FurnaceGUI::initSystemPresets() { "Alpha denshi Alpha-68K (extended channel 3; drums mode)", { DIV_SYSTEM_OPN_EXT, 64, 0, 3, // 3MHz DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, // 3.58MHz - // software controlled 8 bit DAC + DIV_SYSTEM_PCM_DAC, 64, 0, 7613|(7<<16), // software controlled 8 bit DAC 0 } )); @@ -1935,10 +2082,27 @@ void FurnaceGUI::initSystemPresets() { 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 } )); @@ -2051,6 +2215,13 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Irem M72", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_PCM_DAC, 64, 0, 7811|(7<<16), + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("DefleMask-compatible","these configurations are compatible with DefleMask.\nselect this if you need to save as .dmf or work with that program."); @@ -2100,7 +2271,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 9b4bb17b5..843e356fd 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -35,6 +35,12 @@ void FurnaceGUI::drawSampleEdit() { nextWindow=GUI_WINDOW_NOTHING; } if (!sampleEditOpen) return; + if (mobileUI) { + patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f)); + patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f))); + ImGui::SetNextWindowPos(patWindowPos); + ImGui::SetNextWindowSize(patWindowSize); + } if (ImGui::Begin("Sample Editor",&sampleEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) { if (curSample<0 || curSample>=(int)e->song.sample.size()) { ImGui::Text("no sample selected"); @@ -46,6 +52,12 @@ void FurnaceGUI::drawSampleEdit() { sampleType=sampleDepths[sample->depth]; } } + String loopType="Invalid"; + if (sample->loopModeloopMode]!=NULL) { + loopType=sampleLoopModes[sample->loopMode]; + } + } if (!settings.sampleLayout) { ImGui::Text("Name"); ImGui::SameLine(); @@ -65,7 +77,7 @@ void FurnaceGUI::drawSampleEdit() { if (sampleDepths[i]==NULL) continue; if (ImGui::Selectable(sampleDepths[i])) { sample->prepareUndo(true); - sample->depth=DivSampleDepth(i); + sample->depth=(DivSampleDepth)i; e->renderSamplesP(); updateSampleTex=true; MARK_MODIFIED; @@ -93,13 +105,16 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::TableNextColumn(); - ImGui::Text("Loop Mode"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - int mode=(int)sample->loopMode; - if (ImGui::Combo("##LoopMode",&mode,loopMode,DIV_SAMPLE_LOOPMODE_MAX,DIV_SAMPLE_LOOPMODE_MAX)) { MARK_MODIFIED - if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { + bool doLoop=(sample->isLoopable()); + if (ImGui::Checkbox("Loop",&doLoop)) { MARK_MODIFIED + if (doLoop) { + sample->loop=true; sample->loopStart=0; + sample->loopEnd=sample->samples; + } else { + sample->loop=false; + sample->loopStart=-1; + sample->loopEnd=sample->samples; } if (sample->loopEnd>sample->samples) { sample->loopEnd=sample->samples; @@ -112,15 +127,33 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("Length: %d",sample->samples); bool doLoop=(sample->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT); if (doLoop) { + ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::Text("Loop start"); + ImGui::Text("Loop Mode"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##LoopStart",&sample->loopStart,1,10)) { MARK_MODIFIED - if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { + if (ImGui::BeginCombo("##SampleLoopMode",loopType.c_str())) { + for (int i=0; iprepareUndo(true); + sample->loopMode=(DivSampleLoopMode)i; + e->renderSamplesP(); + updateSampleTex=true; + MARK_MODIFIED; + } + } + ImGui::EndCombo(); + } + ImGui::TableNextColumn(); + ImGui::Text("Loop Start"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##LoopStartPosition",&sample->loopStart,1,10)) { MARK_MODIFIED + if (sample->loopStart<0) { sample->loopStart=0; } - if (sample->loopStart>=(int)sample->loopEnd) { + if (sample->loopStart>sample->loopEnd) { sample->loopStart=sample->loopEnd; } updateSampleTex=true; @@ -129,18 +162,13 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("Loop End"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - int end=(int)sample->loopEnd; - if (ImGui::InputInt("##LoopEnd",&end,1,10)) { MARK_MODIFIED - if (end<0) { - end=0; + if (ImGui::InputInt("##LoopEndPosition",&sample->loopEnd,1,10)) { MARK_MODIFIED + if (sample->loopEndloopStart) { + sample->loopEnd=sample->loopStart; } - if (endloopStart) { - end=sample->loopStart; + if (sample->loopEnd>=(int)sample->samples) { + sample->loopEnd=sample->samples; } - if (end>(int)sample->samples) { - end=(int)sample->samples; - } - sample->loopEnd=end; updateSampleTex=true; } } @@ -157,20 +185,20 @@ void FurnaceGUI::drawSampleEdit() { ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode)); + pushToggleColors(!sampleDragMode); if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { sampleDragMode=false; } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Select"); } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(sampleDragMode)); + pushToggleColors(sampleDragMode); if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) { sampleDragMode=true; } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Draw"); } @@ -367,7 +395,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]() { @@ -544,14 +572,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()) { @@ -610,7 +638,7 @@ void FurnaceGUI::drawSampleEdit() { if (sampleDepths[i]==NULL) continue; if (ImGui::Selectable(sampleDepths[i])) { sample->prepareUndo(true); - sample->depth=DivSampleDepth(i); + sample->depth=(DivSampleDepth)i; e->renderSamplesP(); updateSampleTex=true; MARK_MODIFIED; @@ -639,13 +667,16 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::TableNextColumn(); - ImGui::Text("Loop Mode"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - int mode=(int)sample->loopMode; - if (ImGui::Combo("##LoopMode",&mode,loopMode,DIV_SAMPLE_LOOPMODE_MAX,DIV_SAMPLE_LOOPMODE_MAX)) { MARK_MODIFIED - if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { + bool doLoop=(sample->isLoopable()); + if (ImGui::Checkbox("Loop",&doLoop)) { MARK_MODIFIED + if (doLoop) { + sample->loop=true; sample->loopStart=0; + sample->loopEnd=sample->samples; + } else { + sample->loop=false; + sample->loopStart=-1; + sample->loopEnd=sample->samples; } if (sample->loopEnd>sample->samples) { sample->loopEnd=sample->samples; @@ -660,15 +691,33 @@ void FurnaceGUI::drawSampleEdit() { if (doLoop) { ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::Text("Loop start"); + ImGui::Text("Loop Mode"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##LoopStart",&sample->loopStart,1,10)) { MARK_MODIFIED - if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { + if (ImGui::BeginCombo("##SampleLoopMode",loopType.c_str())) { + for (int i=0; iprepareUndo(true); + sample->loopMode=(DivSampleLoopMode)i; + e->renderSamplesP(); + updateSampleTex=true; + MARK_MODIFIED; + } + } + ImGui::EndCombo(); + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Loop Start"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##LoopStartPosition",&sample->loopStart,1,10)) { MARK_MODIFIED + if (sample->loopStart<0) { sample->loopStart=0; } - if (sample->loopStart>=(int)sample->loopEnd) { - sample->loopStart=loopEnd; + if (sample->loopStart>sample->loopEnd) { + sample->loopStart=sample->loopEnd; } updateSampleTex=true; } @@ -676,18 +725,13 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("Loop End"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - int end=(int)sample->loopEnd; - if (ImGui::InputInt("##LoopEnd",&end,1,10)) { MARK_MODIFIED - if (end<0) { - end=0; + if (ImGui::InputInt("##LoopEndPosition",&sample->loopEnd,1,10)) { MARK_MODIFIED + if (sample->loopEndloopStart) { + sample->loopEnd=sample->loopStart; } - if (endloopStart) { - end=sample->loopStart; + if (sample->loopEnd>=(int)sample->samples) { + sample->loopEnd=sample->samples; } - if (end>(int)(sample->samples)) { - end=sample->samples; - } - sample->loopEnd=end; updateSampleTex=true; } } @@ -704,20 +748,20 @@ void FurnaceGUI::drawSampleEdit() { ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode)); + pushToggleColors(!sampleDragMode); if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { sampleDragMode=false; } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Select"); } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(sampleDragMode)); + pushToggleColors(sampleDragMode); if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) { sampleDragMode=true; } - ImGui::PopStyleColor(); + popToggleColors(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Draw"); } @@ -1056,7 +1100,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]() { @@ -1203,7 +1247,7 @@ void FurnaceGUI::drawSampleEdit() { for (int i=0; iisLoopable() && ((scaledPos>=sample->loopStart) && (scaledPos<(int)(sample->loopEnd)))) { + if (sample->isLoopable() && (scaledPos>=sample->loopStart && scaledPos<=sample->loopEnd)) { data[i*availX+j]=bgColorLoop; } else { data[i*availX+j]=bgColor; @@ -1316,6 +1360,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(); } diff --git a/src/gui/sampleUtil.h b/src/gui/sampleUtil.h index 981a56046..187f5ebf6 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 cb2064e62..b52b648e3 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(); @@ -638,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(); } @@ -913,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"); @@ -1026,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:"); @@ -1148,6 +1266,102 @@ 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; + } + if (ImGui::RadioButton("Real (stereo)##CHV4",settings.channelVolStyle==4)) { + settings.channelVolStyle=4; + } + + 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; + } + + bool channelTextCenterB=settings.channelTextCenter; + if (ImGui::Checkbox("Center channel name",&channelTextCenterB)) { + settings.channelTextCenter=channelTextCenterB; + } + + ImGui::Separator(); + bool insEditColorizeB=settings.insEditColorize; if (ImGui::Checkbox("Colorize instrument editor using instrument type",&insEditColorizeB)) { settings.insEditColorize=insEditColorizeB; @@ -1173,11 +1387,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; @@ -1202,7 +1411,7 @@ void FurnaceGUI::drawSettings() { if (ImGui::Checkbox("Unsigned FM detune values",&unsignedDetuneB)) { settings.unsignedDetune=unsignedDetuneB; } - + // sorry. temporarily disabled until ImGui has a way to add separators in tables arbitrarily. /*bool sysSeparatorsB=settings.sysSeparators; if (ImGui::Checkbox("Add separators between systems in Orders",&sysSeparatorsB)) { @@ -1360,6 +1569,7 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_FILE_AUDIO,"Audio"); UI_COLOR_CONFIG(GUI_COLOR_FILE_WAVE,"Wavetable"); UI_COLOR_CONFIG(GUI_COLOR_FILE_VGM,"VGM"); + UI_COLOR_CONFIG(GUI_COLOR_FILE_ZSM,"ZSM"); UI_COLOR_CONFIG(GUI_COLOR_FILE_FONT,"Font"); UI_COLOR_CONFIG(GUI_COLOR_FILE_OTHER,"Other"); ImGui::TreePop(); @@ -1411,12 +1621,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")) { @@ -1427,11 +1637,11 @@ void FurnaceGUI::drawSettings() { ImGui::TreePop(); } if (ImGui::TreeNode("Instrument Types")) { - UI_COLOR_CONFIG(GUI_COLOR_INSTR_FM,"FM (4-operator)"); - UI_COLOR_CONFIG(GUI_COLOR_INSTR_STD,"Standard"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_FM,"FM (OPN)"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_STD,"SN76489/Sega PSG"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_GB,"Game Boy"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_C64,"C64"); - UI_COLOR_CONFIG(GUI_COLOR_INSTR_AMIGA,"Amiga/Sample"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_AMIGA,"Amiga/Generic Sample"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_PCE,"PC Engine"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_AY,"AY-3-8910/SSG"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_AY8930,"AY8930"); @@ -1445,7 +1655,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"); @@ -1459,10 +1673,23 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_SNES,"SNES"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_SU,"Sound Unit"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_NAMCO,"Namco WSG"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_OPL_DRUMS,"FM (OPL Drums)"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_OPM,"FM (OPM)"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_NES,"NES"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_MSM6258,"MSM6258"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_MSM6295,"MSM6295"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_ADPCMA,"ADPCM-A"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_ADPCMB,"ADPCM-B"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_SEGAPCM,"Sega PCM"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_QSOUND,"QSound"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_YMZ280B,"YMZ280B"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_RF5C68,"RF5C68"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown"); 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"); @@ -1504,12 +1731,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"); @@ -1602,6 +1838,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); @@ -1648,7 +1885,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; } @@ -1761,7 +1998,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; @@ -1964,6 +2201,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); @@ -1972,6 +2210,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",""); @@ -2008,12 +2247,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); @@ -2055,6 +2296,24 @@ void FurnaceGUI::syncSettings() { 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",1); + settings.channelVolStyle=e->getConfInt("channelVolStyle",0); + settings.channelFeedbackStyle=e->getConfInt("channelFeedbackStyle",1); + settings.channelFont=e->getConfInt("channelFont",1); + settings.channelTextCenter=e->getConfInt("channelTextCenter",1); + settings.maxRecentFile=e->getConfInt("maxRecentFile",10); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2068,6 +2327,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); @@ -2104,6 +2364,8 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.roundedMenus,0,1); clampSetting(settings.loadJapanese,0,1); clampSetting(settings.loadChinese,0,1); + 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); @@ -2141,6 +2403,18 @@ void FurnaceGUI::syncSettings() { 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,4); + clampSetting(settings.channelFeedbackStyle,0,3); + clampSetting(settings.channelFont,0,1); + clampSetting(settings.channelTextCenter,0,1); + clampSetting(settings.maxRecentFile,0,30); settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys","")); if (settings.initialSys.size()<4) { @@ -2165,7 +2439,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); @@ -2173,7 +2447,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",""); @@ -2184,6 +2458,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); @@ -2192,6 +2467,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); @@ -2234,6 +2510,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); @@ -2263,6 +2541,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); @@ -2276,6 +2555,23 @@ void FurnaceGUI::commitSettings() { 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("channelTextCenter",settings.channelTextCenter); + 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()); @@ -2688,6 +2988,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 @@ -2850,6 +3170,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; @@ -2916,7 +3242,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; @@ -2950,7 +3276,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!"); } @@ -2971,6 +3297,7 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".wav",uiColors[GUI_COLOR_FILE_AUDIO],ICON_FA_FILE_AUDIO_O); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmc",uiColors[GUI_COLOR_FILE_AUDIO],ICON_FA_FILE_AUDIO_O); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgm",uiColors[GUI_COLOR_FILE_VGM],ICON_FA_FILE_AUDIO_O); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".zsm",uiColors[GUI_COLOR_FILE_ZSM],ICON_FA_FILE_AUDIO_O); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttf",uiColors[GUI_COLOR_FILE_FONT],ICON_FA_FONT); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".otf",uiColors[GUI_COLOR_FILE_FONT],ICON_FA_FONT); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttc",uiColors[GUI_COLOR_FILE_FONT],ICON_FA_FONT); diff --git a/src/gui/songInfo.cpp b/src/gui/songInfo.cpp index e90043b7a..8d4e0999e 100644 --- a/src/gui/songInfo.cpp +++ b/src/gui/songInfo.cpp @@ -39,7 +39,7 @@ void FurnaceGUI::drawSongInfo() { ImGui::TableNextColumn(); float avail=ImGui::GetContentRegionAvail().x; ImGui::SetNextItemWidth(avail); - if (ImGui::InputText("##Name",&e->song.name)) { MARK_MODIFIED + if (ImGui::InputText("##Name",&e->song.name,ImGuiInputTextFlags_UndoRedo)) { MARK_MODIFIED updateWindowTitle(); } if (e->song.insLen==2) { @@ -61,9 +61,40 @@ void FurnaceGUI::drawSongInfo() { ImGui::Text("Author"); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(avail); - if (ImGui::InputText("##Author",&e->song.author)) { + if (ImGui::InputText("##Author",&e->song.author,ImGuiInputTextFlags_UndoRedo)) { MARK_MODIFIED; } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Album"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputText("##Category",&e->song.category,ImGuiInputTextFlags_UndoRedo)) { + MARK_MODIFIED; + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("System"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(MAX(16.0f*dpiScale,avail-autoButtonSize-ImGui::GetStyle().ItemSpacing.x)); + if (ImGui::InputText("##SystemName",&e->song.systemName,ImGuiInputTextFlags_UndoRedo)) { + MARK_MODIFIED; + updateWindowTitle(); + e->song.autoSystem=false; + } + ImGui::SameLine(); + pushToggleColors(e->song.autoSystem); + if (ImGui::Button("Auto")) { + e->song.autoSystem=!e->song.autoSystem; + if (e->song.autoSystem) { + autoDetectSystem(); + updateWindowTitle(); + } + } + popToggleColors(); + autoButtonSize=ImGui::GetItemRectSize().x; + ImGui::EndTable(); } @@ -176,7 +207,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/gui/songNotes.cpp b/src/gui/songNotes.cpp index 74f4ed7ff..338c7da24 100644 --- a/src/gui/songNotes.cpp +++ b/src/gui/songNotes.cpp @@ -30,7 +30,7 @@ void FurnaceGUI::drawNotes() { } if (!notesOpen) return; if (ImGui::Begin("Song Comments",¬esOpen,globalWinFlags)) { - ImGui::InputTextMultiline("##SongNotes",&e->song.notes,ImGui::GetContentRegionAvail()); + ImGui::InputTextMultiline("##SongNotes",&e->song.notes,ImGui::GetContentRegionAvail(),ImGuiInputTextFlags_UndoRedo); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_NOTES; ImGui::End(); diff --git a/src/gui/subSongs.cpp b/src/gui/subSongs.cpp index b13b84204..f076298f2 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()) { @@ -90,7 +91,7 @@ void FurnaceGUI::drawSubSongs() { ImGui::Text("Name"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputText("##SubSongName",&e->curSubSong->name)) { + if (ImGui::InputText("##SubSongName",&e->curSubSong->name,ImGuiInputTextFlags_UndoRedo)) { MARK_MODIFIED; } } diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index bea65c4b3..ec3235ba2 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -109,6 +109,104 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } 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; + } case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: { @@ -119,7 +217,7 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool if (ImGui::RadioButton("PAL (3.55MHz)",(flags&15)==1)) { copyOfFlags=(flags&(~15))|1; } - if (ImGui::RadioButton("BBC Micro (4MHz)",(flags&15)==2)) { + if (ImGui::RadioButton("Arcade (4MHz)",(flags&15)==2)) { copyOfFlags=(flags&(~15))|2; } if (ImGui::RadioButton("Half NTSC (1.79MHz)",(flags&15)==3)) { @@ -260,6 +358,14 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool if (ImGui::Checkbox("Stereo##_AY_STEREO",&stereo)) { copyOfFlags=(flags&(~0x40))|(stereo?0x40:0); } + if (stereo) { + int sep=256-((flags>>8)&255); + if (CWSliderInt("Separation",&sep,1,256)) { + if (sep<1) sep=1; + if (sep>256) sep=256; + copyOfFlags=(flags&(~0xff00))|((256-sep)<<8); + } + } ImGui::EndDisabled(); bool clockSel=flags&0x80; ImGui::BeginDisabled((type==DIV_SYSTEM_AY8910) && ((flags&0x30)!=16)); @@ -304,6 +410,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)) { @@ -578,7 +702,7 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool case DIV_SYSTEM_OPL3: case DIV_SYSTEM_OPL3_DRUMS: { ImGui::Text("Clock rate:"); - if (ImGui::RadioButton("14.32MHz (MTSC)",(flags&255)==0)) { + if (ImGui::RadioButton("14.32MHz (NTSC)",(flags&255)==0)) { copyOfFlags=(flags&(~255))|0; } if (ImGui::RadioButton("14.19MHz (PAL)",(flags&255)==1)) { @@ -600,10 +724,10 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool if (ImGui::RadioButton("16.9344MHz",(flags&255)==0)) { copyOfFlags=(flags&(~255))|0; } - if (ImGui::RadioButton("14.32MHz (MTSC)",(flags&255)==1)) { + if (ImGui::RadioButton("14.32MHz (NTSC)",(flags&255)==1)) { copyOfFlags=(flags&(~255))|1; } - if (ImGui::RadioButton("14.19MHz (PAL)",(flags&255)==3)) { + if (ImGui::RadioButton("14.19MHz (PAL)",(flags&255)==2)) { copyOfFlags=(flags&(~255))|2; } if (ImGui::RadioButton("16MHz",(flags&255)==3)) { @@ -618,16 +742,18 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool 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,1,65536)) { - if (sampRate<1) sampRate=1; + 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 depth:"); + ImGui::Text("Output bit depth:"); if (CWSliderInt("##BitDepth",&bitDepth,1,16)) { if (bitDepth<1) bitDepth=1; if (bitDepth>16) bitDepth=16; @@ -638,12 +764,12 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } break; } - case DIV_SYSTEM_GB: case DIV_SYSTEM_SWAN: case DIV_SYSTEM_VERA: case DIV_SYSTEM_BUBSYS_WSG: case DIV_SYSTEM_YMU759: case DIV_SYSTEM_PET: + case DIV_SYSTEM_SNES: case DIV_SYSTEM_T6W28: ImGui::Text("nothing to configure"); break; @@ -657,6 +783,9 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool if (copyOfFlags!=flags) { if (chan>=0) { e->setSysFlags(chan,copyOfFlags,restart); + if (e->song.autoSystem) { + autoDetectSystem(); + } updateWindowTitle(); } else { flags=copyOfFlags; diff --git a/src/gui/sysEx.cpp b/src/gui/sysEx.cpp index 5b2b66576..5a72649ee 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); @@ -49,6 +60,7 @@ bool FurnaceGUI::parseSysEx(unsigned char* data, size_t len) { op.rs=reader.readC(); reader.readC(); // EBS - ignore op.am=reader.readC(); + // TODO: don't ignore after I add KVS to Furnace reader.readC(); // KVS - ignore op.tl=3+((99-reader.readC())*124)/99; unsigned char freq=reader.readC(); @@ -137,7 +149,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 000000000..386d26583 --- /dev/null +++ b/src/gui/sysManager.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 "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); + if (e->song.autoSystem) { + autoDetectSystem(); + } + 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()+")"); + } + if (e->song.autoSystem) { + autoDetectSystem(); + } + 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 000000000..f59e547ad --- /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 4ff2182e2..87f5ad38f 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 89602b422..ef2750a2e 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 1f5f9560a..79229621a 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,14 @@ DivEngine e; FurnaceGUI g; #endif +FurnaceCLI cli; + String outName; String vgmOutName; +String zsmOutName; +String cmdOutName; int loops=1; +int benchMode=0; DivAudioExportModes outMode=DIV_EXPORT_MODE_ONE; #ifdef HAVE_GUI @@ -58,6 +65,7 @@ bool consoleMode=true; #endif bool displayEngineFailError=false; +bool cmdOutBinary=false; std::vector params; @@ -109,6 +117,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; @@ -169,8 +182,9 @@ TAParamResult pVersion(String) { 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"); + printf("- vgsound_emu (second version, modified version) by cam900 (zlib license)\n"); return TA_PARAM_QUIT; } @@ -220,6 +234,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); @@ -232,6 +259,18 @@ TAParamResult pVGMOut(String val) { return TA_PARAM_SUCCESS; } +TAParamResult pZSMOut(String val) { + zsmOutName=val; + e.setAudio(DIV_AUDIO_DUMMY); + 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("Z","zsmout",true,pZSMOut,"","output .zsm data for Commander X16 Zsound")); + 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")); @@ -254,6 +296,8 @@ 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.")); } @@ -279,7 +323,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); @@ -287,6 +331,8 @@ int main(int argc, char** argv) { #endif outName=""; vgmOutName=""; + zsmOutName=""; + cmdOutName=""; initParams(); @@ -414,7 +460,32 @@ int main(int argc, char** argv) { 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) { @@ -440,25 +511,39 @@ 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 diff --git a/src/utfutils.cpp b/src/utfutils.cpp index c99528986..4c727777b 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 087913a43..76c894708 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 000000000..6ef11feae --- /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