Merge branch 'tildearrow:master' into master

This commit is contained in:
LoKiToon 2022-04-11 20:18:47 +03:00 committed by GitHub
commit 10594c57cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
258 changed files with 39286 additions and 8505 deletions

1
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1 @@
<!-- NOTICE: if you are contributing a new system, please be sure to ask tildearrow first in order to get system IDs allocated! -->

View File

@ -18,17 +18,16 @@ jobs:
strategy:
matrix:
config:
- { name: 'Windows MSVC', os: windows-latest, compiler: msvc, shell: bash }
- { name: 'Windows MinGW', os: windows-latest, compiler: mingw, shell: 'msys2 {0}' }
- { name: 'macOS', os: macos-latest, shell: bash }
- { name: 'Ubuntu', os: ubuntu-18.04, shell: bash }
- { name: 'Windows MSVC x86', os: windows-latest, compiler: msvc, arch: x86 }
- { 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 }
fail-fast: false
name: ${{ matrix.config.name }}
runs-on: ${{ matrix.config.os }}
defaults:
run:
shell: ${{ matrix.config.shell }}
steps:
- name: Checkout
@ -36,53 +35,123 @@ jobs:
with:
submodules: recursive
- name: Set Windows arch identifiers
id: windows-identify
if: ${{ matrix.config.compiler == 'msvc' || matrix.config.compiler == 'mingw' }}
run: |
vswhere_target="${{ matrix.config.arch }}"
msvc_target="${{ matrix.config.arch }}"
mingw_target="${{ matrix.config.arch }}"
if [ '${{ matrix.config.arch }}' == 'x86' ]; then
msvc_target="Win32"
elif [ '${{ matrix.config.arch }}' == 'x86_64' ]; then
vswhere_target="amd64"
msvc_target="x64"
fi
if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then
echo "vswhere target: ${vswhere_target}"
echo "MSVC target: ${msvc_target}"
else
echo "MinGW cross target: ${mingw_target}"
fi
echo "::set-output name=vswhere-target::${vswhere_target}"
echo "::set-output name=msvc-target::${msvc_target}"
echo "::set-output name=mingw-target::${mingw_target}"
- name: Set package identifier
id: package-identify
run: |
package_name="furnace-${GITHUB_SHA}"
package_ext=""
if [ '${{ runner.os }}' == 'Windows' ] || [ '${{ matrix.config.compiler }}' == 'mingw' ]; then
package_name="${package_name}-Windows"
if [ '${{ matrix.config.compiler }}' == 'mingw' ]; then
package_name="${package_name}-MinGW"
else
package_name="${package_name}-MSVC"
fi
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_ext=".dmg"
else
package_name="${package_name}-Linux"
package_ext=".AppImage"
fi
echo "Package identifier: ${package_name}"
echo "Package file: ${package_name}${package_ext}"
echo "::set-output name=id::${package_name}"
echo "::set-output name=filename::${package_name}${package_ext}"
- name: Set build cores amount
id: build-cores
run: |
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
set amount=2
if [ '${{ runner.os }}' == 'macOS' ]; then
amount=3
fi
echo "Amount of cores we can build with: ${amount}"
echo "::set-output name=amount::${amount}"
- name: Setup Toolchain [Windows MSVC]
if: ${{ runner.os == 'Windows' && matrix.config.compiler == 'msvc' }}
if: ${{ matrix.config.compiler == 'msvc' }}
uses: seanmiddleditch/gha-setup-vsdevenv@v3
with:
arch: ${{ steps.windows-identify.outputs.vswhere-target }}
- name: Setup Toolchain [Windows MinGW]
if: ${{ runner.os == 'Windows' && matrix.config.compiler == 'mingw' }}
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: true
install: |
mingw-w64-x86_64-toolchain
mingw-w64-x86_64-cmake
make
- name: Install Dependencies [macOS]
if: ${{ runner.os == 'macOS' }}
if: ${{ matrix.config.compiler == 'mingw' }}
run: |
export HOMEBREW_NO_INSTALL_CLEANUP=1
brew update
brew install \
pkg-config \
sdl2 \
libsndfile \
zlib \
jack
sudo apt update
sudo apt install \
mingw-w64 \
mingw-w64-tools
- name: Install Dependencies [Ubuntu]
if: ${{ runner.os == 'Linux' }}
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
run: |
sudo apt update
sudo apt install \
libsdl2-dev \
libfmt-dev \
librtmidi-dev \
libsndfile1-dev \
zlib1g-dev \
libjack-jackd2-dev
libjack-jackd2-dev \
appstream
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
chmod +x appimagetool-x86_64.AppImage
- name: Configure
- name: Configure (System Libraries)
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
run: |
export USE_WAE=ON
export CMAKE_EXTRA_ARGS=()
if [ '${{ runner.os }}' == 'Windows' ]; then
if [ '${{ matrix.config.compiler }}' == 'mingw' ]; then
CMAKE_EXTRA_ARGS+=('-G' 'MSYS Makefiles')
elif [ '${{ matrix.config.compiler }}' == 'msvc' ]; then
# We don't want all the MSVC warnings to cause errors yet
export USE_WAE=OFF
if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then
CMAKE_EXTRA_ARGS+=('-DCMAKE_GENERATOR_PLATFORM=${{ steps.windows-identify.outputs.msvc-target }}')
elif [ '${{ matrix.config.compiler }}' == 'mingw' ]; then
CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-${{ steps.windows-identify.outputs.mingw-target }}.cmake')
else
# Test with system libs
CMAKE_EXTRA_ARGS+=(
'-DSYSTEM_FMT=OFF'
'-DSYSTEM_LIBSNDFILE=ON'
'-DSYSTEM_RTMIDI=ON'
'-DSYSTEM_ZLIB=ON'
'-DWITH_JACK=ON'
)
# Too old on Ubuntu
if [ '${{ runner.os }}' == 'macOS' ]; then
CMAKE_EXTRA_ARGS+=('-DSYSTEM_SDL2=ON')
fi
fi
@ -93,16 +162,122 @@ jobs:
-DWARNINGS_ARE_ERRORS=${USE_WAE} \
"${CMAKE_EXTRA_ARGS[@]}"
- name: Build
- name: Build (System Libraries)
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
run: |
export VERBOSE=1
cmake \
--build ${PWD}/build \
--config ${{ env.BUILD_TYPE }} \
--parallel 2
--parallel ${{ steps.build-cores.outputs.amount }}
- name: Install
- name: Install (System Libraries)
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
run: |
cmake \
--install ${PWD}/build \
--config ${{ env.BUILD_TYPE }}
- name: Cleanup (System Libraries)
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
run: |
rm -rf build/ target/
- name: Configure
run: |
export USE_WAE=ON
export CMAKE_EXTRA_ARGS=()
if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then
CMAKE_EXTRA_ARGS+=('-DCMAKE_GENERATOR_PLATFORM=${{ steps.windows-identify.outputs.msvc-target }}')
# Force static linking
# 1. Make MSVC runtime configurable
CMAKE_EXTRA_ARGS+=('-DCMAKE_POLICY_DEFAULT_CMP0091=NEW')
# 2. Use static (debug) runtime
if [ '${{ env.BUILD_TYPE }}' == 'Debug' ]; then
CMAKE_EXTRA_ARGS+=('-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebug')
else
CMAKE_EXTRA_ARGS+=('-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded')
fi
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"')
fi
cmake \
-B ${PWD}/build \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
-DWARNINGS_ARE_ERRORS=${USE_WAE} \
"${CMAKE_EXTRA_ARGS[@]}"
- name: Build
run: |
cmake \
--build ${PWD}/build \
--config ${{ env.BUILD_TYPE }} \
--parallel ${{ steps.build-cores.outputs.amount }}
- name: Package [Windows]
if: ${{ runner.os == 'Windows' || matrix.config.compiler == 'mingw' }}
run: |
binPath=build
if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then
binPath="${binPath}/${{ env.BUILD_TYPE }}"
fi
if [ '${{ matrix.config.compiler }}' == 'mingw' ] && [ '${{ env.BUILD_TYPE }}' == 'Release' ]; then
# arch-specific strip prefix
# TODO maybe extract from cross toolchain files?
toolPrefix="-w64-mingw32-"
if [ '${{ matrix.config.arch }}' == 'x86_64' ]; then
toolPrefix="x86_64${toolPrefix}"
else
toolPrefix="i686${toolPrefix}"
fi
${toolPrefix}strip -s "${binPath}/furnace.exe"
fi
mkdir ${{ steps.package-identify.outputs.filename }}
pushd ${{ steps.package-identify.outputs.filename }}
cp -v ../LICENSE LICENSE.txt
cp -v ../README.md README.txt
cp -vr ../{papers,demos} ../${binPath}/furnace.exe ./
popd
- name: Package [macOS]
if: ${{ runner.os == 'macOS' }}
run: |
pushd build
cpack
mv Furnace-*-Darwin.dmg ../${{ steps.package-identify.outputs.filename }}
popd
- name: Package [Ubuntu]
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
run: |
if [ '${{ env.BUILD_TYPE }}' == 'Release' ]; then
strip -s build/furnace
fi
mkdir -p target/furnace.AppDir
make -C ${PWD}/build DESTDIR=${PWD}/target/furnace.AppDir install
pushd target
pushd furnace.AppDir
cp -v usr/share/{icons/hicolor/1024x1024/apps/furnace.png,applications/furnace.desktop} ./
ln -s furnace.png .DirIcon
mv -v usr/share/metainfo/{furnace.appdata,org.tildearrow.furnace.metainfo}.xml
cp -v ../../res/AppRun ./
popd
../appimagetool-x86_64.AppImage furnace.AppDir
mv Furnace-*.AppImage ../${{ steps.package-identify.outputs.filename }}
popd
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: ${{ steps.package-identify.outputs.id }}
path: ${{ steps.package-identify.outputs.filename }}

3
.gitmodules vendored
View File

@ -19,3 +19,6 @@
[submodule "extern/adpcm"]
path = extern/adpcm
url = https://github.com/superctr/adpcm
[submodule "extern/Nuked-OPL3"]
path = extern/Nuked-OPL3
url = https://github.com/nukeykt/Nuked-OPL3.git

View File

@ -13,8 +13,8 @@ endif()
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_PROJECT_VERSION_MAJOR 0)
set(CMAKE_PROJECT_VERSION_MINOR 5)
set(CMAKE_PROJECT_VERSION_PATCH 7)
set(CMAKE_PROJECT_VERSION_MINOR 6)
set(CMAKE_PROJECT_VERSION_PATCH 0)
if (ANDROID)
set(BUILD_GUI_DEFAULT OFF)
@ -65,7 +65,7 @@ list(APPEND DEPENDENCIES_LIBRARIES ${CMAKE_THREAD_LIBS_INIT})
if (SYSTEM_FMT)
if (PKG_CONFIG_FOUND)
pkg_check_modules(FMT fmt)
pkg_check_modules(FMT fmt>=7.1.0)
if (FMT_FOUND)
list(APPEND DEPENDENCIES_INCLUDE_DIRS ${FMT_INCLUDE_DIRS})
list(APPEND DEPENDENCIES_COMPILE_OPTIONS ${FMT_CFLAGS_OTHER})
@ -150,52 +150,43 @@ endif()
if (SYSTEM_SDL2)
if (PKG_CONFIG_FOUND)
pkg_check_modules(SDL sdl>=${SYSTEM_SDL_MIN_VER})
if (SDL_FOUND)
list(APPEND DEPENDENCIES_INCLUDE_DIRS ${SDL_INCLUDE_DIRS})
list(APPEND DEPENDENCIES_COMPILE_OPTIONS ${SDL_CFLAGS_OTHER})
list(APPEND DEPENDENCIES_LIBRARIES ${SDL_LIBRARIES})
list(APPEND DEPENDENCIES_LIBRARY_DIRS ${SDL_LIBRARY_DIRS})
list(APPEND DEPENDENCIES_LINK_OPTIONS ${SDL_LDFLAGS_OTHER})
list(APPEND DEPENDENCIES_LEGACY_LDFLAGS ${SDL_LDFLAGS})
pkg_check_modules(SDL2 sdl2>=${SYSTEM_SDL_MIN_VER})
if (SDL2_FOUND)
list(APPEND DEPENDENCIES_INCLUDE_DIRS ${SDL2_INCLUDE_DIRS})
list(APPEND DEPENDENCIES_COMPILE_OPTIONS ${SDL2_CFLAGS_OTHER})
list(APPEND DEPENDENCIES_LIBRARIES ${SDL2_LIBRARIES})
list(APPEND DEPENDENCIES_LIBRARY_DIRS ${SDL2_LIBRARY_DIRS})
list(APPEND DEPENDENCIES_LINK_OPTIONS ${SDL2_LDFLAGS_OTHER})
list(APPEND DEPENDENCIES_LEGACY_LDFLAGS ${SDL2_LDFLAGS})
endif()
endif()
if (NOT SDL_FOUND)
find_package(SDL ${SYSTEM_SDL_MIN_VER})
if (SDL_FOUND)
list(APPEND DEPENDENCIES_INCLUDE_DIRS ${SDL_INCLUDE_DIR})
list(APPEND DEPENDENCIES_LIBRARIES ${SDL_LIBRARY})
else()
if (PKG_CONFIG_FOUND)
pkg_check_modules(SDL2 sdl2>=${SYSTEM_SDL_MIN_VER})
if (SDL2_FOUND)
list(APPEND DEPENDENCIES_INCLUDE_DIRS ${SDL2_INCLUDE_DIRS})
list(APPEND DEPENDENCIES_COMPILE_OPTIONS ${SDL2_CFLAGS_OTHER})
list(APPEND DEPENDENCIES_LIBRARIES ${SDL2_LIBRARIES})
list(APPEND DEPENDENCIES_LIBRARY_DIRS ${SDL2_LIBRARY_DIRS})
list(APPEND DEPENDENCIES_LINK_OPTIONS ${SDL2_LDFLAGS_OTHER})
list(APPEND DEPENDENCIES_LEGACY_LDFLAGS ${SDL2_LDFLAGS})
endif()
endif()
if (NOT SDL2_FOUND)
find_package(SDL2 ${SYSTEM_SDL_MIN_VER} REQUIRED)
list(APPEND DEPENDENCIES_INCLUDE_DIRS ${SDL2_INCLUDE_DIR})
list(APPEND DEPENDENCIES_LIBRARIES ${SDL2_LIBRARY})
endif()
endif()
if (NOT SDL2_FOUND)
find_package(SDL2 ${SYSTEM_SDL_MIN_VER} REQUIRED)
list(APPEND DEPENDENCIES_INCLUDE_DIRS ${SDL2_INCLUDE_DIR})
list(APPEND DEPENDENCIES_LIBRARIES ${SDL2_LIBRARY})
endif()
message(STATUS "Using system-installed SDL2")
else()
set(SDL_SHARED OFF)
set(SDL_STATIC ON)
set(SDL_SHARED OFF CACHE BOOL "Force no dynamically-linked SDL" FORCE)
set(SDL_STATIC ON CACHE BOOL "Force statically-linked SDL" 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.
# This should probably go in a FAQ.
set(SDL_LIBC ON CACHE BOOL "Tell SDL that we want it to use our C runtime (required for proper static linking)" FORCE)
add_subdirectory(extern/SDL EXCLUDE_FROM_ALL)
list(APPEND DEPENDENCIES_INCLUDE_DIRS extern/SDL/include)
list(APPEND DEPENDENCIES_LIBRARIES SDL2-static)
# Work around add_subdirectory'd SDL not propagating HAVE_LIBC to MSVC furnace build
if (MSVC)
list(APPEND DEPENDENCIES_COMPILE_OPTIONS "/DHAVE_LIBC")
endif()
message(STATUS "Using vendored SDL2")
endif()
set(AUDIO_SOURCES
src/audio/abstract.cpp
src/audio/midi.cpp
src/audio/sdl.cpp
)
@ -218,6 +209,7 @@ endif()
if (USE_RTMIDI)
list(APPEND AUDIO_SOURCES src/audio/rtmidi.cpp)
message(STATUS "Building with RtMidi")
list(APPEND DEPENDENCIES_DEFINES HAVE_RTMIDI)
else()
message(STATUS "Building without RtMidi")
endif()
@ -245,6 +237,7 @@ extern/adpcm/ymz_codec.c
extern/Nuked-OPN2/ym3438.c
extern/opm/opm.c
extern/Nuked-OPLL/opll.c
extern/Nuked-OPL3/opl3.c
src/engine/platform/sound/sn76496.cpp
src/engine/platform/sound/ay8910.cpp
src/engine/platform/sound/saa1099.cpp
@ -252,6 +245,10 @@ src/engine/platform/sound/gb/apu.c
src/engine/platform/sound/gb/timing.c
src/engine/platform/sound/pce_psg.cpp
src/engine/platform/sound/nes/apu.c
src/engine/platform/sound/nes/fds.c
src/engine/platform/sound/nes/mmc5.c
src/engine/platform/sound/vera_psg.c
src/engine/platform/sound/vera_pcm.c
src/engine/platform/sound/c64/sid.cc
src/engine/platform/sound/c64/voice.cc
@ -283,6 +280,18 @@ 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/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/ym2610Interface.cpp
src/engine/blip_buf.c
@ -292,6 +301,8 @@ src/engine/config.cpp
src/engine/dispatchContainer.cpp
src/engine/engine.cpp
src/engine/fileOps.cpp
src/engine/fileOpsIns.cpp
src/engine/filter.cpp
src/engine/instrument.cpp
src/engine/macroInt.cpp
src/engine/pattern.cpp
@ -300,6 +311,7 @@ src/engine/sample.cpp
src/engine/song.cpp
src/engine/sysDef.cpp
src/engine/wavetable.cpp
src/engine/waveSynth.cpp
src/engine/vgmOps.cpp
src/engine/platform/abstract.cpp
src/engine/platform/genesis.cpp
@ -308,20 +320,35 @@ src/engine/platform/sms.cpp
src/engine/platform/opll.cpp
src/engine/platform/gb.cpp
src/engine/platform/pce.cpp
src/engine/platform/mmc5.cpp
src/engine/platform/nes.cpp
src/engine/platform/c64.cpp
src/engine/platform/arcade.cpp
src/engine/platform/tx81z.cpp
src/engine/platform/ym2610.cpp
src/engine/platform/ym2610ext.cpp
src/engine/platform/ym2610b.cpp
src/engine/platform/ym2610bext.cpp
src/engine/platform/ay.cpp
src/engine/platform/ay8930.cpp
src/engine/platform/opl.cpp
src/engine/platform/fds.cpp
src/engine/platform/tia.cpp
src/engine/platform/saa.cpp
src/engine/platform/amiga.cpp
src/engine/platform/pcspkr.cpp
src/engine/platform/segapcm.cpp
src/engine/platform/qsound.cpp
src/engine/platform/dummy.cpp
src/engine/platform/x1_010.cpp
src/engine/platform/lynx.cpp
src/engine/platform/swan.cpp
src/engine/platform/vera.cpp
src/engine/platform/bubsyswsg.cpp
src/engine/platform/n163.cpp
src/engine/platform/pet.cpp
src/engine/platform/vic20.cpp
src/engine/platform/vrc6.cpp
src/engine/platform/dummy.cpp
)
if (WIN32)
@ -352,15 +379,40 @@ src/gui/font_unifont.cpp
src/gui/font_icon.cpp
src/gui/fonts.cpp
src/gui/debug.cpp
src/gui/fileDialog.cpp
src/gui/intConst.cpp
src/gui/guiConst.cpp
src/gui/about.cpp
src/gui/channels.cpp
src/gui/compatFlags.cpp
src/gui/cursor.cpp
src/gui/dataList.cpp
src/gui/debugWindow.cpp
src/gui/doAction.cpp
src/gui/editing.cpp
src/gui/editControls.cpp
src/gui/insEdit.cpp
src/gui/log.cpp
src/gui/mixer.cpp
src/gui/midiMap.cpp
src/gui/newSong.cpp
src/gui/orders.cpp
src/gui/osc.cpp
src/gui/pattern.cpp
src/gui/piano.cpp
src/gui/presets.cpp
src/gui/regView.cpp
src/gui/sampleEdit.cpp
src/gui/settings.cpp
src/gui/songInfo.cpp
src/gui/songNotes.cpp
src/gui/stats.cpp
src/gui/sysConf.cpp
src/gui/util.cpp
src/gui/waveEdit.cpp
src/gui/volMeter.cpp
src/gui/gui.cpp
)
@ -403,13 +455,20 @@ endif()
if (NOT MSVC)
set(WARNING_FLAGS -Wall -Wextra -Wno-unused-parameter)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
list(APPEND WARNING_FLAGS -Wno-cast-function-type)
endif()
if (WARNINGS_ARE_ERRORS)
list(APPEND WARNING_FLAGS -Werror)
endif()
else()
# /wd4100 == -Wno-unused-parameter
add_compile_options("/source-charset:utf-8")
set(WARNING_FLAGS /W4 /wd4100 /D_CRT_SECURE_NO_WARNINGS)
add_compile_options("/utf-8")
set(WARNING_FLAGS /W2 /D_CRT_SECURE_NO_WARNINGS)
list(APPEND WARNING_FLAGS
/wd4244 # implicit type conversions
/wd4305 # truncations
/wd4309 # truncations of constant values
)
if (WARNINGS_ARE_ERRORS)
list(APPEND WARNING_FLAGS /WX)
endif()

View File

@ -33,6 +33,23 @@ the coding style is described here:
- indent switch cases
- preprocessor directives not intended
- if macro comprises more than one line, indent
- prefer built-in types:
- `bool`
- `signed char` or `unsigned char` are 8-bit
- when the type is `char`, **always** specify whether it is signed or not.
- unspecified `char` is signed on x86 and unsigned on ARM, so yeah.
- the only situation in where unspecified `char` is allowed is for C strings (`const char*`).
- `short` or `unsigned short` are 16-bit
- `int` or `unsigned int` are 32-bit
- `float` is 32-bit
- `double` is 64-bit
- `long long int` or `unsigned long long int` are 64-bit
- avoid using 64-bit numbers as I still build for 32-bit systems.
- two `long`s are required to make Windows happy.
- `size_t` are 32-bit or 64-bit, depending on architecture.
- in float/double operations, always use decimal and `f` if single-precision.
- e.g. `1.0f` or `1.0` instead of `1`.
- don't use `auto` unless needed.
some files (particularly the ones in `src/engine/platform/sound` and `extern/`) don't follow this style.

View File

