mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-26 06:25:16 +00:00
Merge branch 'tildearrow:master' into master
This commit is contained in:
commit
10594c57cd
258 changed files with 39286 additions and 8505 deletions
1
.github/pull_request_template.md
vendored
Normal file
1
.github/pull_request_template.md
vendored
Normal 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! -->
|
257
.github/workflows/build.yml
vendored
257
.github/workflows/build.yml
vendored
|
@ -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
3
.gitmodules
vendored
|
@ -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
|
||||
|
|
139
CMakeLists.txt
139
CMakeLists.txt
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
63
README.md
63
README.md
|
@ -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
BIN
demos/Another_winter.fur
Normal file
Binary file not shown.
BIN
demos/Coconut_Mall.fur
Normal file
BIN
demos/Coconut_Mall.fur
Normal file
Binary file not shown.
BIN
demos/silverlining.fur
Normal file
BIN
demos/silverlining.fur
Normal file
Binary file not shown.
BIN
demos/yky.fur
Normal file
BIN
demos/yky.fur
Normal file
Binary file not shown.
1
extern/Nuked-OPL3
vendored
Submodule
1
extern/Nuked-OPL3
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit bb5c8d08a85779c42b75c79d7b84f365a1b93b66
|
22
extern/Nuked-OPLL/opll.c
vendored
22
extern/Nuked-OPLL/opll.c
vendored
|
@ -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));
|
||||
|
|
2
extern/Nuked-OPLL/opll.h
vendored
2
extern/Nuked-OPLL/opll.h
vendored
|
@ -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);
|
||||
|
|
2
extern/igfd/ImGuiFileDialog.cpp
vendored
2
extern/igfd/ImGuiFileDialog.cpp
vendored
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
4
extern/pfd-fixed/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
Makefile
|
||||
cmake_install.cmake
|
5
extern/pfd-fixed/.lgtm.yml
vendored
Normal file
5
extern/pfd-fixed/.lgtm.yml
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
extraction:
|
||||
cpp:
|
||||
index:
|
||||
build_command:
|
||||
- make -C examples
|
6
extern/pfd-fixed/CMakeLists.txt
vendored
Normal file
6
extern/pfd-fixed/CMakeLists.txt
vendored
Normal 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
14
extern/pfd-fixed/COPYING
vendored
Normal 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
64
extern/pfd-fixed/README.md
vendored
Normal 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&utm_medium=referral&utm_content=samhocevar/portable-file-dialogs&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
97
extern/pfd-fixed/doc/message.md
vendored
Normal 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
40
extern/pfd-fixed/doc/notify.md
vendored
Normal 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
90
extern/pfd-fixed/doc/open_file.md
vendored
Normal 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
120
extern/pfd-fixed/doc/pfd.md
vendored
Normal 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
73
extern/pfd-fixed/doc/save_file.md
vendored
Normal 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
55
extern/pfd-fixed/doc/select_folder.md
vendored
Normal 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
11
extern/pfd-fixed/examples/.gitignore
vendored
Normal 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
110
extern/pfd-fixed/examples/example.cpp
vendored
Normal 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, don’t 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();
|
||||
}
|
||||
|
96
extern/pfd-fixed/examples/example.vcxproj
vendored
Normal file
96
extern/pfd-fixed/examples/example.vcxproj
vendored
Normal 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
42
extern/pfd-fixed/examples/kill.cpp
vendored
Normal 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 didn’t 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
96
extern/pfd-fixed/examples/kill.vcxproj
vendored
Normal 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
1733
extern/pfd-fixed/portable-file-dialogs.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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.
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
23
papers/doc/4-instrument/n163.md
Normal file
23
papers/doc/4-instrument/n163.md
Normal 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
|
|
@ -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
|
||||
|
|
7
papers/doc/4-instrument/scc.md
Normal file
7
papers/doc/4-instrument/scc.md
Normal 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
|
8
papers/doc/4-instrument/vera.md
Normal file
8
papers/doc/4-instrument/vera.md
Normal 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
|
7
papers/doc/4-instrument/vrc6.md
Normal file
7
papers/doc/4-instrument/vrc6.md
Normal 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
|
8
papers/doc/4-instrument/wonderswan.md
Normal file
8
papers/doc/4-instrument/wonderswan.md
Normal 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
|
11
papers/doc/4-instrument/x1_010.md
Normal file
11
papers/doc/4-instrument/x1_010.md
Normal 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
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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...
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
|
||||
|
|
15
papers/doc/7-systems/bubblesystem.md
Normal file
15
papers/doc/7-systems/bubblesystem.md
Normal 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.
|
26
papers/doc/7-systems/fds.md
Normal file
26
papers/doc/7-systems/fds.md
Normal 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
|
22
papers/doc/7-systems/lynx.md
Normal file
22
papers/doc/7-systems/lynx.md
Normal 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.
|
12
papers/doc/7-systems/mmc5.md
Normal file
12
papers/doc/7-systems/mmc5.md
Normal 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.
|
25
papers/doc/7-systems/n163.md
Normal file
25
papers/doc/7-systems/n163.md
Normal 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)
|
|
@ -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.
|
||||
|
|
46
papers/doc/7-systems/opl.md
Normal file
46
papers/doc/7-systems/opl.md
Normal 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).
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
7
papers/doc/7-systems/pcspkr.md
Normal file
7
papers/doc/7-systems/pcspkr.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# PC Speaker
|
||||
|
||||
40 years of one square beep - and still going! Single channel, no volume control...
|
||||
|
||||
# effects
|
||||
|
||||
ha! effects...
|
11
papers/doc/7-systems/pet.md
Normal file
11
papers/doc/7-systems/pet.md
Normal 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.
|
|
@ -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
|
||||
|
||||
|
|
12
papers/doc/7-systems/segapcm.md
Normal file
12
papers/doc/7-systems/segapcm.md
Normal 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.
|
|
@ -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.
|
||||
|
||||
|
|
15
papers/doc/7-systems/vera.md
Normal file
15
papers/doc/7-systems/vera.md
Normal 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.
|
11
papers/doc/7-systems/vic20.md
Normal file
11
papers/doc/7-systems/vic20.md
Normal 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`)
|
18
papers/doc/7-systems/vrc6.md
Normal file
18
papers/doc/7-systems/vrc6.md
Normal 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.
|
17
papers/doc/7-systems/wonderswan.md
Normal file
17
papers/doc/7-systems/wonderswan.md
Normal 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).
|
47
papers/doc/7-systems/x1-010.md
Normal file
47
papers/doc/7-systems/x1-010.md
Normal 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.
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
58
papers/doc/7-systems/ym2610b.md
Normal file
58
papers/doc/7-systems/ym2610b.md
Normal 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.
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
299
papers/format.md
299
papers/format.md
|
@ -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)
|
||||
```
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
4
scripts/Cross-MinGW-x86.cmake
Normal file
4
scripts/Cross-MinGW-x86.cmake
Normal file
|
@ -0,0 +1,4 @@
|
|||
set(CMAKE_SYSTEM_NAME Windows)
|
||||
set(CMAKE_SYSTEM_PROCESSOR i686)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/Cross-MinGW.cmake)
|
4
scripts/Cross-MinGW-x86_64.cmake
Normal file
4
scripts/Cross-MinGW-x86_64.cmake
Normal 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
14
scripts/Cross-MinGW.cmake
Normal 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)
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
}
|
|
@ -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
61
src/audio/midi.cpp
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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) {}
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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="";
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
@ -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
810
src/engine/fileOpsIns.cpp
Normal 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
87
src/engine/filter.cpp
Normal 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
43
src/engine/filter.h
Normal 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
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue