Merge branch 'master' into ZSMv1

This commit is contained in:
ZeroByteOrg 2022-08-16 11:24:18 -05:00
commit 941d45ad80
141 changed files with 6303 additions and 1262 deletions

View file

@ -22,8 +22,10 @@ jobs:
- { name: 'Windows MSVC x86_64', os: windows-latest, compiler: msvc, arch: x86_64 } - { 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', os: ubuntu-20.04, compiler: mingw, arch: x86 }
- { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 } - { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 }
- { name: 'macOS', os: macos-latest } - { name: 'macOS x86_64', os: macos-latest, arch: x86_64 }
- { name: 'Ubuntu', os: ubuntu-18.04 } - { name: 'macOS ARM', os: macos-latest, arch: arm64 }
- { name: 'Linux x86_64', os: ubuntu-18.04, arch: x86_64 }
- { name: 'Linux ARM', os: ubuntu-18.04, arch: armhf }
fail-fast: false fail-fast: false
name: ${{ matrix.config.name }} name: ${{ matrix.config.name }}
@ -76,10 +78,10 @@ jobs:
package_name="${package_name}-${{ matrix.config.arch }}" package_name="${package_name}-${{ matrix.config.arch }}"
package_ext="" # Directory, uploading will automatically zip it package_ext="" # Directory, uploading will automatically zip it
elif [ '${{ runner.os }}' == 'macOS' ]; then elif [ '${{ runner.os }}' == 'macOS' ]; then
package_name="${package_name}-macOS" package_name="${package_name}-macOS-${{ matrix.config.arch }}"
package_ext=".dmg" package_ext=".dmg"
else else
package_name="${package_name}-Linux" package_name="${package_name}-Linux-${{ matrix.config.arch }}"
package_ext=".AppImage" package_ext=".AppImage"
fi fi
@ -116,8 +118,8 @@ jobs:
mingw-w64 \ mingw-w64 \
mingw-w64-tools mingw-w64-tools
- name: Install Dependencies [Ubuntu] - name: Install Dependencies [Linux x86_64]
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }}
run: | run: |
sudo apt update sudo apt update
sudo apt install \ sudo apt install \
@ -131,8 +133,31 @@ jobs:
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
chmod +x appimagetool-x86_64.AppImage chmod +x appimagetool-x86_64.AppImage
- name: Install Dependencies [Linux armhf]
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'armhf' }}
run: |
sudo sed -ri "s/^deb /deb [arch=amd64] /" /etc/apt/sources.list
echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports/ bionic main universe" | sudo tee -a /etc/apt/sources.list
echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main universe" | sudo tee -a /etc/apt/sources.list
sudo dpkg --add-architecture armhf
sudo apt update
sudo apt install \
crossbuild-essential-armhf \
appstream
sudo apt install \
libsdl2-dev:armhf \
libfmt-dev:armhf \
librtmidi-dev:armhf \
libsndfile1-dev:armhf \
zlib1g-dev:armhf \
libjack-jackd2-dev:armhf
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-armhf"
chmod +x appimagetool-x86_64.AppImage
ls /usr/arm-linux-gnueabihf/lib
- name: Configure (System Libraries) - name: Configure (System Libraries)
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }}
run: | run: |
export USE_WAE=ON export USE_WAE=ON
export CMAKE_EXTRA_ARGS=() export CMAKE_EXTRA_ARGS=()
@ -163,7 +188,7 @@ jobs:
"${CMAKE_EXTRA_ARGS[@]}" "${CMAKE_EXTRA_ARGS[@]}"
- name: Build (System Libraries) - name: Build (System Libraries)
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }}
run: | run: |
cmake \ cmake \
--build ${PWD}/build \ --build ${PWD}/build \
@ -171,14 +196,14 @@ jobs:
--parallel ${{ steps.build-cores.outputs.amount }} --parallel ${{ steps.build-cores.outputs.amount }}
- name: Install (System Libraries) - name: Install (System Libraries)
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }}
run: | run: |
cmake \ cmake \
--install ${PWD}/build \ --install ${PWD}/build \
--config ${{ env.BUILD_TYPE }} --config ${{ env.BUILD_TYPE }}
- name: Cleanup (System Libraries) - name: Cleanup (System Libraries)
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }}
run: | run: |
rm -rf build/ target/ rm -rf build/ target/
@ -201,7 +226,13 @@ jobs:
elif [ '${{ matrix.config.compiler }}' == 'mingw' ]; then elif [ '${{ matrix.config.compiler }}' == 'mingw' ]; then
CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-${{ steps.windows-identify.outputs.mingw-target }}.cmake') CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-${{ steps.windows-identify.outputs.mingw-target }}.cmake')
elif [ '${{ runner.os }}' == 'macOS' ]; then elif [ '${{ runner.os }}' == 'macOS' ]; then
CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"') if [ '${{ matrix.config.arch }}' == 'arm64' ]; then
CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="11.0"' '-DCMAKE_OSX_ARCHITECTURES=arm64')
else
CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"')
fi
elif [ '${{ runner.os }}' == 'Linux' ] && [ '${{ matrix.config.arch }}' == 'armhf' ]; then
CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-Linux-armhf.cmake')
fi fi
cmake \ cmake \
@ -255,7 +286,7 @@ jobs:
mv Furnace-*-Darwin.dmg ../${{ steps.package-identify.outputs.filename }} mv Furnace-*-Darwin.dmg ../${{ steps.package-identify.outputs.filename }}
popd popd
- name: Package [Ubuntu] - name: Package [Linux]
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
run: | run: |
#if [ '${{ env.BUILD_TYPE }}' == 'Release' ]; then #if [ '${{ env.BUILD_TYPE }}' == 'Release' ]; then
@ -273,7 +304,11 @@ jobs:
cp -v ../../res/AppRun ./ cp -v ../../res/AppRun ./
popd popd
../appimagetool-x86_64.AppImage furnace.AppDir if [ '${{ matrix.config.arch }}' == 'armhf' ]; then
../appimagetool-x86_64.AppImage --runtime-file=../runtime-armhf furnace.AppDir
else
../appimagetool-x86_64.AppImage furnace.AppDir
fi
mv Furnace-*.AppImage ../${{ steps.package-identify.outputs.filename }} mv Furnace-*.AppImage ../${{ steps.package-identify.outputs.filename }}
popd popd

View file