@ -2,9 +2,14 @@
![screenshot](papers/screenshot1.png)
this is a work-in-progress chiptune tracker compatible with DefleMask modules (.dmf).
this is a multi-system chiptune tracker.
[downloads](#downloads) | [discussion](#discussion) | [help](#help) | [developer info](#developer-info)
[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).
## features
@ -23,12 +28,14 @@ this is a work-in-progress chiptune tracker compatible with DefleMask modules (.
- Amiga
- TIA (Atari 2600/7800)
- multiple sound chips in a single song!
- DefleMask compatibility - loads .dmf modules, .dmp instruments and .dmw wavetables
- clean-room design (guesswork and ABX tests only, no decompilation involved)
- bug/quirk implementation for increased playback accuracy
- VGM and audio file export
- accurate emulation cores whether possible (Nuked, MAME, SameBoy, Mednafen PCE, puNES, reSID, Stella, SAASound and ymfm)
- additional features on top:
- FM macros!
- negative octaves
- arbitrary pitch samples
- sample loop points
- SSG envelopes in Neo Geo
@ -36,21 +43,28 @@ this is a work-in-progress chiptune tracker compatible with DefleMask modules (.
- ability to change tempo mid-song with `Cxxx` effect (`xxx` between `000` and `3ff`)
- open-source under GPLv2 or later.
## downloads
***
# quick references
check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage).
- **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z).
- **help**: check out the [documentation](papers/doc/README.md). it's mostly incomplete, but has details on effects.
## discussion
## unofficial packages
see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z).
[![Packaging status](https://repology.org/badge/tiny-repos/furnace.svg)](https://repology.org/project/furnace/versions)
## help
check out the [documentation](papers/doc/README.md). it's mostly incomplete, but has details on effects.
some people have provided packages for Unix/Unix-like distributions. here's a list.
- **Arch Linux**: [furnace-git is in the AUR.](https://aur.archlinux.org/packages/furnace-git) thank you Essem!
- **FreeBSD**: [a package in ports](https://www.freshports.org/audio/furnace/) is available courtesy of ehaupt.
- **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608.
- **OpenSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari.
***
# developer info
**NOTE: do not download the project's source as a .zip or .tar.gz as these do not include the project's submodules which are necessary to proceed with building.**
[![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml)
**NOTE: do not download the project's source as a .zip or .tar.gz as these do not include the project's submodules which are necessary to proceed with building. please instead use Git as shown below.**
## dependencies
@ -97,6 +111,7 @@ cd build
cmake ..
make
```
Alternatively, build scripts are provided in the `scripts/` folder in the root of the repository.
### CMake options
@ -135,6 +150,9 @@ this will play a compatible file.
this will play a compatible file and enable the commands view.
**note that these commands only actually work in Linux environments. on other command lines, such as Windows' Command Prompt, or MacOS Terminal, it may not work correctly.**
***
# notes
> how do I use Neo Geo SSG envelopes?
@ -161,6 +179,9 @@ the following effects are provided:
a lower envelope period will make the envelope run faster.
***
# frequently asked questions
> how do I use C64 absolute filter/duty?
on Instrument Editor in the C64 tab there are two options to toggle these.
@ -168,25 +189,29 @@ also provided are two effects:
- `3xxx`: set fine duty.
- `4xxx`: set fine cutoff. `xxx` range is 000-7ff.
additionally, you can change the cutoff and/or duty as a macro inside an instrument by clicking the `absolute cutoff macro` and/or `absolute duty macro` checkbox at the bottom of the instrument. (for the filter, you also need to click the checkbox that says `volume macro is cutoff macro`.)
> my song sounds very odd at a certain point
> Q: how do I use PCM on a PCM-capable system?
file a bug report. use the Issues page.
A: Two possibilities: the recommended way is via creating the "Amiga/Sample" type instrument and assigning sample to it, or via old, Deflemask-compatible method, using `17xx` effect
it's probably another playback inaccuracy.
> Q: my song sounds very odd at a certain point
> my song sounds correct, but it doesn't in DefleMask
A: file a bug report. use the Issues page. it's probably another playback inaccuracy.
file a bug report **here**. it still is a playback inaccuracy.
> Q: my song sounds correct, but it doesn't in DefleMask
> my C64 song sounds terrible after saving as .dmf!
A: file a bug report **here**. it still is a playback inaccuracy.
that's a limitation of the DefleMask format. save in Furnace song format instead (.fur).
> Q: my C64 song sounds terrible after saving as .dmf!
> how do I solo channels?
A: that's a limitation of the DefleMask format. save in Furnace song format instead (.fur).
right click on the channel name.
> Q: how do I solo channels?
A: right click on the channel name.
***
# footnotes
copyright (C) 2021-2022 tildearrow and contributors.

BIN
demos/Another_winter.fur Normal file

Binary file not shown.

BIN
demos/Coconut_Mall.fur Normal file

Binary file not shown.

BIN
demos/silverlining.fur Normal file

Binary file not shown.

BIN
demos/yky.fur Normal file

Binary file not shown.

1
extern/Nuked-OPL3 vendored Submodule

@ -0,0 +1 @@
Subproject commit bb5c8d08a85779c42b75c79d7b84f365a1b93b66

View File

@ -290,6 +290,28 @@ static void OPLL_DoModeWrite(opll_t *chip) {
}
}
const opll_patch_t* OPLL_GetPatchROM(uint32_t chip_type) {
switch (chip_type) {
case opll_type_ds1001:
return patch_ds1001;
break;
case opll_type_ymf281:
case opll_type_ymf281b:
return patch_ymf281;
break;
case opll_type_ym2423:
return patch_ym2423;
break;
case opll_type_ym2413:
case opll_type_ym2413b:
case opll_type_ym2420:
default:
return patch_ym2413;
break;
}
return patch_ym2413;
}
void OPLL_Reset(opll_t *chip, uint32_t chip_type) {
uint32_t i;
memset(chip, 0, sizeof(opll_t));

View File

@ -193,6 +193,8 @@ typedef struct {
} opll_t;
const opll_patch_t* OPLL_GetPatchROM(uint32_t chip_type);
void OPLL_Reset(opll_t *chip, uint32_t chip_type);
void OPLL_Clock(opll_t *chip, int32_t *buffer);
void OPLL_Write(opll_t *chip, uint32_t port, uint8_t data);

View File

@ -3877,6 +3877,7 @@ namespace IGFD
static ImGuiSelectableFlags selectableFlags = ImGuiSelectableFlags_AllowDoubleClick |
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SpanAvailWidth;
// TODO BUG?!
va_list args;
va_start(args, vFmt);
vsnprintf(fdi.puVariadicBuffer, MAX_FILE_DIALOG_NAME_BUFFER, vFmt, args);
@ -4074,6 +4075,7 @@ namespace IGFD
if (ImGui::TableNextColumn()) // file name
{
// TODO BUG?!?!?!
needToBreakTheloop = prSelectableItem(i, infos, selected, _str.c_str());
if (needToBreakTheloop==2) escape=true;
}

View File

@ -26,6 +26,7 @@
#include "imgui.h"
#include "imgui_impl_sdlrenderer.h"
#include <SDL_render.h>
#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
#include <stddef.h> // intptr_t
#else
@ -184,6 +185,7 @@ void ImGui_ImplSDLRenderer_RenderDrawData(ImDrawData* draw_data)
// Bind texture, Draw
SDL_Texture* tex = (SDL_Texture*)pcmd->GetTexID();
SDL_SetTextureScaleMode(tex, SDL_ScaleModeBest); // ???
SDL_RenderGeometryRaw(bd->SDLRenderer, tex,
xy, (int)sizeof(ImDrawVert),
color, (int)sizeof(ImDrawVert),

4
extern/pfd-fixed/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
CMakeCache.txt
CMakeFiles
Makefile
cmake_install.cmake

5
extern/pfd-fixed/.lgtm.yml vendored Normal file
View File

@ -0,0 +1,5 @@
extraction:
cpp:
index:
build_command:
- make -C examples

6
extern/pfd-fixed/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.1.0)
project(portable_file_dialogs VERSION 1.00 LANGUAGES CXX)
add_library(${PROJECT_NAME} INTERFACE)
target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

14
extern/pfd-fixed/COPYING vendored Normal file
View File

@ -0,0 +1,14 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

64
extern/pfd-fixed/README.md vendored Normal file
View File

@ -0,0 +1,64 @@
# Portable File Dialogs
A free C++11 file dialog library.
- works on Windows, Mac OS X, Linux
- **single-header**, no extra library dependencies
- **synchronous *or* asynchronous** (does not block the rest of your program!)
- **cancelable** (kill asynchronous dialogues without user interaction)
- **secure** (immune to shell-quote vulnerabilities)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/a25d3fd6959a4333871f630ac70b6e09)](https://www.codacy.com/manual/samhocevar/portable-file-dialogs?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=samhocevar/portable-file-dialogs&amp;utm_campaign=Badge_Grade)
## Status
The library is now pretty robust. It is not as feature-complete as
[Tiny File Dialogs](https://sourceforge.net/projects/tinyfiledialogs/),
but has asynchonous dialogs, more maintainable code, and fewer potential
security issues.
The currently available backends are:
- Win32 API (all known versions of Windows)
- Mac OS X (using AppleScript)
- GNOME desktop (using [Zenity](https://en.wikipedia.org/wiki/Zenity) or its clones Matedialog and Qarma)
- KDE desktop (using [KDialog](https://github.com/KDE/kdialog))
Experimental support for Emscripten is on its way.
## Documentation
- [`pfd`](doc/pfd.md) general documentation
- [`pfd::message`](doc/message.md) message box
- [`pfd::notify`](doc/notify.md) notification
- [`pfd::open_file`](doc/open_file.md) file open
- [`pfd::save_file`](doc/save_file.md) file save
- [`pfd::select_folder`](doc/select_folder.md) folder selection
## History
- 0.1.0 (July 16, 2020): first public release
## Screenshots (Windows 10)
![warning-win32](https://user-images.githubusercontent.com/245089/47136607-76919a00-d2b4-11e8-8f42-e2d62c4f9570.png)
![notify-win32](https://user-images.githubusercontent.com/245089/47142453-2ff76c00-d2c3-11e8-871a-1a110ac91eb2.png)
![open-win32](https://user-images.githubusercontent.com/245089/47155865-0f8cd900-d2e6-11e8-8041-1e20b6f77dee.png)
## Screenshots (Mac OS X, dark theme)
![warning-osxdark](https://user-images.githubusercontent.com/245089/56053001-22dba700-5d53-11e9-8233-ca7a2c58188d.png)
![notify-osxdark](https://user-images.githubusercontent.com/245089/56053188-bc0abd80-5d53-11e9-8298-68aa96315c6c.png)
![open-osxdark](https://user-images.githubusercontent.com/245089/56053378-39363280-5d54-11e9-9583-9f1c978fa0db.png)
## Screenshots (Linux, GNOME desktop)
![warning-gnome](https://user-images.githubusercontent.com/245089/47136608-772a3080-d2b4-11e8-9e1d-60a7e743e908.png)
![notify-gnome](https://user-images.githubusercontent.com/245089/47142455-30900280-d2c3-11e8-8b76-ea16c7e502d4.png)
![open-gnome](https://user-images.githubusercontent.com/245089/47155867-0f8cd900-d2e6-11e8-93af-275636491ec4.png)
## Screenshots (Linux, KDE Plasma desktop)
![warning-kde](https://user-images.githubusercontent.com/245089/47149255-4dcccd00-d2d3-11e8-84c9-f85612784680.png)
![notify-kde](https://user-images.githubusercontent.com/245089/47149206-27a72d00-d2d3-11e8-8f1b-96e462f08c2b.png)
![open-kde](https://user-images.githubusercontent.com/245089/47155866-0f8cd900-d2e6-11e8-8006-f14b948afc55.png)

97
extern/pfd-fixed/doc/message.md vendored Normal file
View File

@ -0,0 +1,97 @@
## Message Box API
Displaying a message box is done using the `pfd::message` class. It can be provided a title, a
message text, a `choice` representing which buttons need to be rendered, and an `icon` for the
message:
```cpp
pfd::message::message(std::string const &title,
std::string const &text,
pfd::choice choice = pfd::choice::ok_cancel,
pfd::icon icon = pfd::icon::info);
enum class pfd::choice { ok, ok_cancel, yes_no, yes_no_cancel };
enum class pfd::icon { info, warning, error, question };
```
The pressed button is queried using `pfd::message::result()`. If the dialog box is closed by any
other means, the `pfd::button::cancel` is assumed:
```cpp
pfd::button pfd::message::result();
enum class pfd::button { ok, cancel, yes, no };
```
It is possible to ask the dialog box whether the user took action using the `pfd::message::ready()`
method, with an optional `timeout` argument. If the user did not press a button within `timeout`
milliseconds, the function will return `false`:
```cpp
bool pfd::message::ready(int timeout = pfd::default_wait_timeout);
```
## Example 1: simple notification
The `pfd::message` destructor waits for user action, so this operation will block until the user
closes the message box:
```cpp
pfd::message("Problem", "An error occurred while doing things",
pfd::choice::ok, pfd::icon::error);
```
## Example 2: retrieving the pressed button
Using `pfd::message::result()` will also wait for user action before returning. This operation will block and return the user choice:
```cpp
// Ask for user opinion
auto button = pfd::message("Action requested", "Do you want to proceed with things?",
pfd::choice::yes_no, pfd::icon::question).result();
// Do something with button…
```
## Example 3: asynchronous message box
Using `pfd::message::ready()` allows the application to perform other tasks while waiting for
user input:
```cpp
// Message box with nice message
auto box = pfd::message("Unsaved Files", "Do you want to save the current "
"document before closing the application?",
pfd::choice::yes_no_cancel,
pfd::icon::warning);
// Do something while waiting for user input
while (!box.ready(1000))
std::cout << "Waited 1 second for user input...\n";
// Act depending on the selected button
switch (box.result())
{
case pfd::button::yes: std::cout << "User agreed.\n"; break;
case pfd::button::no: std::cout << "User disagreed.\n"; break;
case pfd::button::cancel: std::cout << "User freaked out.\n"; break;
}
```
## Screenshots
#### Windows 10
![warning-win32](https://user-images.githubusercontent.com/245089/47136607-76919a00-d2b4-11e8-8f42-e2d62c4f9570.png)
#### Mac OS X
![warning-osx-dark](https://user-images.githubusercontent.com/245089/56053001-22dba700-5d53-11e9-8233-ca7a2c58188d.png) ![warning-osx-light](https://user-images.githubusercontent.com/245089/56053055-49014700-5d53-11e9-8306-e9a03a25e044.png)
#### Linux (GNOME desktop)
![warning-gnome](https://user-images.githubusercontent.com/245089/47140824-8662ab80-d2bf-11e8-9c87-2742dd5b58af.png)
#### Linux (KDE desktop)
![warning-kde](https://user-images.githubusercontent.com/245089/47149255-4dcccd00-d2d3-11e8-84c9-f85612784680.png)

40
extern/pfd-fixed/doc/notify.md vendored Normal file
View File

@ -0,0 +1,40 @@
## Notification API
Displaying a desktop notification is done using the `pfd::notify` class. It can be provided a
title, a message text, and an `icon` for the notification style:
```cpp
pfd::notify::notify(std::string const &title,
std::string const &text,
pfd::icon icon = pfd::icon::info);
enum class pfd::icon { info, warning, error };
```
## Example
Displaying a notification is straightforward. Emoji are supported:
```cpp
pfd::notify("System event", "Something might be on fire 🔥",
pfd::icon::warning);
```
The `pfd::notify` object needs not be kept around, letting the object clean up itself is enough.
## Screenshots
Windows 10:
![notify-win32](https://user-images.githubusercontent.com/245089/47142453-2ff76c00-d2c3-11e8-871a-1a110ac91eb2.png)
Mac OS X (dark theme):
![image](https://user-images.githubusercontent.com/245089/56053188-bc0abd80-5d53-11e9-8298-68aa96315c6c.png)
Mac OS X (light theme):
![image](https://user-images.githubusercontent.com/245089/56053137-92ea2d00-5d53-11e9-8cf2-049486c45713.png)
Linux (GNOME desktop):
![notify-gnome](https://user-images.githubusercontent.com/245089/47142455-30900280-d2c3-11e8-8b76-ea16c7e502d4.png)
Linux (KDE desktop):
![notify-kde](https://user-images.githubusercontent.com/245089/47149206-27a72d00-d2d3-11e8-8f1b-96e462f08c2b.png)

90
extern/pfd-fixed/doc/open_file.md vendored Normal file
View File

@ -0,0 +1,90 @@
## File Open API
The `pfd::open_file` class handles file opening dialogs. It can be provided a title, a starting
directory and/or pre-selected file, an optional filter for recognised file types, and an optional
flag to allow multiple selection:
```cpp
pfd::open_file::open_file(std::string const &title,
std::string const &initial_path,
std::vector<std::string> filters = { "All Files", "*" },
pfd::opt option = pfd::opt::none);
```
The `option` parameter can be `pfd::opt::multiselect` to allow selecting multiple files.
The selected files are queried using `pfd::open_file::result()`. If the user canceled the
operation, the returned list is empty:
```cpp
std::vector<std::string> pfd::open_file::result();
```
It is possible to ask the file open dialog whether the user took action using the
`pfd::message::ready()` method, with an optional `timeout` argument. If the user did not validate
the dialog within `timeout` milliseconds, the function will return `false`:
```cpp
bool pfd::open_file::ready(int timeout = pfd::default_wait_timeout);
```
## Example 1: simple file selection
Using `pfd::open_file::result()` will wait for user action before returning. This operation will
block and return the user choice:
```cpp
auto selection = pfd::open_file("Select a file").result();
if (!selection.empty())
std::cout << "User selected file " << selection[0] << "\n";
```
## Example 2: filters
The filter list enumerates filter names and corresponded space-separated wildcard lists. It
defaults to `{ "All Files", "*" }`, but here is how to use other options:
```cpp
auto selection = pfd::open_file("Select a file", ".",
{ "Image Files", "*.png *.jpg *.jpeg *.bmp",
"Audio Files", "*.wav *.mp3",
"All Files", "*" },
pfd::opt::multiselect).result();
// Do something with selection
for (auto const &filename : dialog.result())
std::cout << "Selected file: " << filename << "\n";
```
## Example 3: asynchronous file open
Using `pfd::open_file::ready()` allows the application to perform other tasks while waiting for
user input:
```cpp
// File open dialog
auto dialog = pfd::open_file("Select file to open");
// Do something while waiting for user input
while (!dialog.ready(1000))
std::cout << "Waited 1 second for user input...\n";
// Act depending on the user choice
std::cout << "Number of selected files: " << dialog.result().size() << "\n";
```
## Screenshots
Windows 10:
![open-win32](https://user-images.githubusercontent.com/245089/47155865-0f8cd900-d2e6-11e8-8041-1e20b6f77dee.png)
Mac OS X (dark theme):
![image](https://user-images.githubusercontent.com/245089/56053378-39363280-5d54-11e9-9583-9f1c978fa0db.png)
Mac OS X (light theme):
![image](https://user-images.githubusercontent.com/245089/56053413-4fdc8980-5d54-11e9-85e3-e9e5d0e10772.png)
Linux (GNOME desktop):
![open-gnome](https://user-images.githubusercontent.com/245089/47155867-0f8cd900-d2e6-11e8-93af-275636491ec4.png)
Linux (KDE desktop):
![open-kde](https://user-images.githubusercontent.com/245089/47155866-0f8cd900-d2e6-11e8-8006-f14b948afc55.png)

120
extern/pfd-fixed/doc/pfd.md vendored Normal file
View File

@ -0,0 +1,120 @@
## Portable File Dialogs documentation
The library can be used either as a [header-only library](https://en.wikipedia.org/wiki/Header-only),
or as a [single file library](https://github.com/nothings/single_file_libs).
### Use as header-only library
Just include the main header file wherever needed:
```cpp
#include "portable-file-dialogs.h"
/* ... */
pfd::message::message("Hello", "This is a test");
/* ... */
```
### Use as a single-file library
Defining the `PFD_SKIP_IMPLEMENTATION` macro before including `portable-file-dialogs.h` will
skip all the implementation code and reduce compilation times. You still need to include the
header without the macro at least once, typically in a `pfd-impl.cpp` file.
```cpp
// In pfd-impl.cpp
#include "portable-file-dialogs.h"
```
```cpp
// In all other files
#define PFD_SKIP_IMPLEMENTATION 1
#include "portable-file-dialogs.h"
```
### General concepts
Dialogs inherit from `pfd::dialog` and are created by calling their class constructor. Their
destructor will block until the window is closed by user interaction. So for instance this
will block until the end of the line:
```cpp
pfd::message::message("Hi", "there");
```
Whereas this will only block until the end of the scope, allowing the program to perform
additional operations while the dialog is open:
```cpp
{
auto m = pfd::message::message("Hi", "there");
// ... perform asynchronous operations here
}
```
It is possible to call `bool pfd::dialog::ready(timeout)` on the dialog in order to query its
status and perform asynchronous operations as long as the user has not interacted:
```cpp
{
auto m = pfd::message::message("Hi", "there");
while (!m.ready())
{
// ... perform asynchronous operations here
}
}
```
If necessary, a dialog can be forcibly closed using `bool pfd::dialog::kill()`. Note that this
may be confusing to the user and should only be used in very specific situations. It is also not
possible to close a Windows message box that provides no _Cancel_ button.
```cpp
{
auto m = pfd::message::message("Hi", "there");
while (!m.ready())
{
// ... perform asynchronous operations here
if (too_much_time_has_passed())
m.kill();
}
}
```
Finally, the user response can be retrieved using `pfd::dialog::result()`. The return value of
this function depends on which dialog is being used. See their respective documentation for more
information:
* [`pfd::message`](message.md) (message box)
* [`pfd::notify`](notify.md) (notification)
* [`pfd::open_file`](open_file.md) (file open)
* [`pfd::save_file`](save_file.md) (file save)
* [`pfd::select_folder`](select_folder.md) (folder selection)
### Settings
The library can be queried and configured through the `pfd::settings` class.
```cpp
bool pfd::settings::available();
void pfd::settings::verbose(bool value);
void pfd::settings::rescan();
```
The return value of `pfd::settings::available()` indicates whether a suitable dialog backend (such
as Zenity or KDialog on Linux) has been found. If not, the library will not work and all dialog
invocations will be no-ops. The program will not crash but you should account for this situation
and add a fallback mechanism or exit gracefully.
Calling `pfd::settings::rescan()` will force a rescan of available backends. This may change the
result of `pfd::settings::available()` if a backend was installed on the system in the meantime.
This is probably only useful for debugging purposes.
Calling `pfd::settings::verbose(true)` may help debug the library. It will output debug information
to `std::cout` about some operations being performed.

73
extern/pfd-fixed/doc/save_file.md vendored Normal file
View File

@ -0,0 +1,73 @@
## File Open API
The `pfd::save_file` class handles file saving dialogs. It can be provided a title, a starting
directory and/or pre-selected file, an optional filter for recognised file types, and an optional
flag to allow multiple selection:
```cpp
pfd::save_file::save_file(std::string const &title,
std::string const &initial_path,
std::vector<std::string> filters = { "All Files", "*" },
pfd::opt option = pfd::opt::none);
```
The `option` parameter can be `pfd::opt::force_overwrite` to disable a potential warning when
saving to an existing file.
The selected file is queried using `pfd::save_file::result()`. If the user canceled the
operation, the returned file name is empty:
```cpp
std::string pfd::save_file::result();
```
It is possible to ask the file save dialog whether the user took action using the
`pfd::message::ready()` method, with an optional `timeout` argument. If the user did not validate
the dialog within `timeout` milliseconds, the function will return `false`:
```cpp
bool pfd::save_file::ready(int timeout = pfd::default_wait_timeout);
```
## Example 1: simple file selection
Using `pfd::save_file::result()` will wait for user action before returning. This operation will
block and return the user choice:
```cpp
auto destination = pfd::save_file("Select a file").result();
if (!destination.empty())
std::cout << "User selected file " << destination << "\n";
```
## Example 2: filters
The filter list enumerates filter names and corresponded space-separated wildcard lists. It
defaults to `{ "All Files", "*" }`, but here is how to use other options:
```cpp
auto destination = pfd::save_file("Select a file", ".",
{ "Image Files", "*.png *.jpg *.jpeg *.bmp",
"Audio Files", "*.wav *.mp3",
"All Files", "*" },
pfd::opt::force_overwrite).result();
// Do something with destination
std::cout << "Selected file: " << destination << "\n";
```
## Example 3: asynchronous file save
Using `pfd::save_file::ready()` allows the application to perform other tasks while waiting for
user input:
```cpp
// File save dialog
auto dialog = pfd::save_file("Select file to save");
// Do something while waiting for user input
while (!dialog.ready(1000))
std::cout << "Waited 1 second for user input...\n";
// Act depending on the user choice
std::cout << "User selected file: " << dialog.result() << "\n";
```

55
extern/pfd-fixed/doc/select_folder.md vendored Normal file
View File

@ -0,0 +1,55 @@
## Folder Selection API
The `pfd::select_folder` class handles folder opening dialogs. It can be provided a title, and an
optional starting directory:
```cpp
pfd::select_folder::select_folder(std::string const &title,
std::string const &default_path = "",
pfd::opt option = pfd::opt::none);
```
The `option` parameter can be `pfd::opt::force_path` to force the operating system to use the
provided path. Some systems default to the most recently used path, if applicable.
The selected folder is queried using `pfd::select_folder::result()`. If the user canceled the
operation, the returned string is empty:
```cpp
std::string pfd::select_folder::result();
```
It is possible to ask the folder selection dialog whether the user took action using the
`pfd::message::ready()` method, with an optional `timeout` argument. If the user did not validate
the dialog within `timeout` milliseconds, the function will return `false`:
```cpp
bool pfd::select_folder::ready(int timeout = pfd::default_wait_timeout);
```
## Example 1: simple folder selection
Using `pfd::select_folder::result()` will wait for user action before returning. This operation
will block and return the user choice:
```cpp
auto selection = pfd::select_folder("Select a folder").result();
if (!selection.empty())
std::cout << "User selected folder " << selection << "\n";
```
## Example 2: asynchronous folder open
Using `pfd::select_folder::ready()` allows the application to perform other tasks while waiting for user input:
```cpp
// Folder selection dialog
auto dialog = pfd::select_folder("Select folder to open");
// Do something while waiting for user input
while (!dialog.ready(1000))
std::cout << "Waited 1 second for user input...\n";
// Act depending on the user choice
std::cout << "Selected folder: " << dialog.result() << "\n";
```

11
extern/pfd-fixed/examples/.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
example
example.exe
kill
kill.exe
Debug
Release
*.vcxproj.user
.idea
cmake-build-*

110
extern/pfd-fixed/examples/example.cpp vendored Normal file
View File

@ -0,0 +1,110 @@
//
// Portable File Dialogs
//
// Copyright © 2018—2020 Sam Hocevar <sam@hocevar.net>
//
// This program is free software. It comes without any warranty, to
// the extent permitted by applicable law. You can redistribute it
// and/or modify it under the terms of the Do What the Fuck You Want
// to Public License, Version 2, as published by the WTFPL Task Force.
// See http://www.wtfpl.net/ for more details.
//
#include "portable-file-dialogs.h"
#include <iostream>
#if _WIN32
#define DEFAULT_PATH "C:\\"
#else
#define DEFAULT_PATH "/tmp"
#endif
int main()
{
// Check that a backend is available
if (!pfd::settings::available())
{
std::cout << "Portable File Dialogs are not available on this platform.\n";
return 1;
}
// Set verbosity to true
pfd::settings::verbose(true);
// Notification
pfd::notify("Important Notification",
"This is ' a message, pay \" attention \\ to it!",
pfd::icon::info);
// Message box with nice message
auto m = pfd::message("Personal Message",
"You are an amazing person, dont let anyone make you think otherwise.",
pfd::choice::yes_no_cancel,
pfd::icon::warning);
// Optional: do something while waiting for user action
for (int i = 0; i < 10 && !m.ready(1000); ++i)
std::cout << "Waited 1 second for user input...\n";
// Do something according to the selected button
switch (m.result())
{
case pfd::button::yes: std::cout << "User agreed.\n"; break;
case pfd::button::no: std::cout << "User disagreed.\n"; break;
case pfd::button::cancel: std::cout << "User freaked out.\n"; break;
default: break; // Should not happen
}
// Directory selection
auto dir = pfd::select_folder("Select any directory", DEFAULT_PATH).result();
std::cout << "Selected dir: " << dir << "\n";
// File open
auto f = pfd::open_file("Choose files to read", DEFAULT_PATH,
{ "Text Files (.txt .text)", "*.txt *.text",
"All Files", "*" },
pfd::opt::multiselect);
std::cout << "Selected files:";
for (auto const &name : f.result())
std::cout << " " + name;
std::cout << "\n";
}
// Unused function that just tests the whole API
void api()
{
// pfd::settings
pfd::settings::verbose(true);
pfd::settings::rescan();
// pfd::notify
pfd::notify("", "");
pfd::notify("", "", pfd::icon::info);
pfd::notify("", "", pfd::icon::warning);
pfd::notify("", "", pfd::icon::error);
pfd::notify("", "", pfd::icon::question);
pfd::notify a("", "");
(void)a.ready();
(void)a.ready(42);
// pfd::message
pfd::message("", "");
pfd::message("", "", pfd::choice::ok);
pfd::message("", "", pfd::choice::ok_cancel);
pfd::message("", "", pfd::choice::yes_no);
pfd::message("", "", pfd::choice::yes_no_cancel);
pfd::message("", "", pfd::choice::retry_cancel);
pfd::message("", "", pfd::choice::abort_retry_ignore);
pfd::message("", "", pfd::choice::ok, pfd::icon::info);
pfd::message("", "", pfd::choice::ok, pfd::icon::warning);
pfd::message("", "", pfd::choice::ok, pfd::icon::error);
pfd::message("", "", pfd::choice::ok, pfd::icon::question);
pfd::message b("", "");
(void)b.ready();
(void)b.ready(42);
(void)b.result();
}

View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="example.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="../portable-file-dialogs.h" />
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{10F4364D-27C4-4C74-8079-7C42971E81E7}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>example</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..</AdditionalIncludeDirectories>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

42
extern/pfd-fixed/examples/kill.cpp vendored Normal file
View File

@ -0,0 +1,42 @@
//
// Portable File Dialogs
//
// Copyright © 2018—2020 Sam Hocevar <sam@hocevar.net>
//
// This program is free software. It comes without any warranty, to
// the extent permitted by applicable law. You can redistribute it
// and/or modify it under the terms of the Do What the Fuck You Want
// to Public License, Version 2, as published by the WTFPL Task Force.
// See http://www.wtfpl.net/ for more details.
//
#include "portable-file-dialogs.h"
#include <cstdio>
int main()
{
// Set verbosity to true
pfd::settings::verbose(true);
// Message box with nice message
auto m = pfd::message("Upgrade software?",
"Press OK to upgrade this software.\n"
"\n"
"By default, the software will update itself\n"
"automatically in 10 seconds.",
pfd::choice::ok_cancel,
pfd::icon::warning);
// Wait for an answer for up to 10 seconds
for (int i = 0; i < 10 && !m.ready(1000); ++i)
;
// Upgrade software if user clicked OK, or if user didnt interact
bool upgrade = m.ready() ? m.result() == pfd::button::ok : m.kill();
if (upgrade)
std::cout << "Upgrading software!\n";
else
std::cout << "Not upgrading software.\n";
}

96
extern/pfd-fixed/examples/kill.vcxproj vendored Normal file
View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="kill.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="../portable-file-dialogs.h" />
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{B94D26B1-7EF7-43A2-A973-9A96A08E2E17}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>kill</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..</AdditionalIncludeDirectories>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

1733
extern/pfd-fixed/portable-file-dialogs.h vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -80,4 +80,4 @@ TODO: image
sliders are used for controlling values in a quick manner by being dragged.
alternatively, Ctrl-clicking a slider (Command-click on macOS) will turn it into a number input field for a short period of time, allowing you to input fine values.
alternatively, right-clicking or Ctrl-clicking or a slider (Command-click on macOS) will turn it into a number input field for a short period of time, allowing you to input fine values.

View File

@ -10,6 +10,8 @@ however, effects are continuous, which means you only need to type it once and t
- a note must be present for this effect to work.
- `04xy`: vibrato. `x` is the speed, while `y` is the depth.
- maximum vibrato depth is ±1 semitone.
- `07xy`: tremolo. `x` is the speed, while `y` is the depth.
- maximum tremolo depth is -60 volume steps.
- `08xy`: set panning. `x` is the left channel and `y` is the right one.
- not all systems support this effect.
- `09xx`: set speed 1.
@ -22,6 +24,9 @@ however, effects are continuous, which means you only need to type it once and t
- `0Dxx`: jump to next pattern.
- `0Fxx`: set speed 2.
- `9xxx`: set sample position to `xxx`\*0x100.
- not all systems support this effect.
- `Cxxx`: change song Hz.
- `xxx` may be from `000` to `3ff`.
@ -42,10 +47,20 @@ however, effects are continuous, which means you only need to type it once and t
- `ECxx`: note off after `xx` ticks.
- `EDxx`: delay note by `xx` ticks.
- `EExx`: send external command.
- currently not used, but this eventually will allow you to do special things after I add VGM export.
- this effect is currently incomplete.
- `EFxx`: add or subtract global pitch.
- this effect is rather weird. use with caution.
- `80` is center.
- `F0xx`: change song Hz by BPM value.
- `F1xx`: single tick slide up.
- `F2xx`: single tick slide down.
- `F3xx`: fine volume slide up (64x slower than `0Axy`).
- `F4xx`: fine volume slide down (64x slower than `0Axy`).
- `F8xx`: single tick volume slide up.
- `F9xx`: single tick volume slide down.
- `FAxy`: fast volume slide (4x faster than `0Axy`).
- if `x` is 0 then this is a slide down.
- if `y` is 0 then this is a slide up.
- `FFxx`: end of song/stop playback.
additionally each system has its own effects. [click here for more details](../7-systems/README.md).

View File

@ -10,19 +10,25 @@ double-click to open the instrument editor.
every instrument can be renamed and have its type changed.
depending on the instrument type, there are currently 10 different types of an instrument editor:
depending on the instrument type, there are currently 13 different types of an instrument editor:
- [FM synthesis](fm.md) - for use with YM2612, YM2151 and FM block portion of YM2610.
- [Standard](standard.md) - for use with NES and Sega Master System's PSG sound source and its derivatives.
- [Game Boy](game-boy.md) - for use with Game Boy APU.
- [PC Engine/TurboGrafx-16](pce.md) - for use with PC Engine's wavetable synthesizer.
- [WonderSwan](wonderswan.md) - for use with WonderSwan's wavetable synthesizer.
- [AY8930](8930.md) - for use with Microchip AY8930 E-PSG sound source.
- [Commodore 64](c64.md) - for use with Commodore 64 SID.
- [SAA1099](saa.md) - for use with Philips SAA1099 PSG sound source.
- [TIA](tia.md) - for use with Atari 2600 system.
- [AY-3-8910](ay8910.md) - for use with AY-3-8910 PSG sound source and SSG portion in YM2610.
- [Amiga/sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM and PC Engine's sample playback mode.
- [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console.
- [Amiga/sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM, X1-010 and PC Engine's sample playback mode.
- [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console.
- [VERA](vera.md) - for use with Commander X16 VERA.
- [Seta/Allumer X1-010](x1_010.md) - for use with Wavetable portion in Seta/Allumer X1-010.
- [Konami SCC/Bubble System WSG](scc.md) - for use with Konami SCC and Wavetable portion in Bubble System's sound hardware.
- [Namco 163](n163.md) - for use with Namco 163.
- [Konami VRC6](vrc6.md) - for use with VRC6's PSG sound source.
# macros

View File

@ -0,0 +1,23 @@
# Namco 163 instrument editor
Namco 163 instrument editor consists of two tabs: one controlling various parameters for waveform initialize and macro tab containing 10 macros.
## N163
- [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.
- [Load waveform before playback] - Determines the load initial waveform into RAM before playback.
- [Update waveforms into RAM when every waveform changes] - Determines the update every different waveform changes in playback.
## Macros
- [Volume] - volume levels sequence
- [Arpeggio]- pitch sequence
- [Waveform pos.] - sets the waveform source address in RAM for playback (single nibble unit)
- [Waveform] - sets waveform source for playback immediately or update later
- [Waveform len.] - sets the waveform source length for playback (4 nibble unit)
- [Waveform update] - sets the waveform update trigger behavior for playback
- [Waveform to load] - sets waveform source for load to RAM immediately or later
- [Wave pos. to load] - sets address of waveform for load to RAM (single nibble unit)
- [Wave len. to load] - sets length of waveform for load to RAM (4 nibble unit)
- [Waveform load] - sets the waveform load trigger behavior

View File

@ -3,5 +3,5 @@
PCE instrument editor consists of only three macros, almost like TIA:
- [Volume] - volume sequence
- [Arpeggio] - pitch sequencr
- [Arpeggio] - pitch sequence
- [Waveform] - spicifies wavetables sequence

View File

@ -0,0 +1,7 @@
# Konami SCC/Bubble System WSG instrument editor
SCC/Bubble System WSG instrument editor consists of only three macros:
- [Volume] - volume sequence
- [Arpeggio] - pitch sequence
- [Waveform] - spicifies wavetables sequence

View File

@ -0,0 +1,8 @@
# VERA instrument editor
VERA instrument editor consists of only four macros:
- [Volume] - volume sequence
- [Arpeggio] - pitch sequence
- [Duty cycle] - pulse duty cycle sequence
- [Waveform] - select the waveform used by instrument

View File

@ -0,0 +1,7 @@
# VRC6 instrument editor
VRC6 instrument editor consists of only three macros:
- [Volume] - volume sequence
- [Arpeggio] - pitch sequence
- [Duty cycle] - specifies duty cycle for pulse wave channels

View File

@ -0,0 +1,8 @@
# WonderSwan instrument editor
WS instrument editor consists of only four macros, similar to PCE but with different volume and noise range:
- [Volume] - volume sequence
- [Arpeggio] - pitch sequencr
- [Noise] - noise LFSR tap sequence
- [Waveform] - spicifies wavetables sequence

View File

@ -0,0 +1,11 @@
# X1-010 instrument editor
X1-010 instrument editor consists of 7 macros.
- [Volume] - volume levels sequence
- [Arpeggio]- pitch sequence
- [Waveform] - spicifies wavetables sequence
- [Envelope Mode] - allows shaping an envelope
- [Envelope] - spicifies envelope shape sequence, it's also wavetable.
- [Auto envelope numerator] - sets the envelope to the channel's frequency multiplied by numerator
- [Auto envelope denominator] - ets the envelope to the channel's frequency multiplied by denominator

View File

@ -1,5 +1,5 @@
# wavetable editor
Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.5, wavetable editor affects PC Engine and channel 3 of Game Boy.
Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy.
Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: both Game Boy and PCE can handle max 32 byte waveforms as of now, width 16-level height for GB and 32-level height for PCE. If larger wave will be defined for these two systems, it will be squashed to fit in the constrains of the system.
Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS, Bubble System and N163, and 32-level height for PCE. If larger wave will be defined for these systems, it will be squashed to fit within the constraints of the system.

View File

@ -12,7 +12,8 @@ As of Furnace 0.5.5, the following sound chips have sample support:
- PC Engine/TurboGrafx 16/Huc6280 (same conditions as above)
- Amiga/Paula (on all channels AND resamplable, but you need to make an instrument with the Amiga format and tie it to a sample first)
- Arcade/SEGA PCM (same as above but you don't need to make an instrument for it and you have to use the `20xx` effect command to resample your samples)
- Neo Geo/Neo Geo EXT-Ch2 (on the last 5 channels only and can be resampled the same way as above)
- Neo Geo/Neo Geo EXT-Ch2 (on the last 7 channels only and can be resampled the same way as above)
- Seta/Allumer X1-010 (same as above, and both `1701` and `20xx` effect commands are affected on all 16 channels)
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.

View File

@ -4,18 +4,33 @@ this is a list of systems that Furnace supports, including each system's effects
- [Sega Genesis/Mega Drive](genesis.md)
- [Sega Master System](sms.md)
- [Yamaha OPLL](opll.md)
- [Game Boy](game-boy.md)
- [PC Engine/TurboGrafx-16](pce.md)
- [NES](nes.md)
- [Commodore 64](c64.md)
- [Arcade (YM2151/PCM)](arcade.md)
- [Neo Geo/YM2610](ym2610.md)
- [Taito Arcade/YM2610B](ym2610b.md)
- [AY-3-8910](ay8910.md)
- [Amiga](amiga.md)
- [Yamaha YM2612 standalone](ym2612.md)
- [Yamaha YM2151 standalone](ym2151.md)
- [SegaPCM](segapcm.md)
- [Atari 2600](tia.md)
- [Philips SAA1099](saa1099.md)
- [Microchip AY8930](ay8930.md)
- [VERA](vera.md)
- [Seta/Allumer X1-010](x1-010.md)
- [WonderSwan](wonderswan.md)
- [Bubble System WSG](bubblesystem.md)
- [Namco 163](n163.md)
- [Yamaha OPL](opl.md)
- [PC Speaker](pcspkr.md)
- [Commodore PET](pet.md)
- [Commodore VIC-20](vic20.md)
- [Konami VRC6](vrc6.md)
- [Famicom Disk System](fds.md)
- [Nintendo MMC5](mmc5.md)
Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but does not emulate the chip at all.
Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but...

View File

@ -6,4 +6,8 @@ in this very computer music trackers were born...
# effects
none. as of this moment the Amiga doesn't need any effects in particular, but some may be added in a future.
- `10xx`: toggle low-pass filter. `0` turns it off and `1` turns it on.
- `11xx`: toggle amplitude modulation with the next channel.
- does not work on the last channel.
- `12xx`: toggle period (frequency) modulation with the next channel.
- does not work on the last channel.

View File

@ -1,7 +1,7 @@
# Arcade (Yamaha YM2151/PCM)
this chip combination was used in the Sega OutRun, X and Y arcade boards, and perhaps a few others.
the actual PCM chip had 16 channels, but the number has been cut to 5 in DefleMask for seemingly arbitrary reasons. a system with all 16 channels may be coming soon.
the actual PCM chip had 16 channels, but the number has been cut to 5 in DefleMask for seemingly arbitrary reasons.
# effects

View File

@ -1,8 +1,10 @@
# General Instrument AY-3-8910
this chip was used in several home computers (ZX Spectrum, MSX, Amstrad CPC, Atari ST, etc.), video game consoles (Intellivision and Vectrex), arcade boards and even slot machines!
this chip was used in many home computers (ZX Spectrum, MSX, Amstrad CPC, Atari ST, etc.), video game consoles (Intellivision and Vectrex), arcade boards and even slot machines!
the chip's powerful sound comes from the envelope...
It is a 3-channel PSG sound source. The chip's powerful sound comes from the envelope...
AY-3-8914 variant was used in Intellivision, it's basically original AY with 4 level envelope volume per channel and different register format.
# effects
@ -37,4 +39,8 @@ the chip's powerful sound comes from the envelope...
- in this mode the envelope period is set to the channel's notes, multiplied by a fraction.
- `x` is the numerator.
- `y` is the denominator.
- if `x` or `y` are 0 this will disable auto-envelope mode.
- if `x` or `y` are 0 this will disable auto-envelope mode.
- `2Exx`: write to I/O port A.
- this changes the port's mode to "write". make sure you have connected something to it.
- `2Fxx`: write to I/O port B.
- this changes the port's mode to "write". make sure you have connected something to it.

View File

@ -5,7 +5,7 @@ a backwards-compatible successor to the AY-3-8910, with increased volume resolut
sadly, this soundchip has only ever observed minimal success, and has remained rather obscure since.
it is known for being used in the Covox Sound Master, which didn't sell well either.
while emulation of this chip is mostly complete, the additional noise setup registers are not emulated (yet). whether it ever has been emulated at some point in a now-abandoned tracker with similar goal as Furnace is unknown.
while emulation of this chip is mostly complete, hardware comparison hasn't been performed yet due to its scarcity. it also was emulated in a now-abandoned tracker with similar goal as Furnace, which sparked interest on the chip.
# effects

View File

@ -0,0 +1,15 @@
# Bubble System WSG
a Konami's 2 channel wavetable sound generator logic used at their arcade hardware Bubble System.
It's configured with K005289, 4 bit PROM and DAC.
Also known as K005289, but that's just part of the logic used for pitch and wavetable ROM address.
Waveform select and Volume control are tied with single AY-3-8910 IO for both channels.
Another AY-3-8910 IO is used for reading sound hardware status.
Furnace emulates this configurations as single system, waveform format is 15 level and 32 width.
# effects
- `10xx`: change wave.

View File

@ -0,0 +1,26 @@
# Famicom Disk System
the Famicom Disk System is an expansion device for the Famicom (known as NES outside Japan), a popular console from the '80's.
as it name implies, it allowed people to play games on specialized floppy disks that could be rewritten on vending machines, therefore reducing the cost of ownership and manufacturing.
it also offers an additional 6-bit, 64-byte wavetable sound channel with (somewhat limited) FM capabilities, which is what Furnace supports.
# effects
- `10xx`: change wave.
- `11xx`: set modulation depth.
- `12xy`: set modulation speed high byte and toggle on/off.
- `x` is the toggle. a value of 1 turns on the modulator.
- `y` is the speed.
- `13xx`: set modulation speed low byte.
- `14xx`: set modulator position.
- `15xx`: set modulator wave.
- `xx` points to a wavetable. it should (preferably) have a height of 7 with the values mapping to:
- 0: +0
- 1: +1
- 2: +2
- 3: +3
- 4: reset
- 5: -3
- 6: -2
- 7: -1

View File

@ -0,0 +1,22 @@
# Atari Lynx/MIKEY
The Atari Lynx is a 16 bit handheld console developed by (obviously) Atari Corporation, and initially released in September of 1989, with the worldwide release being in 1990.
The Lynx, while being an incredible handheld for the time (and a lot more powerful than a Game Boy), unfortunately meant nothing in the end due to the Lynx being a market failure, and ending up as one of the things that contributed to the downfall of Atari.
Although the Lynx is still getting (rather impressive) homebrew developed for it, it does not mean that the Lynx is a popular system at all.
The Atari Lynx's custom sound chip and CPU (MIKEY) is a 6502-based 8 bit CPU running at 16MHz, however this information is generally not useful in the context of Furnace.
## Sound capabilities
- The MIKEY has 4 channels of square wave-based sound, which can be modulated with different frequencies (×0, ×1, ×2, ×3, ×4, ×5, ×7, ×10, and ×11) to create wavetable-like results.
- Likewise, when a lot of the modulators are activated, this can provide a "pseudo-white noise"-like effect, whoch can be useful for drums and sound effects.
- The MIKEY also has hard stereo panning capabilities via the `08xx` effect command.
- The MIKEY has four 8-bit DACs (Digital to Analog Converter) — one for each voice — that essentially mean you can play samples on the MIKEY (at the cost of CPU time and memory).
- The MIKEY also has a variety of pitches to choose from, and they go from 32Hz to "above the range of human hearing", according to Atari.
## Effect commands
- `3xxx`: Load LFSR (0 to FFF).
- this is a bitmask.
- for it to work, duty macro in instrument editor must be set to some value, without it LFSR will not be fed with any bits.

View File

@ -0,0 +1,12 @@
# Nintendo MMC5
a mapper chip which made NES cartridges exceeding 1MB possible.
it has two pulse channels which are very similar to the ones found in the NES, but lacking the sweep unit.
additionally, it offers an 8-bit DAC which can be used to play samples. only one game is known to use it, though.
# 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.

View File

@ -0,0 +1,25 @@
# Namco 163
This is Namco's one of NES mapper, with up to 8 wavetable channels. It has also 128 byte of internal RAM, both channel register and wavetables are stored here. Wavetables are variable size and freely allocable anywhere in RAM, it means it can be uses part of or continuously pre-loaded waveform and/or its sequences in RAM. But waveform RAM area becomes smaller as much as activating more channels; Channel register consumes 8 byte for each channels. You must avoid conflict with channel register area and waveform for avoid channel playback broken.
It has can be outputs only single channel at clock; so it's sound quality is more crunchy as much as activating more channels.
Furnace supports both load waveform into RAM and waveform playback simultaneously, and channel limit is dynamically changeable with effect commands.
You must load waveform to RAM first for playback or do something, its load behavior is changeable to auto-update when every waveform changes or manual update.
Both waveform playback and load command is works independently per each channel columns, (Global) commands are don't care about the channel columns for work commands and its load behavior is independent with per-channel column load commands.
# effects
- `10xx`: set waveform for playback.
- `11xx`: set waveform position in RAM for playback. (single nibble unit)
- `12xx`: set waveform length in RAM for playback. (04 to FC, 4 nibble unit)
- `130x`: set playback waveform update behavior. (0: off, bit 0: update now, bit 1: update when every waveform is changed)
- `14xx`: set waveform for load to RAM.
- `15xx`: set waveform position for load to RAM. (single nibble unit)
- `16xx`: set waveform length for load to RAM. (04 to FC, 4 nibble unit)
- `170x`: set waveform load behavior. (0: off, bit 0: load now, bit 1: load when every waveform is changed)
- `180x`: set channel limit (0 to 7, x + 1)
- `20xx`: (Global) set waveform for load to RAM.
- `21xx`: (Global) set waveform position for load to RAM. (single nibble unit)
- `22xx`: (Global) set waveform length for load to RAM. (04 to FC, 4 nibble unit)
- `230x`: (Global) set waveform load behavior. (0: off, bit 0: load now, bit 1: load when every waveform is changed)

View File

@ -2,7 +2,7 @@
the console from Nintendo that plays Super Mario Bros. and helped revive the agonizing video game market in the US during mid-80s.
also known as Famicom.
also known as Famicom. It is a five-channel PSG: first two channels play pulse wave with three different duty cycles, third is a fixed-volume triangle channel, fourth is a noise channel (can work in both pseudo-random and periodic modes) and fifth is a (D)PCM sample channel
# effects
@ -15,4 +15,4 @@ also known as Famicom.
- `14xy`: setup sweep down.
- `x` is the time.
- `y` is the shift.
- set to 0 to disable it.
- set to 0 to disable it.

View File

@ -0,0 +1,46 @@
# Yamaha OPL
a series of FM sound chips which were very popular in DOS land. it was so popular that even Yamaha made a logo for it!
essentially a downgraded version of Yamaha's other FM chips, with only 2 operators per channel.
however, it also had a drums mode, and later chips in the series added more waveforms (than just the typical sine) and even a 4-operator mode.
the original OPL (Yamaha YM3526) was present as an expansion for the Commodore 64 and MSX computers (erm, a variant of it). it only had 9 two-operator channels and drums mode.
its successor, the OPL2 (Yamaha YM3812), added 3 more waveforms and was one of the more popular chips because it was present on the AdLib card for PC.
later Creative would borrow the chip to make the Sound Blaster, and totally destroyed AdLib's dominance.
the OPL3 (Yamaha YMF262) added 9 more channels, 4 more waveforms, rudimentary 4-operator mode (pairing up to 12 channels to make up to six 4-operator channels), quadraphonic output (sadly Furnace only supports stereo) and some other things.
afterwards everyone moved to Windows and software mixed PCM streaming...
# effects
- 10xx: set AM depth. the following values are accepted:
- 0: 1dB (shallow)
- 1: 4.8dB (deep)
- this effect applies to all channels.
- `11xx`: set feedback of channel.
- `12xx`: set operator 1 level.
- `13xx`: set operator 2 level.
- `14xx`: set operator 3 level.
- only in 4-op mode (OPL3).
- `15xx`: set operator 4 level.
- only in 4-op mode (OPL3).
- `16xy`: set multiplier of operator.
- `x` is the operator (1-4; last 2 operators only in 4-op mode).
- `y` is the mutliplier.
- 17xx: set vibrato depth. the following values are accepted:
- 0: normal
- 1: double
- this effect applies to all channels.
- `18xx`: toggle drums mode.
- 0 disables it and 1 enables it.
- only in drums system.
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.
- `1Cxx`: set attack of operator 3.
- only in 4-op mode (OPL3).
- `1Dxx`: set attack of operator 4.
- only in 4-op mode (OPL3).

View File

@ -1,21 +1,39 @@
# Yamaha YM2413/OPLL
The YM2413, otherwise known as OPLL, is a cost-reduced FM synthesis sound chip manufactured by Yamaha Corporation and based on the Yamaha YM3812 sound chip (OPL2).
As of Furnace version 0.5.7pre4, the OPLL sound chip is not yet emulated. It is, however, emulated in Deflemask as of version 1.1.0. Support for loading .DMFs which contain the YM2413 was added in Furnace version 0.5.7pre4.
the YM2413, otherwise known as OPLL, is a cost-reduced FM synthesis sound chip, based on the Yamaha YM3812 (OPL2). thought OPL was downgraded enough? :p
## Technical specifications
The YM2413 is equipped with the following features:
- 9 channels of 2 operator FM synthesis
- A drum/percussion mode, replacing the last 3 voices with 3 rhythm channels
- 1 user-definable patch (this patch can be changed throughout the course of the song)
- 15 pre-defined patches which can all be used at the same time
- Support for ADSR on both the modulator and the carrier
- Sine and half-sine based FM synthesis
- 9 octave note control
- 4096 different frequencies for channels
- 16 unique volume levels (NOTE: Volume 0 is NOT silent.)
- Modulator and carrier key scaling
- Built-in hardware vibrato support
OPLL spawned also a few derivative chips, the best known of these is:
- the myth. the legend. THE VRC7. 6 channels, *rather interesting* instruments sound bank, no drums mode
- Yamaha YM2423, same chip as YM2413, just a different patch set
- Yamaha YMF281, ditto
## Effect commands
TODO: Add effect commands here when YM2413 emulation is added.
# technical specifications
the YM2413 is equipped with the following features:
- 9 channels of 2 operator FM synthesis
- A drum/percussion mode, replacing the last 3 voices with 3 rhythm channels
- 1 user-definable patch (this patch can be changed throughout the course of the song)
- 15 pre-defined patches which can all be used at the same time
- Support for ADSR on both the modulator and the carrier
- Sine and half-sine based FM synthesis
- 9 octave note control
- 4096 different frequencies for channels
- 16 unique volume levels (NOTE: Volume 0 is NOT silent.)
- Modulator and carrier key scaling
- Built-in hardware vibrato support
# effects
- `11xx`: set feedback of channel.
- `12xx`: set operator 1 level.
- `13xx`: set operator 2 level.
- `16xy`: set multiplier of operator.
- `x` is the operator (1 or 2).
- `y` is the mutliplier.
- `18xx`: toggle drums mode.
- 0 disables it and 1 enables it.
- only in drums system.
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.

View File

@ -1,9 +1,11 @@
# PC Engine/TurboGrafx-16
a console from NEC that attempted to enter the fierce battle between Nintendo and Sega, but because its capabilities are a mix of third and fourth generation, it failed to last long.
a console from NEC that, depending on a region:
attempted to enter the fierce battle between Nintendo and Sega, but because its capabilities are a mix of third and fourth generation, it failed to last long. (US and Europe)
was Nintendo's most fearsome rival, completely defeating Sega Mega Drive and defending itself against Super Famicom (Japan)
it has 6 wavetable channels and the last two ones also double as noise channels.
furthermore, it has some PCM!
furthermore, it has some PCM and LFO!
# effects

View File

@ -0,0 +1,7 @@
# PC Speaker
40 years of one square beep - and still going! Single channel, no volume control...
# effects
ha! effects...

View File

@ -0,0 +1,11 @@
# Commodore PET
a computer from 1977 which was leader on US schools back then. subsequently the Apple II took its throne.
maybe no better than a computer terminal, but somebody discovered a way to update the screen at turbo rate - and eventually its sound "chip" (it was nothing more than an 8-bit shift register) was abused as well.
some of these didn't even have sound...
# effects
- 10xx: set waveform. `xx` is a bitmask.

View File

@ -1,6 +1,9 @@
# Philips SAA1099
this was used by the Game Blaster and SAM Coupé. it's pretty similar to the AY-3-8910, but has stereo sound, twice the channels and two envelopes, both of which are highly flexible.
this was used by the Game Blaster and SAM Coupé. it's pretty similar to the AY-3-8910, but has stereo sound, twice the channels and two envelopes, both of which are highly flexible. The envelopes work like this:
an instrument with envelope settings is placed on channel 2 or channel 5
an instrument that is used as an "envelope output", is placed on channel 3 or channel 6. You may want to disable wave output on the output channel.
# effects

View File

@ -0,0 +1,12 @@
# SegaPCM
16 channels of PCM? no way!
yep, that's right! 16 channels of PCM!
a chip used in the Sega OutRun/X/Y arcade boards. eventually the MultiPCM surpassed it with 24 channels, and later they joined the software mixing gang.
# effects
- `20xx`: set PCM frequency.
- `xx` is a 256th fraction of 31250Hz.
- this effect exists for mostly DefleMask compatibility - it is otherwise recommended to use Sample type instruments.

View File

@ -2,7 +2,7 @@
the predecessor to Genesis.
surely had better graphics than NES, but its sound is subject of several jokes.
surely had better graphics than NES, but its sound (fairly weak, 4ch PSG with A-3 is a lowest tone) is subject of several jokes.
this console is powered by a derivative of the Texas Instruments SN76489.

View File

@ -0,0 +1,15 @@
# VERA
this is a video and sound generator chip used in the Commander X16, a modern 8-bit computer created by The 8-Bit Guy.
it has 16 channels of pulse/triangle/saw/noise and one stereo PCM channel.
currently Furnace does not support the PCM channel's stereo mode, though (except for panning).
# effects
- `20xx`: set waveform. the following values are accepted:
- 0: pulse
- 1: saw
- 2: triangle
- 3: noise
- `22xx`: set duty cycle. `xx` may go from 0 to 3F.

View File

@ -0,0 +1,11 @@
# Commodore VIC-20
The Commodore VIC-20 was Commodore's major attempt at making a personal home computer, and is the percursor to the Commodore 64. The VIC-20 was also known as the VC-20 in Germany, and the VIC-1001 in Japan.
It has 4 PSG voices that has a limited but wide tuning range, and like the SN76489, the last voice is dedicated to playing pseudo-white noise.
The 3 pulse wave channels also have different octaves that they can play notes on. The first channel is the bass channel, and it can play notes from octave 1. The next is the 'mid/chord' channel, and it plays notes from octave 2. And rather obviously, the 3rd pulse channel is typically the lead channel, can play notes from octave 3.
## effect commands
- `10xx` Switch waveform (`xx` from `00` to `0F`)

View File

@ -0,0 +1,18 @@
# Konami VRC6
the most popular expansion chip to the NES' sound system.
the chip has 2 pulse wave channels and one sawtooth channel.
volume register is 4 bit for pulse wave and 6 bit for sawtooth, but sawtooth output is corrupted when volume register value is too high. because this register is actually an 8 bit accumulator, its output may wrap around.
For that reason, the sawtooth channel has it's own instrument type. Setting volume macro and pattern editor volume setting too high (above 42/2A) will distort the waveform.
pulse wave duty cycle is 8-level. it can be ignored and it has potential for DAC at this case: volume register in this mode is DAC output and it can be PCM playback through this mode.
Furnace supports this routine for PCM playback, but it consumes a lot of CPU time in real hardware (even if conjunction with VRC6's integrated IRQ timer).
# effects
these effects only are effective in the pulse channels.
- `12xx`: set duty cycle (0 to 7).
- `17xx`: toggle PCM mode.

View File

@ -0,0 +1,17 @@
# WonderSwan
A handheld console released only in Japan by Bandai. Designed by the same people behind Game Boy and Virtual Boy, it has lots of similar elements from those two systems in the sound department.
It has 4 wavetable channels, channel #2 could play PCM, channel #3 has hardware sweep and channel #4 could play noise.
# effects
- `10xx`: change wave.
- `11xx`: setup noise mode (channel 4 only).
- 0: disable.
- 1-8: enable and set tap preset.
- `12xx`: setup sweep period (channel 3 only).
- 0: disable.
- 1-32: enable and set period.
- `13xx`: setup sweep amount (channel 3 only).
- `17xx`: toggle PCM mode (channel 2 only).

View File

@ -0,0 +1,47 @@
# Seta/Allumer X1-010
A sound chip designed by Seta, mainly used in their own arcade hardware from the late 80s to the early 2000s.
It has 2 output channels, but there is no known hardware taking advantage of stereo sound capabilities.
Later hardware paired this with external bankswitching logic, but this isn't emulated yet.
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.
# waveform types
This chip supports 2 types of waveforms, needs to be paired to external 8 KB RAM to access these features:
One is a signed 8 bit mono waveform, operated like other wavetable based sound systems.
These are stored at the lower half of RAM at common case.
The other one ("Envelope") is a 4 bit stereo waveform, multiplied with the above and calculates final output, each nibble is used for each output channel.
These are stored at the upper half of RAM at common case.
Both waveforms are 128 bytes (fixed size), freely allocated at each half of RAM except the channel register area: each half can store total 32/31 waveforms at once.
In furnace, you can enable the envelope shape split mode. When it is set, its waveform will be split to the left and right halves for each output. Each max size is 128 bytes, total 256 bytes.
# effects
- `10xx`: change wave.
- `11xx`: change envelope shape (also wavetable).
- `17xx`: toggle PCM mode.
- `20xx`: set PCM frequency (1 to FF).
- `22xx`: set envelope mode.
- bit 0 sets whether envelope will affect this channel.
- bit 1 toggles the envelope one-shot mode. when it is set, channel is halted after envelope cycle is finished.
- bit 2 toggles the envelope shape split mode. when it is set, envelope shape will be split to left half and right half.
- bit 3/5 sets whether the right/left shape will mirror the original one.
- bit 4/6 sets whether the right/left output will mirror the original one.
- `23xx`: set envelope period.
- `25xx`: slide envelope period up.
- `26xx`: slide envelope period down.
- `29xy`: enable auto-envelope mode.
- in this mode the envelope period is set to the channel's notes, multiplied by a fraction.
- `x` is the numerator.
- `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.

View File

@ -1,6 +1,8 @@
# Yamaha YM2151
the sound chip powering the Arcade system, available for standalone use if you want to make X68000 music or pair it with a 16-channel Sega PCM when it comes.
the sound chip powering several arcade boards and the Sharp X1/X68000. Eight 4-op FM channels, with overpowered LFO and almost unused noise generator.
it also was present on several pinball machines and synthesizers of the era, and later surpassed by the YM2414 (OPZ) present in the world-famous TX81Z.
# effects

View File

@ -2,7 +2,7 @@
originally an arcade board, but SNK shortly adapted it to a rather expensive video game console with the world's biggest cartridges because some people liked the system so much they wanted a home version of it.
its soundchip is a 3-in-1: FM, YM2149 (AY-3-8910 clone) and ADPCM in a single package!
its soundchip is a 4-in-1: 4ch 4-op FM, YM2149 (AY-3-8910 clone) and 2 different format ADPCM in a single package!
# effects
@ -56,4 +56,4 @@ its soundchip is a 3-in-1: FM, YM2149 (AY-3-8910 clone) and ADPCM in a single pa
- in this mode the envelope period is set to the channel's notes, multiplied by a fraction.
- `x` is the numerator.
- `y` is the denominator.
- if `x` or `y` are 0 this will disable auto-envelope mode.
- if `x` or `y` are 0 this will disable auto-envelope mode.

View File

@ -0,0 +1,58 @@
# Taito Arcade/Yamaha YM2610B
YM2610B is basically YM2610 with 2 extra FM channels used at some 90s Taito arcade hardware.
it is backward compatible with the original chip.
# effects
- `10xy`: set LFO parameters.
- `x` toggles the LFO.
- `y` sets its speed.
- `11xx`: set feedback of channel.
- `12xx`: set operator 1 level.
- `13xx`: set operator 2 level.
- `14xx`: set operator 3 level.
- `15xx`: set operator 4 level.
- `16xy`: set multiplier of operator.
- `x` is the operator (1-4).
- `y` is the mutliplier.
- `18xx`: toggle extended channel 2 mode.
- 0 disables it and 1 enables it.
- only in extended channel 2 system.
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.
- `1Cxx`: set attack of operator 3.
- `1Dxx`: set attack of operator 4.
- `20xx`: set SSG channel mode. `xx` may be one of the following:
- `00`: square
- `01`: noise
- `02`: square and noise
- `03`: nothing (apparently)
- `04`: envelope and square
- `05`: envelope and noise
- `06`: envelope and square and noise
- `07`: nothing
- `21xx`: set noise frequency. `xx` is a value between 00 and 1F.
- `22xy`: set envelope mode.
- `x` sets the envelope shape, which may be one of the following:
- `0: \___` decay
- `4: /___` attack once
- `8: \\\\` saw
- `9: \___` decay
- `A: \/\/` inverse obelisco
- `B: \¯¯¯` decay once
- `C: ////` inverse saw
- `D: /¯¯¯` attack
- `E: /\/\` obelisco
- `F: /___` attack once
- if `y` is 1 then the envelope will affect this channel.
- `23xx`: set envelope period low byte.
- `24xx`: set envelope period high byte.
- `25xx`: slide envelope period up.
- `26xx`: slide envelope period down.
- `29xy`: enable SSG auto-envelope mode.
- in this mode the envelope period is set to the channel's notes, multiplied by a fraction.
- `x` is the numerator.
- `y` is the denominator.
- if `x` or `y` are 0 this will disable auto-envelope mode.

View File

@ -1,6 +1,6 @@
# Yamaha YM2612
one of two chips that powered the Sega Genesis.
one of two chips that powered the Sega Genesis. It is a six-channel, four-operator FM synthesizer. Channel #6 can be turned into 8-bit PCM player.
# effects

View File

@ -4,7 +4,8 @@ the Yamaha YMU759 is a sound chip designed for feature phones during the early 2
it is also known as MA-2.
sadly Yamaha didn't care about these chips too much, and the register specs were completely unavailable, which means the YMU759 is totally unsupported and unemulated besides Yamaha's official emulator for it built into MidRadio.
hence Furnace does not emulate the chip (and doesn't even let you add it to a song).
Furnace 0.6 loads DefleMask modules written for this system; however, it doesn't support any of its effects and is simulated using the OPL core.
# effects

View File

@ -29,6 +29,26 @@ furthermore, an `or reserved` indicates this field is always present, but is res
the format versions are:
- 80: Furnace dev80
- 79: Furnace dev79
- 78: Furnace dev78
- 77: Furnace dev77
- 76: Furnace dev76
- 75: Furnace dev75/April Fools' 0.6pre0
- 74: Furnace dev74
- 73: Furnace dev73
- 72: Furnace dev72
- 71: Furnace dev71
- 70: Furnace dev70
- 69: Furnace dev69
- 68: Furnace dev68
- 67: Furnace dev67
- 66: Furnace dev66
- 65: Furnace dev65
- 64: Furnace dev64
- 63: Furnace dev63
- 62: Furnace dev62
- 61: Furnace dev61
- 60: Furnace dev60
- 59: Furnace dev59
- 58: Furnace dev58
@ -100,12 +120,17 @@ size | description
| - 60 is NTSC
| - 50 is PAL
2 | pattern length
| - the limit is 256.
2 | orders length
| - the limit is 256 (>=80) or 127 (<80).
1 | highlight A
1 | highlight B
2 | instrument count
| - the limit is 256.
2 | wavetable count
| - the limit is 256.
2 | sample count
| - the limit is 256.
4 | pattern count
32 | list of sound chips
| - possible soundchips:
@ -118,63 +143,68 @@ size | description
| - 0x06: NES - 5 channels
| - 0x07: C64 (8580) - 3 channels
| - 0x08: Arcade (YM2151+SegaPCM) - 13 channels (compound!)
| - 0x09: Neo Geo (YM2610) - 13 channels
| - bit 6 enables alternate mode:
| - 0x42: Genesis extended - 13 channels
| - 0x43: SMS (SN76489) + OPLL (YM2413) - 13 channels (compound!)
| - 0x46: NES + VRC7 - 11 channels (compound!)
| - 0x47: C64 (6581) - 3 channels
| - 0x49: Neo Geo extended - 16 channels
| - bit 7 for non-DefleMask chips:
| - 0x80: AY-3-8910 - 3 channels
| - 0x81: Amiga - 4 channels
| - 0x82: YM2151 alone - 8 channels
| - 0x83: YM2612 alone - 6 channels
| - 0x84: TIA - 2 channels
| - 0x85: VIC-20 - 4 channels
| - 0x86: PET - 1 channel
| - 0x87: SNES - 8 channels
| - 0x88: VRC6 - 3 channels
| - 0x89: OPLL (YM2413) - 9 channels
| - 0x8a: FDS - 1 channel
| - 0x8b: MMC5 - 3 channels
| - 0x8c: Namco 163 - 8 channels
| - 0x8d: OPN (YM2203) - 6 channels
| - 0x8e: PC-98 (YM2608) - 16 channels
| - 0x8f: OPL (YM3526) - 9 channels
| - 0x90: OPL2 (YM3812) - 9 channels
| - 0x91: OPL3 (YMF262) - 18 channels
| - 0x92: MultiPCM - 24 channels
| - 0x93: Intel 8253 (beeper) - 1 channel
| - 0x94: POKEY - 4 channels
| - 0x95: RF5C68 - 8 channels
| - 0x96: WonderSwan - 4 channels
| - 0x97: Philips SAA1099 - 6 channels
| - 0x98: OPZ (YM2414) - 8 channels
| - 0x99: Pokémon Mini - 1 channel
| - 0x9a: AY8930 - 3 channels
| - 0x9b: SegaPCM - 16 channels
| - 0x9c: Virtual Boy - 6 channels
| - 0x9d: VRC7 - 6 channels
| - 0x9e: YM2610B - 16 channels
| - 0x9f: ZX Spectrum (beeper) - 6 channels
| - 0xa0: YM2612 extended - 9 channels
| - 0xa1: Konami SCC - 5 channels
| - 0xa2: OPL drums (YM3526) - 11 channels
| - 0xa3: OPL2 drums (YM3812) - 11 channels
| - 0xa4: OPL3 drums (YMF262) - 20 channels
| - 0xa5: OPL3 4-op (YMF262) - 12 channels
| - 0xa6: OPL3 4-op + drums (YMF262) - 14 channels
| - 0xa7: OPLL drums (YM2413) - 11 channels
| - 0xa8: Atari Lynx - 4 channels
| - 0xe0: QSound - 19 channels
| - 0x09: Neo Geo CD (YM2610) - 13 channels
| - 0x42: Genesis extended - 13 channels
| - 0x43: SMS (SN76489) + OPLL (YM2413) - 13 channels (compound!)
| - 0x46: NES + VRC7 - 11 channels (compound!)
| - 0x47: C64 (6581) - 3 channels
| - 0x49: Neo Geo CD extended - 16 channels
| - 0x80: AY-3-8910 - 3 channels
| - 0x81: Amiga - 4 channels
| - 0x82: YM2151 alone - 8 channels
| - 0x83: YM2612 alone - 6 channels
| - 0x84: TIA - 2 channels
| - 0x85: VIC-20 - 4 channels
| - 0x86: PET - 1 channel
| - 0x87: SNES - 8 channels
| - 0x88: VRC6 - 3 channels
| - 0x89: OPLL (YM2413) - 9 channels
| - 0x8a: FDS - 1 channel
| - 0x8b: MMC5 - 3 channels
| - 0x8c: Namco 163 - 8 channels
| - 0x8d: OPN (YM2203) - 6 channels
| - 0x8e: PC-98 (YM2608) - 16 channels
| - 0x8f: OPL (YM3526) - 9 channels
| - 0x90: OPL2 (YM3812) - 9 channels
| - 0x91: OPL3 (YMF262) - 18 channels
| - 0x92: MultiPCM - 24 channels
| - 0x93: Intel 8253 (beeper) - 1 channel
| - 0x94: POKEY - 4 channels
| - 0x95: RF5C68 - 8 channels
| - 0x96: WonderSwan - 4 channels
| - 0x97: Philips SAA1099 - 6 channels
| - 0x98: OPZ (YM2414) - 8 channels
| - 0x99: Pokémon Mini - 1 channel
| - 0x9a: AY8930 - 3 channels
| - 0x9b: SegaPCM - 16 channels
| - 0x9c: Virtual Boy - 6 channels
| - 0x9d: VRC7 - 6 channels
| - 0x9e: YM2610B - 16 channels
| - 0x9f: ZX Spectrum (beeper) - 6 channels
| - 0xa0: YM2612 extended - 9 channels
| - 0xa1: Konami SCC - 5 channels
| - 0xa2: OPL drums (YM3526) - 11 channels
| - 0xa3: OPL2 drums (YM3812) - 11 channels
| - 0xa4: OPL3 drums (YMF262) - 20 channels
| - 0xa5: Neo Geo (YM2610) - 14 channels
| - 0xa6: Neo Geo extended (YM2610) - 17 channels
| - 0xa7: OPLL drums (YM2413) - 11 channels
| - 0xa8: Atari Lynx - 4 channels
| - 0xa9: SegaPCM (for Deflemask Compatibility) - 5 channels
| - 0xaa: MSM6295 - 4 channels
| - 0xab: MSM6258 - 1 channel
| - 0xac: Commander X16 (VERA) - 17 channels
| - 0xad: Bubble System WSG - 2 channels
| - 0xb0: Seta/Allumer X1-010 - 16 channels
| - 0xde: YM2610B extended - 19 channels
| - 0xe0: QSound - 19 channels
| - (compound!) means that the system is composed of two or more chips,
| and has to be flattened.
32 | sound chip volumes
| - signed char, 64=1.0, 127=~2.0
32 | sound chip panning
| - signed char, -128=left, 127=right
128 | sound chip parameters (TODO)
128 | sound chip parameters
STR | song name
STR | song author
4f | A-4 tuning
@ -192,7 +222,12 @@ size | description
1 | wack algorithm macro (>=47) or reserved
1 | broken shortcut slides (>=49) or reserved
1 | ignore duplicate slides (>=50) or reserved
6 | reserved
1 | stop portamento on note off (>=62) or reserved
1 | continuous vibrato (>=62) or reserved
1 | broken DAC mode (>=64) or reserved
1 | one tick cut (>=65) or reserved
1 | instrument change allowed during porta (>=66) or reserved
1 | reset note base on arpeggio effect stop (0000) (>=69) or reserved
4?? | pointers to instruments
4?? | pointers to wavetables
4?? | pointers to samples
@ -201,6 +236,7 @@ size | description
| - a table of bytes
| - size=channels*ordLen
| - read orders then channels
| - the maximum value of a cell is FF (>=80) or 7F (<80).
??? | effect columns
| - size=channels
1?? | channel hide status
@ -214,6 +250,15 @@ size | description
STR | song comment
4f | master volume, 1.0f=100% (>=59)
| this is 2.0f for modules before 59
--- | **extended compatibility flags** (>=70)
1 | broken speed selection
1 | no slides on first tick (>=71) or reserved
1 | next row reset arp pos (>=71) or reserved
1 | ignore jump at end (>=71) or reserved
1 | buggy portamento after slide (>=72) or reserved
1 | new ins affects envelope (Game Boy) (>=72) or reserved
1 | ExtCh channel state is shared (>=78) or reserved
25 | reserved
```
# instrument
@ -237,7 +282,9 @@ size | description
1 | feedback
1 | fms
1 | ams
1 | operator count (always 4)
1 | operator count
| - this is either 2 or 4, and is ignored on non-OPL systems.
| - always read 4 ops regardless of this value.
1 | OPLL preset (>=60) or reserved
| - 0: custom
| - 1-15: pre-defined patches
@ -426,6 +473,133 @@ size | description
4 | DT macro release
4 | D2R macro release
4 | SSG-EG macro release
--- | **extended op macro headers** × 4 (>=61)
4 | DAM macro length
4 | DVB macro length
4 | EGT macro length
4 | KSL macro length
4 | SUS macro length
4 | VIB macro length
4 | WS macro length
4 | KSR macro length
4 | DAM macro loop
4 | DVB macro loop
4 | EGT macro loop
4 | KSL macro loop
4 | SUS macro loop
4 | VIB macro loop
4 | WS macro loop
4 | KSR macro loop
4 | DAM macro release
4 | DVB macro release
4 | EGT macro release
4 | KSL macro release
4 | SUS macro release
4 | VIB macro release
4 | WS macro release
4 | KSR macro release
1 | DAM macro open
1 | DVB macro open
1 | EGT macro open
1 | KSL macro open
1 | SUS macro open
1 | VIB macro open
1 | WS macro open
1 | KSR macro open
--- | **extended op macros** × 4 (>=61)
1?? | DAM macro
1?? | DVB macro
1?? | EGT macro
1?? | KSL macro
1?? | SUS macro
1?? | VIB macro
1?? | WS macro
1?? | KSR macro
--- | **OPL drums mode data** (>=63)
1 | fixed frequency mode
1 | reserved
2 | kick frequency
2 | snare/hi-hat frequency
2 | tom/top frequency
--- | **Sample instrument extra data** (>=67)
1 | use note map
| - only read the following two data structures if this is true!
4?? | note frequency × 120
| - 480 bytes
2?? | note sample × 120
| - 240 bytes
--- | **Namco 163 data** (>=73)
4 | initial waveform
1 | wave position
1 | wave length
1 | wave mode:
| - bit 1: update on change
| - bit 0: load on playback
1 | reserved
--- | **even more macros** (>=76)
4 | left panning macro length
4 | right panning macro length
4 | phase reset macro length
4 | extra 4 macro length
4 | extra 5 macro length
4 | extra 6 macro length
4 | extra 7 macro length
4 | extra 8 macro length
4 | left panning macro loop
4 | right panning macro loop
4 | phase reset macro loop
4 | extra 4 macro loop
4 | extra 5 macro loop
4 | extra 6 macro loop
4 | extra 7 macro loop
4 | extra 8 macro loop
4 | left panning macro release
4 | right panning macro release
4 | phase reset macro release
4 | extra 4 macro release
4 | extra 5 macro release
4 | extra 6 macro release
4 | extra 7 macro release
4 | extra 8 macro release
1 | left panning macro open
1 | right panning macro open
1 | phase reset macro open
1 | extra 4 macro open
1 | extra 5 macro open
1 | extra 6 macro open
1 | extra 7 macro open
1 | extra 8 macro open
--- | **even more macro data** (>=76)
4?? | left panning macro
4?? | right panning macro
4?? | phase reset macro
4?? | extra 4 macro
4?? | extra 5 macro
4?? | extra 6 macro
4?? | extra 7 macro
4?? | extra 8 macro
--- | **FDS instrument data** (>=76)
4 | modulation speed
4 | modulation depth
1 | init modulation table with first wave
3 | reserved
32 | modulation table
--- | **OPZ instrument extra data** (>=77)
1 | fms2
1 | ams2
--- | **wavetable synth data** (>=79)
4 | first wave
4 | second wave
1 | rate divider
1 | effect
| - bit 7: single or dual effect
1 | enabled
1 | global
1 | speed (+1)
1 | parameter 1
1 | parameter 2
1 | parameter 3
1 | parameter 4
```
# wavetable
@ -488,10 +662,29 @@ size | description
| - size: rows*(4+effectColumns*2)*2
| - read shorts in this order:
| - note
| - 0: empty/invalid
| - 1: C#
| - 2: D
| - 3: D#
| - 4: E
| - 5: F
| - 6: F#
| - 7: G
| - 8: G#
| - 9: A
| - 10: A#
| - 11: B
| - 12: C (of next octave)
| - 100: note off
| - 100: note release
| - 100: macro release
| - octave
| - this is an signed char stored in a short.
| - therefore octave value 255 is actually octave -1.
| - instrument
| - volume
| - effect and effect data...
| - for instrument, volume, effect and effect data, a value of -1 means empty.
STR | pattern name (>=51)
```

View File

@ -15,17 +15,17 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string>0.5.7</string>
<string>0.6pre0</string>
<key>CFBundleName</key>
<string>Furnace</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.5.7</string>
<string>0.6pre0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0.5.7</string>
<string>0.6pre0</string>
<key>NSHumanReadableCopyright</key>
<string></string>
<key>NSHighResolutionCapable</key>

View File

@ -0,0 +1,4 @@
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR i686)
include(${CMAKE_CURRENT_LIST_DIR}/Cross-MinGW.cmake)

View File

@ -0,0 +1,4 @@
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
include(${CMAKE_CURRENT_LIST_DIR}/Cross-MinGW.cmake)

14
scripts/Cross-MinGW.cmake Normal file
View File

@ -0,0 +1,14 @@
set(TARGET_PREFIX ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32)
set(CMAKE_C_COMPILER ${TARGET_PREFIX}-gcc-posix)
set(CMAKE_CXX_COMPILER ${TARGET_PREFIX}-g++-posix)
set(PKG_CONFIG_EXECUTABLE ${TARGET_PREFIX}-pkg-config)
set(CMAKE_FIND_ROOT_PATH /usr/${TARGET_PREFIX})
# Search host system for programs
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# Search target system for libraries, headers & CMake packages
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

View File

@ -1,6 +1,6 @@
#!/bin/bash
# make Windows release
# this script shall be run from Linux with MinGW installed!
# this script shall be run from Arch Linux with MinGW installed!
if [ ! -e /tmp/furnace ]; then
ln -s "$PWD" /tmp/furnace || exit 1
@ -14,7 +14,8 @@ fi
cd win32build
i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Werror" -DBUILD_SHARED_LIBS=OFF .. || exit 1
# TODO: potential Arch-ism?
i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" -DBUILD_SHARED_LIBS=OFF .. || exit 1
make -j8 || exit 1
i686-w64-mingw32-strip -s furnace.exe || exit 1
@ -30,3 +31,7 @@ cp -r ../../papers papers || exit 1
cp -r ../../demos demos || exit 1
zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos
furName=$(git describe --tags | sed "s/v0/0/")
mv furnace.zip furnace-"$furName"-win32.zip

View File

@ -1,6 +1,6 @@
#!/bin/bash
# make Windows release
# this script shall be run from Linux with MinGW installed!
# this script shall be run from Arch Linux with MinGW installed!
if [ ! -e /tmp/furnace ]; then
ln -s "$PWD" /tmp/furnace || exit 1
@ -14,7 +14,8 @@ fi
cd winbuild
x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Werror" .. || exit 1
# TODO: potential Arch-ism?
x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" .. || exit 1
make -j8 || exit 1
x86_64-w64-mingw32-strip -s furnace.exe || exit 1
@ -30,3 +31,7 @@ cp -r ../../papers papers || exit 1
cp -r ../../demos demos || exit 1
zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos
furName=$(git describe --tags | sed "s/v0/0/")
mv furnace.zip furnace-"$furName"-win64.zip

View File

@ -55,4 +55,66 @@ bool TAAudio::init(TAAudioDesc& request, TAAudioDesc& response) {
}
TAAudio::~TAAudio() {
}
bool TAMidiIn::gather() {
return false;
}
bool TAMidiOut::send(const TAMidiMessage& what) {
return false;
}
bool TAMidiIn::isDeviceOpen() {
return false;
}
bool TAMidiOut::isDeviceOpen() {
return false;
}
bool TAMidiIn::openDevice(String name) {
return false;
}
bool TAMidiOut::openDevice(String name) {
return false;
}
bool TAMidiIn::closeDevice() {
return false;
}
bool TAMidiOut::closeDevice() {
return false;
}
std::vector<String> TAMidiIn::listDevices() {
return std::vector<String>();
}
std::vector<String> TAMidiOut::listDevices() {
return std::vector<String>();
}
bool TAMidiIn::init() {
return false;
}
bool TAMidiOut::init() {
return false;
}
bool TAMidiIn::quit() {
return true;
}
bool TAMidiOut::quit() {
return true;
}
TAMidiIn::~TAMidiIn() {
}
TAMidiOut::~TAMidiOut() {
}

View File

@ -52,6 +52,7 @@ 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; i<desc.inChans; i++) {

61
src/audio/midi.cpp Normal file
View File

@ -0,0 +1,61 @@
/**
* 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 "taAudio.h"
#ifdef HAVE_RTMIDI
#include "rtmidi.h"
#endif
bool TAAudio::initMidi(bool jack) {
#ifndef HAVE_RTMIDI
return false;
#else
midiIn=new TAMidiInRtMidi;
midiOut=new TAMidiOutRtMidi;
if (!midiIn->init()) {
delete midiIn;
midiIn=NULL;
return false;
}
if (!midiOut->init()) {
midiIn->quit();
delete midiOut;
delete midiIn;
midiOut=NULL;
midiIn=NULL;
return false;
}
return true;
#endif
}
void TAAudio::quitMidi() {
if (midiIn!=NULL) {
midiIn->quit();
delete midiIn;
midiIn=NULL;
}
if (midiOut!=NULL) {
midiOut->quit();
delete midiOut;
midiOut=NULL;
}
}

View File

@ -1 +1,235 @@
/**
* 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 "rtmidi.h"
#include "../ta-log.h"
#include "taAudio.h"
// --- IN ---
bool TAMidiInRtMidi::gather() {
std::vector<unsigned char> msg;
if (port==NULL) return false;
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));
}
queue.push(m);
}
return true;
}
std::vector<String> TAMidiInRtMidi::listDevices() {
std::vector<String> ret;
logD("listing devices.");
if (port==NULL) return ret;
try {
unsigned int count=port->getPortCount();
logD("got port count.");
for (unsigned int i=0; i<count; i++) {
String name=port->getPortName(i);
if (name!="") ret.push_back(name);
}
} catch (RtMidiError& e) {
logW("could not get MIDI inputs! %s",e.what());
}
return ret;
}
bool TAMidiInRtMidi::isDeviceOpen() {
return isOpen;
}
bool TAMidiInRtMidi::openDevice(String name) {
if (port==NULL) return false;
if (isOpen) return false;
try {
bool portOpen=false;
unsigned int count=port->getPortCount();
for (unsigned int i=0; i<count; i++) {
if (port->getPortName(i)==name) {
port->openPort(i);
portOpen=true;
break;
}
}
isOpen=portOpen;
if (!portOpen) logW("could not find MIDI in device...");
return portOpen;
} catch (RtMidiError& e) {
logW("could not open MIDI in device! %s",e.what());
return false;
}
return true;
}
bool TAMidiInRtMidi::closeDevice() {
if (port==NULL) return false;
if (!isOpen) return false;
try {
port->closePort();
} catch (RtMidiError& e) {
logW("could not close MIDI in device! %s",e.what());
isOpen=false; // still
return false;
}
isOpen=false;
return true;
}
bool TAMidiInRtMidi::init() {
if (port!=NULL) return true;
try {
port=new RtMidiIn;
} catch (RtMidiError& e) {
logW("could not initialize RtMidi in! %s",e.what());
return false;
}
return true;
}
bool TAMidiInRtMidi::quit() {
if (port!=NULL) {
delete port;
port=NULL;
}
return true;
}
// --- OUT ---
bool TAMidiOutRtMidi::send(const TAMidiMessage& what) {
if (!isOpen) return false;
if (what.type<0x80) return false;
size_t len=0;
switch (what.type&0xf0) {
case TA_MIDI_NOTE_OFF:
case TA_MIDI_NOTE_ON:
case TA_MIDI_AFTERTOUCH:
case TA_MIDI_CONTROL:
case TA_MIDI_PITCH_BEND:
len=3;
break;
case TA_MIDI_PROGRAM:
case TA_MIDI_CHANNEL_AFTERTOUCH:
len=2;
break;
}
if (len==0) switch (what.type) {
case TA_MIDI_SYSEX: // currently not supported :<
return false;
break;
case TA_MIDI_MTC_FRAME:
case TA_MIDI_SONG_SELECT:
len=2;
break;
case TA_MIDI_POSITION:
len=3;
break;
default:
len=1;
break;
}
port->sendMessage((const unsigned char*)&what.type,len);
return true;
}
bool TAMidiOutRtMidi::isDeviceOpen() {
return isOpen;
}
bool TAMidiOutRtMidi::openDevice(String name) {
if (port==NULL) return false;
if (isOpen) return false;
try {
bool portOpen=false;
unsigned int count=port->getPortCount();
for (unsigned int i=0; i<count; i++) {
if (port->getPortName(i)==name) {
port->openPort(i);
portOpen=true;
break;
}
}
isOpen=portOpen;
if (!portOpen) logW("could not find MIDI out device...");
return portOpen;
} catch (RtMidiError& e) {
logW("could not open MIDI out device! %s",e.what());
return false;
}
return true;
}
bool TAMidiOutRtMidi::closeDevice() {
if (port==NULL) return false;
if (!isOpen) return false;
try {
port->closePort();
} catch (RtMidiError& e) {
logW("could not close MIDI out device! %s",e.what());
isOpen=false; // still
return false;
}
isOpen=false;
return true;
}
std::vector<String> TAMidiOutRtMidi::listDevices() {
std::vector<String> ret;
if (port==NULL) return ret;
try {
unsigned int count=port->getPortCount();
for (unsigned int i=0; i<count; i++) {
String name=port->getPortName(i);
if (name!="") ret.push_back(name);
}
} catch (RtMidiError& e) {
logW("could not get MIDI outputs! %s",e.what());
}
return ret;
}
bool TAMidiOutRtMidi::init() {
if (port!=NULL) return true;
try {
port=new RtMidiOut;
} catch (RtMidiError& e) {
logW("could not initialize RtMidi out! %s",e.what());
return false;
}
return true;
}
bool TAMidiOutRtMidi::quit() {
if (port!=NULL) {
delete port;
port=NULL;
}
return true;
}

View File

@ -17,4 +17,37 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "../../extern/rtmidi/RtMidi.h"
#include "../../extern/rtmidi/RtMidi.h"
#include "taAudio.h"
class TAMidiInRtMidi: public TAMidiIn {
RtMidiIn* port;
bool isOpen;
public:
bool gather();
bool isDeviceOpen();
bool openDevice(String name);
bool closeDevice();
std::vector<String> listDevices();
bool quit();
bool init();
TAMidiInRtMidi():
port(NULL),
isOpen(false) {}
};
class TAMidiOutRtMidi: public TAMidiOut {
RtMidiOut* port;
bool isOpen;
public:
bool send(const TAMidiMessage& what);
bool isDeviceOpen();
bool openDevice(String name);
bool closeDevice();
std::vector<String> listDevices();
bool quit();
bool init();
TAMidiOutRtMidi():
port(NULL),
isOpen(false) {}
};

View File

@ -30,6 +30,7 @@ void taSDLProcess(void* inst, unsigned char* buf, int nframes) {
void TAAudioSDL::onProcess(unsigned char* buf, int nframes) {
if (audioProcCallback!=NULL) {
if (midiIn!=NULL) midiIn->gather();
audioProcCallback(audioProcCallbackUser,inBufs,outBufs,desc.inChans,desc.outChans,desc.bufsize);
}
float* fbuf=(float*)buf;
@ -74,7 +75,7 @@ std::vector<String> TAAudioSDL::listAudioDevices() {
std::vector<String> ret;
if (!audioSysStarted) {
if (SDL_Init(SDL_INIT_AUDIO)<0) {
logE("could not initialize SDL to list audio devices\n");
logE("could not initialize SDL to list audio devices");
} else {
audioSysStarted=true;
}
@ -95,12 +96,12 @@ std::vector<String> TAAudioSDL::listAudioDevices() {
bool TAAudioSDL::init(TAAudioDesc& request, TAAudioDesc& response) {
if (initialized) {
logE("audio already initialized\n");
logE("audio already initialized");
return false;
}
if (!audioSysStarted) {
if (SDL_Init(SDL_INIT_AUDIO)<0) {
logE("could not initialize SDL\n");
logE("could not initialize SDL");
return false;
}
audioSysStarted=true;
@ -118,7 +119,7 @@ bool TAAudioSDL::init(TAAudioDesc& request, TAAudioDesc& response) {
ai=SDL_OpenAudioDevice(request.deviceName.empty()?NULL:request.deviceName.c_str(),0,&ac,&ar,SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
if (ai==0) {
logE("could not open audio device: %s\n",SDL_GetError());
logE("could not open audio device: %s",SDL_GetError());
return false;
}

View File

@ -20,6 +20,7 @@
#ifndef _TAAUDIO_H
#define _TAAUDIO_H
#include "../ta-utils.h"
#include <queue>
#include <vector>
struct SampleRateChangeEvent {
@ -89,48 +90,63 @@ enum TAMidiMessageTypes {
};
struct TAMidiMessage {
double time;
unsigned char type;
union {
struct {
unsigned char note, vol;
} note;
struct {
unsigned char which, val;
} control;
unsigned char patch;
unsigned char pressure;
struct {
unsigned char low, high;
} pitch;
struct {
unsigned int vendor;
} sysEx;
unsigned char timeCode;
struct {
unsigned char low, high;
} position;
unsigned char song;
} data;
unsigned char data[7];
unsigned char* sysExData;
size_t sysExLen;
void submitSysEx(std::vector<unsigned char> data);
void done();
TAMidiMessage(unsigned char t, unsigned char d0, unsigned char d1):
time(0.0),
type(t),
sysExData(NULL),
sysExLen(0) {
memset(&data,0,sizeof(data));
data[0]=d0;
data[1]=d1;
}
TAMidiMessage():
time(0.0),
type(0),
sysExData(NULL),
sysExLen(0) {
memset(&data,0,sizeof(data));
}
};
class TAMidiIn {
public:
std::queue<TAMidiMessage> queue;
virtual bool gather();
bool next(TAMidiMessage& where);
virtual bool isDeviceOpen();
virtual bool openDevice(String name);
virtual bool closeDevice();
virtual std::vector<String> listDevices();
virtual bool init();
virtual bool quit();
TAMidiIn() {
}
virtual ~TAMidiIn();
};
class TAMidiOut {
std::queue<TAMidiMessage> queue;
public:
bool send(TAMidiMessage& what);
};
class TAMidi {
std::vector<TAMidiIn*> in;
std::vector<TAMidiOut*> out;
virtual bool send(const TAMidiMessage& what);
virtual bool isDeviceOpen();
virtual bool openDevice(String name);
virtual bool closeDevice();
virtual std::vector<String> listDevices();
virtual bool init();
virtual bool quit();
TAMidiOut() {
}
virtual ~TAMidiOut();
};
class TAAudio {
@ -145,7 +161,8 @@ class TAAudio {
void (*sampleRateChanged)(SampleRateChangeEvent);
void (*bufferSizeChanged)(BufferSizeChangeEvent);
public:
TAMidi* midi;
TAMidiIn* midiIn;
TAMidiOut* midiOut;
void setSampleRateChangeCallback(void (*callback)(SampleRateChangeEvent));
void setBufferSizeChangeCallback(void (*callback)(BufferSizeChangeEvent));
@ -155,6 +172,8 @@ class TAAudio {
virtual bool quit();
virtual bool setRun(bool run);
virtual std::vector<String> listAudioDevices();
bool initMidi(bool jack);
void quitMidi();
virtual bool init(TAAudioDesc& request, TAAudioDesc& response);
TAAudio():
@ -165,7 +184,9 @@ class TAAudio {
outBufs(NULL),
audioProcCallback(NULL),
sampleRateChanged(NULL),
bufferSizeChanged(NULL) {}
bufferSizeChanged(NULL),
midiIn(NULL),
midiOut(NULL) {}
virtual ~TAAudio();
};

View File

@ -32,13 +32,13 @@ bool DivEngine::saveConf() {
configFile=configPath+String(CONFIG_FILE);
FILE* f=ps_fopen(configFile.c_str(),"wb");
if (f==NULL) {
logW("could not write config file! %s\n",strerror(errno));
logW("could not write config file! %s",strerror(errno));
return false;
}
for (auto& i: conf) {
String toWrite=fmt::sprintf("%s=%s\n",i.first,i.second);
if (fwrite(toWrite.c_str(),1,toWrite.size(),f)!=toWrite.size()) {
logW("could not write config file! %s\n",strerror(errno));
logW("could not write config file! %s",strerror(errno));
fclose(f);
return false;
}
@ -52,10 +52,10 @@ bool DivEngine::loadConf() {
configFile=configPath+String(CONFIG_FILE);
FILE* f=ps_fopen(configFile.c_str(),"rb");
if (f==NULL) {
logI("creating default config.\n");
logI("creating default config.");
return saveConf();
}
logI("loading config.\n");
logI("loading config.");
while (!feof(f)) {
String key="";
String value="";

View File

@ -29,6 +29,12 @@
#define addWrite(a,v) regWrites.push_back(DivRegWrite(a,v));
// HOW TO ADD A NEW COMMAND:
// add it to this enum. then see playback.cpp.
// there is a const char* cmdName[] array, which contains the command
// names as strings for the commands (and other debug stuff).
//
// if you miss it, the program will crash or misbehave at some point.
enum DivDispatchCmds {
DIV_CMD_NOTE_ON=0,
DIV_CMD_NOTE_OFF,
@ -48,7 +54,9 @@ enum DivDispatchCmds {
DIV_CMD_SAMPLE_MODE,
DIV_CMD_SAMPLE_FREQ,
DIV_CMD_SAMPLE_BANK,
DIV_CMD_SAMPLE_POS,
DIV_CMD_FM_HARD_RESET,
DIV_CMD_FM_LFO,
DIV_CMD_FM_LFO_WAVE,
DIV_CMD_FM_TL,
@ -94,15 +102,50 @@ enum DivDispatchCmds {
DIV_CMD_AY_NOISE_MASK_AND,
DIV_CMD_AY_NOISE_MASK_OR,
DIV_CMD_AY_AUTO_ENVELOPE,
DIV_CMD_AY_IO_WRITE,
DIV_CMD_AY_AUTO_PWM,
DIV_CMD_FDS_MOD_DEPTH,
DIV_CMD_FDS_MOD_HIGH,
DIV_CMD_FDS_MOD_LOW,
DIV_CMD_FDS_MOD_POS,
DIV_CMD_FDS_MOD_WAVE,
DIV_CMD_SAA_ENVELOPE,
DIV_CMD_AMIGA_FILTER,
DIV_CMD_AMIGA_AM,
DIV_CMD_AMIGA_PM,
DIV_CMD_LYNX_LFSR_LOAD,
DIV_CMD_QSOUND_ECHO_FEEDBACK,
DIV_CMD_QSOUND_ECHO_DELAY,
DIV_CMD_QSOUND_ECHO_LEVEL,
DIV_CMD_X1_010_ENVELOPE_SHAPE,
DIV_CMD_X1_010_ENVELOPE_ENABLE,
DIV_CMD_X1_010_ENVELOPE_MODE,
DIV_CMD_X1_010_ENVELOPE_PERIOD,
DIV_CMD_X1_010_ENVELOPE_SLIDE,
DIV_CMD_X1_010_AUTO_ENVELOPE,
DIV_CMD_WS_SWEEP_TIME,
DIV_CMD_WS_SWEEP_AMOUNT,
DIV_CMD_N163_WAVE_POSITION,
DIV_CMD_N163_WAVE_LENGTH,
DIV_CMD_N163_WAVE_MODE,
DIV_CMD_N163_WAVE_LOAD,
DIV_CMD_N163_WAVE_LOADPOS,
DIV_CMD_N163_WAVE_LOADLEN,
DIV_CMD_N163_WAVE_LOADMODE,
DIV_CMD_N163_CHANNEL_LIMIT,
DIV_CMD_N163_GLOBAL_WAVE_LOAD,
DIV_CMD_N163_GLOBAL_WAVE_LOADPOS,
DIV_CMD_N163_GLOBAL_WAVE_LOADLEN,
DIV_CMD_N163_GLOBAL_WAVE_LOADMODE,
DIV_ALWAYS_SET_VOLUME,
DIV_CMD_MAX
@ -277,6 +320,18 @@ class DivDispatch {
*/
virtual int getPortaFloor(int ch);
/**
* get the required amplification level of this dispatch's output.
* @return the amplification level.
*/
virtual float getPostAmp();
/**
* check whether DC offset correction is required.
* @return truth.
*/
virtual bool getDCOffRequired();
/**
* get a description of a dispatch-specific effect.
* @param effect the effect.
@ -293,7 +348,7 @@ class DivDispatch {
/**
* set skip reg writes.
*/
void setSkipRegisterWrites(bool value);
virtual void setSkipRegisterWrites(bool value);
/**
* notify instrument change.
@ -310,6 +365,11 @@ class DivDispatch {
*/
virtual void notifyInsDeletion(void* ins);
/**
* notify that playback stopped.
*/
virtual void notifyPlaybackStop();
/**
* force-retrigger instruments.
*/
@ -362,7 +422,8 @@ class DivDispatch {
virtual ~DivDispatch();
};
#define NOTE_PERIODIC(x) parent->calcBaseFreq(chipClock,CHIP_DIVIDER,x,true)
#define NOTE_PERIODIC(x) round(parent->calcBaseFreq(chipClock,CHIP_DIVIDER,x,true))
#define NOTE_PERIODIC_NOROUND(x) parent->calcBaseFreq(chipClock,CHIP_DIVIDER,x,true)
#define NOTE_FREQUENCY(x) parent->calcBaseFreq(chipClock,CHIP_FREQBASE,x,false)
#define COLOR_NTSC (315000000.0/88.0)

View File

@ -28,17 +28,32 @@
#include "platform/nes.h"
#include "platform/c64.h"
#include "platform/arcade.h"
#include "platform/tx81z.h"
#include "platform/ym2610.h"
#include "platform/ym2610ext.h"
#include "platform/ym2610b.h"
#include "platform/ym2610bext.h"
#include "platform/ay.h"
#include "platform/ay8930.h"
#include "platform/opl.h"
#include "platform/tia.h"
#include "platform/saa.h"
#include "platform/amiga.h"
#include "platform/pcspkr.h"
#include "platform/segapcm.h"
#include "platform/qsound.h"
#include "platform/dummy.h"
#include "platform/vera.h"
#include "platform/x1_010.h"
#include "platform/swan.h"
#include "platform/lynx.h"
#include "platform/bubsyswsg.h"
#include "platform/n163.h"
#include "platform/pet.h"
#include "platform/vic20.h"
#include "platform/vrc6.h"
#include "platform/fds.h"
#include "platform/mmc5.h"
#include "platform/dummy.h"
#include "../ta-log.h"
#include "song.h"
@ -64,6 +79,11 @@ void DivDispatchContainer::flush(size_t count) {
}
void DivDispatchContainer::fillBuf(size_t runtotal, size_t offset, size_t size) {
if (dcOffCompensation && runtotal>0) {
dcOffCompensation=false;
prevSample[0]=bbIn[0][0];
if (dispatch->isStereo()) prevSample[1]=bbIn[1][0];
}
if (lowQuality) {
for (size_t i=0; i<runtotal; i++) {
temp[0]=bbIn[0][i];
@ -111,6 +131,9 @@ void DivDispatchContainer::clear() {
temp[1]=0;
prevSample[0]=0;
prevSample[1]=0;
if (dispatch->getDCOffRequired()) {
dcOffCompensation=true;
}
// run for one cycle to determine DC offset
// TODO: SAA1099 doesn't like that
/*dispatch->acquire(bbIn[0],bbIn[1],0,1);
@ -125,13 +148,13 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
bb[0]=blip_new(32768);
if (bb[0]==NULL) {
logE("not enough memory!\n");
logE("not enough memory!");
return;
}
bb[1]=blip_new(32768);
if (bb[1]==NULL) {
logE("not enough memory!\n");
logE("not enough memory!");
return;
}
@ -142,6 +165,10 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
bbInLen=32768;
switch (sys) {
case DIV_SYSTEM_YMU759:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(759,false);
break;
case DIV_SYSTEM_YM2612:
dispatch=new DivPlatformGenesis;
((DivPlatformGenesis*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0));
@ -182,6 +209,12 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_YM2610_FULL_EXT:
dispatch=new DivPlatformYM2610Ext;
break;
case DIV_SYSTEM_YM2610B:
dispatch=new DivPlatformYM2610B;
break;
case DIV_SYSTEM_YM2610B_EXT:
dispatch=new DivPlatformYM2610BExt;
break;
case DIV_SYSTEM_AMIGA:
dispatch=new DivPlatformAmiga;
break;
@ -191,6 +224,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_AY8930:
dispatch=new DivPlatformAY8930;
break;
case DIV_SYSTEM_FDS:
dispatch=new DivPlatformFDS;
break;
case DIV_SYSTEM_TIA:
dispatch=new DivPlatformTIA;
break;
@ -201,13 +237,43 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
((DivPlatformOPLL*)dispatch)->setVRC7(sys==DIV_SYSTEM_VRC7);
((DivPlatformOPLL*)dispatch)->setProperDrums(sys==DIV_SYSTEM_OPLL_DRUMS);
break;
case DIV_SYSTEM_OPL:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(1,false);
break;
case DIV_SYSTEM_OPL_DRUMS:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(1,true);
break;
case DIV_SYSTEM_OPL2:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(2,false);
break;
case DIV_SYSTEM_OPL2_DRUMS:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(2,true);
break;
case DIV_SYSTEM_OPL3:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(3,false);
break;
case DIV_SYSTEM_OPL3_DRUMS:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(3,true);
break;
case DIV_SYSTEM_OPZ:
dispatch=new DivPlatformTX81Z;
break;
case DIV_SYSTEM_SAA1099: {
int saaCore=eng->getConfInt("saaCore",0);
int saaCore=eng->getConfInt("saaCore",1);
if (saaCore<0 || saaCore>2) saaCore=0;
dispatch=new DivPlatformSAA1099;
((DivPlatformSAA1099*)dispatch)->setCore((DivSAACores)saaCore);
break;
}
case DIV_SYSTEM_PCSPKR:
dispatch=new DivPlatformPCSpeaker;
break;
case DIV_SYSTEM_LYNX:
dispatch=new DivPlatformLynx;
break;
@ -218,8 +284,35 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_SEGAPCM_COMPAT:
dispatch=new DivPlatformSegaPCM;
break;
case DIV_SYSTEM_X1_010:
dispatch=new DivPlatformX1_010;
break;
case DIV_SYSTEM_SWAN:
dispatch=new DivPlatformSwan;
break;
case DIV_SYSTEM_VERA:
dispatch=new DivPlatformVERA;
break;
case DIV_SYSTEM_BUBSYS_WSG:
dispatch=new DivPlatformBubSysWSG;
break;
case DIV_SYSTEM_N163:
dispatch=new DivPlatformN163;
break;
case DIV_SYSTEM_PET:
dispatch=new DivPlatformPET;
break;
case DIV_SYSTEM_VIC20:
dispatch=new DivPlatformVIC20;
break;
case DIV_SYSTEM_VRC6:
dispatch=new DivPlatformVRC6;
break;
case DIV_SYSTEM_MMC5:
dispatch=new DivPlatformMMC5;
break;
default:
logW("this system is not supported yet! using dummy platform.\n");
logW("this system is not supported yet! using dummy platform.");
dispatch=new DivPlatformDummy;
break;
}

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,7 @@
#include "safeWriter.h"
#include "../audio/taAudio.h"
#include "blip_buf.h"
#include <functional>
#include <thread>
#include <mutex>
#include <map>
@ -37,8 +38,15 @@
warnings+=(String("\n")+x); \
}
#define DIV_VERSION "dev60"
#define DIV_ENGINE_VERSION 60
#define BUSY_BEGIN softLocked=false; isBusy.lock();
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev81"
#define DIV_ENGINE_VERSION 81
// for imports
#define DIV_VERSION_MOD 0xff01
enum DivStatusView {
DIV_STATUS_NOTHING=0,
@ -69,17 +77,22 @@ enum DivHaltPositions {
struct DivChannelState {
std::vector<DivDelayedCommand> delayed;
int note, oldNote, pitch, portaSpeed, portaNote;
int note, oldNote, lastIns, pitch, portaSpeed, portaNote;
int volume, volSpeed, cut, rowDelay, volMax;
int delayOrder, delayRow, retrigSpeed, retrigTick;
int vibratoDepth, vibratoRate, vibratoPos, vibratoDir, vibratoFine;
int tremoloDepth, tremoloRate, tremoloPos;
unsigned char arp, arpStage, arpTicks;
bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff, arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, noteOnInhibit;
bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff;
bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, noteOnInhibit, resetArp;
int midiNote, curMidiNote, midiPitch;
bool midiAftertouch;
DivChannelState():
note(-1),
oldNote(-1),
lastIns(-1),
pitch(0),
portaSpeed(-1),
portaNote(-1),
@ -115,7 +128,12 @@ struct DivChannelState {
inPorta(false),
scheduledSlideReset(false),
shorthandPorta(false),
noteOnInhibit(false) {}
noteOnInhibit(false),
resetArp(false),
midiNote(-1),
curMidiNote(-1),
midiPitch(-1),
midiAftertouch(false) {}
};
struct DivNoteEvent {
@ -136,7 +154,7 @@ struct DivDispatchContainer {
int temp[2], prevSample[2];
short* bbIn[2];
short* bbOut[2];
bool lowQuality;
bool lowQuality, dcOffCompensation;
void setRates(double gotRate);
void setQuality(bool lowQual);
@ -154,7 +172,8 @@ struct DivDispatchContainer {
prevSample{0,0},
bbIn{NULL,NULL},
bbOut{NULL,NULL},
lowQuality(false) {}
lowQuality(false),
dcOffCompensation(false) {}
};
class DivEngine {
@ -178,8 +197,16 @@ class DivEngine {
bool halted;
bool forceMono;
bool cmdStreamEnabled;
int ticks, curRow, curOrder, remainingLoops, nextSpeed, divider;
int cycles, clockDrift, stepPlay;
bool softLocked;
bool firstTick;
bool skipping;
bool midiIsDirect;
int softLockCount;
int ticks, curRow, curOrder, remainingLoops, nextSpeed;
double divider;
int cycles;
double clockDrift;
int stepPlay;
int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, totalCmds, lastCmds, cmdsPerSecond, globalPitch;
unsigned char extValue;
unsigned char speed1, speed2;
@ -191,12 +218,14 @@ class DivEngine {
std::map<String,String> conf;
std::queue<DivNoteEvent> pendingNotes;
bool isMuted[DIV_MAX_CHANS];
std::mutex isBusy;
std::mutex isBusy, saveLock;
String configPath;
String configFile;
String lastError;
String warnings;
std::vector<String> audioDevs;
std::vector<String> midiIns;
std::vector<String> midiOuts;
std::vector<DivCommand> cmdStream;
struct SamplePreview {
@ -210,6 +239,9 @@ class DivEngine {
} sPreview;
short vibTable[64];
int reversePitchTable[4096];
int pitchTable[4096];
int midiBaseChan;
blip_buffer_t* samp_bb;
size_t samp_bbInLen;
@ -223,8 +255,13 @@ class DivEngine {
size_t totalProcessed;
DivSystem systemFromFile(unsigned char val);
unsigned char systemToFile(DivSystem val);
// MIDI stuff
std::function<int(const TAMidiMessage&)> midiCallback=[](const TAMidiMessage&) -> int {return -2;};
DivSystem systemFromFileFur(unsigned char val);
unsigned char systemToFileFur(DivSystem val);
DivSystem systemFromFileDMF(unsigned char val);
unsigned char systemToFileDMF(DivSystem val);
int dispatchCmd(DivCommand c);
void processRow(int i, bool afterDelay);
void nextOrder();
@ -235,12 +272,19 @@ class DivEngine {
bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal);
bool perSystemPostEffect(int ch, unsigned char effect, unsigned char effectVal);
void recalcChans();
void renderSamples();
void reset();
void playSub(bool preserveDrift, int goalRow=0);
bool loadDMF(unsigned char* file, size_t len);
bool loadFur(unsigned char* file, size_t len);
bool loadMod(unsigned char* file, size_t len);
void loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadTFI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadVGI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadS3I(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadSBI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
bool initAudioBackend();
bool deinitAudioBackend();
@ -255,6 +299,7 @@ class DivEngine {
bool keyHit[DIV_MAX_CHANS];
float* oscBuf[2];
float oscSize;
int oscReadPos, oscWritePos;
void runExportThread();
void nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size);
@ -262,18 +307,19 @@ class DivEngine {
DivWavetable* getWave(int index);
DivSample* getSample(int index);
// start fresh
void createNew();
void createNew(const int* description);
// load a file.
bool load(unsigned char* f, size_t length);
// save as .dmf.
SafeWriter* saveDMF(unsigned char version);
// save as .fur.
SafeWriter* saveFur();
// if notPrimary is true then the song will not be altered
SafeWriter* saveFur(bool notPrimary=false);
// build a ROM file (TODO).
// specify system to build ROM for.
SafeWriter* buildROM(int sys);
// dump to VGM.
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true);
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171);
// export to an audio file
bool saveAudio(const char* path, int loops, DivAudioExportModes mode);
// wait for audio export to finish
@ -285,8 +331,8 @@ class DivEngine {
// notify wavetable change
void notifyWaveChange(int wave);
// returns whether a system is VGM compatible
bool isVGMExportable(DivSystem which);
// returns the minimum VGM version which may carry the specified system, or 0 if none.
int minVGMVersion(DivSystem which);
// save config
bool saveConf();
@ -309,7 +355,7 @@ class DivEngine {
void setConf(String key, String value);
// calculate base frequency/period
int calcBaseFreq(double clock, double divider, int note, bool period);
double calcBaseFreq(double clock, double divider, int note, bool period);
// calculate frequency/period
int calcFreq(int base, int pitch, bool period=false, int octave=0);
@ -426,10 +472,10 @@ class DivEngine {
unsigned char getSpeed2();
// get Hz
int getHz();
float getHz();
// get current Hz
int getCurHz();
float getCurHz();
// get time
int getTotalTicks(); // 1/1000000th of a second
@ -459,8 +505,12 @@ class DivEngine {
// add instrument
int addInstrument(int refChan=0);
// add instrument from file
bool addInstrumentFromFile(const char* path);
// add instrument from pointer
int addInstrumentPtr(DivInstrument* which);
// get instrument from file
// if the returned vector is empty then there was an error.
std::vector<DivInstrument*> instrumentFromFile(const char* path);
// delete instrument
void delInstrument(int index);
@ -478,7 +528,7 @@ class DivEngine {
int addSample();
// add sample from file
bool addSampleFromFile(const char* path);
int addSampleFromFile(const char* path);
// delete sample
void delSample(int index);
@ -514,6 +564,9 @@ class DivEngine {
// stop note
void noteOff(int chan);
void autoNoteOn(int chan, int ins, int note, int vol=-1);
void autoNoteOff(int chan, int note, int vol=-1);
// go to order
void setOrder(unsigned char order);
@ -521,7 +574,7 @@ class DivEngine {
void setSysFlags(int system, unsigned int flags, bool restart);
// set Hz
void setSongRate(int hz, bool pal);
void setSongRate(float hz, bool pal);
// set remaining loops. -1 means loop forever.
void setLoops(int loops);
@ -550,6 +603,12 @@ class DivEngine {
// get available audio devices
std::vector<String>& getAudioDevices();
// get available MIDI inputs
std::vector<String>& getMidiIns();
// get available MIDI inputs
std::vector<String>& getMidiOuts();
// rescan audio devices
void rescanAudioDevices();
@ -577,6 +636,9 @@ class DivEngine {
// get register cheatsheet
const char** getRegisterSheet(int sys);
// UNSAFE render samples - only execute when locked
void renderSamples();
// public render samples
void renderSamplesP();
@ -604,6 +666,25 @@ class DivEngine {
// switch master
bool switchMaster();
// set MIDI base channel
void setMidiBaseChan(int chan);
// set MIDI direct channel map
void setMidiDirect(bool value);
// set MIDI input callback
// if the specified function returns -2, note feedback will be inhibited.
void setMidiCallback(std::function<int(const TAMidiMessage&)> what);
// perform secure/sync operation
void synchronized(const std::function<void()>& what);
// perform secure/sync song operation
void lockSave(const std::function<void()>& what);
// perform secure/sync song operation (and lock audio too)
void lockEngine(const std::function<void()>& what);
// get audio desc want
TAAudioDesc& getAudioDescWant();
@ -632,6 +713,8 @@ class DivEngine {
size_t qsoundAMemLen;
unsigned char* dpcmMem;
size_t dpcmMemLen;
unsigned char* x1_010Mem;
size_t x1_010MemLen;
DivEngine():
output(NULL),
@ -651,6 +734,11 @@ class DivEngine {
halted(false),
forceMono(false),
cmdStreamEnabled(false),
softLocked(false),
firstTick(false),
skipping(false),
midiIsDirect(false),
softLockCount(0),
ticks(0),
curRow(0),
curOrder(0),
@ -674,6 +762,7 @@ class DivEngine {
view(DIV_STATUS_NOTHING),
haltOn(DIV_HALT_NONE),
audioEngine(DIV_AUDIO_NULL),
midiBaseChan(0),
samp_bbInLen(0),
samp_temp(0),
samp_prevSample(0),
@ -685,6 +774,8 @@ class DivEngine {
totalProcessed(0),
oscBuf{NULL,NULL},
oscSize(1),
oscReadPos(0),
oscWritePos(0),
adpcmAMem(NULL),
adpcmAMemLen(0),
adpcmBMem(NULL),

File diff suppressed because it is too large Load Diff

810
src/engine/fileOpsIns.cpp Normal file
View File

@ -0,0 +1,810 @@
/**
* 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 "../fileutils.h"
#include <fmt/printf.h>
enum DivInsFormats {
DIV_INSFORMAT_DMP,
DIV_INSFORMAT_TFI,
DIV_INSFORMAT_VGI,
DIV_INSFORMAT_FTI,
DIV_INSFORMAT_BTI,
DIV_INSFORMAT_S3I,
DIV_INSFORMAT_SBI,
DIV_INSFORMAT_OPM
};
void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
DivInstrument* ins=new DivInstrument;
// this is a ridiculous mess
unsigned char version=0;
unsigned char sys=0;
try {
reader.seek(0,SEEK_SET);
version=reader.readC();
logD(".dmp version %d",version);
} catch (EndOfFileException& e) {
lastError="premature end of file";
logE("premature end of file!");
delete ins;
return;
}
if (version>11) {
lastError="unknown instrument version!";
delete ins;
return;
}
ins->name=stripPath;
if (version>=11) { // 1.0
try {
sys=reader.readC();
switch (sys) {
case 1: // YMU759
ins->type=DIV_INS_FM;
logD("instrument type is YMU759");
break;
case 2: // Genesis
ins->type=DIV_INS_FM;
logD("instrument type is Genesis");
break;
case 3: // SMS
ins->type=DIV_INS_STD;
logD("instrument type is SMS");
break;
case 4: // Game Boy
ins->type=DIV_INS_GB;
logD("instrument type is Game Boy");
break;
case 5: // PC Engine
ins->type=DIV_INS_PCE;
logD("instrument type is PC Engine");
break;
case 6: // NES
ins->type=DIV_INS_STD;
logD("instrument type is NES");
break;
case 7: case 0x17: // C64
ins->type=DIV_INS_C64;
logD("instrument type is C64");
break;
case 8: // Arcade
ins->type=DIV_INS_FM;
logD("instrument type is Arcade");
break;
default:
logD("instrument type is unknown");
lastError="unknown instrument type!";
delete ins;
return;
break;
}
} catch (EndOfFileException& e) {
lastError="premature end of file";
logE("premature end of file!");
delete ins;
return;
}
}
try {
bool mode=true;
if (version>1) {
mode=reader.readC();
logD("instrument mode is %d",mode);
if (mode==0) {
if (version<11) {
ins->type=DIV_INS_STD;
}
} else {
ins->type=DIV_INS_FM;
}
} else {
ins->type=DIV_INS_FM;
}
if (mode) { // FM
logD("reading FM data...");
if (version<10) {
if (version>1) {
// bullcrap! no way to determine the instrument type other than a vague FM/STD!
if (reader.size()==51) {
reader.readC();
ins->fm.ops=4;
} else {
ins->fm.ops=reader.readC()?4:2;
}
} else {
ins->fm.ops=reader.readC()?2:4;
}
} else {
ins->fm.ops=4;
}
if (version>1) { // HELP! in which version of the format did we start storing FMS!
ins->fm.fms=reader.readC();
}
ins->fm.fb=reader.readC();
ins->fm.alg=reader.readC();
// DITTO
if (sys!=1) ins->fm.ams=reader.readC();
for (int j=0; j<ins->fm.ops; j++) {
logD("OP%d is at %d",j,reader.tell());
ins->fm.op[j].mult=reader.readC();
ins->fm.op[j].tl=reader.readC();
ins->fm.op[j].ar=reader.readC();
ins->fm.op[j].dr=reader.readC();
ins->fm.op[j].sl=reader.readC();
ins->fm.op[j].rr=reader.readC();
ins->fm.op[j].am=reader.readC();
// what the hell how do I tell!
if (sys==1) { // YMU759
ins->fm.op[j].ws=reader.readC();
ins->fm.op[j].ksl=reader.readC();
ins->fm.op[j].vib=reader.readC();
ins->fm.op[j].egt=reader.readC();
ins->fm.op[j].sus=reader.readC();
ins->fm.op[j].ksr=reader.readC();
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();
}
}
} else { // STD
logD("reading STD data...");
if (ins->type!=DIV_INS_GB) {
ins->std.volMacro.len=reader.readC();
if (version>5) {
for (int i=0; i<ins->std.volMacro.len; i++) {
ins->std.volMacro.val[i]=reader.readI();
}
} else {
for (int i=0; i<ins->std.volMacro.len; i++) {
ins->std.volMacro.val[i]=reader.readC();
}
}
if (version<11) for (int i=0; i<ins->std.volMacro.len; i++) {
if (ins->std.volMacro.val[i]>15 && ins->type==DIV_INS_STD) ins->type=DIV_INS_PCE;
}
if (ins->std.volMacro.len>0) {
ins->std.volMacro.open=true;
ins->std.volMacro.loop=reader.readC();
} else {
ins->std.volMacro.open=false;
}
}
ins->std.arpMacro.len=reader.readC();
if (version>5) {
for (int i=0; i<ins->std.arpMacro.len; i++) {
ins->std.arpMacro.val[i]=reader.readI();
}
} else {
for (int i=0; i<ins->std.arpMacro.len; i++) {
ins->std.arpMacro.val[i]=reader.readC();
}
}
if (ins->std.arpMacro.len>0) {
ins->std.arpMacro.open=true;
ins->std.arpMacro.loop=reader.readC();
} else {
ins->std.arpMacro.open=false;
}
if (version>8) { // TODO: when?
ins->std.arpMacro.mode=reader.readC();
}
ins->std.dutyMacro.len=reader.readC();
if (version>5) {
for (int i=0; i<ins->std.dutyMacro.len; i++) {
ins->std.dutyMacro.val[i]=reader.readI();
}
} else {
for (int i=0; i<ins->std.dutyMacro.len; i++) {
ins->std.dutyMacro.val[i]=reader.readC();
}
}
if (ins->std.dutyMacro.len>0) {
ins->std.dutyMacro.open=true;
ins->std.dutyMacro.loop=reader.readC();
} else {
ins->std.dutyMacro.open=false;
}
ins->std.waveMacro.len=reader.readC();
if (version>5) {
for (int i=0; i<ins->std.waveMacro.len; i++) {
ins->std.waveMacro.val[i]=reader.readI();
}
} else {
for (int i=0; i<ins->std.waveMacro.len; i++) {
ins->std.waveMacro.val[i]=reader.readC();
}
}
if (ins->std.waveMacro.len>0) {
ins->std.waveMacro.open=true;
ins->std.waveMacro.loop=reader.readC();
} else {
ins->std.waveMacro.open=false;
}
if (ins->type==DIV_INS_C64) {
ins->c64.triOn=reader.readC();
ins->c64.sawOn=reader.readC();
ins->c64.pulseOn=reader.readC();
ins->c64.noiseOn=reader.readC();
ins->c64.a=reader.readC();
ins->c64.d=reader.readC();
ins->c64.s=reader.readC();
ins->c64.r=reader.readC();
ins->c64.duty=(reader.readC()*4095)/100;
ins->c64.ringMod=reader.readC();
ins->c64.oscSync=reader.readC();
ins->c64.toFilter=reader.readC();
if (version<0x07) { // TODO: UNSURE
ins->c64.volIsCutoff=reader.readI();
} else {
ins->c64.volIsCutoff=reader.readC();
}
ins->c64.initFilter=reader.readC();
ins->c64.res=reader.readC();
ins->c64.cut=(reader.readC()*2047)/100;
ins->c64.hp=reader.readC();
ins->c64.bp=reader.readC();
ins->c64.lp=reader.readC();
ins->c64.ch3off=reader.readC();
}
if (ins->type==DIV_INS_GB) {
ins->gb.envVol=reader.readC();
ins->gb.envDir=reader.readC();
ins->gb.envLen=reader.readC();
ins->gb.soundLen=reader.readC();
}
}
} catch (EndOfFileException& e) {
lastError="premature end of file";
logE("premature end of file!");
delete ins;
return;
}
ret.push_back(ins);
}
void DivEngine::loadTFI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
DivInstrument* ins=new DivInstrument;
try {
reader.seek(0,SEEK_SET);
ins->type=DIV_INS_FM;
ins->name=stripPath;
ins->fm.alg=reader.readC();
ins->fm.fb=reader.readC();
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=ins->fm.op[i];
op.mult=reader.readC();
op.dt=reader.readC();
op.tl=reader.readC();
op.rs=reader.readC();
op.ar=reader.readC();
op.dr=reader.readC();
op.d2r=reader.readC();
op.rr=reader.readC();
op.sl=reader.readC();
op.ssgEnv=reader.readC();
}
} catch (EndOfFileException& e) {
lastError="premature end of file";
logE("premature end of file!");
delete ins;
return;
}
ret.push_back(ins);
}
void DivEngine::loadVGI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
DivInstrument* ins=new DivInstrument;
try {
reader.seek(0,SEEK_SET);
ins->type=DIV_INS_FM;
ins->name=stripPath;
ins->fm.alg=reader.readC();
ins->fm.fb=reader.readC();
unsigned char fmsams=reader.readC();
ins->fm.fms=fmsams&7;
ins->fm.ams=fmsams>>4;
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=ins->fm.op[i];
op.mult=reader.readC();
op.dt=reader.readC();
op.tl=reader.readC();
op.rs=reader.readC();
op.ar=reader.readC();
op.dr=reader.readC();
if (op.dr&0x80) {
op.am=1;
op.dr&=0x7f;
}
op.d2r=reader.readC();
op.rr=reader.readC();
op.sl=reader.readC();
op.ssgEnv=reader.readC();
}
} catch (EndOfFileException& e) {
lastError="premature end of file";
logE("premature end of file!");
delete ins;
return;
}
ret.push_back(ins);
}
void DivEngine::loadS3I(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
DivInstrument* ins=new DivInstrument;
try {
reader.seek(0, SEEK_SET);
uint8_t s3i_type = reader.readC();
if (s3i_type >= 2) {
ins->type = DIV_INS_OPL;
// skip internal filename - we'll use the long name description
reader.seek(12, SEEK_CUR);
// skip reserved bytes
reader.seek(3, SEEK_CUR);
// 12-byte opl value
uint8_t s3i_Mcharacteristics = reader.readC();
uint8_t s3i_Ccharacteristics = reader.readC();
uint8_t s3i_Mscaling_output = reader.readC();
uint8_t s3i_Cscaling_output = reader.readC();
uint8_t s3i_Meg_AD = reader.readC();
uint8_t s3i_Ceg_AD = reader.readC();
uint8_t s3i_Meg_SR = reader.readC();
uint8_t s3i_Ceg_SR = reader.readC();
uint8_t s3i_Mwave = reader.readC();
uint8_t s3i_Cwave = reader.readC();
uint8_t s3i_FeedConnect = reader.readC();
DivInstrumentFM::Operator& opM = ins->fm.op[0];
DivInstrumentFM::Operator& opC = ins->fm.op[1];
ins->fm.ops = 2;
opM.mult = s3i_Mcharacteristics & 0xF;
opM.ksr = ((s3i_Mcharacteristics >> 4) & 0x1);
opM.sus = ((s3i_Mcharacteristics >> 5) & 0x1);
opM.vib = ((s3i_Mcharacteristics >> 6) & 0x1);
opM.am = ((s3i_Mcharacteristics >> 7) & 0x1);
opM.tl = s3i_Mscaling_output & 0x3F;
opM.ksl = ((s3i_Mscaling_output >> 6) & 0x3);
opM.ar = ((s3i_Meg_AD >> 4) & 0xF);
opM.dr = (s3i_Meg_AD & 0xF);
opM.rr = (s3i_Meg_SR & 0xF);
opM.sl = ((s3i_Meg_SR >> 4) & 0xF);
opM.ws = s3i_Mwave;
ins->fm.alg = (s3i_FeedConnect & 0x1);
ins->fm.fb = ((s3i_FeedConnect >> 1) & 0x7);
opC.mult = s3i_Ccharacteristics & 0xF;
opC.ksr = ((s3i_Ccharacteristics >> 4) & 0x1);
opC.sus = ((s3i_Ccharacteristics >> 5) & 0x1);
opC.vib = ((s3i_Ccharacteristics >> 6) & 0x1);
opC.am = ((s3i_Ccharacteristics >> 7) & 0x1);
opC.tl = s3i_Cscaling_output & 0x3F;
opC.ksl = ((s3i_Cscaling_output >> 6) & 0x3);
opC.ar = ((s3i_Ceg_AD >> 4) & 0xF);
opC.dr = (s3i_Ceg_AD & 0xF);
opC.rr = (s3i_Ceg_SR & 0xF);
opC.sl = ((s3i_Ceg_SR >> 4) & 0xF);
opC.ws = s3i_Cwave;
// Skip more stuff we don't need
reader.seek(21, SEEK_CUR);
} else {
logE("S3I PCM samples currently not supported.");
}
ins->name = reader.readString(28);
int s3i_signature = reader.readI();
if (s3i_signature != 0x49524353) {
logW("S3I signature invalid.");
};
} catch (EndOfFileException& e) {
lastError = "premature end of file";
logE("premature end of file!");
delete ins;
return;
}
ret.push_back(ins);
}
void DivEngine::loadSBI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
DivInstrument* ins=new DivInstrument;
try {
reader.seek(0, SEEK_SET);
ins->type = DIV_INS_OPL;
int sbi_header = reader.readI();
// SBI header determines format
bool is_2op = (sbi_header == 0x1A494253); // SBI\x1A
bool is_4op = (sbi_header == 0x1A504F34); // 4OP\x1A
bool is_6op = (sbi_header == 0x1A504F36); // 6OP\x1A - Freq Monster 801-specific
// 32-byte null terminated instrument name
ins->name = reader.readString(32);
// 2op SBI
uint8_t sbi_Mcharacteristics = reader.readC();
uint8_t sbi_Ccharacteristics = reader.readC();
uint8_t sbi_Mscaling_output = reader.readC();
uint8_t sbi_Cscaling_output = reader.readC();
uint8_t sbi_Meg_AD = reader.readC();
uint8_t sbi_Ceg_AD = reader.readC();
uint8_t sbi_Meg_SR = reader.readC();
uint8_t sbi_Ceg_SR = reader.readC();
uint8_t sbi_Mwave = reader.readC();
uint8_t sbi_Cwave = reader.readC();
uint8_t sbi_FeedConnect = reader.readC();
// 4op SBI
uint8_t sbi_M4characteristics;
uint8_t sbi_C4characteristics;
uint8_t sbi_M4scaling_output;
uint8_t sbi_C4scaling_output;
uint8_t sbi_M4eg_AD;
uint8_t sbi_C4eg_AD;
uint8_t sbi_M4eg_SR;
uint8_t sbi_C4eg_SR;
uint8_t sbi_M4wave;
uint8_t sbi_C4wave;
uint8_t sbi_4opConnect;
if (is_2op) {
DivInstrumentFM::Operator& opM = ins->fm.op[0];
DivInstrumentFM::Operator& opC = ins->fm.op[1];
ins->fm.ops = 2;
opM.mult = sbi_Mcharacteristics & 0xF;
opM.ksr = ((sbi_Mcharacteristics >> 4) & 0x1);
opM.sus = ((sbi_Mcharacteristics >> 5) & 0x1);
opM.vib = ((sbi_Mcharacteristics >> 6) & 0x1);
opM.am = ((sbi_Mcharacteristics >> 7) & 0x1);
opM.tl = sbi_Mscaling_output & 0x3F;
opM.ksl = ((sbi_Mscaling_output >> 6) & 0x3);
opM.ar = ((sbi_Meg_AD >> 4) & 0xF);
opM.dr = (sbi_Meg_AD & 0xF);
opM.rr = (sbi_Meg_SR & 0xF);
opM.sl = ((sbi_Meg_SR >> 4) & 0xF);
opM.ws = sbi_Mwave;
ins->fm.alg = (sbi_FeedConnect & 0x1);
ins->fm.fb = ((sbi_FeedConnect >> 1) & 0x7);
opC.mult = sbi_Ccharacteristics & 0xF;
opC.ksr = ((sbi_Ccharacteristics >> 4) & 0x1);
opC.sus = ((sbi_Ccharacteristics >> 5) & 0x1);
opC.vib = ((sbi_Ccharacteristics >> 6) & 0x1);
opC.am = ((sbi_Ccharacteristics >> 7) & 0x1);
opC.tl = sbi_Cscaling_output & 0x3F;
opC.ksl = ((sbi_Cscaling_output >> 6) & 0x3);
opC.ar = ((sbi_Ceg_AD >> 4) & 0xF);
opC.dr = (sbi_Ceg_AD & 0xF);
opC.rr = (sbi_Ceg_SR & 0xF);
opC.sl = ((sbi_Ceg_SR >> 4) & 0xF);
opC.ws = sbi_Cwave;
// Ignore rest of file - rest is 'reserved padding'.
reader.seek(0, SEEK_END);
}
if (is_4op || is_6op) {
// Operator placement is different so need to place in correct registers.
// Note: 6op is an unofficial extension of 4op SBIs by Darron Broad (Freq Monster 801).
// We'll only use the 4op portion here for pure OPL3.
DivInstrumentFM::Operator& opM = ins->fm.op[0];
DivInstrumentFM::Operator& opC = ins->fm.op[2];
DivInstrumentFM::Operator& opM4 = ins->fm.op[1];
DivInstrumentFM::Operator& opC4 = ins->fm.op[3];
ins->fm.ops = 4;
sbi_M4characteristics = reader.readC();
sbi_C4characteristics = reader.readC();
sbi_M4scaling_output = reader.readC();
sbi_C4scaling_output = reader.readC();
sbi_M4eg_AD = reader.readC();
sbi_C4eg_AD = reader.readC();
sbi_M4eg_SR = reader.readC();
sbi_C4eg_SR = reader.readC();
sbi_M4wave = reader.readC();
sbi_C4wave = reader.readC();
sbi_4opConnect = reader.readC();
ins->fm.alg = (sbi_FeedConnect & 0x1) | ((sbi_4opConnect & 0x1) << 1);
ins->fm.fb = ((sbi_FeedConnect >> 1) & 0x7);
opM.mult = sbi_Mcharacteristics & 0xF;
opM.ksr = ((sbi_Mcharacteristics >> 4) & 0x1);
opM.sus = ((sbi_Mcharacteristics >> 5) & 0x1);
opM.vib = ((sbi_Mcharacteristics >> 6) & 0x1);
opM.am = ((sbi_Mcharacteristics >> 7) & 0x1);
opM.tl = sbi_Mscaling_output & 0x3F;
opM.ksl = ((sbi_Mscaling_output >> 6) & 0x3);
opM.ar = ((sbi_Meg_AD >> 4) & 0xF);
opM.dr = (sbi_Meg_AD & 0xF);
opM.rr = (sbi_Meg_SR & 0xF);
opM.sl = ((sbi_Meg_SR >> 4) & 0xF);
opM.ws = sbi_Mwave;
opC.mult = sbi_Ccharacteristics & 0xF;
opC.ksr = ((sbi_Ccharacteristics >> 4) & 0x1);
opC.sus = ((sbi_Ccharacteristics >> 5) & 0x1);
opC.vib = ((sbi_Ccharacteristics >> 6) & 0x1);
opC.am = ((sbi_Ccharacteristics >> 7) & 0x1);
opC.tl = sbi_Cscaling_output & 0x3F;
opC.ksl = ((sbi_Cscaling_output >> 6) & 0x3);
opC.ar = ((sbi_Ceg_AD >> 4) & 0xF);
opC.dr = (sbi_Ceg_AD & 0xF);
opC.rr = (sbi_Ceg_SR & 0xF);
opC.sl = ((sbi_Ceg_SR >> 4) & 0xF);
opC.ws = sbi_Cwave;
opM4.mult = sbi_M4characteristics & 0xF;
opM4.ksr = ((sbi_M4characteristics >> 4) & 0x1);
opM4.sus = ((sbi_M4characteristics >> 5) & 0x1);
opM4.vib = ((sbi_M4characteristics >> 6) & 0x1);
opM4.am = ((sbi_M4characteristics >> 7) & 0x1);
opM4.tl = sbi_M4scaling_output & 0x3F;
opM4.ksl = ((sbi_M4scaling_output >> 6) & 0x3);
opM4.ar = ((sbi_M4eg_AD >> 4) & 0xF);
opM4.dr = (sbi_M4eg_AD & 0xF);
opM4.rr = (sbi_M4eg_SR & 0xF);
opM4.sl = ((sbi_M4eg_SR >> 4) & 0xF);
opM4.ws = sbi_M4wave;
opC4.mult = sbi_C4characteristics & 0xF;
opC4.ksr = ((sbi_C4characteristics >> 4) & 0x1);
opC4.sus = ((sbi_C4characteristics >> 5) & 0x1);
opC4.vib = ((sbi_C4characteristics >> 6) & 0x1);
opC4.am = ((sbi_C4characteristics >> 7) & 0x1);
opC4.tl = sbi_C4scaling_output & 0x3F;
opC4.ksl = ((sbi_C4scaling_output >> 6) & 0x3);
opC4.ar = ((sbi_C4eg_AD >> 4) & 0xF);
opC4.dr = (sbi_C4eg_AD & 0xF);
opC4.rr = (sbi_C4eg_SR & 0xF);
opC4.sl = ((sbi_C4eg_SR >> 4) & 0xF);
opC4.ws = sbi_C4wave;
// Ignore rest of file once we've read in all we need.
// Note: Freq Monster 801 adds a ton of other additional fields irrelevant to chip registers.
reader.seek(0, SEEK_END);
}
} catch (EndOfFileException& e) {
lastError = "premature end of file";
logE("premature end of file!");
delete ins;
return;
}
ret.push_back(ins);
}
void DivEngine::loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
DivInstrument* ins[128];
memset(ins,0,128*sizeof(void*));
try {
String line;
} catch (EndOfFileException& e) {
lastError="premature end of file";
logE("premature end of file!");
return;
}
}
std::vector<DivInstrument*> DivEngine::instrumentFromFile(const char* path) {
std::vector<DivInstrument*> ret;
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;
}
}
FILE* f=ps_fopen(path,"rb");
if (f==NULL) {
lastError=strerror(errno);
return ret;
}
unsigned char* buf;
ssize_t len;
if (fseek(f,0,SEEK_END)!=0) {
lastError=strerror(errno);
fclose(f);
return ret;
}
len=ftell(f);
if (len<0) {
lastError=strerror(errno);
fclose(f);
return ret;
}
if (len==0) {
lastError=strerror(errno);
fclose(f);
return ret;
}
if (fseek(f,0,SEEK_SET)!=0) {
lastError=strerror(errno);
fclose(f);
return ret;
}
buf=new unsigned char[len];
if (fread(buf,1,len,f)!=(size_t)len) {
logW("did not read entire instrument file buffer!");
lastError="did not read entire instrument file!";
delete[] buf;
return ret;
}
fclose(f);
SafeReader reader=SafeReader(buf,len);
unsigned char magic[16];
bool isFurnaceInstr=false;
try {
reader.read(magic,16);
if (memcmp("-Furnace instr.-",magic,16)==0) {
isFurnaceInstr=true;
}
} catch (EndOfFileException& e) {
reader.seek(0,SEEK_SET);
}
if (isFurnaceInstr) {
DivInstrument* ins=new DivInstrument;
try {
short version=reader.readS();
reader.readS(); // reserved
if (version>DIV_ENGINE_VERSION) {
warnings="this instrument is made with a more recent version of Furnace!";
}
unsigned int dataPtr=reader.readI();
reader.seek(dataPtr,SEEK_SET);
if (ins->readInsData(reader,version)!=DIV_DATA_SUCCESS) {
lastError="invalid instrument header/data!";
delete ins;
delete[] buf;
return ret;
} else {
ret.push_back(ins);
}
} catch (EndOfFileException& e) {
lastError="premature end of file";
logE("premature end of file!");
delete ins;
delete[] buf;
return ret;
}
} else { // read as a different format
const char* ext=strrchr(path,'.');
DivInsFormats format=DIV_INSFORMAT_DMP;
if (ext!=NULL) {
String extS;
for (; *ext; ext++) {
char i=*ext;
if (i>='A' && i<='Z') {
i+='a'-'A';
}
extS+=i;
}
if (extS==String(".dmp")) {
format=DIV_INSFORMAT_DMP;
} else if (extS==String(".tfi")) {
format=DIV_INSFORMAT_TFI;
} else if (extS==String(".vgi")) {
format=DIV_INSFORMAT_VGI;
} else if (extS==String(".fti")) {
format=DIV_INSFORMAT_FTI;
} else if (extS==String(".bti")) {
format=DIV_INSFORMAT_BTI;
} else if (extS==String(".s3i")) {
format=DIV_INSFORMAT_S3I;
} else if (extS==String(".sbi")) {
format=DIV_INSFORMAT_SBI;
} else if (extS==String(".opm")) {
format=DIV_INSFORMAT_OPM;
}
}
switch (format) {
case DIV_INSFORMAT_DMP: {
loadDMP(reader,ret,stripPath);
break;
}
case DIV_INSFORMAT_TFI:
loadTFI(reader,ret,stripPath);
break;
case DIV_INSFORMAT_VGI:
loadVGI(reader,ret,stripPath);
break;
case DIV_INSFORMAT_FTI: // TODO
break;
case DIV_INSFORMAT_BTI: // TODO
break;
case DIV_INSFORMAT_OPM: // TODO
break;
case DIV_INSFORMAT_S3I:
loadS3I(reader,ret,stripPath);
break;
case DIV_INSFORMAT_SBI:
loadSBI(reader,ret,stripPath);
break;
}
if (reader.tell()<reader.size()) {
addWarning("https://github.com/tildearrow/furnace/issues/84");
addWarning("there is more data at the end of the file! what happened here!");
addWarning(fmt::sprintf("exactly %d bytes, if you are curious",reader.size()-reader.tell()));
}
}
return ret;
}

87
src/engine/filter.cpp Normal file
View File

@ -0,0 +1,87 @@
/**
* 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 <math.h>
#include "filter.h"
#include "../ta-log.h"
float* DivFilterTables::cubicTable=NULL;
float* DivFilterTables::sincTable=NULL;
float* DivFilterTables::sincIntegralTable=NULL;
// portions from Schism Tracker (scripts/lutgen.c)
// licensed under same license as this program.
float* DivFilterTables::getCubicTable() {
if (cubicTable==NULL) {
logD("initializing cubic spline table.");
cubicTable=new float[4096];
for (int i=0; i<1024; i++) {
float x=(float)i/1024.0;
cubicTable[(i<<2)]=-0.5*pow(x,3)+1.0*pow(x,2)-0.5*x;
cubicTable[1+(i<<2)]=1.5*pow(x,3)-2.5*pow(x,2)+1.0;
cubicTable[2+(i<<2)]=-1.5*pow(x,3)+2.0*pow(x,2)+0.5*x;
cubicTable[3+(i<<2)]=0.5*pow(x,3)-0.5*pow(x,2);
}
}
return cubicTable;
}
float* DivFilterTables:: getSincTable() {
if (sincTable==NULL) {
logD("initializing sinc table.");
sincTable=new float[65536];
sincTable[0]=1.0f;
for (int i=1; i<65536; i++) {
int mapped=((i&8191)<<3)|(i>>13);
double x=(double)i*M_PI/8192.0;
sincTable[mapped]=sin(x)/x;
}
for (int i=0; i<65536; i++) {
int mapped=((i&8191)<<3)|(i>>13);
sincTable[mapped]*=pow(cos(M_PI*(double)i/131072.0),2.0);
}
}
return sincTable;
}
float* DivFilterTables::getSincIntegralTable() {
if (sincIntegralTable==NULL) {
logD("initializing sinc integral table.");
sincIntegralTable=new float[65536];
sincIntegralTable[0]=-0.5f;
for (int i=1; i<65536; i++) {
int mapped=((i&8191)<<3)|(i>>13);
int mappedPrev=(((i-1)&8191)<<3)|((i-1)>>13);
double x=(double)i*M_PI/8192.0;
double sinc=sin(x)/x;
sincIntegralTable[mapped]=sincIntegralTable[mappedPrev]+(sinc/8192.0);
}
for (int i=0; i<65536; i++) {
int mapped=((i&8191)<<3)|(i>>13);
sincIntegralTable[mapped]*=pow(cos(M_PI*(double)i/131072.0),2.0);
}
}
return sincIntegralTable;
}

43
src/engine/filter.h Normal file
View File

@ -0,0 +1,43 @@
/**
* 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.
*/
class DivFilterTables {
public:
static float* cubicTable;
static float* sincTable;
static float* sincIntegralTable;
/**
* get a 1024x4 cubic spline table.
* @return the table.
*/
static float* getCubicTable();
/**
* get a 8192x8 one-side sine-windowed sinc table.
* @return the table.
*/
static float* getSincTable();
/**
* get a 8192x8 one-side sine-windowed sinc integral table.
* @return the table.
*/
static float* getSincIntegralTable();
};

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,10 @@
#include "dataErrors.h"
#include "../ta-utils.h"
enum DivInstrumentType {
// NOTICE!
// before adding new instrument types to this struct, please ask me first.
// absolutely zero support granted to conflicting formats.
enum DivInstrumentType: unsigned short {
DIV_INS_STD=0,
DIV_INS_FM=1,
DIV_INS_GB=2,
@ -48,14 +51,37 @@ enum DivInstrumentType {
DIV_INS_BEEPER=21,
DIV_INS_SWAN=22,
DIV_INS_MIKEY=23,
DIV_INS_VERA=24,
DIV_INS_X1_010=25,
DIV_INS_VRC6_SAW=26,
DIV_INS_MAX,
};
// FM operator structure:
// - OPN:
// - AM, AR, DR, MULT, RR, SL, TL, RS, DT, D2R, SSG-EG
// - OPM:
// - AM, AR, DR, MULT, RR, SL, TL, DT2, RS, DT, D2R
// - OPLL:
// - AM, AR, DR, MULT, RR, SL, TL, SSG-EG&8 = EG-S
// - KSL, VIB, KSR
// - OPL:
// - AM, AR, DR, MULT, RR, SL, TL, SSG-EG&8 = EG-S
// - KSL, VIB, WS (OPL2/3), KSR
// - OPZ:
// - AM, AR, DR, MULT (CRS), RR, SL, TL, DT2, RS, DT, D2R
// - WS, DVB = MULT (FINE), DAM = REV, KSL = EGShift, EGT = Fixed
struct DivInstrumentFM {
unsigned char alg, fb, fms, ams, ops, opllPreset;
unsigned char alg, fb, fms, ams, fms2, ams2, ops, opllPreset;
bool fixedDrums;
unsigned short kickFreq, snareHatFreq, tomTopFreq;
struct Operator {
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
unsigned char dam, dvb, egt, ksl, sus, vib, ws, ksr; // YMU759/OPL/OPZ
Operator():
enable(true),
am(0),
ar(0),
dr(0),
@ -82,8 +108,14 @@ struct DivInstrumentFM {
fb(0),
fms(0),
ams(0),
ops(4),
opllPreset(0) {
fms2(0),
ams2(0),
ops(2),
opllPreset(0),
fixedDrums(false),
kickFreq(0x520),
snareHatFreq(0x550),
tomTopFreq(0x1c0) {
// default instrument
fb=4;
op[0].tl=42;
@ -120,152 +152,98 @@ struct DivInstrumentFM {
}
};
// this is getting out of hand
struct DivInstrumentMacro {
String name;
int val[256];
unsigned int mode;
bool open;
unsigned char len;
signed char loop;
signed char rel;
DivInstrumentMacro(String n, bool initOpen=false):
name(n),
mode(0),
open(initOpen),
len(0),
loop(-1),
rel(-1) {
memset(val,0,256*sizeof(int));
}
};
struct DivInstrumentSTD {
int volMacro[256];
int arpMacro[256];
int dutyMacro[256];
int waveMacro[256];
int pitchMacro[256];
int ex1Macro[256];
int ex2Macro[256];
int ex3Macro[256];
int algMacro[256];
int fbMacro[256];
int fmsMacro[256];
int amsMacro[256];
bool arpMacroMode;
unsigned char volMacroHeight, dutyMacroHeight, waveMacroHeight;
bool volMacroOpen, arpMacroOpen, dutyMacroOpen, waveMacroOpen;
bool pitchMacroOpen, ex1MacroOpen, ex2MacroOpen, ex3MacroOpen;
bool algMacroOpen, fbMacroOpen, fmsMacroOpen, amsMacroOpen;
unsigned char volMacroLen, arpMacroLen, dutyMacroLen, waveMacroLen;
unsigned char pitchMacroLen, ex1MacroLen, ex2MacroLen, ex3MacroLen;
unsigned char algMacroLen, fbMacroLen, fmsMacroLen, amsMacroLen;
signed char volMacroLoop, arpMacroLoop, dutyMacroLoop, waveMacroLoop;
signed char pitchMacroLoop, ex1MacroLoop, ex2MacroLoop, ex3MacroLoop;
signed char algMacroLoop, fbMacroLoop, fmsMacroLoop, amsMacroLoop;
signed char volMacroRel, arpMacroRel, dutyMacroRel, waveMacroRel;
signed char pitchMacroRel, ex1MacroRel, ex2MacroRel, ex3MacroRel;
signed char algMacroRel, fbMacroRel, fmsMacroRel, amsMacroRel;
DivInstrumentMacro volMacro;
DivInstrumentMacro arpMacro;
DivInstrumentMacro dutyMacro;
DivInstrumentMacro waveMacro;
DivInstrumentMacro pitchMacro;
DivInstrumentMacro ex1Macro;
DivInstrumentMacro ex2Macro;
DivInstrumentMacro ex3Macro;
DivInstrumentMacro algMacro;
DivInstrumentMacro fbMacro;
DivInstrumentMacro fmsMacro;
DivInstrumentMacro amsMacro;
DivInstrumentMacro panLMacro;
DivInstrumentMacro panRMacro;
DivInstrumentMacro phaseResetMacro;
DivInstrumentMacro ex4Macro;
DivInstrumentMacro ex5Macro;
DivInstrumentMacro ex6Macro;
DivInstrumentMacro ex7Macro;
DivInstrumentMacro ex8Macro;
struct OpMacro {
// ar, dr, mult, rr, sl, tl, dt2, rs, dt, d2r, ssgEnv;
unsigned char amMacro[256];
unsigned char arMacro[256];
unsigned char drMacro[256];
unsigned char multMacro[256];
unsigned char rrMacro[256];
unsigned char slMacro[256];
unsigned char tlMacro[256];
unsigned char dt2Macro[256];
unsigned char rsMacro[256];
unsigned char dtMacro[256];
unsigned char d2rMacro[256];
unsigned char ssgMacro[256];
bool amMacroOpen, arMacroOpen, drMacroOpen, multMacroOpen;
bool rrMacroOpen, slMacroOpen, tlMacroOpen, dt2MacroOpen;
bool rsMacroOpen, dtMacroOpen, d2rMacroOpen, ssgMacroOpen;
unsigned char amMacroLen, arMacroLen, drMacroLen, multMacroLen;
unsigned char rrMacroLen, slMacroLen, tlMacroLen, dt2MacroLen;
unsigned char rsMacroLen, dtMacroLen, d2rMacroLen, ssgMacroLen;
signed char amMacroLoop, arMacroLoop, drMacroLoop, multMacroLoop;
signed char rrMacroLoop, slMacroLoop, tlMacroLoop, dt2MacroLoop;
signed char rsMacroLoop, dtMacroLoop, d2rMacroLoop, ssgMacroLoop;
signed char amMacroRel, arMacroRel, drMacroRel, multMacroRel;
signed char rrMacroRel, slMacroRel, tlMacroRel, dt2MacroRel;
signed char rsMacroRel, dtMacroRel, d2rMacroRel, ssgMacroRel;
DivInstrumentMacro amMacro;
DivInstrumentMacro arMacro;
DivInstrumentMacro drMacro;
DivInstrumentMacro multMacro;
DivInstrumentMacro rrMacro;
DivInstrumentMacro slMacro;
DivInstrumentMacro tlMacro;
DivInstrumentMacro dt2Macro;
DivInstrumentMacro rsMacro;
DivInstrumentMacro dtMacro;
DivInstrumentMacro d2rMacro;
DivInstrumentMacro ssgMacro;
DivInstrumentMacro damMacro;
DivInstrumentMacro dvbMacro;
DivInstrumentMacro egtMacro;
DivInstrumentMacro kslMacro;
DivInstrumentMacro susMacro;
DivInstrumentMacro vibMacro;
DivInstrumentMacro wsMacro;
DivInstrumentMacro ksrMacro;
OpMacro():
amMacroOpen(false), arMacroOpen(false), drMacroOpen(false), multMacroOpen(false),
rrMacroOpen(false), slMacroOpen(false), tlMacroOpen(true), dt2MacroOpen(false),
rsMacroOpen(false), dtMacroOpen(false), d2rMacroOpen(false), ssgMacroOpen(false),
amMacroLen(0), arMacroLen(0), drMacroLen(0), multMacroLen(0),
rrMacroLen(0), slMacroLen(0), tlMacroLen(0), dt2MacroLen(0),
rsMacroLen(0), dtMacroLen(0), d2rMacroLen(0), ssgMacroLen(0),
amMacroLoop(-1), arMacroLoop(-1), drMacroLoop(-1), multMacroLoop(-1),
rrMacroLoop(-1), slMacroLoop(-1), tlMacroLoop(-1), dt2MacroLoop(-1),
rsMacroLoop(-1), dtMacroLoop(-1), d2rMacroLoop(-1), ssgMacroLoop(-1),
amMacroRel(-1), arMacroRel(-1), drMacroRel(-1), multMacroRel(-1),
rrMacroRel(-1), slMacroRel(-1), tlMacroRel(-1), dt2MacroRel(-1),
rsMacroRel(-1), dtMacroRel(-1), d2rMacroRel(-1), ssgMacroRel(-1) {
memset(amMacro,0,256);
memset(arMacro,0,256);
memset(drMacro,0,256);
memset(multMacro,0,256);
memset(rrMacro,0,256);
memset(slMacro,0,256);
memset(tlMacro,0,256);
memset(dt2Macro,0,256);
memset(rsMacro,0,256);
memset(dtMacro,0,256);
memset(d2rMacro,0,256);
memset(ssgMacro,0,256);
}
amMacro("am"), arMacro("ar"), drMacro("dr"), multMacro("mult"),
rrMacro("rr"), slMacro("sl"), tlMacro("tl",true), dt2Macro("dt2"),
rsMacro("rs"), dtMacro("dt"), d2rMacro("d2r"), ssgMacro("ssg"),
damMacro("dam"), dvbMacro("dvb"), egtMacro("egt"), kslMacro("ksl"),
susMacro("sus"), vibMacro("vib"), wsMacro("ws"), ksrMacro("ksr") {}
} opMacros[4];
DivInstrumentSTD():
arpMacroMode(false),
volMacroHeight(15),
dutyMacroHeight(3),
waveMacroHeight(63),
volMacroOpen(true),
arpMacroOpen(false),
dutyMacroOpen(false),
waveMacroOpen(false),
pitchMacroOpen(false),
ex1MacroOpen(false),
ex2MacroOpen(false),
ex3MacroOpen(false),
algMacroOpen(false),
fbMacroOpen(false),
fmsMacroOpen(false),
amsMacroOpen(false),
volMacroLen(0),
arpMacroLen(0),
dutyMacroLen(0),
waveMacroLen(0),
pitchMacroLen(0),
ex1MacroLen(0),
ex2MacroLen(0),
ex3MacroLen(0),
algMacroLen(0),
fbMacroLen(0),
fmsMacroLen(0),
amsMacroLen(0),
volMacroLoop(-1),
arpMacroLoop(-1),
dutyMacroLoop(-1),
waveMacroLoop(-1),
pitchMacroLoop(-1),
ex1MacroLoop(-1),
ex2MacroLoop(-1),
ex3MacroLoop(-1),
algMacroLoop(-1),
fbMacroLoop(-1),
fmsMacroLoop(-1),
amsMacroLoop(-1),
volMacroRel(-1),
arpMacroRel(-1),
dutyMacroRel(-1),
waveMacroRel(-1),
pitchMacroRel(-1),
ex1MacroRel(-1),
ex2MacroRel(-1),
ex3MacroRel(-1),
algMacroRel(-1),
fbMacroRel(-1),
fmsMacroRel(-1),
amsMacroRel(-1) {
memset(volMacro,0,256*sizeof(int));
memset(arpMacro,0,256*sizeof(int));
memset(dutyMacro,0,256*sizeof(int));
memset(waveMacro,0,256*sizeof(int));
memset(pitchMacro,0,256*sizeof(int));
memset(ex1Macro,0,256*sizeof(int));
memset(ex2Macro,0,256*sizeof(int));
memset(ex3Macro,0,256*sizeof(int));
memset(algMacro,0,256*sizeof(int));
memset(fbMacro,0,256*sizeof(int));
memset(fmsMacro,0,256*sizeof(int));
memset(amsMacro,0,256*sizeof(int));
}
volMacro("vol",true),
arpMacro("arp"),
dutyMacro("duty"),
waveMacro("wave"),
pitchMacro("pitch"),
ex1Macro("ex1"),
ex2Macro("ex2"),
ex3Macro("ex3"),
algMacro("alg"),
fbMacro("fb"),
fmsMacro("fms"),
amsMacro("ams"),
panLMacro("panL"),
panRMacro("panR"),
phaseResetMacro("phaseReset"),
ex4Macro("ex4"),
ex5Macro("ex5"),
ex6Macro("ex6"),
ex7Macro("ex7"),
ex8Macro("ex8") {}
};
struct DivInstrumentGB {
@ -314,9 +292,84 @@ struct DivInstrumentC64 {
struct DivInstrumentAmiga {
short initSample;
bool useNoteMap;
int noteFreq[120];
short noteMap[120];
DivInstrumentAmiga():
initSample(0) {}
initSample(0),
useNoteMap(false) {
memset(noteMap,-1,120*sizeof(short));
memset(noteFreq,0,120*sizeof(int));
}
};
struct DivInstrumentN163 {
int wave, wavePos, waveLen;
unsigned char waveMode;
DivInstrumentN163():
wave(-1),
wavePos(0),
waveLen(32),
waveMode(3) {}
};
struct DivInstrumentFDS {
signed char modTable[32];
int modSpeed, modDepth;
// this is here for compatibility.
bool initModTableWithFirstWave;
DivInstrumentFDS():
modSpeed(0),
modDepth(0),
initModTableWithFirstWave(false) {
memset(modTable,0,32);
}
};
enum DivWaveSynthEffects {
DIV_WS_NONE=0,
// one waveform effects
DIV_WS_INVERT,
DIV_WS_ADD,
DIV_WS_SUBTRACT,
DIV_WS_AVERAGE,
DIV_WS_PHASE,
DIV_WS_SINGLE_MAX,
// two waveform effects
DIV_WS_NONE_DUAL=128,
DIV_WS_WIPE,
DIV_WS_FADE,
DIV_WS_PING_PONG,
DIV_WS_OVERLAY,
DIV_WS_NEGATIVE_OVERLAY,
DIV_WS_PHASE_DUAL,
DIV_WS_DUAL_MAX
};
struct DivInstrumentWaveSynth {
int wave1, wave2;
unsigned char rateDivider;
unsigned char effect;
bool oneShot, enabled, global;
unsigned char speed, param1, param2, param3, param4;
DivInstrumentWaveSynth():
wave1(0),
wave2(0),
rateDivider(1),
effect(DIV_WS_NONE),
oneShot(false),
enabled(false),
global(false),
speed(0),
param1(0),
param2(0),
param3(0),
param4(0) {}
};
struct DivInstrument {
@ -328,14 +381,34 @@ struct DivInstrument {
DivInstrumentGB gb;
DivInstrumentC64 c64;
DivInstrumentAmiga amiga;
DivInstrumentN163 n163;
DivInstrumentFDS fds;
DivInstrumentWaveSynth ws;
/**
* save the instrument to a SafeWriter.
* @param w the SafeWriter in question.
*/
void putInsData(SafeWriter* w);
/**
* read instrument data in .fui format.
* @param reader the reader.
* @param version the format version.
* @return a DivDataErrors.
*/
DivDataErrors readInsData(SafeReader& reader, short version);
/**
* save this instrument to a file.
* @param path file path.
* @return whether it was successful.
*/
bool save(const char* path);
DivInstrument():
name(""),
mode(false),
type(DIV_INS_STD) {
type(DIV_INS_FM) {
}
};
#endif

View File

@ -20,65 +20,41 @@
#include "macroInt.h"
#include "instrument.h"
#define doMacro(finished,had,has,val,pos,source,sourceLen,sourceLoop,sourceRel) \
if (finished) finished=false; \
if (had!=has) { \
finished=true; \
} \
had=has; \
if (has) { \
val=source[pos++]; \
if (sourceRel>=0 && pos>sourceRel && !released) { \
if (sourceLoop<sourceLen && sourceLoop>=0 && sourceLoop<sourceRel) { \
pos=sourceLoop; \
} else { \
pos--; \
} \
} \
if (pos>=sourceLen) { \
if (sourceLoop<sourceLen && sourceLoop>=0 && (sourceLoop>=sourceRel || sourceRel>=sourceLen)) { \
pos=sourceLoop; \
} else { \
has=false; \
} \
} \
void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released) {
if (finished) {
finished=false;
}
if (had!=has) {
finished=true;
}
had=has;
if (has) {
val=source.val[pos++];
if (source.rel>=0 && pos>source.rel && !released) {
if (source.loop<source.len && source.loop>=0 && source.loop<source.rel) {
pos=source.loop;
} else {
pos--;
}
}
if (pos>=source.len) {
if (source.loop<source.len && source.loop>=0 && (source.loop>=source.rel || source.rel>=source.len)) {
pos=source.loop;
} else {
has=false;
}
}
}
}
void DivMacroInt::next() {
if (ins==NULL) return;
doMacro(finishedVol,hadVol,hasVol,vol,volPos,ins->std.volMacro,ins->std.volMacroLen,ins->std.volMacroLoop,ins->std.volMacroRel);
doMacro(finishedArp,hadArp,hasArp,arp,arpPos,ins->std.arpMacro,ins->std.arpMacroLen,ins->std.arpMacroLoop,ins->std.arpMacroRel);
doMacro(finishedDuty,hadDuty,hasDuty,duty,dutyPos,ins->std.dutyMacro,ins->std.dutyMacroLen,ins->std.dutyMacroLoop,ins->std.dutyMacroRel);
doMacro(finishedWave,hadWave,hasWave,wave,wavePos,ins->std.waveMacro,ins->std.waveMacroLen,ins->std.waveMacroLoop,ins->std.waveMacroRel);
doMacro(finishedPitch,hadPitch,hasPitch,pitch,pitchPos,ins->std.pitchMacro,ins->std.pitchMacroLen,ins->std.pitchMacroLoop,ins->std.pitchMacroRel);
doMacro(finishedEx1,hadEx1,hasEx1,ex1,ex1Pos,ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel);
doMacro(finishedEx2,hadEx2,hasEx2,ex2,ex2Pos,ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel);
doMacro(finishedEx3,hadEx3,hasEx3,ex3,ex3Pos,ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel);
doMacro(finishedAlg,hadAlg,hasAlg,alg,algPos,ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel);
doMacro(finishedFb,hadFb,hasFb,fb,fbPos,ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel);
doMacro(finishedFms,hadFms,hasFms,fms,fmsPos,ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel);
doMacro(finishedAms,hadAms,hasAms,ams,amsPos,ins->std.amsMacro,ins->std.amsMacroLen,ins->std.amsMacroLoop,ins->std.amsMacroRel);
for (int i=0; i<4; i++) {
DivInstrumentSTD::OpMacro& m=ins->std.opMacros[i];
IntOp& o=op[i];
doMacro(o.finishedAm,o.hadAm,o.hasAm,o.am,o.amPos,m.amMacro,m.amMacroLen,m.amMacroLoop,m.amMacroRel);
doMacro(o.finishedAr,o.hadAr,o.hasAr,o.ar,o.arPos,m.arMacro,m.arMacroLen,m.arMacroLoop,m.arMacroRel);
doMacro(o.finishedDr,o.hadDr,o.hasDr,o.dr,o.drPos,m.drMacro,m.drMacroLen,m.drMacroLoop,m.drMacroRel);
doMacro(o.finishedMult,o.hadMult,o.hasMult,o.mult,o.multPos,m.multMacro,m.multMacroLen,m.multMacroLoop,m.multMacroRel);
doMacro(o.finishedRr,o.hadRr,o.hasRr,o.rr,o.rrPos,m.rrMacro,m.rrMacroLen,m.rrMacroLoop,m.rrMacroRel);
doMacro(o.finishedSl,o.hadSl,o.hasSl,o.sl,o.slPos,m.slMacro,m.slMacroLen,m.slMacroLoop,m.slMacroRel);
doMacro(o.finishedTl,o.hadTl,o.hasTl,o.tl,o.tlPos,m.tlMacro,m.tlMacroLen,m.tlMacroLoop,m.tlMacroRel);
doMacro(o.finishedDt2,o.hadDt2,o.hasDt2,o.dt2,o.dt2Pos,m.dt2Macro,m.dt2MacroLen,m.dt2MacroLoop,m.dt2MacroRel);
doMacro(o.finishedRs,o.hadRs,o.hasRs,o.rs,o.rsPos,m.rsMacro,m.rsMacroLen,m.rsMacroLoop,m.rsMacroRel);
doMacro(o.finishedDt,o.hadDt,o.hasDt,o.dt,o.dtPos,m.dtMacro,m.dtMacroLen,m.dtMacroLoop,m.dtMacroRel);
doMacro(o.finishedD2r,o.hadD2r,o.hasD2r,o.d2r,o.d2rPos,m.d2rMacro,m.d2rMacroLen,m.d2rMacroLoop,m.d2rMacroRel);
doMacro(o.finishedSsg,o.hadSsg,o.hasSsg,o.ssg,o.ssgPos,m.ssgMacro,m.ssgMacroLen,m.ssgMacroLoop,m.ssgMacroRel);
// run macros
// TODO: potentially get rid of list to avoid allocations
for (size_t i=0; i<macroListLen; i++) {
if (macroList[i]!=NULL && macroSource[i]!=NULL) {
macroList[i]->doMacro(*macroSource[i],released);
}
}
}
@ -86,205 +62,159 @@ void DivMacroInt::release() {
released=true;
}
#define ADD_MACRO(m,s) \
macroList[macroListLen]=&m; \
macroSource[macroListLen++]=&s;
void DivMacroInt::init(DivInstrument* which) {
ins=which;
volPos=0;
arpPos=0;
dutyPos=0;
wavePos=0;
pitchPos=0;
ex1Pos=0;
ex2Pos=0;
ex3Pos=0;
algPos=0;
fbPos=0;
fmsPos=0;
amsPos=0;
// initialize
for (size_t i=0; i<macroListLen; i++) {
if (macroList[i]!=NULL) macroList[i]->init();
}
macroListLen=0;
released=false;
hasVol=false;
hasArp=false;
hasDuty=false;
hasWave=false;
hasPitch=false;
hasEx1=false;
hasEx2=false;
hasEx3=false;
hasAlg=false;
hasFb=false;
hasFms=false;
hasAms=false;
hadVol=false;
hadArp=false;
hadDuty=false;
hadWave=false;
hadPitch=false;
hadEx1=false;
hadEx2=false;
hadEx3=false;
hadAlg=false;
hadFb=false;
hadFms=false;
hadAms=false;
willVol=false;
willArp=false;
willDuty=false;
willWave=false;
willPitch=false;
willEx1=false;
willEx2=false;
willEx3=false;
willAlg=false;
willFb=false;
willFms=false;
willAms=false;
op[0]=IntOp();
op[1]=IntOp();
op[2]=IntOp();
op[3]=IntOp();
arpMode=false;
if (ins==NULL) return;
if (ins->std.volMacroLen>0) {
hadVol=true;
hasVol=true;
willVol=true;
// prepare common macro
if (ins->std.volMacro.len>0) {
ADD_MACRO(vol,ins->std.volMacro);
}
if (ins->std.arpMacroLen>0) {
hadArp=true;
hasArp=true;
willArp=true;
if (ins->std.arpMacro.len>0) {
ADD_MACRO(arp,ins->std.arpMacro);
}
if (ins->std.dutyMacroLen>0) {
hadDuty=true;
hasDuty=true;
willDuty=true;
if (ins->std.dutyMacro.len>0) {
ADD_MACRO(duty,ins->std.dutyMacro);
}
if (ins->std.waveMacroLen>0) {
hadWave=true;
hasWave=true;
willWave=true;
if (ins->std.waveMacro.len>0) {
ADD_MACRO(wave,ins->std.waveMacro);
}
if (ins->std.pitchMacroLen>0) {
hadPitch=true;
hasPitch=true;
willPitch=true;
if (ins->std.pitchMacro.len>0) {
ADD_MACRO(pitch,ins->std.pitchMacro);
}
if (ins->std.ex1MacroLen>0) {
hadEx1=true;
hasEx1=true;
willEx1=true;
if (ins->std.ex1Macro.len>0) {
ADD_MACRO(ex1,ins->std.ex1Macro);
}
if (ins->std.ex2MacroLen>0) {
hadEx2=true;
hasEx2=true;
willEx2=true;
if (ins->std.ex2Macro.len>0) {
ADD_MACRO(ex2,ins->std.ex2Macro);
}
if (ins->std.ex3MacroLen>0) {
hadEx3=true;
hasEx3=true;
willEx3=true;
if (ins->std.ex3Macro.len>0) {
ADD_MACRO(ex3,ins->std.ex3Macro);
}
if (ins->std.algMacroLen>0) {
hadAlg=true;
hasAlg=true;
willAlg=true;
if (ins->std.algMacro.len>0) {
ADD_MACRO(alg,ins->std.algMacro);
}
if (ins->std.fbMacroLen>0) {
hadFb=true;
hasFb=true;
willFb=true;
if (ins->std.fbMacro.len>0) {
ADD_MACRO(fb,ins->std.fbMacro);
}
if (ins->std.fmsMacroLen>0) {
hadFms=true;
hasFms=true;
willFms=true;
if (ins->std.fmsMacro.len>0) {
ADD_MACRO(fms,ins->std.fmsMacro);
}
if (ins->std.amsMacroLen>0) {
hadAms=true;
hasAms=true;
willAms=true;
if (ins->std.amsMacro.len>0) {
ADD_MACRO(ams,ins->std.amsMacro);
}
if (ins->std.arpMacroMode) {
arpMode=true;
if (ins->std.panLMacro.len>0) {
ADD_MACRO(panL,ins->std.panLMacro);
}
if (ins->std.panRMacro.len>0) {
ADD_MACRO(panR,ins->std.panRMacro);
}
if (ins->std.phaseResetMacro.len>0) {
ADD_MACRO(phaseReset,ins->std.phaseResetMacro);
}
if (ins->std.ex4Macro.len>0) {
ADD_MACRO(ex4,ins->std.ex4Macro);
}
if (ins->std.ex5Macro.len>0) {
ADD_MACRO(ex5,ins->std.ex5Macro);
}
if (ins->std.ex6Macro.len>0) {
ADD_MACRO(ex6,ins->std.ex6Macro);
}
if (ins->std.ex7Macro.len>0) {
ADD_MACRO(ex7,ins->std.ex7Macro);
}
if (ins->std.ex8Macro.len>0) {
ADD_MACRO(ex8,ins->std.ex8Macro);
}
// prepare FM operator macros
for (int i=0; i<4; i++) {
DivInstrumentSTD::OpMacro& m=ins->std.opMacros[i];
IntOp& o=op[i];
if (m.amMacro.len>0) {
ADD_MACRO(o.am,m.amMacro);
}
if (m.arMacro.len>0) {
ADD_MACRO(o.ar,m.arMacro);
}
if (m.drMacro.len>0) {
ADD_MACRO(o.dr,m.drMacro);
}
if (m.multMacro.len>0) {
ADD_MACRO(o.mult,m.multMacro);
}
if (m.rrMacro.len>0) {
ADD_MACRO(o.rr,m.rrMacro);
}
if (m.slMacro.len>0) {
ADD_MACRO(o.sl,m.slMacro);
}
if (m.tlMacro.len>0) {
ADD_MACRO(o.tl,m.tlMacro);
}
if (m.dt2Macro.len>0) {
ADD_MACRO(o.dt2,m.dt2Macro);
}
if (m.rsMacro.len>0) {
ADD_MACRO(o.rs,m.rsMacro);
}
if (m.dtMacro.len>0) {
ADD_MACRO(o.dt,m.dtMacro);
}
if (m.d2rMacro.len>0) {
ADD_MACRO(o.d2r,m.d2rMacro);
}
if (m.ssgMacro.len>0) {
ADD_MACRO(o.ssg,m.ssgMacro);
}
if (m.amMacroLen>0) {
o.hadAm=true;
o.hasAm=true;
o.willAm=true;
if (m.damMacro.len>0) {
ADD_MACRO(o.dam,m.damMacro);
}
if (m.arMacroLen>0) {
o.hadAr=true;
o.hasAr=true;
o.willAr=true;
if (m.dvbMacro.len>0) {
ADD_MACRO(o.dvb,m.dvbMacro);
}
if (m.drMacroLen>0) {
o.hadDr=true;
o.hasDr=true;
o.willDr=true;
if (m.egtMacro.len>0) {
ADD_MACRO(o.egt,m.egtMacro);
}
if (m.multMacroLen>0) {
o.hadMult=true;
o.hasMult=true;
o.willMult=true;
if (m.kslMacro.len>0) {
ADD_MACRO(o.ksl,m.kslMacro);
}
if (m.rrMacroLen>0) {
o.hadRr=true;
o.hasRr=true;
o.willRr=true;
if (m.susMacro.len>0) {
ADD_MACRO(o.sus,m.susMacro);
}
if (m.slMacroLen>0) {
o.hadSl=true;
o.hasSl=true;
o.willSl=true;
if (m.vibMacro.len>0) {
ADD_MACRO(o.vib,m.vibMacro);
}
if (m.tlMacroLen>0) {
o.hadTl=true;
o.hasTl=true;
o.willTl=true;
if (m.wsMacro.len>0) {
ADD_MACRO(o.ws,m.wsMacro);
}
if (m.dt2MacroLen>0) {
o.hadDt2=true;
o.hasDt2=true;
o.willDt2=true;
}
if (m.rsMacroLen>0) {
o.hadRs=true;
o.hasRs=true;
o.willRs=true;
}
if (m.dtMacroLen>0) {
o.hadDt=true;
o.hasDt=true;
o.willDt=true;
}
if (m.d2rMacroLen>0) {
o.hadD2r=true;
o.hasD2r=true;
o.willD2r=true;
}
if (m.ssgMacroLen>0) {
o.hadSsg=true;
o.hasSsg=true;
o.willSsg=true;
if (m.ksrMacro.len>0) {
ADD_MACRO(o.ksr,m.ksrMacro);
}
}
for (size_t i=0; i<macroListLen; i++) {
macroList[i]->prepare(*macroSource[i]);
}
}
void DivMacroInt::notifyInsDeletion(DivInstrument* which) {
if (ins==which) {
init(NULL);
}
}
}

View File

@ -22,164 +22,123 @@
#include "instrument.h"
struct DivMacroStruct {
int pos;
int val;
bool has, had, finished, will;
unsigned int mode;
void doMacro(DivInstrumentMacro& source, bool released);
void init() {
pos=mode=0;
has=had=will=false;
}
void prepare(DivInstrumentMacro& source) {
has=had=will=true;
mode=source.mode;
}
DivMacroStruct():
pos(0),
val(0),
has(false),
had(false),
finished(false),
will(false),
mode(0) {}
};
class DivMacroInt {
DivInstrument* ins;
int volPos, arpPos, dutyPos, wavePos, pitchPos, ex1Pos, ex2Pos, ex3Pos;
int algPos, fbPos, fmsPos, amsPos;
DivMacroStruct* macroList[128];
DivInstrumentMacro* macroSource[128];
size_t macroListLen;
bool released;
public:
int vol;
int arp;
int duty, wave, pitch, ex1, ex2, ex3;
int alg, fb, fms, ams;
bool hasVol, hasArp, hasDuty, hasWave, hasPitch, hasEx1, hasEx2, hasEx3, hasAlg, hasFb, hasFms, hasAms;
bool hadVol, hadArp, hadDuty, hadWave, hadPitch, hadEx1, hadEx2, hadEx3, hadAlg, hadFb, hadFms, hadAms;
bool finishedVol, finishedArp, finishedDuty, finishedWave, finishedPitch, finishedEx1, finishedEx2, finishedEx3;
bool finishedAlg, finishedFb, finishedFms, finishedAms;
bool willVol, willArp, willDuty, willWave, willPitch, willEx1, willEx2, willEx3, willAlg, willFb, willFms, willAms;
bool arpMode;
// common macro
DivMacroStruct vol;
DivMacroStruct arp;
DivMacroStruct duty, wave, pitch, ex1, ex2, ex3;
DivMacroStruct alg, fb, fms, ams;
DivMacroStruct panL, panR, phaseReset, ex4, ex5, ex6, ex7, ex8;
// FM operator macro
struct IntOp {
int amPos, arPos, drPos, multPos;
int rrPos, slPos, tlPos, dt2Pos;
int rsPos, dtPos, d2rPos, ssgPos;
int am, ar, dr, mult;
int rr, sl, tl, dt2;
int rs, dt, d2r, ssg;
bool hasAm, hasAr, hasDr, hasMult;
bool hasRr, hasSl, hasTl, hasDt2;
bool hasRs, hasDt, hasD2r, hasSsg;
bool hadAm, hadAr, hadDr, hadMult;
bool hadRr, hadSl, hadTl, hadDt2;
bool hadRs, hadDt, hadD2r, hadSsg;
bool finishedAm, finishedAr, finishedDr, finishedMult;
bool finishedRr, finishedSl, finishedTl, finishedDt2;
bool finishedRs, finishedDt, finishedD2r, finishedSsg;
bool willAm, willAr, willDr, willMult;
bool willRr, willSl, willTl, willDt2;
bool willRs, willDt, willD2r, willSsg;
DivMacroStruct am, ar, dr, mult;
DivMacroStruct rr, sl, tl, dt2;
DivMacroStruct rs, dt, d2r, ssg;
DivMacroStruct dam, dvb, egt, ksl;
DivMacroStruct sus, vib, ws, ksr;
IntOp():
amPos(0),
arPos(0),
drPos(0),
multPos(0),
rrPos(0),
slPos(0),
tlPos(0),
dt2Pos(0),
rsPos(0),
dtPos(0),
d2rPos(0),
ssgPos(0),
am(0),
ar(0),
dr(0),
mult(0),
rr(0),
sl(0),
tl(0),
dt2(0),
rs(0),
dt(0),
d2r(0),
ssg(0),
hasAm(false), hasAr(false), hasDr(false), hasMult(false),
hasRr(false), hasSl(false), hasTl(false), hasDt2(false),
hasRs(false), hasDt(false), hasD2r(false), hasSsg(false),
hadAm(false), hadAr(false), hadDr(false), hadMult(false),
hadRr(false), hadSl(false), hadTl(false), hadDt2(false),
hadRs(false), hadDt(false), hadD2r(false), hadSsg(false),
finishedAm(false), finishedAr(false), finishedDr(false), finishedMult(false),
finishedRr(false), finishedSl(false), finishedTl(false), finishedDt2(false),
finishedRs(false), finishedDt(false), finishedD2r(false), finishedSsg(false),
willAm(false), willAr(false), willDr(false), willMult(false),
willRr(false), willSl(false), willTl(false), willDt2(false),
willRs(false), willDt(false), willD2r(false), willSsg(false) {}
am(),
ar(),
dr(),
mult(),
rr(),
sl(),
tl(),
dt2(),
rs(),
dt(),
d2r(),
ssg(),
dam(),
dvb(),
egt(),
ksl(),
sus(),
vib(),
ws(),
ksr() {}
} op[4];
/**
* trigger macro release.
*/
void release();
/**
* trigger next macro tick.
*/
void next();
/**
* initialize the macro interpreter.
* @param which an instrument, or NULL.
*/
void init(DivInstrument* which);
/**
* notify this macro interpreter that an instrument has been deleted.
* @param which the instrument in question.
*/
void notifyInsDeletion(DivInstrument* which);
DivMacroInt():
ins(NULL),
volPos(0),
arpPos(0),
dutyPos(0),
wavePos(0),
pitchPos(0),
ex1Pos(0),
ex2Pos(0),
ex3Pos(0),
algPos(0),
fbPos(0),
fmsPos(0),
amsPos(0),
macroListLen(0),
released(false),
vol(0),
arp(0),
duty(0),
wave(0),
pitch(0),
ex1(0),
ex2(0),
ex3(0),
alg(0),
fb(0),
fms(0),
ams(0),
hasVol(false),
hasArp(false),
hasDuty(false),
hasWave(false),
hasPitch(false),
hasEx1(false),
hasEx2(false),
hasEx3(false),
hasAlg(false),
hasFb(false),
hasFms(false),
hasAms(false),
hadVol(false),
hadArp(false),
hadDuty(false),
hadWave(false),
hadPitch(false),
hadEx1(false),
hadEx2(false),
hadEx3(false),
hadAlg(false),
hadFb(false),
hadFms(false),
hadAms(false),
finishedVol(false),
finishedArp(false),
finishedDuty(false),
finishedWave(false),
finishedPitch(false),
finishedEx1(false),
finishedEx2(false),
finishedEx3(false),
finishedAlg(false),
finishedFb(false),
finishedFms(false),
finishedAms(false),
willVol(false),
willArp(false),
willDuty(false),
willWave(false),
willPitch(false),
willEx1(false),
willEx2(false),
willEx3(false),
willAlg(false),
willFb(false),
willFms(false),
willAms(false),
arpMode(false) {}
vol(),
arp(),
duty(),
wave(),
pitch(),
ex1(),
ex2(),
ex3(),
alg(),
fb(),
fms(),
ams(),
panL(),
panR(),
phaseReset(),
ex4(),
ex5(),
ex6(),
ex7(),
ex8() {
memset(macroList,0,128*sizeof(void*));
memset(macroSource,0,128*sizeof(void*));
}
};
#endif

Some files were not shown because too many files have changed in this diff Show More