@ -22,6 +22,8 @@ set(USE_SDL2_DEFAULT ON)
set(USE_SNDFILE_DEFAULT ON) set(USE_SNDFILE_DEFAULT ON)
set(SYSTEM_SDL2_DEFAULT OFF) set(SYSTEM_SDL2_DEFAULT OFF)
include(CheckIncludeFile)
if (ANDROID) if (ANDROID)
set(USE_RTMIDI_DEFAULT OFF) set(USE_RTMIDI_DEFAULT OFF)
set(USE_BACKWARD_DEFAULT OFF) set(USE_BACKWARD_DEFAULT OFF)
@ -31,7 +33,16 @@ if (ANDROID)
endif() endif()
else() else()
set(USE_RTMIDI_DEFAULT ON) set(USE_RTMIDI_DEFAULT ON)
set(USE_BACKWARD_DEFAULT ON) if (WIN32 OR APPLE)
set(USE_BACKWARD_DEFAULT ON)
else()
CHECK_INCLUDE_FILE(execinfo.h EXECINFO_FOUND)
if (EXECINFO_FOUND)
set(USE_BACKWARD_DEFAULT ON)
else()
set(USE_BACKWARD_DEFAULT OFF)
endif()
endif()
endif() endif()
find_package(PkgConfig) find_package(PkgConfig)
@ -55,6 +66,8 @@ option(SYSTEM_RTMIDI "Use a system-installed version of RtMidi instead of the ve
option(SYSTEM_ZLIB "Use a system-installed version of zlib instead of the vendored one" OFF) option(SYSTEM_ZLIB "Use a system-installed version of zlib instead of the vendored one" OFF)
option(SYSTEM_SDL2 "Use a system-installed version of SDL2 instead of the vendored one" ${SYSTEM_SDL2_DEFAULT}) option(SYSTEM_SDL2 "Use a system-installed version of SDL2 instead of the vendored one" ${SYSTEM_SDL2_DEFAULT})
option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be treated as errors" OFF) option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be treated as errors" OFF)
option(WITH_DEMOS "Install demo songs" ON)
option(WITH_INSTRUMENTS "Install instruments" ON)
set(DEPENDENCIES_INCLUDE_DIRS "") set(DEPENDENCIES_INCLUDE_DIRS "")
@ -221,6 +234,11 @@ if (USE_SDL2)
set(SDL_SHARED OFF CACHE BOOL "Force no dynamically-linked SDL" FORCE) set(SDL_SHARED OFF CACHE BOOL "Force no dynamically-linked SDL" FORCE)
set(SDL_STATIC ON CACHE BOOL "Force statically-linked SDL" FORCE) set(SDL_STATIC ON CACHE BOOL "Force statically-linked SDL" FORCE)
endif() endif()
# https://github.com/libsdl-org/SDL/issues/5535
# disable PipeWire support due to an unfixable bug:
# Looks like their headers have a C90 violation... I imagine they're probably on C99 so not the craziest bug in the world. Definitely file this at the PipeWire repository as well so they know this is out there.
set(SDL_PIPEWIRE OFF CACHE BOOL "Use Pipewire audio" FORCE)
# https://github.com/libsdl-org/SDL/issues/1481 # https://github.com/libsdl-org/SDL/issues/1481
# On 2014-06-22 17:15:50 +0000, Sam Lantinga wrote: # 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. # If you link SDL statically, you also need to define HAVE_LIBC so it builds with the C runtime that your application uses.
@ -450,6 +468,7 @@ src/engine/platform/scc.cpp
src/engine/platform/ymz280b.cpp src/engine/platform/ymz280b.cpp
src/engine/platform/namcowsg.cpp src/engine/platform/namcowsg.cpp
src/engine/platform/rf5c68.cpp src/engine/platform/rf5c68.cpp
src/engine/platform/pcmdac.cpp
src/engine/platform/dummy.cpp src/engine/platform/dummy.cpp
) )
@ -463,6 +482,10 @@ if (WIN32)
list(APPEND ENGINE_SOURCES res/furnace.rc) list(APPEND ENGINE_SOURCES res/furnace.rc)
endif() endif()
set(CLI_SOURCES
src/cli/cli.cpp
)
set(GUI_SOURCES set(GUI_SOURCES
extern/imgui_patched/imgui.cpp extern/imgui_patched/imgui.cpp
extern/imgui_patched/imgui_draw.cpp extern/imgui_patched/imgui_draw.cpp
@ -510,6 +533,7 @@ src/gui/midiMap.cpp
src/gui/newSong.cpp src/gui/newSong.cpp
src/gui/orders.cpp src/gui/orders.cpp
src/gui/osc.cpp src/gui/osc.cpp
src/gui/patManager.cpp
src/gui/pattern.cpp src/gui/pattern.cpp
src/gui/piano.cpp src/gui/piano.cpp
src/gui/presets.cpp src/gui/presets.cpp
@ -545,14 +569,17 @@ endif()
if (NOT WIN32 AND NOT APPLE) if (NOT WIN32 AND NOT APPLE)
list(APPEND GUI_SOURCES src/gui/icon.c) list(APPEND GUI_SOURCES src/gui/icon.c)
include(CheckIncludeFile)
CHECK_INCLUDE_FILE(sys/io.h SYS_IO_FOUND) CHECK_INCLUDE_FILE(sys/io.h SYS_IO_FOUND)
CHECK_INCLUDE_FILE(linux/input.h LINUX_INPUT_FOUND) CHECK_INCLUDE_FILE(linux/input.h LINUX_INPUT_FOUND)
CHECK_INCLUDE_FILE(linux/kd.h LINUX_KD_FOUND) CHECK_INCLUDE_FILE(linux/kd.h LINUX_KD_FOUND)
if (SYS_IO_FOUND) if (SYS_IO_FOUND)
list(APPEND DEPENDENCIES_DEFINES HAVE_SYS_IO) try_compile(HAVE_INOUTB ${CMAKE_BINARY_DIR}/check SOURCES ${CMAKE_SOURCE_DIR}/src/check/check_sysIO.c)
message(STATUS "PC speaker output: outb()") if (HAVE_INOUTB)
list(APPEND DEPENDENCIES_DEFINES HAVE_SYS_IO)
message(STATUS "PC speaker output: outb()")
else()
message(STATUS "sys/io.h found but inb()/outb() not present")
endif()
endif() endif()
if (LINUX_INPUT_FOUND) if (LINUX_INPUT_FOUND)
list(APPEND DEPENDENCIES_DEFINES HAVE_LINUX_INPUT) list(APPEND DEPENDENCIES_DEFINES HAVE_LINUX_INPUT)
@ -564,13 +591,24 @@ if (NOT WIN32 AND NOT APPLE)
endif() endif()
endif() endif()
set(USED_SOURCES ${ENGINE_SOURCES} ${AUDIO_SOURCES} src/main.cpp) if (NOT WIN32)
try_compile(HAVE_DIRENT_TYPE ${CMAKE_BINARY_DIR}/check SOURCES ${CMAKE_SOURCE_DIR}/src/check/check_dirent_type.c)
if (HAVE_DIRENT_TYPE)
list(APPEND DEPENDENCIES_DEFINES HAVE_DIRENT_TYPE)
endif()
endif()
set(USED_SOURCES ${ENGINE_SOURCES} ${AUDIO_SOURCES} ${CLI_SOURCES} src/main.cpp)
if (USE_BACKWARD) if (USE_BACKWARD)
list(APPEND USED_SOURCES src/backtrace.cpp) list(APPEND USED_SOURCES src/backtrace.cpp)
if (WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if (WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
list(APPEND DEPENDENCIES_LIBRARIES dbghelp psapi) list(APPEND DEPENDENCIES_LIBRARIES dbghelp psapi)
endif() endif()
find_library(EXECINFO_IS_LIBRARY execinfo)
if (EXECINFO_IS_LIBRARY)
list(APPEND DEPENDENCIES_LIBRARIES execinfo)
endif()
message(STATUS "Using backward-cpp") message(STATUS "Using backward-cpp")
else() else()
message(STATUS "Not using backward-cpp") message(STATUS "Not using backward-cpp")
@ -669,22 +707,27 @@ if (PKG_CONFIG_FOUND AND (SYSTEM_FMT OR SYSTEM_LIBSNDFILE OR SYSTEM_ZLIB OR SYST
endif() endif()
if (NOT ANDROID OR TERMUX) if (NOT ANDROID OR TERMUX)
install(TARGETS furnace RUNTIME DESTINATION bin)
if (NOT WIN32 AND NOT APPLE) if (NOT WIN32 AND NOT APPLE)
include(GNUInstallDirs) include(GNUInstallDirs)
install(TARGETS furnace RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(FILES res/furnace.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) install(FILES res/furnace.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
install(FILES res/furnace.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo) install(FILES res/furnace.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
install(DIRECTORY papers DESTINATION ${CMAKE_INSTALL_DOCDIR}) install(DIRECTORY papers DESTINATION ${CMAKE_INSTALL_DOCDIR})
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/furnace) install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/furnace)
install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace) if (WITH_DEMOS)
install(DIRECTORY instruments DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace) install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
endif()
if (WITH_INSTRUMENTS)
install(DIRECTORY instruments DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
endif()
foreach(num 16 32 64 128 256 512) foreach(num 16 32 64 128 256 512)
set(res ${num}x${num}) set(res ${num}x${num})
install(FILES res/icon.iconset/icon_${res}.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${res}/apps) install(FILES res/icon.iconset/icon_${res}.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${res}/apps)
install(FILES res/icon.iconset/icon_${res}@2x.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${res}@2/apps) install(FILES res/icon.iconset/icon_${res}@2x.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${res}@2/apps)
endforeach() endforeach()
install(FILES res/logo.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/1024x1024/apps) install(FILES res/logo.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/1024x1024/apps)
else()
install(TARGETS furnace RUNTIME DESTINATION bin)
endif() endif()
set(CPACK_PACKAGE_NAME "Furnace") set(CPACK_PACKAGE_NAME "Furnace")

View file

@ -53,6 +53,11 @@ the coding style is described here:
- don't use `auto` unless needed. - don't use `auto` unless needed.
- use `String` for `std::string` (this is typedef'd in ta-utils.h). - use `String` for `std::string` (this is typedef'd in ta-utils.h).
- prefer using operator for String (std::string) comparisons (a==""). - prefer using operator for String (std::string) comparisons (a=="").
- if you have to work with C strings, only use safe C string operations:
- snprintf
- strncpy
- strncat
- any other operation which specifies a limit
some files (particularly the ones in `src/engine/platform/sound` and `extern/`) don't follow this style. some files (particularly the ones in `src/engine/platform/sound` and `extern/`) don't follow this style.

View file

@ -61,6 +61,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a
- SID (6581/8580) used in Commodore 64 - SID (6581/8580) used in Commodore 64
- Mikey used in Atari Lynx - Mikey used in Atari Lynx
- ZX Spectrum beeper (SFX-like engine) - ZX Spectrum beeper (SFX-like engine)
- Commodore PET
- TIA used in Atari 2600 - TIA used in Atari 2600
- Game Boy - Game Boy
- modern/fantasy: - modern/fantasy:
@ -202,6 +203,8 @@ Available options:
| `SYSTEM_ZLIB` | `OFF` | Use a system-installed version of zlib instead of the vendored one | | `SYSTEM_ZLIB` | `OFF` | Use a system-installed version of zlib instead of the vendored one |
| `SYSTEM_SDL2` | `OFF` | Use a system-installed version of SDL2 instead of the vendored one | | `SYSTEM_SDL2` | `OFF` | Use a system-installed version of SDL2 instead of the vendored one |
| `WARNINGS_ARE_ERRORS` | `OFF` (but consider enabling this & reporting any errors that arise from it!) | Whether warnings in furnace's C++ code should be treated as errors | | `WARNINGS_ARE_ERRORS` | `OFF` (but consider enabling this & reporting any errors that arise from it!) | Whether warnings in furnace's C++ code should be treated as errors |
| `WITH_DEMOS` | `ON` | Install demo songs on `make install` |
| `WITH_INSTRUMENTS` | `ON` | Install demo instruments on `make install` |
## console usage ## console usage

View file

@ -1,9 +1,5 @@
# to-do for 0.6pre1.5-0.6pre2 # to-do for 0.6pre1.5-0.6pre2
- rewrite the system name detection function anyway
- this involves the addition of a new "system" field in the song (which solves the problem)
- songs made in older versions will go through old system name detection for compatibility
- Game Boy envelope macro/sequence
- volume commands should work on Game Boy - volume commands should work on Game Boy
- ability to customize `OFF`, `===` and `REL` - ability to customize `OFF`, `===` and `REL`
- stereo separation control for AY - stereo separation control for AY

BIN
demos/Bullet_Hell.fur Normal file

Binary file not shown.

BIN
demos/Egyptian_Rule.fur Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
demos/Phoenix_cover.fur Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
demos/home_wfl_opl3.fur Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -221,6 +221,14 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <syscall.h> #include <syscall.h>
#include <unistd.h> #include <unistd.h>
// https://github.com/tildearrow/furnace/issues/588
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#include <dlfcn.h>
#undef _GNU_SOURCE
#else
#include <dlfcn.h>
#endif
#if BACKWARD_HAS_BFD == 1 #if BACKWARD_HAS_BFD == 1
// NOTE: defining PACKAGE{,_VERSION} is required before including // NOTE: defining PACKAGE{,_VERSION} is required before including
@ -233,13 +241,6 @@
#define PACKAGE_VERSION #define PACKAGE_VERSION
#endif #endif
#include <bfd.h> #include <bfd.h>
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#include <dlfcn.h>
#undef _GNU_SOURCE
#else
#include <dlfcn.h>
#endif
#endif #endif
#if BACKWARD_HAS_DW == 1 #if BACKWARD_HAS_DW == 1
@ -254,13 +255,6 @@
#include <libdwarf.h> #include <libdwarf.h>
#include <libelf.h> #include <libelf.h>
#include <map> #include <map>
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#include <dlfcn.h>
#undef _GNU_SOURCE
#else
#include <dlfcn.h>
#endif
#endif #endif
#if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1) #if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1)

View file

@ -58,7 +58,7 @@ SOFTWARE.
#ifndef PATH_MAX #ifndef PATH_MAX
#define PATH_MAX 260 #define PATH_MAX 260
#endif // PATH_MAX #endif // PATH_MAX
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined (__EMSCRIPTEN__) #elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined (__EMSCRIPTEN__) || defined(__HAIKU__)
#define UNIX #define UNIX
#define stricmp strcasecmp #define stricmp strcasecmp
#include <sys/types.h> #include <sys/types.h>
@ -1547,28 +1547,53 @@ namespace IGFD
for (i = 0; i < n; i++) for (i = 0; i < n; i++)
{ {
struct dirent* ent = files[i]; struct dirent* ent = files[i];
std::string where = path + std::string("/") + std::string(ent->d_name);
char fileType = 0; char fileType = 0;
switch (ent->d_type) #ifdef HAVE_DIRENT_TYPE
if (ent->d_type != DT_UNKNOWN)
{ {
case DT_REG: switch (ent->d_type)
fileType = 'f'; break; {
case DT_DIR: case DT_REG:
fileType = 'd'; break; fileType = 'f'; break;
case DT_LNK: case DT_DIR:
std::string where = path+std::string("/")+std::string(ent->d_name); fileType = 'd'; break;
DIR* dirTest = opendir(where.c_str()); case DT_LNK:
if (dirTest==NULL) { DIR* dirTest = opendir(where.c_str());
if (errno==ENOTDIR) { if (dirTest == NULL)
fileType = 'f'; {
} else { if (errno == ENOTDIR)
fileType = 'l'; {
} fileType = 'f';
} else { }
fileType = 'd'; else
closedir(dirTest); {
} fileType = 'l';
break; }
}
else
{
fileType = 'd';
closedir(dirTest);
}
break;
}
}
else
#endif // HAVE_DIRENT_TYPE
{
struct stat filestat;
if (stat(where.c_str(), &filestat) == 0)
{
if (S_ISDIR(filestat.st_mode))
{
fileType = 'd';
}
else
{
fileType = 'f';
}
}
} }
auto fileNameExt = ent->d_name; auto fileNameExt = ent->d_name;

View file

@ -368,7 +368,7 @@ static nfdresult_t AllocPathSet( IShellItemArray *shellItems, nfdpathset_t *path
static nfdresult_t SetDefaultPath( IFileDialog *dialog, const char *defaultPath ) static nfdresult_t SetDefaultPath( IFileDialog *dialog, const char *defaultPath )
{ {
if ( !defaultPath || strlen(defaultPath) == 0 ) if ( !defaultPath || strlen(defaultPath) == 0 || strcmp(defaultPath,"\\")==0 )
return NFD_OKAY; return NFD_OKAY;
wchar_t *defaultPathW = {0}; wchar_t *defaultPathW = {0};

Binary file not shown.

View file

@ -2,7 +2,7 @@
Namco 163 instrument editor consists of two tabs: one controlling various parameters for waveform initialize and macro tab containing 10 macros. Namco 163 instrument editor consists of two tabs: one controlling various parameters for waveform initialize and macro tab containing 10 macros.
## N163 ## Namco 163
- [Initial Waveform] - Determines the initial waveform for playing. - [Initial Waveform] - Determines the initial waveform for playing.
- [Initial Waveform position in RAM] - Determines the initial waveform position will be load to RAM. - [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. - [Initial Waveform length in RAM] - Determines the initial waveform length will be load to RAM.

View file

@ -4,29 +4,54 @@ In the context of Furnace, a sound sample (usually just referred to as a sample)
In Furnace, these samples can be generated by importing a .wav (think of it as an higher quality MP3) file. In Furnace, these samples can be generated by importing a .wav (think of it as an higher quality MP3) file.
## supported systems ## supported chips
As of Furnace 0.6, the following sound chips have sample support: as of Furnace 0.6, the following sound chips have sample support:
- NES/Ricoh 2A03 (with DPCM support and only on channel 5)
- Sega Genesis/YM2612 (channel 6 only; but only if there exists a `1701` effect that gets played on or before a trigger for a sample, or if you are using an instrument with Sample type)
- PC Engine/TurboGrafx 16/Huc6280 (same conditions as above)
- Amiga/Paula (on all channels)
- Arcade/SEGA PCM (same as above)
- Neo Geo/Neo Geo CD (on the last 7 channels (6 if you are using Neo Geo CD) only and can be resampled the same way as above)
- Seta/Allumer X1-010 (same as YM2612)
- Atari Lynx
- MSM6258 and MSM6295
- YMU759/MA-2 (last channel only)
- QSound
- ZX Spectrum 48k
- RF5C68
- WonderSwan
- Tildearrow Sound Unit
- VERA (last channel only)
- Y8590 (last channel only)
- And a few more that I've forgotten to mention.
Furnace also has a feature where you can make an Amiga formarted instrument on the YM2612 and Huc6280 to resample a sample you have in the module. - NES/Ricoh 2A03 (with DPCM support and only on channel 5)
- Sega Genesis/YM2612 (channel 6 only)
- PC Engine/TurboGrafx-16/HuC6280
- Amiga/Paula
- SegaPCM
- Neo Geo/Neo Geo CD/YM2610 (ADPCM channels only)
- Seta/Allumer X1-010
- Atari Lynx
- MSM6258 and MSM6295
- YMU759/MA-2 (last channel only)
- QSound
- ZX Spectrum 48k (1-bit)
- RF5C68
- WonderSwan
- tildearrow Sound Unit
- VERA (last channel only)
- Y8950 (last channel only)
- a few more that I've forgotten to mention
## compatible sample mode
effect `17xx` enables/disables compatible sample mode whether supported (e.g. on Sega Genesis or PC Engine).
in this mode, samples are mapped to notes in an octave from C to B, allowing you to use up to 12 samples.
if you need to use more samples, you may change the sample bank using effect `EBxx`.
use of this mode is discouraged in favor of Sample type instruments.
## notes
due to limitations in some of those sound chips, some restrictions exist:
- Amiga: sample lengths and loop will be set to an even number, and your sample can't be longer than 131070.
- NES: if on DPCM mode, only a limited selection of frequencies is available, and loop position isn't supported (only entire sample).
- SegaPCM: your sample can't be longer than 65535, and the maximum frequency is 31.25KHz.
- QSound: your sample can't be longer than 65535, and the loop length shall not be greater than 32767.
- Neo Geo (ADPCM-A): no looping supported. your samples will play at ~18.5KHz.
- Neo Geo (ADPCM-B): no loop position supported (only entire sample), and the maximum frequency is ~55KHz.
- YM2608: the maximum frequency is ~55KHz.
- MSM6258/MSM6295: no arbitrary frequency.
- ZX Spectrum Beeper: your sample can't be longer than 2048, and it always plays at ~55KHz.
- Seta/Allumer X1-010: frequency resolution is terrible in the lower end. your sample can't be longer than 131072.
furthermore, many of these chips have a limited amount of sample memory. check memory usage in window > statistics.
# the sample editor # the sample editor
@ -34,11 +59,12 @@ You can actually tweak your samples in Furnace's sample editor, which can be acc
In there, you can modify certain data pertaining to your sample, such as the: In there, you can modify certain data pertaining to your sample, such as the:
- volume of the sample in percentage, where 100% is the current level of the sample (note that you can distort it if you put it too high) - volume of the sample in percentage, where 100% is the current level of the sample (note that you can distort it if you put it too high)
- the sample rate, from 0Hz (no sample movement) to 65535Hz (65.5kHz). - the sample rate.
- what frequencies to filter, along with filter level/sweep and resonance options (much like the C64) - what frequencies to filter, along with filter level/sweep and resonance options (much like the C64)
- and many more. - and many more.
The changes you make will be applied as soon as you've committed them to your sample, but they can be undoed and redoed, just like text. The changes you make will be applied as soon as you've committed them to your sample, but they can be undoed and redoed, just like text.
# tips # tips
If you have a sample you wanna use that is about 44100 or anything over 32000Hz, downsample the sample to 32000Hz so that the pitch of the sample in Furnace stays like the original audio file. You can do this in Audacity by going to the bottom left of the screen (If you see "Project Rate (Hz)" you are there), change the project rate to 32000Hz and save the file to wav in Audacity using "File -> Export -> Export as WAV".
if you have a sample you wanna use that is about 44100 or anything over 32000Hz, downsample the sample to 32000Hz so that the pitch of the sample in Furnace stays like the original audio file,

View file

@ -27,6 +27,7 @@ this is a list of systems that Furnace supports, including each system's effects
- [WonderSwan](wonderswan.md) - [WonderSwan](wonderswan.md)
- [Bubble System WSG](bubblesystem.md) - [Bubble System WSG](bubblesystem.md)
- [Namco 163](n163.md) - [Namco 163](n163.md)
- [Namco WSG](namco.md)
- [Yamaha OPL](opl.md) - [Yamaha OPL](opl.md)
- [PC Speaker](pcspkr.md) - [PC Speaker](pcspkr.md)
- [Commodore PET](pet.md) - [Commodore PET](pet.md)

View file

@ -1,4 +1,4 @@
# Namco C163 # Namco 163 (also called N163, Namco C163, Namco 106 (sic), Namco 160 or Namco 129)
This is one of Namco's NES mappers, with up to 8 wavetable channels. It has also 128 byte of internal RAM, and both channel register and wavetables are stored here. Wavetables are variable size and freely allocable anywhere in RAM, it means it can use part of or continuously pre-loaded waveform and its sequences in RAM. But waveform RAM area becomes smaller as more channels are activated; as channel registers consumes 8 bytes for each channel. You must avoid conflict with channel register area and waveform for avoid broken channel playback. This is one of Namco's NES mappers, with up to 8 wavetable channels. It has also 128 byte of internal RAM, and both channel register and wavetables are stored here. Wavetables are variable size and freely allocable anywhere in RAM, it means it can use part of or continuously pre-loaded waveform and its sequences in RAM. But waveform RAM area becomes smaller as more channels are activated; as channel registers consumes 8 bytes for each channel. You must avoid conflict with channel register area and waveform for avoid broken channel playback.

View file

@ -1,5 +1,7 @@
# Yamaha OPZ (YM2414) # Yamaha OPZ (YM2414)
**disclaimer: despite the name, this has nothing to do with teenage engineering's OP-Z synth!**
this is the YM2151's little-known successor, used in the Yamaha TX81Z and a few other Yamaha synthesizers. oh, and the Korg Z3 too. this is the YM2151's little-known successor, used in the Yamaha TX81Z and a few other Yamaha synthesizers. oh, and the Korg Z3 too.
it adds these features on top of the YM2151: it adds these features on top of the YM2151:

View file

@ -25,7 +25,7 @@ This is a fantasy sound chip, used in the specs2 fantasy computer designed by ti
- `17xx`: set volume sweep period low byte - `17xx`: set volume sweep period low byte
- `18xx`: set volume sweep period high byte - `18xx`: set volume sweep period high byte
- `19xx`: set cutoff sweep period low byte - `19xx`: set cutoff sweep period low byte
- `1Axx`: set cutoff sweep period low byte - `1Axx`: set cutoff sweep period high byte
- `1Bxx`: set frequency sweep boundary - `1Bxx`: set frequency sweep boundary
- `1Cxx`: set volume sweep boundary - `1Cxx`: set volume sweep boundary
- `1Dxx`: set cutoff sweep boundary - `1Dxx`: set cutoff sweep boundary

View file

@ -8,9 +8,9 @@ Allumer rebadged it for their own arcade hardware.
It has 16 channels, which can all be switched between PCM sample or wavetable playback mode. 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. Wavetable playback needs to paired with envelope, similar to AY PSG, but shapes are stored in RAM and as such are user-definable.
In furnace, this chip can be configured for original arcade mono output or stereo output - it simulates early 'incorrect' emulation on some mono hardware, but it is also based on the assumption that each channel is connected to each output. In Furnace, this chip can be configured for original arcade mono output or stereo output - it simulates early 'incorrect' emulation on some mono hardware, but it is also based on the assumption that each channel is connected to each output.
# waveform types # Waveform types
This chip supports 2 types of waveforms, needs to be paired to external 8 KB RAM to access these features: This chip supports 2 types of waveforms, needs to be paired to external 8 KB RAM to access these features:
@ -44,4 +44,4 @@ In furnace, you can enable the envelope shape split mode. When it is set, its wa
- `y` is the denominator. - `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.
* PCM frequency: 255 step, fomula: `step * (Chip clock / 8192)`; 1.95KHz to 498KHz if Chip clock is 16MHz. * PCM frequency: 255 step, formula: `step * (Chip clock / 8192)`; 1.95KHz to 498KHz if Chip clock is 16MHz.

View file

@ -2,9 +2,12 @@
Rather than having a dedicated sound synthesizer, early ZX Spectrum models had one piezo beeper, controlled by Z80 CPU and ULA chip. It's capabilities should be on par with an IBM PC speaker... right? Rather than having a dedicated sound synthesizer, early ZX Spectrum models had one piezo beeper, controlled by Z80 CPU and ULA chip. It's capabilities should be on par with an IBM PC speaker... right?
Not really - very soon talented programmers found out ways to output much more than one square wave channel. A lot of ZX beeper routines do exist, as of 0.6pre1 Furnace supports only one - Follin-like engine with 6 channels of narrow pulse wave and click drums. Not really - very soon talented programmers found out ways to output much more than one square wave channel. A lot of ZX beeper routines do exist, but as of 0.6pre1 Furnace supports only one - Follin-like engine with 6 channels of narrow pulse wave and click drums.
# effects # effects
- `12xx`: set pulse width - `12xx`: set pulse width.
- `17xx`: trigger overlay drums. - `17xx`: trigger overlay drum.
- `xx` is the sample number.
- overlay drums are 1-bit and always play at 55930Hz (NTSC) or 55420Hz (PAL).
- the maximum length is 2048!

View file

@ -4,28 +4,23 @@
TODO TODO
## pattern data ## macro data
read sequentially. read length, loop and then release (1 byte).
if it is a 2-byte macro, read a dummy byte.
first byte determines what to read next: then read data.
## binary command stream
read channel, command and values.
if channel is 80 or higher, then it is a special command:
``` ```
NVI..EEE fb xx xx xx xx: set tick rate
fc xx xx: wait xxxx ticks
N: note fd xx: wait xx ticks
V: volume fe: wait one tick
I: instrument ff: stop
EEE: effect count (0-7)
``` ```
if you read 0, end of pattern.
otherwise read in following order:
1. note
2. volume
3. instrument
4. effect and effect value
then read number of rows until next value, minus 1.

View file

@ -32,6 +32,11 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
the format versions are: the format versions are:
- 106: Furnace dev106
- 105: Furnace dev105
- 104: Furnace dev104
- 103: Furnace dev103
- 102: Furnace 0.6pre1 (dev102)
- 101: Furnace 0.6pre1 (dev101) - 101: Furnace 0.6pre1 (dev101)
- 100: Furnace 0.6pre1 - 100: Furnace 0.6pre1
- 99: Furnace dev99 - 99: Furnace dev99
@ -238,6 +243,11 @@ size | description
| - 0xbe: YM2612 extra features - 7 channels | - 0xbe: YM2612 extra features - 7 channels
| - 0xbf: T6W28 - 4 channels | - 0xbf: T6W28 - 4 channels
| - 0xc0: PCM DAC - 1 channel | - 0xc0: PCM DAC - 1 channel
| - 0xc1: YM2612 CSM - 10 channels
| - 0xc2: Neo Geo CSM (YM2610) - 18 channels
| - 0xc3: OPN CSM - 10 channels
| - 0xc4: PC-98 CSM - 20 channels
| - 0xc5: YM2610B CSM - 20 channels
| - 0xde: YM2610B extended - 19 channels | - 0xde: YM2610B extended - 19 channels
| - 0xe0: QSound - 19 channels | - 0xe0: QSound - 19 channels
| - 0xfd: Dummy System - 8 channels | - 0xfd: Dummy System - 8 channels
@ -330,6 +340,13 @@ size | description
1 | number of additional subsongs 1 | number of additional subsongs
3 | reserved 3 | reserved
4?? | pointers to subsong data 4?? | pointers to subsong data
--- | **additional metadata** (>=103)
STR | system name
STR | album/category/game name
STR | song name (Japanese)
STR | song author (Japanese)
STR | system name (Japanese)
STR | album/category/game name (Japanese)
``` ```
# subsong # subsong
@ -796,6 +813,43 @@ size | description
1 | vib depth 1 | vib depth
1 | am depth 1 | am depth
23 | reserved 23 | reserved
--- | **Sound Unit data** (>=104)
1 | use sample
1 | switch roles of phase reset timer and frequency
--- | **Game Boy envelope sequence** (>=105)
1 | length
??? | hardware sequence data
| size is length*3:
| 1 byte: command
| - 0: set envelope
| - 1: set sweep
| - 2: wait
| - 3: wait for release
| - 4: loop
| - 5: loop until release
| 2 bytes: data
| - for set envelope:
| - 1 byte: parameter
| - bit 4-7: volume
| - bit 3: direction
| - bit 0-2: length
| - 1 byte: sound length
| - for set sweep:
| - 1 byte: parameter
| - bit 4-6: length
| - bit 3: direction
| - bit 0-2: shift
| - 1 byte: nothing
| - for wait:
| - 1 byte: length (in ticks)
| - 1 byte: nothing
| - for wait for release:
| - 2 bytes: nothing
| - for loop/loop until release:
| - 2 bytes: position
--- | **Game Boy extra flags** (>=106)
1 | use software envelope
1 | always init hard env on new note
``` ```
# wavetable # wavetable
@ -812,7 +866,47 @@ size | description
4?? | wavetable data 4?? | wavetable data
``` ```
# sample # sample (>=102)
this is the new sample storage format used in Furnace dev102 and higher.
```
size | description
-----|------------------------------------
4 | "SMP2" block ID
4 | size of this block
STR | sample name
4 | length
4 | compatibility rate
4 | C-4 rate
1 | depth
| - 0: ZX Spectrum overlay drum (1-bit)
| - 1: 1-bit NES DPCM (1-bit)
| - 3: YMZ ADPCM
| - 4: QSound ADPCM
| - 5: ADPCM-A
| - 6: ADPCM-B
| - 8: 8-bit PCM
| - 9: BRR (SNES)
| - 10: VOX
| - 16: 16-bit PCM
3 | reserved
4 | loop start
| - -1 means no loop
4 | loop end
| - -1 means no loop
16 | sample presence bitfields
| - for future use.
| - indicates whether the sample should be present in the memory of a system.
| - read 4 32-bit numbers (for 4 memory banks per system, e.g. YM2610
| does ADPCM-A and ADPCM-B on separate memory banks).
??? | sample data
| - size is length
```
# old sample (<102)
this format is present when saving using previous Furnace versions.
``` ```
size | description size | description
@ -821,7 +915,7 @@ size | description
4 | size of this block 4 | size of this block
STR | sample name STR | sample name
4 | length 4 | length
4 | rate 4 | compatibility rate
2 | volume (<58) or reserved 2 | volume (<58) or reserved
2 | pitch (<58) or reserved 2 | pitch (<58) or reserved
1 | depth 1 | depth

View file

@ -0,0 +1,15 @@
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(TARGET_PREFIX arm-linux-gnueabihf)
set(CMAKE_C_COMPILER ${TARGET_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER ${TARGET_PREFIX}-g++)
set(PKG_CONFIG_EXECUTABLE ${TARGET_PREFIX}-pkg-config)
set(CMAKE_FIND_ROOT_PATH /usr/${TARGET_PREFIX} /usr/lib/${TARGET_PREFIX})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)

32
src/asm/6502/macroInt.s Normal file
View file

@ -0,0 +1,32 @@
macroState=$50 ; pointer to state
macroAddr=$52 ; pointer to address
; macro state takes 4 bytes
; macroPos bits:
; 7: had
; 6: will
; x: macro
macroIntRun:
lda macroAddr,x
ora macroAddr+1,x
beq :+
; do macro
: rts
; set the macro address, then call
; x: macro
macroIntInit:
lda #0
sta macroState,x
sta macroPos,x
txa
rol
tax
lda macroAddr,x
ora macroAddr+1,x
beq :+
lda #$40
sta macroState,x
: rts

View file

@ -52,17 +52,30 @@ void TAAudioJACK::onBufferSize(jack_nframes_t bufsize) {
} }
void TAAudioJACK::onProcess(jack_nframes_t nframes) { 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++) { for (int i=0; i<desc.inChans; i++) {
iInBufs[i]=(float*)jack_port_get_buffer(ai[i],nframes); iInBufs[i]=(float*)jack_port_get_buffer(ai[i],nframes);
memcpy(iInBufs[i],inBufs[i],desc.bufsize*sizeof(float)); if (nframes>desc.bufsize) {
delete[] inBufs[i];
inBufs[i]=new float[nframes];
}
memcpy(iInBufs[i],inBufs[i],nframes*sizeof(float));
}
for (int i=0; i<desc.outChans; i++) {
if (nframes>desc.bufsize) {
delete[] outBufs[i];
outBufs[i]=new float[nframes];
}
}
if (audioProcCallback!=NULL) {
if (midiIn!=NULL) midiIn->gather();
audioProcCallback(audioProcCallbackUser,inBufs,outBufs,desc.inChans,desc.outChans,nframes);
} }
for (int i=0; i<desc.outChans; i++) { for (int i=0; i<desc.outChans; i++) {
iOutBufs[i]=(float*)jack_port_get_buffer(ao[i],nframes); iOutBufs[i]=(float*)jack_port_get_buffer(ao[i],nframes);
memcpy(iOutBufs[i],outBufs[i],desc.bufsize*sizeof(float)); memcpy(iOutBufs[i],outBufs[i],nframes*sizeof(float));
}
if (nframes!=desc.bufsize) {
desc.bufsize=nframes;
} }
} }

View file

@ -46,23 +46,29 @@ String sanitizePortName(const String& name) {
bool TAMidiInRtMidi::gather() { bool TAMidiInRtMidi::gather() {
std::vector<unsigned char> msg; std::vector<unsigned char> msg;
if (port==NULL) return false; if (port==NULL) return false;
while (true) { try {
TAMidiMessage m; while (true) {
double t=port->getMessage(&msg); TAMidiMessage m;
if (msg.empty()) break; double t=port->getMessage(&msg);
if (msg.empty()) break;
// parse message // parse message
m.time=t; m.time=t;
m.type=msg[0]; m.type=msg[0];
if (m.type!=TA_MIDI_SYSEX && msg.size()>1) { if (m.type!=TA_MIDI_SYSEX && msg.size()>1) {
memcpy(m.data,msg.data()+1,MIN(msg.size()-1,7)); memcpy(m.data,msg.data()+1,MIN(msg.size()-1,7));
} else if (m.type==TA_MIDI_SYSEX) { } else if (m.type==TA_MIDI_SYSEX) {
m.sysExData.reset(new unsigned char[msg.size()]); m.sysExData.reset(new unsigned char[msg.size()]);
m.sysExLen=msg.size(); m.sysExLen=msg.size();
logD("got a SysEx of length %ld!",msg.size()); logD("got a SysEx of length %ld!",msg.size());
memcpy(m.sysExData.get(),msg.data(),msg.size()); memcpy(m.sysExData.get(),msg.data(),msg.size());
}
queue.push(m);
} }
queue.push(m); } catch (RtMidiError& e) {
logE("MIDI input error! %s",e.what());
closeDevice();
return false;
} }
return true; return true;
} }
@ -180,7 +186,12 @@ bool TAMidiOutRtMidi::send(const TAMidiMessage& what) {
return false; return false;
} }
len=what.sysExLen; len=what.sysExLen;
port->sendMessage(what.sysExData.get(),len); try {
port->sendMessage(what.sysExData.get(),len);
} catch (RtMidiError& e) {
logE("MIDI output error! %s",e.what());
return false;
}
return true; return true;
break; break;
case TA_MIDI_MTC_FRAME: case TA_MIDI_MTC_FRAME:
@ -194,7 +205,12 @@ bool TAMidiOutRtMidi::send(const TAMidiMessage& what) {
len=1; len=1;
break; break;
} }
port->sendMessage((const unsigned char*)&what.type,len); try {
port->sendMessage((const unsigned char*)&what.type,len);
} catch (RtMidiError& e) {
logE("MIDI output error! %s",e.what());
return false;
}
return true; return true;
} }

View file

@ -0,0 +1,7 @@
#include <dirent.h>
int main(int, char**) {
struct dirent deTest = { };
unsigned char deType = deTest.d_type;
return 0;
}

6
src/check/check_sysIO.c Normal file
View file

@ -0,0 +1,6 @@
#include <sys/io.h>
int main(int, char**) {
inb(0x61);
outb(0x00,0x61);
}

148
src/cli/cli.cpp Normal file
View file

@ -0,0 +1,148 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "cli.h"
#include "../ta-log.h"
bool cliQuit=false;
#ifndef _WIN32
static void handleTerm(int) {
cliQuit=true;
}
#endif
void FurnaceCLI::bindEngine(DivEngine* eng) {
e=eng;
}
bool FurnaceCLI::loop() {
bool escape=false;
bool escapeSecondStage=false;
while (!cliQuit) {
#ifdef _WIN32
int c;
c=fgetc(stdin);
if (c==EOF) break;
#else
unsigned char c;
if (read(STDIN_FILENO,&c,1)<=0) continue;
#endif
if (escape) {
if (escapeSecondStage) {
switch (c) {
case 'C': // right
e->setOrder(e->getOrder()+1);
escape=false;
escapeSecondStage=false;
break;
case 'D': // left
e->setOrder(e->getOrder()-1);
escape=false;
escapeSecondStage=false;
break;
default:
escape=false;
escapeSecondStage=false;
break;
}
} else {
switch (c) {
case '[': case 'O':
escapeSecondStage=true;
break;
default:
escape=false;
break;
}
}
} else {
switch (c) {
case 0x1b: // <ESC>
escape=true;
break;
case 'h': // left
e->setOrder(e->getOrder()-1);
break;
case 'l': // right
e->setOrder(e->getOrder()+1);
break;
case ' ':
if (e->isHalted()) {
e->resume();
} else {
e->halt();
}
break;
}
}
}
printf("\n");
return true;
}
bool FurnaceCLI::finish() {
#ifdef _WIN32
#else
if (tcsetattr(0,TCSAFLUSH,&termpropold)!=0) {
logE("could not set console attributes!");
logE("you may have to run `reset` on your terminal.");
return false;
}
#endif
return true;
}
// blatantly copied from tildearrow/tfmxplay
bool FurnaceCLI::init() {
#ifdef _WIN32
winin=GetStdHandle(STD_INPUT_HANDLE);
winout=GetStdHandle(STD_OUTPUT_HANDLE);
int termprop=0;
int termpropi=0;
GetConsoleMode(winout,(LPDWORD)&termprop);
GetConsoleMode(winin,(LPDWORD)&termpropi);
termprop|=ENABLE_VIRTUAL_TERMINAL_PROCESSING;
termpropi&=~ENABLE_LINE_INPUT;
SetConsoleMode(winout,termprop);
SetConsoleMode(winin,termpropi);
#else
sigemptyset(&intsa.sa_mask);
intsa.sa_flags=0;
intsa.sa_handler=handleTerm;
sigaction(SIGINT,&intsa,NULL);
if (tcgetattr(0,&termprop)!=0) {
logE("could not get console attributes!");
return false;
}
memcpy(&termpropold,&termprop,sizeof(struct termios));
termprop.c_lflag&=~ECHO;
termprop.c_lflag&=~ICANON;
if (tcsetattr(0,TCSAFLUSH,&termprop)!=0) {
logE("could not set console attributes!");
return false;
}
#endif
return true;
}
FurnaceCLI::FurnaceCLI():
e(NULL) {
}

56
src/cli/cli.h Normal file
View file

@ -0,0 +1,56 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _FUR_CLI_H
#define _FUR_CLI_H
#include "../engine/engine.h"
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <signal.h>
#include <unistd.h>
#include <termios.h>
#include <sys/ioctl.h>
#endif
class FurnaceCLI {
DivEngine* e;
#ifdef _WIN32
HANDLE winin;
HANDLE winout;
#else
struct sigaction intsa;
struct termios termprop;
struct termios termpropold;
#endif
public:
void bindEngine(DivEngine* eng);
bool loop();
bool finish();
bool init();
FurnaceCLI();
};
#endif

View file

@ -23,11 +23,84 @@
#include <fmt/printf.h> #include <fmt/printf.h>
#ifdef _WIN32 #ifdef _WIN32
#include "winStuff.h"
#define CONFIG_FILE "\\furnace.cfg" #define CONFIG_FILE "\\furnace.cfg"
#else #else
#ifdef __HAIKU__
#include <support/SupportDefs.h>
#include <storage/FindDirectory.h>
#endif
#include <unistd.h>
#include <pwd.h>
#include <sys/stat.h>
#define CONFIG_FILE "/furnace.cfg" #define CONFIG_FILE "/furnace.cfg"
#endif #endif
void DivEngine::initConfDir() {
#ifdef _WIN32
// maybe move this function in here instead?
configPath=getWinConfigPath();
#elif defined (IS_MOBILE)
configPath=SDL_GetPrefPath();
#else
#ifdef __HAIKU__
char userSettingsDir[PATH_MAX];
status_t findUserDir = find_directory(B_USER_SETTINGS_DIRECTORY,0,true,userSettingsDir,PATH_MAX);
if (findUserDir==B_OK) {
configPath=userSettingsDir;
} else {
logW("unable to find/create user settings directory (%s)!",strerror(findUserDir));
configPath=".";
return;
}
#else
// TODO this should check XDG_CONFIG_HOME first
char* home=getenv("HOME");
if (home==NULL) {
int uid=getuid();
struct passwd* entry=getpwuid(uid);
if (entry==NULL) {
logW("unable to determine home directory (%s)!",strerror(errno));
configPath=".";
return;
} else {
configPath=entry->pw_dir;
}
} else {
configPath=home;
}
#ifdef __APPLE__
configPath+="/Library/Application Support";
#else
// FIXME this doesn't honour XDG_CONFIG_HOME *at all*
configPath+="/.config";
#endif // __APPLE__
#endif // __HAIKU__
#ifdef __APPLE__
configPath+="/Furnace";
#else
configPath+="/furnace";
#endif // __APPLE__
struct stat st;
std::string pathSep="/";
configPath+=pathSep;
size_t sepPos=configPath.find(pathSep,1);
while (sepPos!=std::string::npos) {
std::string subpath=configPath.substr(0,sepPos++);
if (stat(subpath.c_str(),&st)!=0) {
logI("creating config path element %s ...",subpath.c_str());
if (mkdir(subpath.c_str(),0755)!=0) {
logW("could not create config path element %s! (%s)",subpath.c_str(),strerror(errno));
configPath=".";
return;
}
}
sepPos=configPath.find(pathSep,sepPos);
}
configPath.resize(configPath.length()-pathSep.length());
#endif // _WIN32
}
bool DivEngine::saveConf() { bool DivEngine::saveConf() {
configFile=configPath+String(CONFIG_FILE); configFile=configPath+String(CONFIG_FILE);
FILE* f=ps_fopen(configFile.c_str(),"wb"); FILE* f=ps_fopen(configFile.c_str(),"wb");

View file

@ -55,6 +55,18 @@ enum DivDispatchCmds {
DIV_CMD_PRE_PORTA, // (inPorta, isPortaOrSlide) DIV_CMD_PRE_PORTA, // (inPorta, isPortaOrSlide)
DIV_CMD_PRE_NOTE, // used in C64 (note) DIV_CMD_PRE_NOTE, // used in C64 (note)
// these will be used in ROM export.
// do NOT implement!
DIV_CMD_HINT_VIBRATO, // (speed, depth)
DIV_CMD_HINT_VIBRATO_RANGE, // (range)
DIV_CMD_HINT_VIBRATO_SHAPE, // (shape)
DIV_CMD_HINT_PITCH, // (pitch)
DIV_CMD_HINT_ARPEGGIO, // (note1, note2)
DIV_CMD_HINT_VOLUME, // (vol)
DIV_CMD_HINT_VOL_SLIDE, // (amount, oneTick)
DIV_CMD_HINT_PORTA, // (target, speed)
DIV_CMD_HINT_LEGATO, // (note)
DIV_CMD_SAMPLE_MODE, // (enabled) DIV_CMD_SAMPLE_MODE, // (enabled)
DIV_CMD_SAMPLE_FREQ, // (frequency) DIV_CMD_SAMPLE_FREQ, // (frequency)
DIV_CMD_SAMPLE_BANK, // (bank) DIV_CMD_SAMPLE_BANK, // (bank)
@ -399,6 +411,12 @@ class DivDispatch {
*/ */
virtual bool getDCOffRequired(); virtual bool getDCOffRequired();
/**
* check whether PRE_NOTE command is desired.
* @return truth.
*/
virtual bool getWantPreNote();
/** /**
* get a description of a dispatch-specific effect. * get a description of a dispatch-specific effect.
* @param effect the effect. * @param effect the effect.

View file

@ -54,6 +54,7 @@
#include "platform/su.h" #include "platform/su.h"
#include "platform/swan.h" #include "platform/swan.h"
#include "platform/lynx.h" #include "platform/lynx.h"
#include "platform/zxbeeper.h"
#include "platform/bubsyswsg.h" #include "platform/bubsyswsg.h"
#include "platform/n163.h" #include "platform/n163.h"
#include "platform/pet.h" #include "platform/pet.h"
@ -64,9 +65,9 @@
#include "platform/scc.h" #include "platform/scc.h"
#include "platform/ymz280b.h" #include "platform/ymz280b.h"
#include "platform/rf5c68.h" #include "platform/rf5c68.h"
#include "platform/pcmdac.h"
#include "platform/dummy.h" #include "platform/dummy.h"
#include "../ta-log.h" #include "../ta-log.h"
#include "platform/zxbeeper.h"
#include "song.h" #include "song.h"
void DivDispatchContainer::setRates(double gotRate) { void DivDispatchContainer::setRates(double gotRate) {
@ -395,6 +396,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
dispatch=new DivPlatformNamcoWSG; dispatch=new DivPlatformNamcoWSG;
((DivPlatformNamcoWSG*)dispatch)->setDeviceType(30); ((DivPlatformNamcoWSG*)dispatch)->setDeviceType(30);
break; break;
case DIV_SYSTEM_PCM_DAC:
dispatch=new DivPlatformPCMDAC;
break;
case DIV_SYSTEM_DUMMY: case DIV_SYSTEM_DUMMY:
dispatch=new DivPlatformDummy; dispatch=new DivPlatformDummy;
break; break;

View file

@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include "dispatch.h"
#define _USE_MATH_DEFINES #define _USE_MATH_DEFINES
#include "engine.h" #include "engine.h"
#include "instrument.h" #include "instrument.h"
@ -27,15 +28,11 @@
#include "../audio/sdlAudio.h" #include "../audio/sdlAudio.h"
#endif #endif
#include <stdexcept> #include <stdexcept>
#ifndef _WIN32
#include <unistd.h>
#include <pwd.h>
#include <sys/stat.h>
#endif
#ifdef HAVE_JACK #ifdef HAVE_JACK
#include "../audio/jack.h" #include "../audio/jack.h"
#endif #endif
#include <math.h> #include <math.h>
#include <float.h>
#ifdef HAVE_SNDFILE #ifdef HAVE_SNDFILE
#include "sfWrapper.h" #include "sfWrapper.h"
#endif #endif
@ -181,6 +178,308 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) {
} }
} }
#define EXPORT_BUFSIZE 2048
double DivEngine::benchmarkPlayback() {
float* outBuf[2];
outBuf[0]=new float[EXPORT_BUFSIZE];
outBuf[1]=new float[EXPORT_BUFSIZE];
curOrder=0;
prevOrder=0;
remainingLoops=1;
playSub(false);
std::chrono::high_resolution_clock::time_point timeStart=std::chrono::high_resolution_clock::now();
// benchmark
while (playing) {
nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE);
}
std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now();
delete[] outBuf[0];
delete[] outBuf[1];
double t=(double)(std::chrono::duration_cast<std::chrono::microseconds>(timeEnd-timeStart).count())/1000000.0;
printf("[RESULT] %fs\n",t);
return t;
}
double DivEngine::benchmarkSeek() {
double t[20];
curOrder=curSubSong->ordersLen-1;
prevOrder=curSubSong->ordersLen-1;
// benchmark
for (int i=0; i<20; i++) {
std::chrono::high_resolution_clock::time_point timeStart=std::chrono::high_resolution_clock::now();
playSub(false);
std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now();
t[i]=(double)(std::chrono::duration_cast<std::chrono::microseconds>(timeEnd-timeStart).count())/1000000.0;
printf("[#%d] %fs\n",i+1,t[i]);
}
double tMin=DBL_MAX;
double tMax=0.0;
double tAvg=0.0;
for (int i=0; i<20; i++) {
if (t[i]<tMin) tMin=t[i];
if (t[i]>tMax) tMax=t[i];
tAvg+=t[i];
}
tAvg/=20.0;
printf("[RESULT] min %fs max %fs average %fs\n",tMin,tMax,tAvg);
return tAvg;
}
#define WRITE_TICK \
if (!wroteTick) { \
wroteTick=true; \
if (binary) { \
if (tick-lastTick>255) { \
w->writeC(0xfc); \
w->writeS(tick-lastTick); \
} else if (tick-lastTick>1) { \
w->writeC(0xfd); \
w->writeC(tick-lastTick); \
} else { \
w->writeC(0xfe); \
} \
} else { \
w->writeText(fmt::sprintf(">> TICK %d\n",tick)); \
} \
lastTick=tick; \
}
void writePackedCommandValues(SafeWriter* w, const DivCommand& c) {
w->writeC(c.cmd);
switch (c.cmd) {
case DIV_CMD_NOTE_ON:
case DIV_CMD_HINT_LEGATO:
if (c.value==DIV_NOTE_NULL) {
w->writeC(0xff);
} else {
w->writeC(c.value+60);
}
break;
case DIV_CMD_NOTE_OFF:
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
break;
case DIV_CMD_INSTRUMENT:
case DIV_CMD_HINT_VIBRATO_RANGE:
case DIV_CMD_HINT_VIBRATO_SHAPE:
case DIV_CMD_HINT_PITCH:
case DIV_CMD_HINT_VOLUME:
case DIV_CMD_SAMPLE_MODE:
case DIV_CMD_SAMPLE_FREQ:
case DIV_CMD_SAMPLE_BANK:
case DIV_CMD_SAMPLE_POS:
case DIV_CMD_SAMPLE_DIR:
case DIV_CMD_FM_HARD_RESET:
case DIV_CMD_FM_LFO:
case DIV_CMD_FM_LFO_WAVE:
case DIV_CMD_FM_FB:
case DIV_CMD_FM_EXTCH:
case DIV_CMD_FM_AM_DEPTH:
case DIV_CMD_FM_PM_DEPTH:
case DIV_CMD_STD_NOISE_FREQ:
case DIV_CMD_STD_NOISE_MODE:
case DIV_CMD_WAVE:
case DIV_CMD_GB_SWEEP_TIME:
case DIV_CMD_GB_SWEEP_DIR:
case DIV_CMD_PCE_LFO_MODE:
case DIV_CMD_PCE_LFO_SPEED:
case DIV_CMD_NES_DMC:
case DIV_CMD_C64_CUTOFF:
case DIV_CMD_C64_RESONANCE:
case DIV_CMD_C64_FILTER_MODE:
case DIV_CMD_C64_RESET_TIME:
case DIV_CMD_C64_RESET_MASK:
case DIV_CMD_C64_FILTER_RESET:
case DIV_CMD_C64_DUTY_RESET:
case DIV_CMD_C64_EXTENDED:
case DIV_CMD_AY_ENVELOPE_SET:
case DIV_CMD_AY_ENVELOPE_LOW:
case DIV_CMD_AY_ENVELOPE_HIGH:
case DIV_CMD_AY_ENVELOPE_SLIDE:
case DIV_CMD_AY_NOISE_MASK_AND:
case DIV_CMD_AY_NOISE_MASK_OR:
case DIV_CMD_AY_AUTO_ENVELOPE:
w->writeC(c.value);
break;
case DIV_CMD_PANNING:
case DIV_CMD_HINT_VIBRATO:
case DIV_CMD_HINT_ARPEGGIO:
case DIV_CMD_HINT_PORTA:
case DIV_CMD_FM_TL:
case DIV_CMD_FM_AM:
case DIV_CMD_FM_AR:
case DIV_CMD_FM_DR:
case DIV_CMD_FM_SL:
case DIV_CMD_FM_D2R:
case DIV_CMD_FM_RR:
case DIV_CMD_FM_DT:
case DIV_CMD_FM_DT2:
case DIV_CMD_FM_RS:
case DIV_CMD_FM_KSR:
case DIV_CMD_FM_VIB:
case DIV_CMD_FM_SUS:
case DIV_CMD_FM_WS:
case DIV_CMD_FM_SSG:
case DIV_CMD_FM_REV:
case DIV_CMD_FM_EG_SHIFT:
case DIV_CMD_FM_MULT:
case DIV_CMD_FM_FINE:
case DIV_CMD_AY_IO_WRITE:
case DIV_CMD_AY_AUTO_PWM:
w->writeC(c.value);
w->writeC(c.value2);
break;
case DIV_CMD_PRE_PORTA:
w->writeC((c.value?0x80:0)|(c.value2?0x40:0));
break;
case DIV_CMD_HINT_VOL_SLIDE:
case DIV_CMD_C64_FINE_DUTY:
case DIV_CMD_C64_FINE_CUTOFF:
w->writeS(c.value);
break;
case DIV_CMD_FM_FIXFREQ:
w->writeS((c.value<<12)|(c.value2&0x7ff));
break;
case DIV_CMD_NES_SWEEP:
w->writeC((c.value?8:0)|(c.value2&0x77));
break;
default:
logW("unimplemented command %s!",cmdName[c.cmd]);
break;
}
}
SafeWriter* DivEngine::saveCommand(bool binary) {
stop();
repeatPattern=false;
setOrder(0);
BUSY_BEGIN_SOFT;
// determine loop point
int loopOrder=0;
int loopRow=0;
int loopEnd=0;
walkSong(loopOrder,loopRow,loopEnd);
logI("loop point: %d %d",loopOrder,loopRow);
SafeWriter* w=new SafeWriter;
w->init();
// write header
if (binary) {
w->write("FCS",4);
} else {
w->writeText("# Furnace Command Stream\n\n");
w->writeText("[Information]\n");
w->writeText(fmt::sprintf("name: %s\n",song.name));
w->writeText(fmt::sprintf("author: %s\n",song.author));
w->writeText(fmt::sprintf("category: %s\n",song.category));
w->writeText(fmt::sprintf("system: %s\n",song.systemName));
w->writeText("\n");
w->writeText("[SubSongInformation]\n");
w->writeText(fmt::sprintf("name: %s\n",curSubSong->name));
w->writeText(fmt::sprintf("tickRate: %f\n",curSubSong->hz));
w->writeText("\n");
w->writeText("[SysDefinition]\n");
// TODO
w->writeText("\n");
}
// play the song ourselves
bool done=false;
playSub(false);
if (!binary) {
w->writeText("[Stream]\n");
}
int tick=0;
bool oldCmdStreamEnabled=cmdStreamEnabled;
cmdStreamEnabled=true;
double curDivider=divider;
int lastTick=0;
while (!done) {
if (nextTick(false,true) || !playing) {
done=true;
}
// get command stream
bool wroteTick=false;
if (curDivider!=divider) {
curDivider=divider;
WRITE_TICK;
if (binary) {
w->writeC(0xfb);
w->writeI((int)(curDivider*65536));
} else {
w->writeText(fmt::sprintf(">> SET_RATE %f\n",curDivider));
}
}
for (DivCommand& i: cmdStream) {
switch (i.cmd) {
// strip away hinted/useless commands
case DIV_ALWAYS_SET_VOLUME:
break;
case DIV_CMD_GET_VOLUME:
break;
case DIV_CMD_VOLUME:
break;
case DIV_CMD_NOTE_PORTA:
break;
case DIV_CMD_LEGATO:
break;
case DIV_CMD_PITCH:
break;
case DIV_CMD_PRE_NOTE:
break;
default:
WRITE_TICK;
if (binary) {
w->writeC(i.chan);
writePackedCommandValues(w,i);
} else {
w->writeText(fmt::sprintf(" %d: %s %d %d\n",i.chan,cmdName[i.cmd],i.value,i.value2));
}
break;
}
}
cmdStream.clear();
tick++;
}
cmdStreamEnabled=oldCmdStreamEnabled;
if (binary) {
w->writeC(0xff);
} else {
if (!playing) {
w->writeText(">> END\n");
} else {
w->writeText(">> LOOP 0\n");
}
}
remainingLoops=-1;
playing=false;
freelance=false;
extValuePresent=false;
BUSY_END;
return w;
}
void _runExportThread(DivEngine* caller) { void _runExportThread(DivEngine* caller) {
caller->runExportThread(); caller->runExportThread();
} }
@ -189,8 +488,6 @@ bool DivEngine::isExporting() {
return exporting; return exporting;
} }
#define EXPORT_BUFSIZE 2048
#ifdef HAVE_SNDFILE #ifdef HAVE_SNDFILE
void DivEngine::runExportThread() { void DivEngine::runExportThread() {
size_t fadeOutSamples=got.rate*exportFadeOut; size_t fadeOutSamples=got.rate*exportFadeOut;
@ -786,7 +1083,7 @@ void DivEngine::initSongWithDesc(const int* description) {
} }
} }
void DivEngine::createNew(const int* description) { void DivEngine::createNew(const int* description, String sysName) {
quitDispatch(); quitDispatch();
BUSY_BEGIN; BUSY_BEGIN;
saveLock.lock(); saveLock.lock();
@ -796,6 +1093,11 @@ void DivEngine::createNew(const int* description) {
if (description!=NULL) { if (description!=NULL) {
initSongWithDesc(description); initSongWithDesc(description);
} }
if (sysName=="") {
song.systemName=getSongSystemLegacyName(song,!getConfInt("noMultiSystem",0));
} else {
song.systemName=sysName;
}
recalcChans(); recalcChans();
saveLock.unlock(); saveLock.unlock();
BUSY_END; BUSY_END;
@ -1879,11 +2181,13 @@ void DivEngine::delInstrument(int index) {
song.ins.erase(song.ins.begin()+index); song.ins.erase(song.ins.begin()+index);
song.insLen=song.ins.size(); song.insLen=song.ins.size();
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
for (int j=0; j<256; j++) { for (size_t j=0; j<song.subsong.size(); j++) {
if (curPat[i].data[j]==NULL) continue; for (int k=0; k<256; k++) {
for (int k=0; k<curSubSong->patLen; k++) { if (song.subsong[j]->pat[i].data[k]==NULL) continue;
if (curPat[i].data[j]->data[k][2]>index) { for (int l=0; l<song.subsong[j]->patLen; l++) {
curPat[i].data[j]->data[k][2]--; if (song.subsong[j]->pat[i].data[k]->data[l][2]>index) {
song.subsong[j]->pat[i].data[k]->data[l][2]--;
}
} }
} }
} }
@ -1894,7 +2198,10 @@ void DivEngine::delInstrument(int index) {
} }
int DivEngine::addWave() { int DivEngine::addWave() {
if (song.wave.size()>=256) return -1; if (song.wave.size()>=256) {
lastError="too many wavetables!";
return -1;
}
BUSY_BEGIN; BUSY_BEGIN;
saveLock.lock(); saveLock.lock();
DivWavetable* wave=new DivWavetable; DivWavetable* wave=new DivWavetable;
@ -1906,50 +2213,62 @@ int DivEngine::addWave() {
return waveCount; return waveCount;
} }
bool DivEngine::addWaveFromFile(const char* path, bool addRaw) { int DivEngine::addWavePtr(DivWavetable* which) {
if (song.wave.size()>=256) { if (song.wave.size()>=256) {
lastError="too many wavetables!"; lastError="too many wavetables!";
return false; delete which;
return -1;
} }
BUSY_BEGIN;
saveLock.lock();
int waveCount=(int)song.wave.size();
song.wave.push_back(which);
song.waveLen=waveCount+1;
saveLock.unlock();
BUSY_END;
return song.waveLen;
}
DivWavetable* DivEngine::waveFromFile(const char* path, bool addRaw) {
FILE* f=ps_fopen(path,"rb"); FILE* f=ps_fopen(path,"rb");
if (f==NULL) { if (f==NULL) {
lastError=fmt::sprintf("%s",strerror(errno)); lastError=fmt::sprintf("%s",strerror(errno));
return false; return NULL;
} }
unsigned char* buf; unsigned char* buf;
ssize_t len; ssize_t len;
if (fseek(f,0,SEEK_END)!=0) { if (fseek(f,0,SEEK_END)!=0) {
fclose(f); fclose(f);
lastError=fmt::sprintf("could not seek to end: %s",strerror(errno)); lastError=fmt::sprintf("could not seek to end: %s",strerror(errno));
return false; return NULL;
} }
len=ftell(f); len=ftell(f);
if (len<0) { if (len<0) {
fclose(f); fclose(f);
lastError=fmt::sprintf("could not determine file size: %s",strerror(errno)); lastError=fmt::sprintf("could not determine file size: %s",strerror(errno));
return false; return NULL;
} }
if (len==(SIZE_MAX>>1)) { if (len==(SIZE_MAX>>1)) {
fclose(f); fclose(f);
lastError="file size is invalid!"; lastError="file size is invalid!";
return false; return NULL;
} }
if (len==0) { if (len==0) {
fclose(f); fclose(f);
lastError="file is empty"; lastError="file is empty";
return false; return NULL;
} }
if (fseek(f,0,SEEK_SET)!=0) { if (fseek(f,0,SEEK_SET)!=0) {
fclose(f); fclose(f);
lastError=fmt::sprintf("could not seek to beginning: %s",strerror(errno)); lastError=fmt::sprintf("could not seek to beginning: %s",strerror(errno));
return false; return NULL;
} }
buf=new unsigned char[len]; buf=new unsigned char[len];
if (fread(buf,1,len,f)!=(size_t)len) { if (fread(buf,1,len,f)!=(size_t)len) {
logW("did not read entire wavetable file buffer!"); logW("did not read entire wavetable file buffer!");
delete[] buf; delete[] buf;
lastError=fmt::sprintf("could not read entire file: %s",strerror(errno)); lastError=fmt::sprintf("could not read entire file: %s",strerror(errno));
return false; return NULL;
} }
fclose(f); fclose(f);
@ -1977,7 +2296,7 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) {
lastError="invalid wavetable header/data!"; lastError="invalid wavetable header/data!";
delete wave; delete wave;
delete[] buf; delete[] buf;
return false; return NULL;
} }
} else { } else {
try { try {
@ -2018,7 +2337,7 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) {
} else { } else {
delete wave; delete wave;
delete[] buf; delete[] buf;
return false; return NULL;
} }
} }
} catch (EndOfFileException& e) { } catch (EndOfFileException& e) {
@ -2036,7 +2355,7 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) {
} else { } else {
delete wave; delete wave;
delete[] buf; delete[] buf;
return false; return NULL;
} }
} }
} }
@ -2044,17 +2363,10 @@ bool DivEngine::addWaveFromFile(const char* path, bool addRaw) {
delete wave; delete wave;
delete[] buf; delete[] buf;
lastError="premature end of file"; lastError="premature end of file";
return false; return NULL;
} }
BUSY_BEGIN; return wave;
saveLock.lock();
int waveCount=(int)song.wave.size();
song.wave.push_back(wave);
song.waveLen=waveCount+1;
saveLock.unlock();
BUSY_END;
return true;
} }
void DivEngine::delWave(int index) { void DivEngine::delWave(int index) {
@ -2070,7 +2382,10 @@ void DivEngine::delWave(int index) {
} }
int DivEngine::addSample() { int DivEngine::addSample() {
if (song.sample.size()>=256) return -1; if (song.sample.size()>=256) {
lastError="too many samples!";
return -1;
}
BUSY_BEGIN; BUSY_BEGIN;
saveLock.lock(); saveLock.lock();
DivSample* sample=new DivSample; DivSample* sample=new DivSample;
@ -2086,11 +2401,28 @@ int DivEngine::addSample() {
return sampleCount; return sampleCount;
} }
int DivEngine::addSampleFromFile(const char* path) { int DivEngine::addSamplePtr(DivSample* which) {
if (song.sample.size()>=256) { if (song.sample.size()>=256) {
lastError="too many samples!"; lastError="too many samples!";
delete which;
return -1; return -1;
} }
int sampleCount=(int)song.sample.size();
BUSY_BEGIN;
saveLock.lock();
song.sample.push_back(which);
song.sampleLen=sampleCount+1;
saveLock.unlock();
renderSamples();
BUSY_END;
return sampleCount;
}
DivSample* DivEngine::sampleFromFile(const char* path) {
if (song.sample.size()>=256) {
lastError="too many samples!";
return NULL;
}
BUSY_BEGIN; BUSY_BEGIN;
warnings=""; warnings="";
@ -2123,7 +2455,6 @@ int DivEngine::addSampleFromFile(const char* path) {
if (extS==".dmc") { // read as .dmc if (extS==".dmc") { // read as .dmc
size_t len=0; size_t len=0;
DivSample* sample=new DivSample; DivSample* sample=new DivSample;
int sampleCount=(int)song.sample.size();
sample->name=stripPath; sample->name=stripPath;
FILE* f=ps_fopen(path,"rb"); FILE* f=ps_fopen(path,"rb");
@ -2131,7 +2462,7 @@ int DivEngine::addSampleFromFile(const char* path) {
BUSY_END; BUSY_END;
lastError=fmt::sprintf("could not open file! (%s)",strerror(errno)); lastError=fmt::sprintf("could not open file! (%s)",strerror(errno));
delete sample; delete sample;
return -1; return NULL;
} }
if (fseek(f,0,SEEK_END)<0) { if (fseek(f,0,SEEK_END)<0) {
@ -2139,7 +2470,7 @@ int DivEngine::addSampleFromFile(const char* path) {
BUSY_END; BUSY_END;
lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno)); lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno));
delete sample; delete sample;
return -1; return NULL;
} }
len=ftell(f); len=ftell(f);
@ -2149,7 +2480,7 @@ int DivEngine::addSampleFromFile(const char* path) {
BUSY_END; BUSY_END;
lastError="file is empty!"; lastError="file is empty!";
delete sample; delete sample;
return -1; return NULL;
} }
if (len==(SIZE_MAX>>1)) { if (len==(SIZE_MAX>>1)) {
@ -2157,7 +2488,7 @@ int DivEngine::addSampleFromFile(const char* path) {
BUSY_END; BUSY_END;
lastError="file is invalid!"; lastError="file is invalid!";
delete sample; delete sample;
return -1; return NULL;
} }
if (fseek(f,0,SEEK_SET)<0) { if (fseek(f,0,SEEK_SET)<0) {
@ -2165,12 +2496,12 @@ int DivEngine::addSampleFromFile(const char* path) {
BUSY_END; BUSY_END;
lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno)); lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno));
delete sample; delete sample;
return -1; return NULL;
} }
sample->rate=33144; sample->rate=33144;
sample->centerRate=33144; sample->centerRate=33144;
sample->depth=1; sample->depth=DIV_SAMPLE_DEPTH_1BIT_DPCM;
sample->init(len*8); sample->init(len*8);
if (fread(sample->dataDPCM,1,len,f)==0) { if (fread(sample->dataDPCM,1,len,f)==0) {
@ -2178,22 +2509,16 @@ int DivEngine::addSampleFromFile(const char* path) {
BUSY_END; BUSY_END;
lastError=fmt::sprintf("could not read file! (%s)",strerror(errno)); lastError=fmt::sprintf("could not read file! (%s)",strerror(errno));
delete sample; delete sample;
return -1; return NULL;
} }
saveLock.lock();
song.sample.push_back(sample);
song.sampleLen=sampleCount+1;
saveLock.unlock();
renderSamples();
BUSY_END; BUSY_END;
return sampleCount; return sample;
} }
} }
#ifndef HAVE_SNDFILE #ifndef HAVE_SNDFILE
lastError="Furnace was not compiled with libsndfile!"; lastError="Furnace was not compiled with libsndfile!";
return -1; return NULL;
#else #else
SF_INFO si; SF_INFO si;
SFWrapper sfWrap; SFWrapper sfWrap;
@ -2205,15 +2530,15 @@ int DivEngine::addSampleFromFile(const char* path) {
if (err==SF_ERR_SYSTEM) { if (err==SF_ERR_SYSTEM) {
lastError=fmt::sprintf("could not open file! (%s %s)",sf_error_number(err),strerror(errno)); lastError=fmt::sprintf("could not open file! (%s %s)",sf_error_number(err),strerror(errno));
} else { } else {
lastError=fmt::sprintf("could not open file! (%s)",sf_error_number(err)); lastError=fmt::sprintf("could not open file! (%s)\nif this is raw sample data, you may import it by right-clicking the Load Sample icon and selecting \"import raw\".",sf_error_number(err));
} }
return -1; return NULL;
} }
if (si.frames>16777215) { if (si.frames>16777215) {
lastError="this sample is too big! max sample size is 16777215."; lastError="this sample is too big! max sample size is 16777215.";
sfWrap.doClose(); sfWrap.doClose();
BUSY_END; BUSY_END;
return -1; return NULL;
} }
void* buf=NULL; void* buf=NULL;
sf_count_t sampleLen=sizeof(short); sf_count_t sampleLen=sizeof(short);
@ -2245,9 +2570,9 @@ int DivEngine::addSampleFromFile(const char* path) {
int index=0; int index=0;
if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) { if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) {
sample->depth=8; sample->depth=DIV_SAMPLE_DEPTH_8BIT;
} else { } else {
sample->depth=16; sample->depth=DIV_SAMPLE_DEPTH_16BIT;
} }
sample->init(si.frames); sample->init(si.frames);
if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) { if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) {
@ -2302,6 +2627,7 @@ int DivEngine::addSampleFromFile(const char* path) {
if(inst.loop_count && inst.loops[0].mode == SF_LOOP_FORWARD) if(inst.loop_count && inst.loops[0].mode == SF_LOOP_FORWARD)
{ {
sample->loopStart=inst.loops[0].start; sample->loopStart=inst.loops[0].start;
sample->loopEnd=inst.loops[0].end;
if(inst.loops[0].end < (unsigned int)sampleCount) if(inst.loops[0].end < (unsigned int)sampleCount)
sampleCount=inst.loops[0].end; sampleCount=inst.loops[0].end;
} }
@ -2310,16 +2636,181 @@ int DivEngine::addSampleFromFile(const char* path) {
if (sample->centerRate<4000) sample->centerRate=4000; if (sample->centerRate<4000) sample->centerRate=4000;
if (sample->centerRate>64000) sample->centerRate=64000; if (sample->centerRate>64000) sample->centerRate=64000;
sfWrap.doClose(); sfWrap.doClose();
saveLock.lock();
song.sample.push_back(sample);
song.sampleLen=sampleCount+1;
saveLock.unlock();
renderSamples();
BUSY_END; BUSY_END;
return sampleCount; return sample;
#endif #endif
} }
DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign) {
if (song.sample.size()>=256) {
lastError="too many samples!";
return NULL;
}
if (channels<1) {
lastError="invalid channel count";
return NULL;
}
if (depth!=DIV_SAMPLE_DEPTH_8BIT && depth!=DIV_SAMPLE_DEPTH_16BIT) {
if (channels!=1) {
lastError="channel count has to be 1 for non-8/16-bit format";
return NULL;
}
}
BUSY_BEGIN;
warnings="";
const char* pathRedux=strrchr(path,DIR_SEPARATOR);
if (pathRedux==NULL) {
pathRedux=path;
} else {
pathRedux++;
}
String stripPath;
const char* pathReduxEnd=strrchr(pathRedux,'.');
if (pathReduxEnd==NULL) {
stripPath=pathRedux;
} else {
for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) {
stripPath+=*i;
}
}
size_t len=0;
size_t lenDivided=0;
DivSample* sample=new DivSample;
sample->name=stripPath;
FILE* f=ps_fopen(path,"rb");
if (f==NULL) {
BUSY_END;
lastError=fmt::sprintf("could not open file! (%s)",strerror(errno));
delete sample;
return NULL;
}
if (fseek(f,0,SEEK_END)<0) {
fclose(f);
BUSY_END;
lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno));
delete sample;
return NULL;
}
len=ftell(f);
if (len==0) {
fclose(f);
BUSY_END;
lastError="file is empty!";
delete sample;
return NULL;
}
if (len==(SIZE_MAX>>1)) {
fclose(f);
BUSY_END;
lastError="file is invalid!";
delete sample;
return NULL;
}
if (fseek(f,0,SEEK_SET)<0) {
fclose(f);
BUSY_END;
lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno));
delete sample;
return NULL;
}
lenDivided=len/channels;
unsigned int samples=lenDivided;
switch (depth) {
case DIV_SAMPLE_DEPTH_1BIT:
case DIV_SAMPLE_DEPTH_1BIT_DPCM:
samples=lenDivided*8;
break;
case DIV_SAMPLE_DEPTH_YMZ_ADPCM:
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM:
case DIV_SAMPLE_DEPTH_ADPCM_A:
case DIV_SAMPLE_DEPTH_ADPCM_B:
case DIV_SAMPLE_DEPTH_VOX:
samples=lenDivided*2;
break;
case DIV_SAMPLE_DEPTH_8BIT:
samples=lenDivided;
break;
case DIV_SAMPLE_DEPTH_BRR:
samples=16*((lenDivided+8)/9);
break;
case DIV_SAMPLE_DEPTH_16BIT:
samples=(lenDivided+1)/2;
break;
default:
break;
}
if (samples>16777215) {
fclose(f);
BUSY_END;
lastError="this sample is too big! max sample size is 16777215.";
delete sample;
return NULL;
}
sample->rate=32000;
sample->centerRate=32000;
sample->depth=depth;
sample->init(samples);
unsigned char* buf=new unsigned char[len];
if (fread(buf,1,len,f)==0) {
fclose(f);
BUSY_END;
lastError=fmt::sprintf("could not read file! (%s)",strerror(errno));
delete[] buf;
delete sample;
return NULL;
}
fclose(f);
// import sample
size_t pos=0;
if (depth==DIV_SAMPLE_DEPTH_16BIT) {
for (unsigned int i=0; i<samples; i++) {
int accum=0;
for (int j=0; j<channels; j++) {
if (pos+1>=len) break;
if (bigEndian) {
accum+=(short)(((short)((buf[pos]<<8)|buf[pos+1]))^(unsign?0x8000:0));
} else {
accum+=(short)(((short)(buf[pos]|(buf[pos+1]<<8)))^(unsign?0x8000:0));
}
pos+=2;
}
accum/=channels;
sample->data16[i]=accum;
}
} else if (depth==DIV_SAMPLE_DEPTH_8BIT) {
for (unsigned int i=0; i<samples; i++) {
int accum=0;
for (int j=0; j<channels; j++) {
if (pos>=len) break;
accum+=(signed char)(buf[pos++]^(unsign?0x80:0));
}
accum/=channels;
sample->data8[i]=accum;
}
} else {
memcpy(sample->getCurBuf(),buf,len);
}
delete[] buf;
BUSY_END;
return sample;
}
void DivEngine::delSample(int index) { void DivEngine::delSample(int index) {
BUSY_BEGIN; BUSY_BEGIN;
sPreview.sample=-1; sPreview.sample=-1;
@ -2497,13 +2988,15 @@ void DivEngine::moveOrderDown() {
void DivEngine::exchangeIns(int one, int two) { void DivEngine::exchangeIns(int one, int two) {
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
for (int j=0; j<256; j++) { for (size_t j=0; j<song.subsong.size(); j++) {
if (curPat[i].data[j]==NULL) continue; for (int k=0; k<256; k++) {
for (int k=0; k<curSubSong->patLen; k++) { if (song.subsong[j]->pat[i].data[k]==NULL) continue;
if (curPat[i].data[j]->data[k][2]==one) { for (int l=0; l<curSubSong->patLen; l++) {
curPat[i].data[j]->data[k][2]=two; if (song.subsong[j]->pat[i].data[k]->data[l][2]==one) {
} else if (curPat[i].data[j]->data[k][2]==two) { song.subsong[j]->pat[i].data[k]->data[l][2]=two;
curPat[i].data[j]->data[k][2]=one; } else if (song.subsong[j]->pat[i].data[k]->data[l][2]==two) {
song.subsong[j]->pat[i].data[k]->data[l][2]=one;
}
} }
} }
} }
@ -2927,36 +3420,6 @@ void DivEngine::quitDispatch() {
BUSY_END; BUSY_END;
} }
#define CHECK_CONFIG_DIR_MAC() \
configPath+="/Library/Application Support/Furnace"; \
if (stat(configPath.c_str(),&st)<0) { \
logI("creating config dir..."); \
if (mkdir(configPath.c_str(),0755)<0) { \
logW("could not make config dir! (%s)",strerror(errno)); \
configPath="."; \
} \
}
#define CHECK_CONFIG_DIR() \
configPath+="/.config"; \
if (stat(configPath.c_str(),&st)<0) { \
logI("creating user config dir..."); \
if (mkdir(configPath.c_str(),0755)<0) { \
logW("could not make user config dir! (%s)",strerror(errno)); \
configPath="."; \
} \
} \
if (configPath!=".") { \
configPath+="/furnace"; \
if (stat(configPath.c_str(),&st)<0) { \
logI("creating config dir..."); \
if (mkdir(configPath.c_str(),0755)<0) { \
logW("could not make config dir! (%s)",strerror(errno)); \
configPath="."; \
} \
} \
}
bool DivEngine::initAudioBackend() { bool DivEngine::initAudioBackend() {
// load values // load values
if (audioEngine==DIV_AUDIO_NULL) { if (audioEngine==DIV_AUDIO_NULL) {
@ -2969,6 +3432,7 @@ bool DivEngine::initAudioBackend() {
lowQuality=getConfInt("audioQuality",0); lowQuality=getConfInt("audioQuality",0);
forceMono=getConfInt("forceMono",0); forceMono=getConfInt("forceMono",0);
clampSamples=getConfInt("clampSamples",0);
lowLatency=getConfInt("lowLatency",0); lowLatency=getConfInt("lowLatency",0);
metroVol=(float)(getConfInt("metroVol",100))/100.0f; metroVol=(float)(getConfInt("metroVol",100))/100.0f;
if (metroVol<0.0f) metroVol=0.0f; if (metroVol<0.0f) metroVol=0.0f;
@ -3065,6 +3529,7 @@ bool DivEngine::initAudioBackend() {
bool DivEngine::deinitAudioBackend() { bool DivEngine::deinitAudioBackend() {
if (output!=NULL) { if (output!=NULL) {
output->quit();
if (output->midiIn) { if (output->midiIn) {
if (output->midiIn->isDeviceOpen()) { if (output->midiIn->isDeviceOpen()) {
logI("closing MIDI input."); logI("closing MIDI input.");
@ -3078,53 +3543,19 @@ bool DivEngine::deinitAudioBackend() {
} }
} }
output->quitMidi(); output->quitMidi();
output->quit();
delete output; delete output;
output=NULL; output=NULL;
audioEngine=DIV_AUDIO_NULL; //audioEngine=DIV_AUDIO_NULL;
} }
return true; return true;
} }
#ifdef _WIN32
#include "winStuff.h"
#endif
bool DivEngine::init() { bool DivEngine::init() {
// register systems // register systems
if (!systemsRegistered) registerSystems(); if (!systemsRegistered) registerSystems();
// init config // init config
#ifdef _WIN32 initConfDir();
configPath=getWinConfigPath();
#elif defined(IS_MOBILE)
configPath=SDL_GetPrefPath("tildearrow","furnace");
#else
struct stat st;
char* home=getenv("HOME");
if (home==NULL) {
int uid=getuid();
struct passwd* entry=getpwuid(uid);
if (entry==NULL) {
logW("unable to determine config directory! (%s)",strerror(errno));
configPath=".";
} else {
configPath=entry->pw_dir;
#ifdef __APPLE__
CHECK_CONFIG_DIR_MAC();
#else
CHECK_CONFIG_DIR();
#endif
}
} else {
configPath=home;
#ifdef __APPLE__
CHECK_CONFIG_DIR_MAC();
#else
CHECK_CONFIG_DIR();
#endif
}
#endif
logD("config path: %s",configPath.c_str()); logD("config path: %s",configPath.c_str());
loadConf(); loadConf();
@ -3140,6 +3571,12 @@ bool DivEngine::init() {
preset.push_back(0); preset.push_back(0);
initSongWithDesc(preset.data()); initSongWithDesc(preset.data());
} }
String sysName=getConfString("initialSysName","");
if (sysName=="") {
song.systemName=getSongSystemLegacyName(song,!getConfInt("noMultiSystem",0));
} else {
song.systemName=sysName;
}
hasLoadedSomething=true; hasLoadedSomething=true;
} }

View file

@ -45,11 +45,15 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false; #define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "0.6pre1 (dev101)" #define DIV_VERSION "dev106"
#define DIV_ENGINE_VERSION 101 #define DIV_ENGINE_VERSION 106
// for imports // for imports
#define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
// "Namco C163"
#define DIV_C163_DEFAULT_NAME "Namco 163"
enum DivStatusView { enum DivStatusView {
DIV_STATUS_NOTHING=0, DIV_STATUS_NOTHING=0,
@ -160,7 +164,7 @@ struct DivNoteEvent {
struct DivDispatchContainer { struct DivDispatchContainer {
DivDispatch* dispatch; DivDispatch* dispatch;
blip_buffer_t* bb[2]; blip_buffer_t* bb[2];
size_t bbInLen; size_t bbInLen, runtotal, runLeft, runPos, lastAvail;
int temp[2], prevSample[2]; int temp[2], prevSample[2];
short* bbIn[2]; short* bbIn[2];
short* bbOut[2]; short* bbOut[2];
@ -178,6 +182,10 @@ struct DivDispatchContainer {
dispatch(NULL), dispatch(NULL),
bb{NULL,NULL}, bb{NULL,NULL},
bbInLen(0), bbInLen(0),
runtotal(0),
runLeft(0),
runPos(0),
lastAvail(0),
temp{0,0}, temp{0,0},
prevSample{0,0}, prevSample{0,0},
bbIn{NULL,NULL}, bbIn{NULL,NULL},
@ -276,6 +284,8 @@ enum DivChanTypes {
DIV_CH_OP=5 DIV_CH_OP=5
}; };
extern const char* cmdName[];
class DivEngine { class DivEngine {
DivDispatchContainer disCont[32]; DivDispatchContainer disCont[32];
TAAudio* output; TAAudio* output;
@ -297,6 +307,7 @@ class DivEngine {
bool stopExport; bool stopExport;
bool halted; bool halted;
bool forceMono; bool forceMono;
bool clampSamples;
bool cmdStreamEnabled; bool cmdStreamEnabled;
bool softLocked; bool softLocked;
bool firstTick; bool firstTick;
@ -355,6 +366,7 @@ class DivEngine {
short vibTable[64]; short vibTable[64];
int reversePitchTable[4096]; int reversePitchTable[4096];
int pitchTable[4096]; int pitchTable[4096];
char c163NameCS[1024];
int midiBaseChan; int midiBaseChan;
bool midiPoly; bool midiPoly;
size_t midiAgeCounter; size_t midiAgeCounter;
@ -396,6 +408,7 @@ class DivEngine {
bool loadFur(unsigned char* file, size_t len); bool loadFur(unsigned char* file, size_t len);
bool loadMod(unsigned char* file, size_t len); bool loadMod(unsigned char* file, size_t len);
bool loadFTM(unsigned char* file, size_t len); bool loadFTM(unsigned char* file, size_t len);
bool loadFC(unsigned char* file, size_t len);
void loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath); void loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadTFI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath); void loadTFI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
@ -453,7 +466,7 @@ class DivEngine {
String encodeSysDesc(std::vector<int>& desc); String encodeSysDesc(std::vector<int>& desc);
std::vector<int> decodeSysDesc(String desc); std::vector<int> decodeSysDesc(String desc);
// start fresh // start fresh
void createNew(const int* description); void createNew(const int* description, String sysName);
// load a file. // load a file.
bool load(unsigned char* f, size_t length); bool load(unsigned char* f, size_t length);
// save as .dmf. // save as .dmf.
@ -465,9 +478,11 @@ class DivEngine {
// specify system to build ROM for. // specify system to build ROM for.
SafeWriter* buildROM(int sys); SafeWriter* buildROM(int sys);
// dump to VGM. // dump to VGM.
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171); SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false);
// dump to ZSM. // dump to ZSM.
SafeWriter* saveZSM(unsigned int zsmrate=60, bool loop=true); SafeWriter* saveZSM(unsigned int zsmrate=60, bool loop=true);
// dump command stream.
SafeWriter* saveCommand(bool binary=false);
// export to an audio file // export to an audio file
bool saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime=0.0); bool saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime=0.0);
// wait for audio export to finish // wait for audio export to finish
@ -479,9 +494,16 @@ class DivEngine {
// notify wavetable change // notify wavetable change
void notifyWaveChange(int wave); void notifyWaveChange(int wave);
// benchmark (returns time in seconds)
double benchmarkPlayback();
double benchmarkSeek();
// returns the minimum VGM version which may carry the specified system, or 0 if none. // returns the minimum VGM version which may carry the specified system, or 0 if none.
int minVGMVersion(DivSystem which); int minVGMVersion(DivSystem which);
// determine and setup config dir
void initConfDir();
// save config // save config
bool saveConf(); bool saveConf();
@ -573,7 +595,7 @@ class DivEngine {
DivInstrumentType getPreferInsSecondType(int ch); DivInstrumentType getPreferInsSecondType(int ch);
// get song system name // get song system name
String getSongSystemName(bool isMultiSystemAcceptable=true); String getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAcceptable=true);
// get sys name // get sys name
const char* getSystemName(DivSystem sys); const char* getSystemName(DivSystem sys);
@ -686,8 +708,11 @@ class DivEngine {
// add wavetable // add wavetable
int addWave(); int addWave();
// add wavetable from file // add wavetable from pointer
bool addWaveFromFile(const char* path, bool loadRaw=true); int addWavePtr(DivWavetable* which);
// get wavetable from file
DivWavetable* waveFromFile(const char* path, bool loadRaw=true);
// delete wavetable // delete wavetable
void delWave(int index); void delWave(int index);
@ -695,8 +720,14 @@ class DivEngine {
// add sample // add sample
int addSample(); int addSample();
// add sample from file // add sample from pointer
int addSampleFromFile(const char* path); int addSamplePtr(DivSample* which);
// get sample from file
DivSample* sampleFromFile(const char* path);
// get raw sample
DivSample* sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign);
// delete sample // delete sample
void delSample(int index); void delSample(int index);

File diff suppressed because it is too large Load diff

View file

@ -21,6 +21,7 @@
#include "../ta-log.h" #include "../ta-log.h"
#include "../fileutils.h" #include "../fileutils.h"
#include <fmt/printf.h> #include <fmt/printf.h>
#include <limits.h>
enum DivInsFormats { enum DivInsFormats {
DIV_INSFORMAT_DMP, DIV_INSFORMAT_DMP,
@ -732,6 +733,8 @@ void DivEngine::loadOPLI(SafeReader& reader, std::vector<DivInstrument*>& ret, S
ins = new DivInstrument; ins = new DivInstrument;
ins->type = DIV_INS_OPL; ins->type = DIV_INS_OPL;
ins->name = fmt::sprintf("%s (2)", insName); ins->name = fmt::sprintf("%s (2)", insName);
ins->fm.alg = (feedConnect2nd & 0x1);
ins->fm.fb = ((feedConnect2nd >> 1) & 0xF);
for (int i : {1,0}) { for (int i : {1,0}) {
readOpliOp(reader, ins->fm.op[i]); readOpliOp(reader, ins->fm.op[i]);
} }
@ -1262,7 +1265,7 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, St
patchNameRead = lfoRead = characteristicRead = m1Read = c1Read = m2Read = c2Read = false; patchNameRead = lfoRead = characteristicRead = m1Read = c1Read = m2Read = c2Read = false;
newPatch = NULL; newPatch = NULL;
}; };
auto readIntStrWithinRange = [](String&& input, int limitLow, int limitHigh) -> int { auto readIntStrWithinRange = [](String&& input, int limitLow = INT_MIN, int limitHigh = INT_MAX) -> int {
int x = std::stoi(input.c_str()); int x = std::stoi(input.c_str());
if (x > limitHigh || x < limitLow) { if (x > limitHigh || x < limitLow) {
throw std::invalid_argument(fmt::sprintf("%s is out of bounds of range [%d..%d]", input, limitLow, limitHigh)); throw std::invalid_argument(fmt::sprintf("%s is out of bounds of range [%d..%d]", input, limitLow, limitHigh));
@ -1280,7 +1283,7 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, St
op.mult = readIntStrWithinRange(reader.readStringToken(), 0, 15); op.mult = readIntStrWithinRange(reader.readStringToken(), 0, 15);
op.dt = fmDtRegisterToFurnace(readIntStrWithinRange(reader.readStringToken(), 0, 7)); op.dt = fmDtRegisterToFurnace(readIntStrWithinRange(reader.readStringToken(), 0, 7));
op.dt2 = readIntStrWithinRange(reader.readStringToken(), 0, 3); op.dt2 = readIntStrWithinRange(reader.readStringToken(), 0, 3);
op.am = readIntStrWithinRange(reader.readStringToken(), 0, 1); op.am = readIntStrWithinRange(reader.readStringToken(), 0) > 0 ? 1 : 0;
}; };
auto seekGroupValStart = [](SafeReader& reader, int pos) { auto seekGroupValStart = [](SafeReader& reader, int pos) {
// Seek to position then move to next ':' character // Seek to position then move to next ':' character
@ -1497,6 +1500,8 @@ void DivEngine::loadWOPL(SafeReader& reader, std::vector<DivInstrument*>& ret, S
ins = new DivInstrument; ins = new DivInstrument;
ins->type = DIV_INS_OPL; ins->type = DIV_INS_OPL;
ins->name = fmt::sprintf("%s (2)", insName); ins->name = fmt::sprintf("%s (2)", insName);
ins->fm.alg = (feedConnect2nd & 0x1);
ins->fm.fb = ((feedConnect2nd >> 1) & 0xF);
for (int i : {1,0}) { for (int i : {1,0}) {
patchSum += readWoplOp(reader, ins->fm.op[i]); patchSum += readWoplOp(reader, ins->fm.op[i]);
} }

View file

@ -387,8 +387,12 @@ void DivInstrument::putInsData(SafeWriter* w) {
// sample map // sample map
w->writeC(amiga.useNoteMap); w->writeC(amiga.useNoteMap);
if (amiga.useNoteMap) { if (amiga.useNoteMap) {
w->write(amiga.noteFreq,120*sizeof(unsigned int)); for (int note=0; note<120; note++) {
w->write(amiga.noteMap,120*sizeof(short)); w->writeI(amiga.noteMap[note].freq);
}
for (int note=0; note<120; note++) {
w->writeS(amiga.noteMap[note].map);
}
} }
// N163 // N163
@ -524,6 +528,21 @@ void DivInstrument::putInsData(SafeWriter* w) {
w->writeC(0); w->writeC(0);
} }
// Sound Unit
w->writeC(su.useSample);
w->writeC(su.switchRoles);
// GB hardware sequence
w->writeC(gb.hwSeqLen);
for (int i=0; i<gb.hwSeqLen; i++) {
w->writeC(gb.hwSeq[i].cmd);
w->writeS(gb.hwSeq[i].data);
}
// GB additional flags
w->writeC(gb.softEnv);
w->writeC(gb.alwaysInit);
blockEndSeek=w->tell(); blockEndSeek=w->tell();
w->seek(blockStartSeek,SEEK_SET); w->seek(blockStartSeek,SEEK_SET);
w->writeI(blockEndSeek-blockStartSeek-4); w->writeI(blockEndSeek-blockStartSeek-4);
@ -932,8 +951,12 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
if (version>=67) { if (version>=67) {
amiga.useNoteMap=reader.readC(); amiga.useNoteMap=reader.readC();
if (amiga.useNoteMap) { if (amiga.useNoteMap) {
reader.read(amiga.noteFreq,120*sizeof(unsigned int)); for (int note=0; note<120; note++) {
reader.read(amiga.noteMap,120*sizeof(short)); amiga.noteMap[note].freq=reader.readI();
}
for (int note=0; note<120; note++) {
amiga.noteMap[note].map=reader.readS();
}
} }
} }
@ -1067,6 +1090,27 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
for (int k=0; k<23; k++) reader.readC(); for (int k=0; k<23; k++) reader.readC();
} }
// Sound Unit
if (version>=104) {
su.useSample=reader.readC();
su.switchRoles=reader.readC();
}
// GB hardware sequence
if (version>=105) {
gb.hwSeqLen=reader.readC();
for (int i=0; i<gb.hwSeqLen; i++) {
gb.hwSeq[i].cmd=reader.readC();
gb.hwSeq[i].data=reader.readS();
}
}
// GB additional flags
if (version>=106) {
gb.softEnv=reader.readC();
gb.alwaysInit=reader.readC();
}
return DIV_DATA_SUCCESS; return DIV_DATA_SUCCESS;
} }
@ -1106,3 +1150,148 @@ bool DivInstrument::save(const char* path) {
w->finish(); w->finish();
return true; return true;
} }
bool DivInstrument::saveDMP(const char* path) {
SafeWriter* w=new SafeWriter();
w->init();
// write version
w->writeC(11);
// guess the system
switch (type) {
case DIV_INS_FM:
// we can't tell between Genesis, Neo Geo and Arcade ins type yet
w->writeC(0x02);
w->writeC(1);
break;
case DIV_INS_STD:
// we can't tell between SMS and NES ins type yet
w->writeC(0x03);
w->writeC(0);
break;
case DIV_INS_GB:
w->writeC(0x04);
w->writeC(0);
break;
case DIV_INS_C64:
w->writeC(0x07);
w->writeC(0);
break;
case DIV_INS_PCE:
w->writeC(0x06);
w->writeC(0);
break;
case DIV_INS_OPLL:
// ???
w->writeC(0x13);
w->writeC(1);
break;
case DIV_INS_OPZ:
// data will be lost
w->writeC(0x08);
w->writeC(1);
break;
case DIV_INS_FDS:
// ???
w->writeC(0x06);
w->writeC(0);
break;
default:
// not supported by .dmp
w->finish();
return false;
}
if (type==DIV_INS_FM || type==DIV_INS_OPLL || type==DIV_INS_OPZ) {
w->writeC(fm.fms);
w->writeC(fm.fb);
w->writeC(fm.alg);
w->writeC(fm.ams);
// TODO: OPLL params
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=fm.op[i];
w->writeC(op.mult);
w->writeC(op.tl);
w->writeC(op.ar);
w->writeC(op.dr);
w->writeC(op.sl);
w->writeC(op.rr);
w->writeC(op.am);
w->writeC(op.rs);
w->writeC(op.dt|(op.dt2<<4));
w->writeC(op.d2r);
w->writeC(op.ssgEnv);
}
} else {
if (type!=DIV_INS_GB) {
w->writeC(std.volMacro.len);
for (int i=0; i<std.volMacro.len; i++) {
w->writeI(std.volMacro.val[i]);
}
if (std.volMacro.len>0) w->writeC(std.volMacro.loop);
}
w->writeC(std.arpMacro.len);
for (int i=0; i<std.arpMacro.len; i++) {
w->writeI(std.arpMacro.val[i]+12);
}
if (std.arpMacro.len>0) w->writeC(std.arpMacro.loop);
w->writeC(std.arpMacro.mode);
w->writeC(std.dutyMacro.len);
for (int i=0; i<std.dutyMacro.len; i++) {
w->writeI(std.dutyMacro.val[i]+12);
}
if (std.dutyMacro.len>0) w->writeC(std.dutyMacro.loop);
w->writeC(std.waveMacro.len);
for (int i=0; i<std.waveMacro.len; i++) {
w->writeI(std.waveMacro.val[i]+12);
}
if (std.waveMacro.len>0) w->writeC(std.waveMacro.loop);
if (type==DIV_INS_C64) {
w->writeC(c64.triOn);
w->writeC(c64.sawOn);
w->writeC(c64.pulseOn);
w->writeC(c64.noiseOn);
w->writeC(c64.a);
w->writeC(c64.d);
w->writeC(c64.s);
w->writeC(c64.r);
w->writeC((c64.duty*100)/4095);
w->writeC(c64.ringMod);
w->writeC(c64.oscSync);
w->writeC(c64.toFilter);
w->writeC(c64.volIsCutoff);
w->writeC(c64.initFilter);
w->writeC(c64.res);
w->writeC((c64.cut*100)/2047);
w->writeC(c64.hp);
w->writeC(c64.lp);
w->writeC(c64.bp);
w->writeC(c64.ch3off);
}
if (type==DIV_INS_GB) {
w->writeC(gb.envVol);
w->writeC(gb.envDir);
w->writeC(gb.envLen);
w->writeC(gb.soundLen);
}
}
FILE* outFile=ps_fopen(path,"wb");
if (outFile==NULL) {
logE("could not save instrument: %s!",strerror(errno));
w->finish();
return false;
}
if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) {
logW("did not write entire instrument!");
}
fclose(outFile);
w->finish();
return true;
}

View file

@ -261,12 +261,32 @@ struct DivInstrumentSTD {
}; };
struct DivInstrumentGB { struct DivInstrumentGB {
unsigned char envVol, envDir, envLen, soundLen; unsigned char envVol, envDir, envLen, soundLen, hwSeqLen;
bool softEnv, alwaysInit;
enum HWSeqCommands: unsigned char {
DIV_GB_HWCMD_ENVELOPE=0,
DIV_GB_HWCMD_SWEEP,
DIV_GB_HWCMD_WAIT,
DIV_GB_HWCMD_WAIT_REL,
DIV_GB_HWCMD_LOOP,
DIV_GB_HWCMD_LOOP_REL,
DIV_GB_HWCMD_MAX
};
struct HWSeqCommand {
unsigned char cmd;
unsigned short data;
} hwSeq[256];
DivInstrumentGB(): DivInstrumentGB():
envVol(15), envVol(15),
envDir(0), envDir(0),
envLen(2), envLen(2),
soundLen(64) {} soundLen(64),
hwSeqLen(0),
softEnv(false),
alwaysInit(false) {
memset(hwSeq,0,256*sizeof(int));
}
}; };
struct DivInstrumentC64 { struct DivInstrumentC64 {
@ -306,12 +326,18 @@ struct DivInstrumentC64 {
}; };
struct DivInstrumentAmiga { struct DivInstrumentAmiga {
struct SampleMap {
int freq;
short map;
SampleMap(int f=0, short m=-1):
freq(f),
map(m) {}
};
short initSample; short initSample;
bool useNoteMap; bool useNoteMap;
bool useWave; bool useWave;
unsigned char waveLen; unsigned char waveLen;
int noteFreq[120]; SampleMap noteMap[120];
short noteMap[120];
/** /**
* get the sample at specified note. * get the sample at specified note.
@ -321,7 +347,7 @@ struct DivInstrumentAmiga {
if (useNoteMap) { if (useNoteMap) {
if (note<0) note=0; if (note<0) note=0;
if (note>119) note=119; if (note>119) note=119;
return noteMap[note]; return noteMap[note].map;
} }
return initSample; return initSample;
} }
@ -334,7 +360,7 @@ struct DivInstrumentAmiga {
if (useNoteMap) { if (useNoteMap) {
if (note<0) note=0; if (note<0) note=0;
if (note>119) note=119; if (note>119) note=119;
return noteFreq[note]; return noteMap[note].freq;
} }
return -1; return -1;
} }
@ -344,8 +370,9 @@ struct DivInstrumentAmiga {
useNoteMap(false), useNoteMap(false),
useWave(false), useWave(false),
waveLen(31) { waveLen(31) {
memset(noteMap,-1,120*sizeof(short)); for (SampleMap& elem: noteMap) {
memset(noteFreq,0,120*sizeof(int)); elem=SampleMap();
}
} }
}; };
@ -430,6 +457,14 @@ struct DivInstrumentWaveSynth {
param4(0) {} param4(0) {}
}; };
struct DivInstrumentSoundUnit {
bool useSample;
bool switchRoles;
DivInstrumentSoundUnit():
useSample(false),
switchRoles(false) {}
};
struct DivInstrument { struct DivInstrument {
String name; String name;
bool mode; bool mode;
@ -443,6 +478,7 @@ struct DivInstrument {
DivInstrumentFDS fds; DivInstrumentFDS fds;
DivInstrumentMultiPCM multipcm; DivInstrumentMultiPCM multipcm;
DivInstrumentWaveSynth ws; DivInstrumentWaveSynth ws;
DivInstrumentSoundUnit su;
/** /**
* save the instrument to a SafeWriter. * save the instrument to a SafeWriter.
@ -464,6 +500,13 @@ struct DivInstrument {
* @return whether it was successful. * @return whether it was successful.
*/ */
bool save(const char* path); bool save(const char* path);
/**
* save this instrument to a file in .dmp format.
* @param path file path.
* @return whether it was successful.
*/
bool saveDMP(const char* path);
DivInstrument(): DivInstrument():
name(""), name(""),
type(DIV_INS_FM) { type(DIV_INS_FM) {

View file

@ -18,6 +18,7 @@
*/ */
#include "engine.h" #include "engine.h"
#include "../ta-log.h"
static DivPattern emptyPat; static DivPattern emptyPat;
@ -40,6 +41,44 @@ DivPattern* DivChannelData::getPattern(int index, bool create) {
return data[index]; return data[index];
} }
std::vector<std::pair<int,int>> DivChannelData::optimize() {
std::vector<std::pair<int,int>> ret;
for (int i=0; i<256; i++) {
if (data[i]!=NULL) {
// compare
for (int j=0; j<256; j++) {
if (j==i) continue;
if (data[j]==NULL) continue;
if (memcmp(data[i]->data,data[j]->data,256*32*sizeof(short))==0) {
delete data[j];
data[j]=NULL;
logV("%d == %d",i,j);
ret.push_back(std::pair<int,int>(j,i));
}
}
}
}
return ret;
}
std::vector<std::pair<int,int>> DivChannelData::rearrange() {
std::vector<std::pair<int,int>> ret;
for (int i=0; i<256; i++) {
if (data[i]==NULL) {
for (int j=i; j<256; j++) {
if (data[j]!=NULL) {
data[i]=data[j];
data[j]=NULL;
logV("%d -> %d",j,i);
ret.push_back(std::pair<int,int>(j,i));
if (++i>=256) break;
}
}
}
}
return ret;
}
void DivChannelData::wipePatterns() { void DivChannelData::wipePatterns() {
for (int i=0; i<256; i++) { for (int i=0; i<256; i++) {
if (data[i]!=NULL) { if (data[i]!=NULL) {
@ -54,81 +93,6 @@ void DivPattern::copyOn(DivPattern* dest) {
memcpy(dest->data,data,sizeof(data)); memcpy(dest->data,data,sizeof(data));
} }
SafeReader* DivPattern::compile(int len, int fxRows) {
SafeWriter w;
w.init();
short lastNote, lastOctave, lastInstr, lastVolume, lastEffect[8], lastEffectVal[8];
unsigned char rows=0;
lastNote=0;
lastOctave=0;
lastInstr=-1;
lastVolume=-1;
memset(lastEffect,-1,8*sizeof(short));
memset(lastEffectVal,-1,8*sizeof(short));
for (int i=0; i<len; i++) {
unsigned char mask=0;
if (data[i][0]!=-1) {
lastNote=data[i][0];
lastOctave=data[i][1];
mask|=128;
}
if (data[i][2]!=-1 && data[i][2]!=lastInstr) {
lastInstr=data[i][2];
mask|=32;
}
if (data[i][3]!=-1 && data[i][3]!=lastVolume) {
lastVolume=data[i][3];
mask|=64;
}
for (int j=0; j<fxRows; j++) {
if (data[i][4+(j<<1)]!=-1) {
lastEffect[j]=data[i][4+(j<<1)];
lastEffectVal[j]=data[i][5+(j<<1)];
mask=(mask&0xf8)|j;
}
}
if (!mask) {
rows++;
continue;
}
if (rows!=0) {
w.writeC(rows);
}
rows=1;
w.writeC(mask);
if (mask&128) {
if (lastNote==100) {
w.writeC(-128);
} else {
w.writeC(lastNote+(lastOctave*12));
}
}
if (mask&64) {
w.writeC(lastVolume);
}
if (mask&32) {
w.writeC(lastInstr);
}
for (int j=0; j<(mask&7); j++) {
w.writeC(lastEffect[j]);
if (lastEffectVal[j]==-1) {
w.writeC(0);
} else {
w.writeC(lastEffectVal[j]);
}
}
}
w.writeC(rows);
w.writeC(0);
return w.toReader();
}
DivChannelData::DivChannelData(): DivChannelData::DivChannelData():
effectCols(1) { effectCols(1) {
memset(data,0,256*sizeof(void*)); memset(data,0,256*sizeof(void*));

View file

@ -18,6 +18,7 @@
*/ */
#include "safeReader.h" #include "safeReader.h"
#include <vector>
struct DivPattern { struct DivPattern {
String name; String name;
@ -28,14 +29,6 @@ struct DivPattern {
* @param dest the destination pattern. * @param dest the destination pattern.
*/ */
void copyOn(DivPattern* dest); void copyOn(DivPattern* dest);
/**
* don't use yet!
* @param len the pattern length
* @param fxRows number of effect ...columns
* @return a SafeReader.
*/
SafeReader* compile(int len=256, int fxRows=1);
DivPattern(); DivPattern();
}; };
@ -59,6 +52,20 @@ struct DivChannelData {
*/ */
DivPattern* getPattern(int index, bool create); DivPattern* getPattern(int index, bool create);
/**
* optimize pattern data.
* not thread-safe! use a mutex!
* @return a list of From -> To pairs
*/
std::vector<std::pair<int,int>> optimize();
/**
* re-arrange NULLs.
* not thread-safe! use a mutex!
* @return a list of From -> To pairs
*/
std::vector<std::pair<int,int>> rearrange();
/** /**
* destroy all patterns on this DivChannelData. * destroy all patterns on this DivChannelData.
*/ */

View file

@ -90,6 +90,10 @@ bool DivDispatch::getDCOffRequired() {
return false; return false;
} }
bool DivDispatch::getWantPreNote() {
return false;
}
const char* DivDispatch::getEffectName(unsigned char effect) { const char* DivDispatch::getEffectName(unsigned char effect) {
return NULL; return NULL;
} }

View file

@ -114,12 +114,10 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le
if (chan[i].audPos<s->samples) { if (chan[i].audPos<s->samples) {
writeAudDat(s->data8[chan[i].audPos++]); writeAudDat(s->data8[chan[i].audPos++]);
} }
if (chan[i].audPos>=s->samples || chan[i].audPos>=131071) { if (s->isLoopable() && chan[i].audPos>=MIN(131071,s->getEndPosition())) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) { chan[i].audPos=s->loopStart;
chan[i].audPos=s->loopStart; } else if (chan[i].audPos>=MIN(131071,s->samples)) {
} else { chan[i].sample=-1;
chan[i].sample=-1;
}
} }
} else { } else {
chan[i].sample=-1; chan[i].sample=-1;

View file

@ -513,6 +513,10 @@ bool DivPlatformC64::getDCOffRequired() {
return true; return true;
} }
bool DivPlatformC64::getWantPreNote() {
return true;
}
void DivPlatformC64::reset() { void DivPlatformC64::reset() {
for (int i=0; i<3; i++) { for (int i=0; i<3; i++) {
chan[i]=DivPlatformC64::Channel(); chan[i]=DivPlatformC64::Channel();

View file

@ -97,6 +97,7 @@ class DivPlatformC64: public DivDispatch {
void setFlags(unsigned int flags); void setFlags(unsigned int flags);
void notifyInsChange(int ins); void notifyInsChange(int ins);
bool getDCOffRequired(); bool getDCOffRequired();
bool getWantPreNote();
DivMacroInt* getChanMacroInt(int ch); DivMacroInt* getChanMacroInt(int ch);
void notifyInsDeletion(void* ins); void notifyInsDeletion(void* ins);
void poke(unsigned int addr, unsigned short val); void poke(unsigned int addr, unsigned short val);

View file

@ -21,8 +21,8 @@
#include "../engine.h" #include "../engine.h"
#include <math.h> #include <math.h>
#define rWrite(a,v) if (!skipRegisterWrites) {GB_apu_write(gb,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } #define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
#define immWrite(a,v) {GB_apu_write(gb,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } #define immWrite(a,v) {writes.emplace(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
#define CHIP_DIVIDER 16 #define CHIP_DIVIDER 16
@ -84,6 +84,12 @@ const char* DivPlatformGB::getEffectName(unsigned char effect) {
void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len) { void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len) {
for (size_t i=start; i<start+len; i++) { for (size_t i=start; i<start+len; i++) {
if (!writes.empty()) {
QueuedWrite& w=writes.front();
GB_apu_write(gb,w.addr,w.val);
writes.pop();
}
GB_advance_cycles(gb,16); GB_advance_cycles(gb,16);
bufL[i]=gb->apu_output.final_sample.left; bufL[i]=gb->apu_output.final_sample.left;
bufR[i]=gb->apu_output.final_sample.right; bufR[i]=gb->apu_output.final_sample.right;
@ -97,10 +103,11 @@ void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len)
void DivPlatformGB::updateWave() { void DivPlatformGB::updateWave() {
rWrite(0x1a,0); rWrite(0x1a,0);
for (int i=0; i<16; i++) { for (int i=0; i<16; i++) {
int nibble1=15-ws.output[i<<1]; int nibble1=15-ws.output[((i<<1)+antiClickWavePos-1)&31];
int nibble2=15-ws.output[1+(i<<1)]; int nibble2=15-ws.output[((1+(i<<1))+antiClickWavePos-1)&31];
rWrite(0x30+i,(nibble1<<4)|nibble2); rWrite(0x30+i,(nibble1<<4)|nibble2);
} }
antiClickWavePos&=31;
} }
static unsigned char chanMuteMask[4]={ static unsigned char chanMuteMask[4]={
@ -151,8 +158,32 @@ static unsigned char noiseTable[256]={
}; };
void DivPlatformGB::tick(bool sysTick) { void DivPlatformGB::tick(bool sysTick) {
if (antiClickEnabled && sysTick && chan[2].freq>0) {
antiClickPeriodCount+=((chipClock>>1)/MAX(parent->getCurHz(),1.0f));
antiClickWavePos+=antiClickPeriodCount/chan[2].freq;
antiClickPeriodCount%=chan[2].freq;
}
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
chan[i].std.next(); chan[i].std.next();
if (chan[i].softEnv) {
if (chan[i].std.vol.had) {
chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15);
if (chan[i].outVol<0) chan[i].outVol=0;
if (i==2) {
rWrite(16+i*5+2,gbVolMap[chan[i].outVol]);
chan[i].soundLen=64;
} else {
chan[i].envLen=0;
chan[i].envDir=1;
chan[i].envVol=chan[i].outVol;
chan[i].soundLen=64;
if (!chan[i].keyOn) chan[i].killIt=true;
}
}
}
if (chan[i].std.arp.had) { if (chan[i].std.arp.had) {
if (i==3) { // noise if (i==3) { // noise
if (chan[i].std.arp.mode) { if (chan[i].std.arp.mode) {
@ -180,10 +211,9 @@ void DivPlatformGB::tick(bool sysTick) {
} }
if (chan[i].std.duty.had) { if (chan[i].std.duty.had) {
chan[i].duty=chan[i].std.duty.val; chan[i].duty=chan[i].std.duty.val;
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB);
if (i!=2) { if (i!=2) {
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63)));
} else { } else if (!chan[i].softEnv) {
if (parent->song.waveDutyIsVol) { if (parent->song.waveDutyIsVol) {
rWrite(16+i*5+2,gbVolMap[(chan[i].std.duty.val&3)<<2]); rWrite(16+i*5+2,gbVolMap[(chan[i].std.duty.val&3)<<2]);
} }
@ -213,6 +243,10 @@ void DivPlatformGB::tick(bool sysTick) {
if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) { if (chan[i].std.phaseReset.val==1) {
chan[i].keyOn=true; chan[i].keyOn=true;
if (i==2) {
antiClickWavePos=0;
antiClickPeriodCount=0;
}
} }
} }
if (i==2) { if (i==2) {
@ -223,14 +257,64 @@ void DivPlatformGB::tick(bool sysTick) {
} }
} }
} }
// run hardware sequence
if (chan[i].active) {
if (--chan[i].hwSeqDelay<=0) {
chan[i].hwSeqDelay=0;
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB);
int hwSeqCount=0;
while (chan[i].hwSeqPos<ins->gb.hwSeqLen && hwSeqCount<4) {
bool leave=false;
unsigned short data=ins->gb.hwSeq[chan[i].hwSeqPos].data;
switch (ins->gb.hwSeq[chan[i].hwSeqPos].cmd) {
case DivInstrumentGB::DIV_GB_HWCMD_ENVELOPE:
if (!chan[i].softEnv) {
chan[i].envLen=data&7;
chan[i].envDir=(data&8)?1:0;
chan[i].envVol=(data>>4)&15;
chan[i].soundLen=data>>8;
chan[i].keyOn=true;
}
break;
case DivInstrumentGB::DIV_GB_HWCMD_SWEEP:
chan[i].sweep=data;
chan[i].sweepChanged=true;
break;
case DivInstrumentGB::DIV_GB_HWCMD_WAIT:
chan[i].hwSeqDelay=data+1;
leave=true;
break;
case DivInstrumentGB::DIV_GB_HWCMD_WAIT_REL:
if (!chan[i].released) {
chan[i].hwSeqPos--;
leave=true;
}
break;
case DivInstrumentGB::DIV_GB_HWCMD_LOOP:
chan[i].hwSeqPos=data-1;
break;
case DivInstrumentGB::DIV_GB_HWCMD_LOOP_REL:
if (!chan[i].released) {
chan[i].hwSeqPos=data-1;
}
break;
}
chan[i].hwSeqPos++;
if (leave) break;
hwSeqCount++;
}
}
}
if (chan[i].sweepChanged) { if (chan[i].sweepChanged) {
chan[i].sweepChanged=false; chan[i].sweepChanged=false;
if (i==0) { if (i==0) {
rWrite(16+i*5,chan[i].sweep); rWrite(16+i*5,chan[i].sweep);
} }
} }
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB);
if (i==3) { // noise if (i==3) { // noise
int ntPos=chan[i].baseFreq; int ntPos=chan[i].baseFreq;
if (ntPos<0) ntPos=0; if (ntPos<0) ntPos=0;
@ -244,10 +328,11 @@ void DivPlatformGB::tick(bool sysTick) {
if (chan[i].keyOn) { if (chan[i].keyOn) {
if (i==2) { // wave if (i==2) { // wave
rWrite(16+i*5,0x80); rWrite(16+i*5,0x80);
rWrite(16+i*5+2,gbVolMap[chan[i].vol]); rWrite(16+i*5+2,gbVolMap[chan[i].outVol]);
} else { } else {
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63)));
rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3)); rWrite(16+i*5+2,((chan[i].envVol<<4))|(chan[i].envLen&7)|((chan[i].envDir&1)<<3));
chan[i].lastKill=chan[i].envVol;
} }
} }
if (chan[i].keyOff) { if (chan[i].keyOff) {
@ -259,15 +344,35 @@ void DivPlatformGB::tick(bool sysTick) {
} }
if (i==3) { // noise if (i==3) { // noise
rWrite(16+i*5+3,(chan[i].freq&0xff)|(chan[i].duty?8:0)); rWrite(16+i*5+3,(chan[i].freq&0xff)|(chan[i].duty?8:0));
rWrite(16+i*5+4,((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((ins->gb.soundLen<64)<<6)); rWrite(16+i*5+4,((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((chan[i].soundLen<64)<<6));
} else { } else {
rWrite(16+i*5+3,(2048-chan[i].freq)&0xff); rWrite(16+i*5+3,(2048-chan[i].freq)&0xff);
rWrite(16+i*5+4,(((2048-chan[i].freq)>>8)&7)|((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((ins->gb.soundLen<63)<<6)); rWrite(16+i*5+4,(((2048-chan[i].freq)>>8)&7)|((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((chan[i].soundLen<63)<<6));
} }
if (chan[i].keyOn) chan[i].keyOn=false; if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false; if (chan[i].keyOff) chan[i].keyOff=false;
chan[i].freqChanged=false; chan[i].freqChanged=false;
} }
if (chan[i].killIt) {
if (i!=2) {
//rWrite(16+i*5+2,8);
int killDelta=chan[i].lastKill-chan[i].outVol+1;
if (killDelta<0) killDelta+=16;
chan[i].lastKill=chan[i].outVol;
if (killDelta!=1) {
rWrite(16+i*5+2,((chan[i].envVol<<4))|8);
for (int j=0; j<killDelta; j++) {
rWrite(16+i*5+2,0x09);
rWrite(16+i*5+2,0x11);
rWrite(16+i*5+2,0x08);
}
}
}
chan[i].killIt=false;
}
chan[i].soManyHacksToMakeItDefleCompatible=false;
} }
} }
@ -291,6 +396,10 @@ int DivPlatformGB::dispatch(DivCommand c) {
} }
chan[c.chan].active=true; chan[c.chan].active=true;
chan[c.chan].keyOn=true; chan[c.chan].keyOn=true;
chan[c.chan].hwSeqPos=0;
chan[c.chan].hwSeqDelay=0;
chan[c.chan].released=false;
chan[c.chan].softEnv=ins->gb.softEnv;
chan[c.chan].macroInit(ins); chan[c.chan].macroInit(ins);
if (c.chan==2) { if (c.chan==2) {
if (chan[c.chan].wave<0) { if (chan[c.chan].wave<0) {
@ -299,17 +408,35 @@ int DivPlatformGB::dispatch(DivCommand c) {
} }
ws.init(ins,32,15,chan[c.chan].insChanged); ws.init(ins,32,15,chan[c.chan].insChanged);
} }
if ((chan[c.chan].insChanged || ins->gb.alwaysInit) && !chan[c.chan].softEnv) {
if (!chan[c.chan].soManyHacksToMakeItDefleCompatible && c.chan!=2) {
chan[c.chan].envVol=ins->gb.envVol;
}
chan[c.chan].envLen=ins->gb.envLen;
chan[c.chan].envDir=ins->gb.envDir;
chan[c.chan].soundLen=ins->gb.soundLen;
if (!chan[c.chan].soManyHacksToMakeItDefleCompatible && c.chan!=2) {
chan[c.chan].vol=chan[c.chan].envVol;
chan[c.chan].outVol=chan[c.chan].envVol;
}
}
if (c.chan==2 && chan[c.chan].softEnv) {
chan[c.chan].soundLen=64;
}
chan[c.chan].insChanged=false; chan[c.chan].insChanged=false;
break; break;
} }
case DIV_CMD_NOTE_OFF: case DIV_CMD_NOTE_OFF:
chan[c.chan].active=false; chan[c.chan].active=false;
chan[c.chan].keyOff=true; chan[c.chan].keyOff=true;
chan[c.chan].hwSeqPos=0;
chan[c.chan].hwSeqDelay=0;
chan[c.chan].macroInit(NULL); chan[c.chan].macroInit(NULL);
break; break;
case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE: case DIV_CMD_ENV_RELEASE:
chan[c.chan].std.release(); chan[c.chan].std.release();
chan[c.chan].released=true;
break; break;
case DIV_CMD_INSTRUMENT: case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) { if (chan[c.chan].ins!=c.value || c.value2==1) {
@ -317,17 +444,33 @@ int DivPlatformGB::dispatch(DivCommand c) {
chan[c.chan].insChanged=true; chan[c.chan].insChanged=true;
if (c.chan!=2) { if (c.chan!=2) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_GB); DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_GB);
chan[c.chan].vol=ins->gb.envVol; if (!ins->gb.softEnv) {
if (parent->song.gbInsAffectsEnvelope) { chan[c.chan].envVol=ins->gb.envVol;
rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3)); chan[c.chan].envLen=ins->gb.envLen;
chan[c.chan].envDir=ins->gb.envDir;
chan[c.chan].soundLen=ins->gb.soundLen;
chan[c.chan].vol=chan[c.chan].envVol;
chan[c.chan].outVol=chan[c.chan].vol;
if (parent->song.gbInsAffectsEnvelope) {
rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(chan[c.chan].envLen&7)|((chan[c.chan].envDir&1)<<3));
}
} }
} }
} }
break; break;
case DIV_CMD_VOLUME: case DIV_CMD_VOLUME:
chan[c.chan].vol=c.value; chan[c.chan].vol=c.value;
chan[c.chan].outVol=c.value;
if (c.chan==2) { if (c.chan==2) {
rWrite(16+c.chan*5+2,gbVolMap[chan[c.chan].vol]); rWrite(16+c.chan*5+2,gbVolMap[chan[c.chan].outVol]);
}
if (!chan[c.chan].softEnv) {
chan[c.chan].envVol=chan[c.chan].vol;
chan[c.chan].soManyHacksToMakeItDefleCompatible=true;
} else if (c.chan!=2) {
chan[c.chan].envVol=chan[c.chan].vol;
if (!chan[c.chan].keyOn) chan[c.chan].killIt=true;
chan[c.chan].freqChanged=true;
} }
break; break;
case DIV_CMD_GET_VOLUME: case DIV_CMD_GET_VOLUME:
@ -462,7 +605,7 @@ void DivPlatformGB::reset() {
} }
memset(gb,0,sizeof(GB_gameboy_t)); memset(gb,0,sizeof(GB_gameboy_t));
memset(regPool,0,128); memset(regPool,0,128);
gb->model=GB_MODEL_DMG_B; gb->model=model;
GB_apu_init(gb); GB_apu_init(gb);
GB_set_sample_rate(gb,rate); GB_set_sample_rate(gb,rate);
// enable all channels // enable all channels
@ -471,12 +614,23 @@ void DivPlatformGB::reset() {
lastPan=0xff; lastPan=0xff;
immWrite(0x25,procMute()); immWrite(0x25,procMute());
immWrite(0x24,0x77); immWrite(0x24,0x77);
antiClickPeriodCount=0;
antiClickWavePos=0;
}
int DivPlatformGB::getPortaFloor(int ch) {
return 24;
} }
bool DivPlatformGB::isStereo() { bool DivPlatformGB::isStereo() {
return true; return true;
} }
bool DivPlatformGB::getDCOffRequired() {
return (model==GB_MODEL_AGB);
}
void DivPlatformGB::notifyInsChange(int ins) { void DivPlatformGB::notifyInsChange(int ins) {
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
if (chan[i].ins==ins) { if (chan[i].ins==ins) {
@ -489,7 +643,7 @@ void DivPlatformGB::notifyWaveChange(int wave) {
if (chan[2].wave==wave) { if (chan[2].wave==wave) {
ws.changeWave1(wave); ws.changeWave1(wave);
updateWave(); updateWave();
if (!chan[2].keyOff) chan[2].keyOn=true; if (!chan[2].keyOff && chan[2].active) chan[2].keyOn=true;
} }
} }
@ -507,6 +661,24 @@ void DivPlatformGB::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); for (DivRegWrite& i: wlist) immWrite(i.addr,i.val);
} }
void DivPlatformGB::setFlags(unsigned int flags) {
antiClickEnabled=!(flags&8);
switch (flags&3) {
case 0:
model=GB_MODEL_DMG_B;
break;
case 1:
model=GB_MODEL_CGB_C;
break;
case 2:
model=GB_MODEL_CGB_E;
break;
case 3:
model=GB_MODEL_AGB;
break;
}
}
int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
chipClock=4194304; chipClock=4194304;
rate=chipClock/16; rate=chipClock/16;
@ -518,7 +690,9 @@ int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int fl
parent=p; parent=p;
dumpWrites=false; dumpWrites=false;
skipRegisterWrites=false; skipRegisterWrites=false;
model=GB_MODEL_DMG_B;
gb=new GB_gameboy_t; gb=new GB_gameboy_t;
setFlags(flags);
reset(); reset();
return 4; return 4;
} }

View file

@ -24,13 +24,18 @@
#include "../macroInt.h" #include "../macroInt.h"
#include "../waveSynth.h" #include "../waveSynth.h"
#include "sound/gb/gb.h" #include "sound/gb/gb.h"
#include <queue>
class DivPlatformGB: public DivDispatch { class DivPlatformGB: public DivDispatch {
struct Channel { struct Channel {
int freq, baseFreq, pitch, pitch2, note, ins; int freq, baseFreq, pitch, pitch2, note, ins;
unsigned char duty, sweep; unsigned char duty, sweep;
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta; bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, released, softEnv, killIt;
signed char vol, outVol, wave; bool soManyHacksToMakeItDefleCompatible;
signed char vol, outVol, wave, lastKill;
unsigned char envVol, envDir, envLen, soundLen;
unsigned short hwSeqPos;
short hwSeqDelay;
DivMacroInt std; DivMacroInt std;
void macroInit(DivInstrument* which) { void macroInit(DivInstrument* which) {
std.init(which); std.init(which);
@ -52,17 +57,38 @@ class DivPlatformGB: public DivDispatch {
keyOn(false), keyOn(false),
keyOff(false), keyOff(false),
inPorta(false), inPorta(false),
released(false),
softEnv(false),
killIt(false),
soManyHacksToMakeItDefleCompatible(false),
vol(15), vol(15),
outVol(15), outVol(15),
wave(-1) {} wave(-1),
lastKill(0),
envVol(0),
envDir(0),
envLen(0),
soundLen(0),
hwSeqPos(0),
hwSeqDelay(0) {}
}; };
Channel chan[4]; Channel chan[4];
DivDispatchOscBuffer* oscBuf[4]; DivDispatchOscBuffer* oscBuf[4];
bool isMuted[4]; bool isMuted[4];
bool antiClickEnabled;
unsigned char lastPan; unsigned char lastPan;
DivWaveSynth ws; DivWaveSynth ws;
struct QueuedWrite {
unsigned char addr;
unsigned char val;
QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {}
};
std::queue<QueuedWrite> writes;
int antiClickPeriodCount, antiClickWavePos;
GB_gameboy_t* gb; GB_gameboy_t* gb;
GB_model_t model;
unsigned char regPool[128]; unsigned char regPool[128];
unsigned char procMute(); unsigned char procMute();
@ -80,7 +106,9 @@ class DivPlatformGB: public DivDispatch {
void forceIns(); void forceIns();
void tick(bool sysTick=true); void tick(bool sysTick=true);
void muteChannel(int ch, bool mute); void muteChannel(int ch, bool mute);
int getPortaFloor(int ch);
bool isStereo(); bool isStereo();
bool getDCOffRequired();
void notifyInsChange(int ins); void notifyInsChange(int ins);
void notifyWaveChange(int wave); void notifyWaveChange(int wave);
void notifyInsDeletion(void* ins); void notifyInsDeletion(void* ins);
@ -88,6 +116,7 @@ class DivPlatformGB: public DivDispatch {
void poke(std::vector<DivRegWrite>& wlist); void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet(); const char** getRegisterSheet();
const char* getEffectName(unsigned char effect); const char* getEffectName(unsigned char effect);
void setFlags(unsigned int flags);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit(); void quit();
~DivPlatformGB(); ~DivPlatformGB();

View file

@ -153,14 +153,13 @@ void DivPlatformGenesis::processDAC() {
if (chan[i].dacPeriod>=(chipClock/576)) { if (chan[i].dacPeriod>=(chipClock/576)) {
if (s->samples>0) { if (s->samples>0) {
while (chan[i].dacPeriod>=(chipClock/576)) { while (chan[i].dacPeriod>=(chipClock/576)) {
if (++chan[i].dacPos>=s->samples) { ++chan[i].dacPos;
if (s->loopStart>=0 && s->loopStart<(int)s->samples && !chan[i].dacDirection) { if (!chan[i].dacDirection && (s->isLoopable() && chan[i].dacPos>=s->getEndPosition())) {
chan[i].dacPos=s->loopStart; chan[i].dacPos=s->loopStart;
} else { } else if (chan[i].dacPos>=s->samples) {
chan[i].dacSample=-1; chan[i].dacSample=-1;
chan[i].dacPeriod=0; chan[i].dacPeriod=0;
break; break;
}
} }
chan[i].dacPeriod-=(chipClock/576); chan[i].dacPeriod-=(chipClock/576);
} }
@ -200,14 +199,13 @@ void DivPlatformGenesis::processDAC() {
chan[5].dacReady=false; chan[5].dacReady=false;
} }
} }
if (++chan[5].dacPos>=s->samples) { chan[5].dacPos++;
if (s->loopStart>=0 && s->loopStart<(int)s->samples && !chan[5].dacDirection) { if (!chan[5].dacDirection && (s->isLoopable() && chan[5].dacPos>=s->getEndPosition())) {
chan[5].dacPos=s->loopStart; chan[5].dacPos=s->loopStart;
} else { } else if (chan[5].dacPos>=s->samples) {
chan[5].dacSample=-1; chan[5].dacSample=-1;
if (parent->song.brokenDACMode) { if (parent->song.brokenDACMode) {
rWrite(0x2b,0); rWrite(0x2b,0);
}
} }
} }
while (chan[5].dacPeriod>=rate) chan[5].dacPeriod-=rate; while (chan[5].dacPeriod>=rate) chan[5].dacPeriod-=rate;

View file

@ -24,6 +24,8 @@
#define CHIP_FREQBASE fmFreqBase #define CHIP_FREQBASE fmFreqBase
#define CHIP_DIVIDER fmDivBase #define CHIP_DIVIDER fmDivBase
#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6])))
int DivPlatformGenesisExt::dispatch(DivCommand c) { int DivPlatformGenesisExt::dispatch(DivCommand c) {
if (c.chan<2) { if (c.chan<2) {
return DivPlatformGenesis::dispatch(c); return DivPlatformGenesis::dispatch(c);
@ -418,6 +420,16 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
} }
} }
if (writeSomething) { if (writeSomething) {
if (chan[7].active) { // CSM
writeMask^=0xf0;
}
/*printf(
"Mask: %c %c %c %c\n",
(writeMask&0x10)?'1':'-',
(writeMask&0x20)?'2':'-',
(writeMask&0x40)?'3':'-',
(writeMask&0x80)?'4':'-'
);*/
immWrite(0x28,writeMask); immWrite(0x28,writeMask);
} }
} }
@ -478,6 +490,13 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
if (chan[7].active) { // CSM if (chan[7].active) { // CSM
writeMask^=0xf0; writeMask^=0xf0;
} }
/*printf(
"Mask: %c %c %c %c\n",
(writeMask&0x10)?'1':'-',
(writeMask&0x20)?'2':'-',
(writeMask&0x40)?'3':'-',
(writeMask&0x80)?'4':'-'
);*/
immWrite(0x28,writeMask); immWrite(0x28,writeMask);
} }
@ -525,7 +544,7 @@ void DivPlatformGenesisExt::forceIns() {
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
} }
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
if (chan[i].active) { if (chan[i].active) {
chan[i].keyOn=true; chan[i].keyOn=true;
chan[i].freqChanged=true; chan[i].freqChanged=true;

View file

@ -158,12 +158,10 @@ void DivPlatformLynx::acquire(short* bufL, short* bufR, size_t start, size_t len
WRITE_OUTPUT(i,(s->data8[chan[i].samplePos++]*chan[i].outVol)>>7); WRITE_OUTPUT(i,(s->data8[chan[i].samplePos++]*chan[i].outVol)>>7);
} }
if (chan[i].samplePos>=(int)s->samples) { if (s->isLoopable() && chan[i].samplePos>=(int)s->getEndPosition()) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) { chan[i].samplePos=s->loopStart;
chan[i].samplePos=s->loopStart; } else if (chan[i].samplePos>=(int)s->samples) {
} else { chan[i].sample=-1;
chan[i].sample=-1;
}
} }
} }
} }

View file

@ -62,12 +62,11 @@ void DivPlatformMMC5::acquire(short* bufL, short* bufR, size_t start, size_t len
if (!isMuted[2]) { if (!isMuted[2]) {
rWrite(0x5011,((unsigned char)s->data8[dacPos]+0x80)); rWrite(0x5011,((unsigned char)s->data8[dacPos]+0x80));
} }
if (++dacPos>=s->samples) { dacPos++;
if (s->loopStart>=0 && s->loopStart<(int)s->samples) { if (s->isLoopable() && dacPos>=s->getEndPosition()) {
dacPos=s->loopStart; dacPos=s->loopStart;
} else { } else if (dacPos>=s->samples) {
dacSample=-1; dacSample=-1;
}
} }
dacPeriod-=rate; dacPeriod-=rate;
} else { } else {
@ -172,7 +171,7 @@ void DivPlatformMMC5::tick(bool sysTick) {
// PCM // PCM
if (chan[2].freqChanged) { if (chan[2].freqChanged) {
chan[2].freq=parent->calcFreq(chan[2].baseFreq,chan[2].pitch,false); chan[2].freq=parent->calcFreq(chan[2].baseFreq,chan[2].pitch,false,0,chan[2].pitch2,1,1);
if (chan[2].furnaceDac) { if (chan[2].furnaceDac) {
double off=1.0; double off=1.0;
if (dacSample>=0 && dacSample<parent->song.sampleLen) { if (dacSample>=0 && dacSample<parent->song.sampleLen) {
@ -202,7 +201,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
} }
dacPos=0; dacPos=0;
dacPeriod=0; dacPeriod=0;
chan[c.chan].baseFreq=parent->song.tuning*pow(2.0f,((float)(c.value+3)/12.0f)); chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
@ -283,7 +282,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
break; break;
case DIV_CMD_NOTE_PORTA: { case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_PERIODIC(c.value2); int destFreq=(c.chan==2)?(parent->calcBaseFreq(1,1,c.value2,false)):(NOTE_PERIODIC(c.value2));
bool return2=false; bool return2=false;
if (destFreq>chan[c.chan].baseFreq) { if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value; chan[c.chan].baseFreq+=c.value;
@ -316,7 +315,11 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
} }
break; break;
case DIV_CMD_LEGATO: case DIV_CMD_LEGATO:
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); if (c.chan==2) {
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)),false);
} else {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
}
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
break; break;

View file

@ -108,12 +108,11 @@ void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
rWrite(0x4011,next); \ rWrite(0x4011,next); \
} \ } \
} \ } \
if (++dacPos>=s->samples) { \ dacPos++; \
if (s->loopStart>=0 && s->loopStart<(int)s->samples) { \ if (s->isLoopable() && dacPos>=s->getEndPosition()) { \
dacPos=s->loopStart; \ dacPos=s->loopStart; \
} else { \ } else if (dacPos>=s->samples) { \
dacSample=-1; \ dacSample=-1; \
} \
} \ } \
dacPeriod-=rate; \ dacPeriod-=rate; \
} else { \ } else { \

View file

@ -293,7 +293,7 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
if (!isMuted[adpcmChan]) { if (!isMuted[adpcmChan]) {
os[0]-=aOut.data[0]>>3; os[0]-=aOut.data[0]>>3;
os[1]-=aOut.data[0]>>3; os[1]-=aOut.data[0]>>3;
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]+=aOut.data[0]; oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=aOut.data[0];
} else { } else {
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=0; oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=0;
} }
@ -771,7 +771,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
int end=s->offB+s->lengthB-1; int end=s->offB+s->lengthB-1;
immWrite(11,(end>>2)&0xff); immWrite(11,(end>>2)&0xff);
immWrite(12,(end>>10)&0xff); immWrite(12,(end>>10)&0xff);
immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat immWrite(7,(s->isLoopable())?0xb0:0xa0); // start/repeat
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
@ -807,7 +807,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
int end=s->offB+s->lengthB-1; int end=s->offB+s->lengthB-1;
immWrite(11,(end>>2)&0xff); immWrite(11,(end>>2)&0xff);
immWrite(12,(end>>10)&0xff); immWrite(12,(end>>10)&0xff);
immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat immWrite(7,(s->isLoopable())?0xb0:0xa0); // start/repeat
int freq=(65536.0*(double)s->rate)/(double)chipRateBase; int freq=(65536.0*(double)s->rate)/(double)chipRateBase;
immWrite(16,freq&0xff); immWrite(16,freq&0xff);
immWrite(17,(freq>>8)&0xff); immWrite(17,(freq>>8)&0xff);
@ -872,6 +872,13 @@ int DivPlatformOPL::dispatch(DivCommand c) {
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
chan[c.chan].fourOp=(ops==4); chan[c.chan].fourOp=(ops==4);
if (chan[c.chan].fourOp) { if (chan[c.chan].fourOp) {
/*
if (chan[c.chan+1].active) {
chan[c.chan+1].keyOff=true;
chan[c.chan+1].keyOn=false;
chan[c.chan+1].active=false;
}*/
chan[c.chan+1].insChanged=true;
chan[c.chan+1].macroInit(NULL); chan[c.chan+1].macroInit(NULL);
} }
update4OpMask=true; update4OpMask=true;

View file

@ -90,12 +90,10 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
chWrite(i,0x04,0xdf); chWrite(i,0x04,0xdf);
chWrite(i,0x06,(((unsigned char)s->data8[chan[i].dacPos]+0x80)>>3)); chWrite(i,0x06,(((unsigned char)s->data8[chan[i].dacPos]+0x80)>>3));
chan[i].dacPos++; chan[i].dacPos++;
if (chan[i].dacPos>=s->samples) { if (s->isLoopable() && chan[i].dacPos>=s->getEndPosition()) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) { chan[i].dacPos=s->loopStart;
chan[i].dacPos=s->loopStart; } else if (chan[i].dacPos>=s->samples) {
} else { chan[i].dacSample=-1;
chan[i].dacSample=-1;
}
} }
chan[i].dacPeriod-=rate; chan[i].dacPeriod-=rate;
} }
@ -117,7 +115,7 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
pce->ResetTS(0); pce->ResetTS(0);
for (int i=0; i<6; i++) { for (int i=0; i<6; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1])<<1; oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP((pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1])<<1,-32768,32767);
} }
tempL[0]=(tempL[0]>>1)+(tempL[0]>>2); tempL[0]=(tempL[0]>>1)+(tempL[0]>>2);
@ -135,14 +133,22 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
} }
void DivPlatformPCE::updateWave(int ch) { void DivPlatformPCE::updateWave(int ch) {
if (chan[ch].pcm) {
chan[ch].deferredWaveUpdate=true;
return;
}
chWrite(ch,0x04,0x5f); chWrite(ch,0x04,0x5f);
chWrite(ch,0x04,0x1f); chWrite(ch,0x04,0x1f);
for (int i=0; i<32; i++) { for (int i=0; i<32; i++) {
chWrite(ch,0x06,chan[ch].ws.output[i]); chWrite(ch,0x06,chan[ch].ws.output[(i+chan[ch].antiClickWavePos)&31]);
} }
chan[ch].antiClickWavePos&=31;
if (chan[ch].active) { if (chan[ch].active) {
chWrite(ch,0x04,0x80|chan[ch].outVol); chWrite(ch,0x04,0x80|chan[ch].outVol);
} }
if (chan[ch].deferredWaveUpdate) {
chan[ch].deferredWaveUpdate=false;
}
} }
// TODO: in octave 6 the noise table changes to a tonal one // TODO: in octave 6 the noise table changes to a tonal one
@ -152,6 +158,13 @@ static unsigned char noiseFreq[12]={
void DivPlatformPCE::tick(bool sysTick) { void DivPlatformPCE::tick(bool sysTick) {
for (int i=0; i<6; i++) { for (int i=0; i<6; i++) {
// anti-click
if (antiClickEnabled && sysTick && chan[i].freq>0) {
chan[i].antiClickPeriodCount+=(chipClock/MAX(parent->getCurHz(),1.0f));
chan[i].antiClickWavePos+=chan[i].antiClickPeriodCount/chan[i].freq;
chan[i].antiClickPeriodCount%=chan[i].freq;
}
chan[i].std.next(); chan[i].std.next();
if (chan[i].std.vol.had) { if (chan[i].std.vol.had) {
chan[i].outVol=VOL_SCALE_LOG(chan[i].vol&31,MIN(31,chan[i].std.vol.val),31); chan[i].outVol=VOL_SCALE_LOG(chan[i].vol&31,MIN(31,chan[i].std.vol.val),31);
@ -220,8 +233,12 @@ void DivPlatformPCE::tick(bool sysTick) {
} }
chan[i].freqChanged=true; chan[i].freqChanged=true;
} }
if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) {
chan[i].antiClickWavePos=0;
chan[i].antiClickPeriodCount=0;
}
if (chan[i].active) { if (chan[i].active) {
if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1)) { if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) || chan[i].deferredWaveUpdate) {
updateWave(i); updateWave(i);
} }
} }
@ -557,10 +574,18 @@ void DivPlatformPCE::setFlags(unsigned int flags) {
} else { } else {
chipClock=COLOR_NTSC; chipClock=COLOR_NTSC;
} }
// flags&4 will be chip revision
antiClickEnabled=!(flags&8);
rate=chipClock/12; rate=chipClock/12;
for (int i=0; i<6; i++) { for (int i=0; i<6; i++) {
oscBuf[i]->rate=rate; oscBuf[i]->rate=rate;
} }
if (pce!=NULL) {
delete pce;
pce=NULL;
}
pce=new PCE_PSG(tempL,tempR,(flags&4)?PCE_PSG::REVISION_HUC6280A:PCE_PSG::REVISION_HUC6280);
} }
void DivPlatformPCE::poke(unsigned int addr, unsigned short val) { void DivPlatformPCE::poke(unsigned int addr, unsigned short val) {
@ -579,8 +604,8 @@ int DivPlatformPCE::init(DivEngine* p, int channels, int sugRate, unsigned int f
isMuted[i]=false; isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer; oscBuf[i]=new DivDispatchOscBuffer;
} }
pce=NULL;
setFlags(flags); setFlags(flags);
pce=new PCE_PSG(tempL,tempR,PCE_PSG::REVISION_HUC6280A);
reset(); reset();
return 6; return 6;
} }
@ -589,7 +614,10 @@ void DivPlatformPCE::quit() {
for (int i=0; i<6; i++) { for (int i=0; i<6; i++) {
delete oscBuf[i]; delete oscBuf[i];
} }
delete pce; if (pce!=NULL) {
delete pce;
pce=NULL;
}
} }
DivPlatformPCE::~DivPlatformPCE() { DivPlatformPCE::~DivPlatformPCE() {

View file

@ -28,12 +28,12 @@
class DivPlatformPCE: public DivDispatch { class DivPlatformPCE: public DivDispatch {
struct Channel { struct Channel {
int freq, baseFreq, pitch, pitch2, note; int freq, baseFreq, pitch, pitch2, note, antiClickPeriodCount, antiClickWavePos;
int dacPeriod, dacRate; int dacPeriod, dacRate;
unsigned int dacPos; unsigned int dacPos;
int dacSample, ins; int dacSample, ins;
unsigned char pan; unsigned char pan;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac, deferredWaveUpdate;
signed char vol, outVol, wave; signed char vol, outVol, wave;
DivMacroInt std; DivMacroInt std;
DivWaveSynth ws; DivWaveSynth ws;
@ -47,6 +47,8 @@ class DivPlatformPCE: public DivDispatch {
pitch(0), pitch(0),
pitch2(0), pitch2(0),
note(0), note(0),
antiClickPeriodCount(0),
antiClickWavePos(0),
dacPeriod(0), dacPeriod(0),
dacRate(0), dacRate(0),
dacPos(0), dacPos(0),
@ -62,6 +64,7 @@ class DivPlatformPCE: public DivDispatch {
noise(false), noise(false),
pcm(false), pcm(false),
furnaceDac(false), furnaceDac(false),
deferredWaveUpdate(false),
vol(31), vol(31),
outVol(31), outVol(31),
wave(-1) {} wave(-1) {}
@ -69,6 +72,7 @@ class DivPlatformPCE: public DivDispatch {
Channel chan[6]; Channel chan[6];
DivDispatchOscBuffer* oscBuf[6]; DivDispatchOscBuffer* oscBuf[6];
bool isMuted[6]; bool isMuted[6];
bool antiClickEnabled;
struct QueuedWrite { struct QueuedWrite {
unsigned char addr; unsigned char addr;
unsigned char val; unsigned char val;

View file

@ -0,0 +1,368 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _USE_MATH_DEFINES
#include "pcmdac.h"
#include "../engine.h"
#include <math.h>
// to ease the driver, freqency register is a 8.16 counter relative to output sample rate
#define CHIP_FREQBASE 65536
void DivPlatformPCMDAC::acquire(short* bufL, short* bufR, size_t start, size_t len) {
const int depthScale=(15-outDepth);
int output=0;
for (size_t h=start; h<start+len; h++) {
if (!chan.active || isMuted) {
bufL[h]=0;
bufR[h]=0;
oscBuf->data[oscBuf->needle++]=0;
continue;
}
if (chan.useWave || (chan.sample>=0 && chan.sample<parent->song.sampleLen)) {
chan.audPos+=chan.freq>>16;
chan.audSub+=(chan.freq&0xffff);
if (chan.audSub>=0x10000) {
chan.audSub-=0x10000;
chan.audPos+=1;
}
if (chan.useWave) {
if (chan.audPos>=(unsigned int)(chan.audLen<<1)) {
chan.audPos=0;
}
output=(chan.ws.output[chan.audPos]^0x80)<<8;
} else {
DivSample* s=parent->getSample(chan.sample);
if (s->samples>0) {
if (s->isLoopable() && chan.audPos>=s->getEndPosition()) {
chan.audPos=s->loopStart;
} else if (chan.audPos>=s->samples) {
chan.sample=-1;
}
if (chan.audPos<s->samples) {
output=s->data16[chan.audPos];
}
} else {
chan.sample=-1;
}
}
}
output=output*chan.vol*chan.envVol/16384;
oscBuf->data[oscBuf->needle++]=output;
if (outStereo) {
bufL[h]=((output*chan.panL)>>(depthScale+8))<<depthScale;
bufR[h]=((output*chan.panR)>>(depthScale+8))<<depthScale;
} else {
output=(output>>depthScale)<<depthScale;
bufL[h]=output;
bufR[h]=output;
}
}
}
void DivPlatformPCMDAC::tick(bool sysTick) {
chan.std.next();
if (chan.std.vol.had) {
chan.envVol=chan.std.vol.val;
}
if (chan.std.arp.had) {
if (!chan.inPorta) {
if (chan.std.arp.mode) {
chan.baseFreq=NOTE_FREQUENCY(chan.std.arp.val);
} else {
chan.baseFreq=NOTE_FREQUENCY(chan.note+chan.std.arp.val);
}
}
chan.freqChanged=true;
} else {
if (chan.std.arp.mode && chan.std.arp.finished) {
chan.baseFreq=NOTE_FREQUENCY(chan.note);
chan.freqChanged=true;
}
}
if (chan.useWave && chan.std.wave.had) {
if (chan.wave!=chan.std.wave.val || chan.ws.activeChanged()) {
chan.wave=chan.std.wave.val;
chan.ws.changeWave1(chan.wave);
if (!chan.keyOff) chan.keyOn=true;
}
}
if (chan.useWave && chan.active) {
chan.ws.tick();
}
if (chan.std.pitch.had) {
if (chan.std.pitch.mode) {
chan.pitch2+=chan.std.pitch.val;
CLAMP_VAR(chan.pitch2,-32768,32767);
} else {
chan.pitch2=chan.std.pitch.val;
}
chan.freqChanged=true;
}
if (chan.std.panL.had) {
int val=chan.std.panL.val&0x7f;
chan.panL=val*2;
}
if (chan.std.panR.had) {
int val=chan.std.panR.val&0x7f;
chan.panR=val*2;
}
if (chan.std.phaseReset.had) {
if (chan.std.phaseReset.val==1) {
chan.audPos=0;
}
}
if (chan.freqChanged || chan.keyOn || chan.keyOff) {
//DivInstrument* ins=parent->getIns(chan.ins,DIV_INS_AMIGA);
double off=1.0;
if (!chan.useWave && chan.sample>=0 && chan.sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan.sample);
off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0;
}
chan.freq=off*parent->calcFreq(chan.baseFreq,chan.pitch,false,2,chan.pitch2,chipClock,CHIP_FREQBASE);
if (chan.freq>16777215) chan.freq=16777215;
if (chan.keyOn) {
if (!chan.std.vol.had) {
chan.envVol=64;
}
chan.keyOn=false;
}
if (chan.keyOff) {
chan.keyOff=false;
}
chan.freqChanged=false;
}
}
int DivPlatformPCMDAC::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan.ins,DIV_INS_AMIGA);
if (ins->amiga.useWave) {
chan.useWave=true;
chan.audLen=(ins->amiga.waveLen+1)>>1;
if (chan.insChanged) {
if (chan.wave<0) {
chan.wave=0;
chan.ws.setWidth(chan.audLen<<1);
chan.ws.changeWave1(chan.wave);
}
}
} else {
chan.sample=ins->amiga.getSample(c.value);
chan.useWave=false;
}
if (c.value!=DIV_NOTE_NULL) {
chan.baseFreq=round(NOTE_FREQUENCY(c.value));
}
if (chan.useWave || chan.sample<0 || chan.sample>=parent->song.sampleLen) {
chan.sample=-1;
}
if (chan.setPos) {
chan.setPos=false;
} else {
chan.audPos=0;
}
chan.audSub=0;
if (c.value!=DIV_NOTE_NULL) {
chan.freqChanged=true;
chan.note=c.value;
}
chan.active=true;
chan.keyOn=true;
chan.macroInit(ins);
if (!parent->song.brokenOutVol && !chan.std.vol.will) {
chan.envVol=64;
}
if (chan.useWave) {
chan.ws.init(ins,chan.audLen<<1,255,chan.insChanged);
}
chan.insChanged=false;
break;
}
case DIV_CMD_NOTE_OFF:
chan.sample=-1;
chan.active=false;
chan.keyOff=true;
chan.macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
chan.std.release();
break;
case DIV_CMD_INSTRUMENT:
if (chan.ins!=c.value || c.value2==1) {
chan.ins=c.value;
chan.insChanged=true;
}
break;
case DIV_CMD_VOLUME:
if (chan.vol!=c.value) {
chan.vol=c.value;
if (!chan.std.vol.has) {
chan.envVol=64;
}
}
break;
case DIV_CMD_GET_VOLUME:
return chan.vol;
break;
case DIV_CMD_PANNING:
chan.panL=c.value;
chan.panR=c.value2;
break;
case DIV_CMD_PITCH:
chan.pitch=c.value;
chan.freqChanged=true;
break;
case DIV_CMD_WAVE:
if (!chan.useWave) break;
chan.wave=c.value;
chan.keyOn=true;
chan.ws.changeWave1(chan.wave);
break;
case DIV_CMD_NOTE_PORTA: {
DivInstrument* ins=parent->getIns(chan.ins,DIV_INS_AMIGA);
chan.sample=ins->amiga.getSample(c.value2);
int destFreq=round(NOTE_FREQUENCY(c.value2));
bool return2=false;
if (destFreq>chan.baseFreq) {
chan.baseFreq+=c.value;
if (chan.baseFreq>=destFreq) {
chan.baseFreq=destFreq;
return2=true;
}
} else {
chan.baseFreq-=c.value;
if (chan.baseFreq<=destFreq) {
chan.baseFreq=destFreq;
return2=true;
}
}
chan.freqChanged=true;
if (return2) {
chan.inPorta=false;
return 2;
}
break;
}
case DIV_CMD_LEGATO: {
chan.baseFreq=round(NOTE_FREQUENCY(c.value+((chan.std.arp.will && !chan.std.arp.mode)?(chan.std.arp.val):(0))));
chan.freqChanged=true;
chan.note=c.value;
break;
}
case DIV_CMD_PRE_PORTA:
if (chan.active && c.value2) {
if (parent->song.resetMacroOnPorta) chan.macroInit(parent->getIns(chan.ins,DIV_INS_AMIGA));
}
chan.inPorta=c.value;
break;
case DIV_CMD_SAMPLE_POS:
if (chan.useWave) break;
chan.audPos=c.value;
chan.setPos=true;
break;
case DIV_CMD_GET_VOLMAX:
return 255;
break;
case DIV_ALWAYS_SET_VOLUME:
return 1;
break;
default:
break;
}
return 1;
}
void DivPlatformPCMDAC::muteChannel(int ch, bool mute) {
isMuted=mute;
}
void DivPlatformPCMDAC::forceIns() {
chan.insChanged=true;
chan.freqChanged=true;
chan.audPos=0;
chan.sample=-1;
}
void* DivPlatformPCMDAC::getChanState(int ch) {
return &chan;
}
DivDispatchOscBuffer* DivPlatformPCMDAC::getOscBuffer(int ch) {
return oscBuf;
}
void DivPlatformPCMDAC::reset() {
chan=DivPlatformPCMDAC::Channel();
chan.std.setEngine(parent);
chan.ws.setEngine(parent);
chan.ws.init(NULL,32,255);
}
bool DivPlatformPCMDAC::isStereo() {
return true;
}
DivMacroInt* DivPlatformPCMDAC::getChanMacroInt(int ch) {
return &chan.std;
}
void DivPlatformPCMDAC::notifyInsChange(int ins) {
if (chan.ins==ins) {
chan.insChanged=true;
}
}
void DivPlatformPCMDAC::notifyWaveChange(int wave) {
if (chan.useWave && chan.wave==wave) {
chan.ws.changeWave1(wave);
}
}
void DivPlatformPCMDAC::notifyInsDeletion(void* ins) {
chan.std.notifyInsDeletion((DivInstrument*)ins);
}
void DivPlatformPCMDAC::setFlags(unsigned int flags) {
// default to 44100Hz 16-bit stereo
if (!flags) flags=0x1f0000|44099;
rate=(flags&0xffff)+1;
// rate can't be too low or the resampler will break
if (rate<1000) rate=1000;
chipClock=rate;
outDepth=(flags>>16)&0xf;
outStereo=(flags>>20)&1;
}
int DivPlatformPCMDAC::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
oscBuf=new DivDispatchOscBuffer;
isMuted=false;
setFlags(flags);
reset();
return 1;
}
void DivPlatformPCMDAC::quit() {
delete oscBuf;
}

View file

@ -0,0 +1,99 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _PCM_DAC_H
#define _PCM_DAC_H
#include "../dispatch.h"
#include <queue>
#include "../macroInt.h"
#include "../waveSynth.h"
class DivPlatformPCMDAC: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, pitch2;
unsigned int audLoc;
unsigned short audLen;
unsigned int audPos;
int audSub;
int sample, wave, ins;
int note;
int panL, panR;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos;
int vol, envVol;
DivMacroInt std;
DivWaveSynth ws;
void macroInit(DivInstrument* which) {
std.init(which);
pitch2=0;
}
Channel():
freq(0),
baseFreq(0),
pitch(0),
pitch2(0),
audLoc(0),
audLen(0),
audPos(0),
audSub(0),
sample(-1),
wave(-1),
ins(-1),
note(0),
panL(255),
panR(255),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
inPorta(false),
useWave(false),
setPos(false),
vol(255),
envVol(64) {}
};
Channel chan;
DivDispatchOscBuffer* oscBuf;
bool isMuted;
int outDepth;
bool outStereo;
friend void putDispatchChan(void*,int,int);
public:
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivDispatchOscBuffer* getOscBuffer(int chan);
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
bool isStereo();
DivMacroInt* getChanMacroInt(int ch);
void setFlags(unsigned int flags);
void notifyInsChange(int ins);
void notifyWaveChange(int wave);
void notifyInsDeletion(void* ins);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
};
#endif

View file

@ -133,8 +133,14 @@ void DivPlatformPET::tick(bool sysTick) {
} }
} }
if (chan.std.pitch.had) { if (chan.std.pitch.had) {
chan.freqChanged=true; if (chan.std.pitch.mode) {
chan.pitch2+=chan.std.pitch.val;
CLAMP_VAR(chan.pitch2,-32768,32767);
} else {
chan.pitch2=chan.std.pitch.val;
} }
chan.freqChanged=true;
}
if (chan.freqChanged || chan.keyOn || chan.keyOff) { if (chan.freqChanged || chan.keyOn || chan.keyOff) {
chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2,chipClock,CHIP_DIVIDER)-2; chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2,chipClock,CHIP_DIVIDER)-2;
if (chan.freq>65535) chan.freq=65535; if (chan.freq>65535) chan.freq=65535;

View file

@ -301,7 +301,7 @@ void DivPlatformQSound::tick(bool sysTick) {
qsound_bank = 0x8000 | (s->offQSound >> 16); qsound_bank = 0x8000 | (s->offQSound >> 16);
qsound_addr = s->offQSound & 0xffff; qsound_addr = s->offQSound & 0xffff;
int length = s->samples; int length = s->getEndPosition();
if (length > 65536 - 16) { if (length > 65536 - 16) {
length = 65536 - 16; length = 65536 - 16;
} }
@ -358,14 +358,14 @@ void DivPlatformQSound::tick(bool sysTick) {
} }
} }
chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,440.0,4096.0); chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,440.0,4096.0);
if (chan[i].freq>0xffff) chan[i].freq=0xffff; if (chan[i].freq>0xefff) chan[i].freq=0xefff;
if (chan[i].keyOn) { if (chan[i].keyOn) {
rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank); rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank);
rWrite(q1_reg_map[Q1V_END][i], qsound_end); rWrite(q1_reg_map[Q1V_END][i], qsound_end);
rWrite(q1_reg_map[Q1V_LOOP][i], qsound_loop); rWrite(q1_reg_map[Q1V_LOOP][i], qsound_loop);
rWrite(q1_reg_map[Q1V_START][i], qsound_addr); rWrite(q1_reg_map[Q1V_START][i], qsound_addr);
rWrite(q1_reg_map[Q1V_PHASE][i], 0x8000); rWrite(q1_reg_map[Q1V_PHASE][i], 0x8000);
//logV("ch %d bank=%04x, addr=%04x, end=%04x, loop=%04x!",i,qsound_bank,qsound_addr,qsound_end,qsound_loop); logV("ch %d bank=%04x, addr=%04x, end=%04x, loop=%04x!",i,qsound_bank,qsound_addr,qsound_end,qsound_loop);
// Write sample address. Enable volume // Write sample address. Enable volume
if (!chan[i].std.vol.had) { if (!chan[i].std.vol.had) {
rWrite(q1_reg_map[Q1V_VOL][i], chan[i].vol << 4); rWrite(q1_reg_map[Q1V_VOL][i], chan[i].vol << 4);

View file

@ -142,7 +142,7 @@ void DivPlatformRF5C68::tick(bool sysTick) {
if (chan[i].audPos>0) { if (chan[i].audPos>0) {
start=start+MIN(chan[i].audPos,s->length8); start=start+MIN(chan[i].audPos,s->length8);
} }
if (s->loopStart>=0) { if (s->isLoopable()) {
loop=start+s->loopStart; loop=start+s->loopStart;
} }
start=MIN(start,getSampleMemCapacity()-31); start=MIN(start,getSampleMemCapacity()-31);
@ -393,7 +393,7 @@ void DivPlatformRF5C68::renderSamples() {
size_t memPos=0; size_t memPos=0;
for (int i=0; i<parent->song.sampleLen; i++) { for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i]; DivSample* s=parent->song.sample[i];
int length=s->length8; int length=s->getEndPosition(DIV_SAMPLE_DEPTH_8BIT);
int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-31,length); int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-31,length);
if (actualLength>0) { if (actualLength>0) {
s->offRF5C68=memPos; s->offRF5C68=memPos;

View file

@ -56,12 +56,10 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t
pcmR+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolR); pcmR+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolR);
} }
chan[i].pcm.pos+=chan[i].pcm.freq; chan[i].pcm.pos+=chan[i].pcm.freq;
if (chan[i].pcm.pos>=(s->samples<<8)) { if (s->isLoopable() && chan[i].pcm.pos>=(s->getEndPosition()<<8)) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) { chan[i].pcm.pos=s->loopStart<<8;
chan[i].pcm.pos=s->loopStart<<8; } else if (chan[i].pcm.pos>=(s->samples<<8)) {
} else { chan[i].pcm.sample=-1;
chan[i].pcm.sample=-1;
}
} }
} else { } else {
oscBuf[i]->data[oscBuf[i]->needle++]=0; oscBuf[i]->data[oscBuf[i]->needle++]=0;
@ -202,7 +200,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
chan[c.chan].macroInit(ins); chan[c.chan].macroInit(ins);
if (dumpWrites) { // Sega PCM writes if (dumpWrites) { // Sega PCM writes
DivSample* s=parent->getSample(chan[c.chan].pcm.sample); DivSample* s=parent->getSample(chan[c.chan].pcm.sample);
int actualLength=(int)s->length8; int actualLength=(int)(s->getEndPosition(DIV_SAMPLE_DEPTH_8BIT));
if (actualLength>0xfeff) actualLength=0xfeff; if (actualLength>0xfeff) actualLength=0xfeff;
addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3)); addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3));
addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff); addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff);
@ -235,7 +233,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
chan[c.chan].furnacePCM=false; chan[c.chan].furnacePCM=false;
if (dumpWrites) { // Sega PCM writes if (dumpWrites) { // Sega PCM writes
DivSample* s=parent->getSample(chan[c.chan].pcm.sample); DivSample* s=parent->getSample(chan[c.chan].pcm.sample);
int actualLength=(int)s->length8; int actualLength=(int)(s->getEndPosition(DIV_SAMPLE_DEPTH_8BIT));
if (actualLength>65536) actualLength=65536; if (actualLength>65536) actualLength=65536;
addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3)); addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3));
addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff); addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff);

View file

@ -1180,11 +1180,11 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
if ((value & 0x80)) { if ((value & 0x80)) {
/* DMG bug: wave RAM gets corrupted if the channel is retriggerred 1 cycle before the APU /* DMG bug: wave RAM gets corrupted if the channel is retriggerred 1 cycle before the APU
reads from it. */ reads from it. */
/*if (!CGB && if (!CGB &&
gb->apu.is_active[GB_WAVE] && gb->apu.is_active[GB_WAVE] &&
gb->apu.wave_channel.sample_countdown == 0 && gb->apu.wave_channel.sample_countdown == 0 &&
gb->apu.wave_channel.enable) { gb->apu.wave_channel.enable) {
unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF;*/ unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF;
/* This glitch varies between models and even specific instances: /* This glitch varies between models and even specific instances:
DMG-B: Most of them behave as emulated. A few behave differently. DMG-B: Most of them behave as emulated. A few behave differently.
@ -1193,7 +1193,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
Additionally, I believe DMGs, including those we behave differently than emulated, Additionally, I believe DMGs, including those we behave differently than emulated,
are all deterministic. */ are all deterministic. */
/*if (offset < 4) { if (offset < 4) {
gb->io_registers[GB_IO_WAV_START] = gb->io_registers[GB_IO_WAV_START + offset]; gb->io_registers[GB_IO_WAV_START] = gb->io_registers[GB_IO_WAV_START + offset];
gb->apu.wave_channel.wave_form[0] = gb->apu.wave_channel.wave_form[offset / 2]; gb->apu.wave_channel.wave_form[0] = gb->apu.wave_channel.wave_form[offset / 2];
gb->apu.wave_channel.wave_form[1] = gb->apu.wave_channel.wave_form[offset / 2 + 1]; gb->apu.wave_channel.wave_form[1] = gb->apu.wave_channel.wave_form[offset / 2 + 1];
@ -1206,7 +1206,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->apu.wave_channel.wave_form + (offset & ~3) * 2, gb->apu.wave_channel.wave_form + (offset & ~3) * 2,
8); 8);
} }
}*/ }
if (!gb->apu.is_active[GB_WAVE]) { if (!gb->apu.is_active[GB_WAVE]) {
gb->apu.is_active[GB_WAVE] = true; gb->apu.is_active[GB_WAVE] = true;
update_sample(gb, GB_WAVE, update_sample(gb, GB_WAVE,

View file

@ -16,7 +16,7 @@ extern "C" {
#define GB_STRUCT_VERSION 13 #define GB_STRUCT_VERSION 13
#define CGB 0 #define CGB (gb->model&GB_MODEL_CGB_FAMILY)
#define GB_MODEL_FAMILY_MASK 0xF00 #define GB_MODEL_FAMILY_MASK 0xF00
#define GB_MODEL_DMG_FAMILY 0x000 #define GB_MODEL_DMG_FAMILY 0x000

View file

@ -1,6 +1,6 @@
/* /*
License: BSD-3-Clause License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900 Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900 Modifiers and Contributors for Furnace: cam900

View file

@ -1,6 +1,6 @@
/* /*
License: BSD-3-Clause License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900 Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900 Modifiers and Contributors for Furnace: cam900

View file

@ -26,37 +26,11 @@
#include <algorithm> #include <algorithm>
#include <limits> #include <limits>
namespace Lynx #if defined( _MSC_VER )
{
namespace #include <intrin.h>
{
static constexpr int64_t CNT_MAX = std::numeric_limits<int64_t>::max() & ~15; static uint32_t popcnt_generic( uint32_t x )
#if defined ( __cpp_lib_bitops )
#define popcnt(X) std::popcount(X)
#elif defined( _MSC_VER )
# include <intrin.h>
uint32_t popcnt( uint32_t x )
{
return __popcnt( x );
}
#elif defined( __GNUC__ )
uint32_t popcnt( uint32_t x )
{
return __builtin_popcount( x );
}
#else
uint32_t popcnt( uint32_t x )
{ {
int v = 0; int v = 0;
while ( x != 0 ) while ( x != 0 )
@ -67,8 +41,61 @@ uint32_t popcnt( uint32_t x )
return v; return v;
} }
#if defined( _M_IX86 ) || defined( _M_X64 )
static uint32_t popcnt_intrinsic( uint32_t x )
{
return __popcnt( x );
}
static uint32_t( *popcnt )( uint32_t );
//detecting popcnt availability on msvc intel
static void selectPOPCNT()
{
int info[4];
__cpuid( info, 1 );
if ( ( info[2] & ( (int)1 << 23 ) ) != 0 )
{
popcnt = &popcnt_intrinsic;
}
else
{
popcnt = &popcnt_generic;
}
}
#else //defined( _M_IX86 ) || defined( _M_X64 )
//MSVC non INTEL should use generic implementation
inline void selectPOPCNT()
{
}
#define popcnt popcnt_generic
#endif #endif
#else //defined( _MSC_VER )
//non MVSC should use builtin implementation
inline void selectPOPCNT()
{
}
#define popcnt __builtin_popcount
#endif
namespace Lynx
{
namespace
{
static constexpr int64_t CNT_MAX = std::numeric_limits<int64_t>::max() & ~15;
int32_t clamp( int32_t v, int32_t lo, int32_t hi ) int32_t clamp( int32_t v, int32_t lo, int32_t hi )
{ {
return v < lo ? lo : ( v > hi ? hi : v ); return v < lo ? lo : ( v > hi ? hi : v );
@ -513,6 +540,7 @@ private:
Mikey::Mikey( uint32_t sampleRate ) : mMikey{ std::make_unique<MikeyPimpl>() }, mQueue{ std::make_unique<ActionQueue>() }, mTick{}, mNextTick{}, mSampleRate{ sampleRate }, mSamplesRemainder{}, mTicksPerSample{ 16000000 / mSampleRate, 16000000 % mSampleRate } Mikey::Mikey( uint32_t sampleRate ) : mMikey{ std::make_unique<MikeyPimpl>() }, mQueue{ std::make_unique<ActionQueue>() }, mTick{}, mNextTick{}, mSampleRate{ sampleRate }, mSamplesRemainder{}, mTicksPerSample{ 16000000 / mSampleRate, 16000000 % mSampleRate }
{ {
selectPOPCNT();
enqueueSampling(); enqueueSampling();
} }

View file

@ -1,6 +1,6 @@
/* /*
License: BSD-3-Clause License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900 Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900, tildearrow Modifiers and Contributors for Furnace: cam900, tildearrow

View file

@ -1,6 +1,6 @@
/* /*
License: BSD-3-Clause License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900 Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900, tildearrow Modifiers and Contributors for Furnace: cam900, tildearrow

View file

@ -1,6 +1,6 @@
/* /*
License: BSD-3-Clause License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900 Copyright holder(s): cam900
Modifiers and Contributors for Furnace: tildearrow Modifiers and Contributors for Furnace: tildearrow

View file

@ -1,6 +1,6 @@
/* /*
License: BSD-3-Clause License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900 Copyright holder(s): cam900
Modifiers and Contributors for Furnace: tildearrow Modifiers and Contributors for Furnace: tildearrow

View file

@ -1,6 +1,6 @@
/* /*
License: BSD-3-Clause License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900 Copyright holder(s): cam900
Modifiers and Contributors for Furnace: tildearrow Modifiers and Contributors for Furnace: tildearrow

View file

@ -1,6 +1,6 @@
/* /*
License: BSD-3-Clause License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900 Copyright holder(s): cam900
Modifiers and Contributors for Furnace: tildearrow Modifiers and Contributors for Furnace: tildearrow

View file

@ -1,6 +1,6 @@
/* /*
License: BSD-3-Clause License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900 Copyright holder(s): cam900
Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst

View file

@ -1,6 +1,6 @@
/* /*
License: BSD-3-Clause License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900 Copyright holder(s): cam900
Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst

View file

@ -5,9 +5,22 @@
#define minval(a,b) (((a)<(b))?(a):(b)) #define minval(a,b) (((a)<(b))?(a):(b))
#define maxval(a,b) (((a)>(b))?(a):(b)) #define maxval(a,b) (((a)>(b))?(a):(b))
#define FILVOL chan[4].special1C
#define ILCTRL chan[4].special1D
#define ILSIZE chan[5].special1C
#define FIL1 chan[5].special1D
#define IL1 chan[6].special1C
#define IL2 chan[6].special1D
#define IL0 chan[7].special1C
#define MVOL chan[7].special1D
void SoundUnit::NextSample(short* l, short* r) { void SoundUnit::NextSample(short* l, short* r) {
// run channels
for (int i=0; i<8; i++) { for (int i=0; i<8; i++) {
if (chan[i].vol==0 && !chan[i].flags.swvol) {fns[i]=0; continue;} if (chan[i].vol==0 && !chan[i].flags.swvol) {
fns[i]=0;
continue;
}
if (chan[i].flags.pcm) { if (chan[i].flags.pcm) {
ns[i]=pcm[chan[i].pcmpos]; ns[i]=pcm[chan[i].pcmpos];
} else switch (chan[i].flags.shape) { } else switch (chan[i].flags.shape) {
@ -48,13 +61,12 @@ void SoundUnit::NextSample(short* l, short* r) {
pcmdec[i]-=32768; pcmdec[i]-=32768;
if (chan[i].pcmpos<chan[i].pcmbnd) { if (chan[i].pcmpos<chan[i].pcmbnd) {
chan[i].pcmpos++; chan[i].pcmpos++;
chan[i].wc++;
if (chan[i].pcmpos==chan[i].pcmbnd) { if (chan[i].pcmpos==chan[i].pcmbnd) {
if (chan[i].flags.pcmloop) { if (chan[i].flags.pcmloop) {
chan[i].pcmpos=chan[i].pcmrst; chan[i].pcmpos=chan[i].pcmrst;
} }
} }
chan[i].pcmpos&=(SOUNDCHIP_PCM_SIZE-1); chan[i].pcmpos&=(pcmSize-1);
} else if (chan[i].flags.pcmloop) { } else if (chan[i].flags.pcmloop) {
chan[i].pcmpos=chan[i].pcmrst; chan[i].pcmpos=chan[i].pcmrst;
} }
@ -221,16 +233,115 @@ void SoundUnit::NextSample(short* l, short* r) {
nsR[i]=0; nsR[i]=0;
} }
} }
// mix
tnsL=(nsL[0]+nsL[1]+nsL[2]+nsL[3]+nsL[4]+nsL[5]+nsL[6]+nsL[7])>>2; tnsL=(nsL[0]+nsL[1]+nsL[2]+nsL[3]+nsL[4]+nsL[5]+nsL[6]+nsL[7])>>2;
tnsR=(nsR[0]+nsR[1]+nsR[2]+nsR[3]+nsR[4]+nsR[5]+nsR[6]+nsR[7])>>2; tnsR=(nsR[0]+nsR[1]+nsR[2]+nsR[3]+nsR[4]+nsR[5]+nsR[6]+nsR[7])>>2;
*l=minval(32767,maxval(-32767,tnsL)); IL1=minval(32767,maxval(-32767,tnsL))>>8;
*r=minval(32767,maxval(-32767,tnsR)); IL2=minval(32767,maxval(-32767,tnsR))>>8;
// write input lines to sample memory
if (ILSIZE&64) {
if (++ilBufPeriod>=((1+(FIL1>>4))<<2)) {
ilBufPeriod=0;
unsigned short ilLowerBound=pcmSize-((1+(ILSIZE&63))<<7);
short next;
if (ilBufPos<ilLowerBound) ilBufPos=ilLowerBound;
switch (ILCTRL&3) {
case 0:
ilFeedback0=ilFeedback1=pcm[ilBufPos];
next=((signed char)IL0)+((pcm[ilBufPos]*(FIL1&15))>>4);
if (next<-128) next=-128;
if (next>127) next=127;
pcm[ilBufPos]=next;
if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound;
break;
case 1:
ilFeedback0=ilFeedback1=pcm[ilBufPos];
next=((signed char)IL1)+((pcm[ilBufPos]*(FIL1&15))>>4);
if (next<-128) next=-128;
if (next>127) next=127;
pcm[ilBufPos]=next;
if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound;
break;
case 2:
ilFeedback0=ilFeedback1=pcm[ilBufPos];
next=((signed char)IL2)+((pcm[ilBufPos]*(FIL1&15))>>4);
if (next<-128) next=-128;
if (next>127) next=127;
pcm[ilBufPos]=next;
if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound;
break;
case 3:
ilFeedback0=pcm[ilBufPos];
next=((signed char)IL1)+((pcm[ilBufPos]*(FIL1&15))>>4);
if (next<-128) next=-128;
if (next>127) next=127;
pcm[ilBufPos]=next;
if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound;
ilFeedback1=pcm[ilBufPos];
next=((signed char)IL2)+((pcm[ilBufPos]*(FIL1&15))>>4);
if (next<-128) next=-128;
if (next>127) next=127;
pcm[ilBufPos]=next;
if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound;
break;
}
}
if (ILCTRL&4) {
if (ILSIZE&128) {
tnsL+=ilFeedback1*(signed char)FILVOL;
tnsR+=ilFeedback0*(signed char)FILVOL;
} else {
tnsL+=ilFeedback0*(signed char)FILVOL;
tnsR+=ilFeedback1*(signed char)FILVOL;
}
}
}
if (dsOut) {
tnsL=minval(32767,maxval(-32767,tnsL<<1));
tnsR=minval(32767,maxval(-32767,tnsR<<1));
short accumL=0;
short accumR=0;
for (int i=0; i<4; i++) {
if ((tnsL>>8)==0 && dsCounterL>0) dsCounterL=0;
dsCounterL+=tnsL>>8;
if (dsCounterL>=0) {
accumL+=4095;
dsCounterL-=127;
} else {
accumL+=-4095;
dsCounterL+=127;
}
if ((tnsR>>8)==0 && dsCounterR>0) dsCounterR=0;
dsCounterR+=tnsR>>8;
if (dsCounterR>=0) {
accumR+=4095;
dsCounterR-=127;
} else {
accumR+=-4095;
dsCounterR+=127;
}
}
*l=accumL;
*r=accumR;
} else {
*l=minval(32767,maxval(-32767,tnsL));
*r=minval(32767,maxval(-32767,tnsR));
}
} }
void SoundUnit::Init() { void SoundUnit::Init(int sampleMemSize, bool dsOutMode) {
pcmSize=sampleMemSize;
dsOut=dsOutMode;
Reset(); Reset();
memset(pcm,0,SOUNDCHIP_PCM_SIZE); memset(pcm,0,pcmSize);
for (int i=0; i<256; i++) { for (int i=0; i<256; i++) {
SCsine[i]=sin((i/128.0f)*M_PI)*127; SCsine[i]=sin((i/128.0f)*M_PI)*127;
SCtriangle[i]=(i>127)?(255-i):(i); SCtriangle[i]=(i>127)?(255-i):(i);
@ -242,9 +353,6 @@ void SoundUnit::Init() {
SCpantabR[128+i]=i-1; SCpantabR[128+i]=i-1;
} }
SCpantabR[128]=0; SCpantabR[128]=0;
for (int i=0; i<8; i++) {
muted[i]=false;
}
} }
void SoundUnit::Reset() { void SoundUnit::Reset() {
@ -272,8 +380,14 @@ void SoundUnit::Reset() {
oldflags[i]=0; oldflags[i]=0;
pcmdec[i]=0; pcmdec[i]=0;
} }
dsCounterL=0;
dsCounterR=0;
tnsL=0; tnsL=0;
tnsR=0; tnsR=0;
ilBufPos=0;
ilBufPeriod=0;
ilFeedback0=0;
ilFeedback1=0;
memset(chan,0,sizeof(SUChannel)*8); memset(chan,0,sizeof(SUChannel)*8);
} }
@ -282,6 +396,8 @@ void SoundUnit::Write(unsigned char addr, unsigned char data) {
} }
SoundUnit::SoundUnit() { SoundUnit::SoundUnit() {
Init(); Init(65536); // default
memset(pcm,0,SOUNDCHIP_PCM_SIZE); for (int i=0; i<8; i++) {
muted[i]=false;
}
} }

View file

@ -3,8 +3,6 @@
#include <math.h> #include <math.h>
#include <assert.h> #include <assert.h>
#define SOUNDCHIP_PCM_SIZE 8192
class SoundUnit { class SoundUnit {
signed char SCsine[256]; signed char SCsine[256];
signed char SCtriangle[256]; signed char SCtriangle[256];
@ -22,8 +20,15 @@ class SoundUnit {
int nshigh[8]; int nshigh[8];
int nsband[8]; int nsband[8];
int tnsL, tnsR; int tnsL, tnsR;
unsigned char ilBufPeriod;
unsigned short ilBufPos;
signed char ilFeedback0;
signed char ilFeedback1;
unsigned short oldfreq[8]; unsigned short oldfreq[8];
unsigned short oldflags[8]; unsigned short oldflags[8];
unsigned int pcmSize;
bool dsOut;
short dsCounterL, dsCounterR;
public: public:
unsigned short resetfreq[8]; unsigned short resetfreq[8];
unsigned short voldcycles[8]; unsigned short voldcycles[8];
@ -81,11 +86,13 @@ class SoundUnit {
unsigned char dir: 1; unsigned char dir: 1;
unsigned char bound; unsigned char bound;
} swcut; } swcut;
unsigned short wc; unsigned char special1C;
unsigned char special1D;
unsigned short restimer; unsigned short restimer;
} chan[8]; } chan[8];
signed char pcm[SOUNDCHIP_PCM_SIZE]; signed char pcm[65536];
bool muted[8]; bool muted[8];
void SetIL0(unsigned char addr);
void Write(unsigned char addr, unsigned char data); void Write(unsigned char addr, unsigned char data);
void NextSample(short* l, short* r); void NextSample(short* l, short* r);
inline int GetSample(int ch) { inline int GetSample(int ch) {
@ -94,7 +101,7 @@ class SoundUnit {
if (ret>32767) ret=32767; if (ret>32767) ret=32767;
return ret; return ret;
} }
void Init(); void Init(int sampleMemSize=8192, bool dsOutMode=false);
void Reset(); void Reset();
SoundUnit(); SoundUnit();
}; };

View file

@ -1,6 +1,6 @@
/* /*
License: BSD-3-Clause License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900 Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900, tildearrow Modifiers and Contributors for Furnace: cam900, tildearrow

View file

@ -1,6 +1,6 @@
/* /*
License: BSD-3-Clause License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900 Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900, tildearrow Modifiers and Contributors for Furnace: cam900, tildearrow

View file

@ -1,6 +1,6 @@
/* /*
License: BSD-3-Clause License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900 Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900, tildearrow Modifiers and Contributors for Furnace: cam900, tildearrow

View file

@ -1,6 +1,6 @@
/* /*
License: BSD-3-Clause License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900 Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900, tildearrow Modifiers and Contributors for Furnace: cam900, tildearrow

View file

@ -26,6 +26,7 @@
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } #define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define chWrite(c,a,v) rWrite(((c)<<5)|(a),v); #define chWrite(c,a,v) rWrite(((c)<<5)|(a),v);
#define CHIP_DIVIDER 2
#define CHIP_FREQBASE 524288 #define CHIP_FREQBASE 524288
const char** DivPlatformSoundUnit::getRegisterSheet() { const char** DivPlatformSoundUnit::getRegisterSheet() {
@ -98,6 +99,13 @@ const char* DivPlatformSoundUnit::getEffectName(unsigned char effect) {
return NULL; return NULL;
} }
double DivPlatformSoundUnit::NOTE_SU(int ch, int note) {
if (chan[ch].switchRoles) {
return NOTE_PERIODIC(note);
}
return NOTE_FREQUENCY(note);
}
void DivPlatformSoundUnit::acquire(short* bufL, short* bufR, size_t start, size_t len) { void DivPlatformSoundUnit::acquire(short* bufL, short* bufR, size_t start, size_t len) {
for (size_t h=start; h<start+len; h++) { for (size_t h=start; h<start+len; h++) {
while (!writes.empty()) { while (!writes.empty()) {
@ -137,15 +145,15 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
if (chan[i].std.arp.had) { if (chan[i].std.arp.had) {
if (!chan[i].inPorta) { if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) { if (chan[i].std.arp.mode) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); chan[i].baseFreq=NOTE_SU(i,chan[i].std.arp.val);
} else { } else {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); chan[i].baseFreq=NOTE_SU(i,chan[i].note+chan[i].std.arp.val);
} }
} }
chan[i].freqChanged=true; chan[i].freqChanged=true;
} else { } else {
if (chan[i].std.arp.mode && chan[i].std.arp.finished) { if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); chan[i].baseFreq=NOTE_SU(i,chan[i].note);
chan[i].freqChanged=true; chan[i].freqChanged=true;
} }
} }
@ -187,9 +195,21 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
chan[i].control=chan[i].std.ex3.val&15; chan[i].control=chan[i].std.ex3.val&15;
writeControl(i); writeControl(i);
} }
if (chan[i].std.ex4.had) {
chan[i].syncTimer=chan[i].std.ex4.val&65535;
chan[i].timerSync=(chan[i].syncTimer>0);
if (chan[i].switchRoles) {
chWrite(i,0x00,chan[i].syncTimer&0xff);
chWrite(i,0x01,chan[i].syncTimer>>8);
} else {
chWrite(i,0x1e,chan[i].syncTimer&0xff);
chWrite(i,0x1f,chan[i].syncTimer>>8);
}
writeControlUpper(i);
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE); chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].switchRoles,2,chan[i].pitch2,chipClock,chan[i].switchRoles?CHIP_DIVIDER:CHIP_FREQBASE);
if (chan[i].pcm) { if (chan[i].pcm) {
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
// TODO: sample map? // TODO: sample map?
@ -206,14 +226,19 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
} }
if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>65535) chan[i].freq=65535; if (chan[i].freq>65535) chan[i].freq=65535;
chWrite(i,0x00,chan[i].freq&0xff); if (chan[i].switchRoles) {
chWrite(i,0x01,chan[i].freq>>8); chWrite(i,0x1e,chan[i].freq&0xff);
chWrite(i,0x1f,chan[i].freq>>8);
} else {
chWrite(i,0x00,chan[i].freq&0xff);
chWrite(i,0x01,chan[i].freq>>8);
}
if (chan[i].keyOn) { if (chan[i].keyOn) {
if (chan[i].pcm) { if (chan[i].pcm) {
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note)); DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note));
if (sample!=NULL) { if (sample!=NULL) {
unsigned int sampleEnd=sample->offSU+sample->samples; unsigned int sampleEnd=sample->offSU+(sample->getEndPosition());
unsigned int off=sample->offSU+chan[i].hasOffset; unsigned int off=sample->offSU+chan[i].hasOffset;
chan[i].hasOffset=0; chan[i].hasOffset=0;
if (sampleEnd>=getSampleMemCapacity(0)) sampleEnd=getSampleMemCapacity(0)-1; if (sampleEnd>=getSampleMemCapacity(0)) sampleEnd=getSampleMemCapacity(0)-1;
@ -221,7 +246,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
chWrite(i,0x0b,off>>8); chWrite(i,0x0b,off>>8);
chWrite(i,0x0c,sampleEnd&0xff); chWrite(i,0x0c,sampleEnd&0xff);
chWrite(i,0x0d,sampleEnd>>8); chWrite(i,0x0d,sampleEnd>>8);
if (sample->loopStart>=0 && sample->loopStart<(int)sample->samples) { if (sample->isLoopable()) {
unsigned int sampleLoop=sample->offSU+sample->loopStart; unsigned int sampleLoop=sample->offSU+sample->loopStart;
if (sampleLoop>=getSampleMemCapacity(0)) sampleLoop=getSampleMemCapacity(0)-1; if (sampleLoop>=getSampleMemCapacity(0)) sampleLoop=getSampleMemCapacity(0)-1;
chWrite(i,0x0e,sampleLoop&0xff); chWrite(i,0x0e,sampleLoop&0xff);
@ -249,14 +274,15 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
switch (c.cmd) { switch (c.cmd) {
case DIV_CMD_NOTE_ON: { case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SU); DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SU);
if (chan[c.chan].pcm && ins->type!=DIV_INS_AMIGA) { chan[c.chan].switchRoles=ins->su.switchRoles;
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA); if (chan[c.chan].pcm && !(ins->type==DIV_INS_AMIGA || ins->su.useSample)) {
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->su.useSample);
writeControl(c.chan); writeControl(c.chan);
writeControlUpper(c.chan); writeControlUpper(c.chan);
} }
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA); chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->su.useSample);
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].baseFreq=NOTE_SU(c.chan,c.value);
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
} }
@ -414,7 +440,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
} }
break; break;
case DIV_CMD_NOTE_PORTA: { case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_FREQUENCY(c.value2); int destFreq=NOTE_SU(c.chan,c.value2);
bool return2=false; bool return2=false;
if (destFreq>chan[c.chan].baseFreq) { if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:(1+(chan[c.chan].baseFreq>>9))); chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:(1+(chan[c.chan].baseFreq>>9)));
@ -446,7 +472,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
chan[c.chan].keyOn=true; chan[c.chan].keyOn=true;
break; break;
case DIV_CMD_LEGATO: case DIV_CMD_LEGATO:
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].baseFreq=NOTE_SU(c.chan,c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
break; break;
@ -454,7 +480,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
if (chan[c.chan].active && c.value2) { if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SU)); if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SU));
} }
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note); if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_SU(c.chan,chan[c.chan].note);
chan[c.chan].inPorta=c.value; chan[c.chan].inPorta=c.value;
break; break;
case DIV_CMD_GET_VOLMAX: case DIV_CMD_GET_VOLMAX:
@ -478,6 +504,11 @@ void DivPlatformSoundUnit::forceIns() {
for (int i=0; i<8; i++) { for (int i=0; i<8; i++) {
chan[i].insChanged=true; chan[i].insChanged=true;
chan[i].freqChanged=true; chan[i].freqChanged=true;
// restore channel attributes
chWrite(i,0x03,chan[i].pan);
writeControl(i);
writeControlUpper(i);
} }
} }
@ -522,6 +553,16 @@ void DivPlatformSoundUnit::reset() {
lfoMode=0; lfoMode=0;
lfoSpeed=255; lfoSpeed=255;
delay=500; delay=500;
// set initial IL status
ilCtrl=initIlCtrl;
ilSize=initIlSize;
fil1=initFil1;
echoVol=initEchoVol;
rWrite(0x9c,echoVol);
rWrite(0x9d,ilCtrl);
rWrite(0xbc,ilSize);
rWrite(0xbd,fil1);
} }
bool DivPlatformSoundUnit::isStereo() { bool DivPlatformSoundUnit::isStereo() {
@ -548,6 +589,15 @@ void DivPlatformSoundUnit::setFlags(unsigned int flags) {
for (int i=0; i<8; i++) { for (int i=0; i<8; i++) {
oscBuf[i]->rate=rate; oscBuf[i]->rate=rate;
} }
initIlCtrl=3|(flags&4);
initIlSize=((flags>>8)&63)|((flags&4)?0x40:0)|((flags&8)?0x80:0);
initFil1=flags>>16;
initEchoVol=flags>>24;
sampleMemSize=flags&16;
su->Init(sampleMemSize?65536:8192,flags&32);
renderSamples();
} }
void DivPlatformSoundUnit::poke(unsigned int addr, unsigned short val) { void DivPlatformSoundUnit::poke(unsigned int addr, unsigned short val) {
@ -563,7 +613,7 @@ const void* DivPlatformSoundUnit::getSampleMem(int index) {
} }
size_t DivPlatformSoundUnit::getSampleMemCapacity(int index) { size_t DivPlatformSoundUnit::getSampleMemCapacity(int index) {
return (index==0)?8192:0; return (index==0)?((sampleMemSize?65536:8192)-((initIlSize&64)?((1+(initIlSize&63))<<7):0)):0;
} }
size_t DivPlatformSoundUnit::getSampleMemUsage(int index) { size_t DivPlatformSoundUnit::getSampleMemUsage(int index) {
@ -576,6 +626,7 @@ void DivPlatformSoundUnit::renderSamples() {
size_t memPos=0; size_t memPos=0;
for (int i=0; i<parent->song.sampleLen; i++) { for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i]; DivSample* s=parent->song.sample[i];
if (s->data8==NULL) continue;
int paddedLen=s->samples; int paddedLen=s->samples;
if (memPos>=getSampleMemCapacity(0)) { if (memPos>=getSampleMemCapacity(0)) {
logW("out of PCM memory for sample %d!",i); logW("out of PCM memory for sample %d!",i);
@ -602,9 +653,8 @@ int DivPlatformSoundUnit::init(DivEngine* p, int channels, int sugRate, unsigned
isMuted[i]=false; isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer; oscBuf[i]=new DivDispatchOscBuffer;
} }
setFlags(flags);
su=new SoundUnit(); su=new SoundUnit();
su->Init(); setFlags(flags);
reset(); reset();
return 6; return 6;
} }

View file

@ -31,7 +31,7 @@ class DivPlatformSoundUnit: public DivDispatch {
int ins, cutoff, baseCutoff, res, control, hasOffset; int ins, cutoff, baseCutoff, res, control, hasOffset;
signed char pan; signed char pan;
unsigned char duty; unsigned char duty;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, phaseReset, filterPhaseReset; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, phaseReset, filterPhaseReset, switchRoles;
bool pcmLoop, timerSync, freqSweep, volSweep, cutSweep; bool pcmLoop, timerSync, freqSweep, volSweep, cutSweep;
unsigned short freqSweepP, volSweepP, cutSweepP; unsigned short freqSweepP, volSweepP, cutSweepP;
unsigned char freqSweepB, volSweepB, cutSweepB; unsigned char freqSweepB, volSweepB, cutSweepB;
@ -67,6 +67,7 @@ class DivPlatformSoundUnit: public DivDispatch {
pcm(false), pcm(false),
phaseReset(false), phaseReset(false),
filterPhaseReset(false), filterPhaseReset(false),
switchRoles(false),
pcmLoop(false), pcmLoop(false),
timerSync(false), timerSync(false),
freqSweep(false), freqSweep(false),
@ -96,6 +97,10 @@ class DivPlatformSoundUnit: public DivDispatch {
}; };
std::queue<QueuedWrite> writes; std::queue<QueuedWrite> writes;
unsigned char lastPan; unsigned char lastPan;
bool sampleMemSize;
unsigned char ilCtrl, ilSize, fil1;
unsigned char initIlCtrl, initIlSize, initFil1;
signed char echoVol, initEchoVol;
int cycles, curChan, delay; int cycles, curChan, delay;
short tempL; short tempL;
@ -104,6 +109,7 @@ class DivPlatformSoundUnit: public DivDispatch {
SoundUnit* su; SoundUnit* su;
size_t sampleMemLen; size_t sampleMemLen;
unsigned char regPool[128]; unsigned char regPool[128];
double NOTE_SU(int ch, int note);
void writeControl(int ch); void writeControl(int ch);
void writeControlUpper(int ch); void writeControlUpper(int ch);

View file

@ -83,12 +83,10 @@ void DivPlatformSwan::acquire(short* bufL, short* bufR, size_t start, size_t len
continue; continue;
} }
rWrite(0x09,(unsigned char)s->data8[dacPos++]+0x80); rWrite(0x09,(unsigned char)s->data8[dacPos++]+0x80);
if (dacPos>=s->samples) { if (s->isLoopable() && dacPos>=s->getEndPosition()) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) { dacPos=s->loopStart;
dacPos=s->loopStart; } else if (dacPos>=s->samples) {
} else { dacSample=-1;
dacSample=-1;
}
} }
dacPeriod-=rate; dacPeriod-=rate;
} }

View file

@ -98,13 +98,11 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len
rWritePCMData(tmp_r&0xff); rWritePCMData(tmp_r&0xff);
} }
chan[16].pcm.pos++; chan[16].pcm.pos++;
if (chan[16].pcm.pos>=s->samples) { if (s->isLoopable() && chan[16].pcm.pos>=s->getEndPosition()) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) { chan[16].pcm.pos=s->loopStart;
chan[16].pcm.pos=s->loopStart; } else if (chan[16].pcm.pos>=s->samples) {
} else { chan[16].pcm.sample=-1;
chan[16].pcm.sample=-1; break;
break;
}
} }
} }
} else { } else {
@ -269,12 +267,12 @@ int DivPlatformVERA::dispatch(DivCommand c) {
chan[16].pcm.pos=0; chan[16].pcm.pos=0;
DivSample* s=parent->getSample(chan[16].pcm.sample); DivSample* s=parent->getSample(chan[16].pcm.sample);
unsigned char ctrl=0x90|chan[16].vol; // always stereo unsigned char ctrl=0x90|chan[16].vol; // always stereo
if (s->depth==16) { if (s->depth==DIV_SAMPLE_DEPTH_16BIT) {
chan[16].pcm.depth16=true; chan[16].pcm.depth16=true;
ctrl|=0x20; ctrl|=0x20;
} else { } else {
chan[16].pcm.depth16=false; chan[16].pcm.depth16=false;
if (s->depth!=8) chan[16].pcm.sample=-1; if (s->depth!=DIV_SAMPLE_DEPTH_8BIT) chan[16].pcm.sample=-1;
} }
rWritePCMCtrl(ctrl); rWritePCMCtrl(ctrl);
} }

View file

@ -77,13 +77,11 @@ void DivPlatformVRC6::acquire(short* bufL, short* bufR, size_t start, size_t len
chWrite(i,0,0x80|chan[i].dacOut); chWrite(i,0,0x80|chan[i].dacOut);
} }
chan[i].dacPos++; chan[i].dacPos++;
if (chan[i].dacPos>=s->samples) { if (s->isLoopable() && chan[i].dacPos>=s->getEndPosition()) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) { chan[i].dacPos=s->loopStart;
chan[i].dacPos=s->loopStart; } else if (chan[i].dacPos>=s->samples) {
} else { chan[i].dacSample=-1;
chan[i].dacSample=-1; chWrite(i,0,0);
chWrite(i,0,0);
}
} }
chan[i].dacPeriod-=rate; chan[i].dacPeriod-=rate;
} }

View file

@ -761,7 +761,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
immWrite(0x104,(end>>5)&0xff); immWrite(0x104,(end>>5)&0xff);
immWrite(0x105,(end>>13)&0xff); immWrite(0x105,(end>>13)&0xff);
immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2);
immWrite(0x100,(s->loopStart>=0)?0xb0:0xa0); // start/repeat immWrite(0x100,(s->isLoopable())?0xb0:0xa0); // start/repeat
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
@ -796,7 +796,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
immWrite(0x104,(end>>5)&0xff); immWrite(0x104,(end>>5)&0xff);
immWrite(0x105,(end>>13)&0xff); immWrite(0x105,(end>>13)&0xff);
immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2);
immWrite(0x100,(s->loopStart>=0)?0xb0:0xa0); // start/repeat immWrite(0x100,(s->isLoopable())?0xb0:0xa0); // start/repeat
int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0); int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0);
immWrite(0x109,freq&0xff); immWrite(0x109,freq&0xff);
immWrite(0x10a,(freq>>8)&0xff); immWrite(0x10a,(freq>>8)&0xff);

View file

@ -793,7 +793,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
immWrite(0x14,(end>>8)&0xff); immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16); immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
@ -828,7 +828,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
immWrite(0x14,(end>>8)&0xff); immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16); immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat
int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0); int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0);
immWrite(0x19,freq&0xff); immWrite(0x19,freq&0xff);
immWrite(0x1a,(freq>>8)&0xff); immWrite(0x1a,(freq>>8)&0xff);

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