Compare commits

..

No commits in common. "master" and "dev121" have entirely different histories.

1379 changed files with 39794 additions and 234550 deletions

1
.gitattributes vendored
View File

@ -1 +0,0 @@
*.sfd text

View File

@ -1,39 +0,0 @@
# IMPORTANT NOTICE
1. this section is exclusively for ISSUES related to Furnace (bugs, major annoyances and others).
2. this section is NOT for Suggestions, Questions, Showcase or any other Discussions that do not meet the criteria and definition of an ISSUE.
- see the Discussions section if you wish to submit these.
3. check whether your issue has been reported already.
- go to the Issues section, and use the search bar that appears on top of the Issues list.
4. include the following information:
- version of Furnace (help > about)
- operating system (and version)
- whether you have downloaded Furnace, or built it from source.
5. provide these details if you believe the issue is operating system and/or computer-specific:
- CPU model
- Windows: go to Control Panel > System.
- macOS: go to the Apple menu and select About This Mac...
- Linux: use `lscpu` or `cat /proc/cpuinfo`.
- graphics card (and driver version)
- Windows: open `dxdiag` and observe the Render tab.
- macOS: go to the Apple menu and select About This Mac...
- this information is not always shown.
- this information is not necessary if you use an Apple silicon Mac.
- Linux: use `glxinfo | grep OpenGL`.
6. if your issue is an abnormal program termination (a "Crash"), you must provide additional details:
- the furnace_crash.txt file that is created by Furnace after a Crash. this file is located in the following paths:
- Windows: `C:\Users\<username>\furnace_crash.txt`
- Linux/other: `/tmp/furnace_crash.txt`
- on macOS this file is not generated. you may retrieve information about the Crash by clicking on "Report..." or "Show Details" in the "quit unexpectedly" dialog that appears following the Crash.
- make sure to remove any personal information for privacy reasons.
- be sure to select "Don't Send" afterwards.
- the furnace.log file located in:
- Windows: `C:\Users\<username>\AppData\Roaming\furnace\furnace.log`
- macOS: `~/Library/Application Support/furnace/furnace.log`
- Linux: `~/.config/furnace/furnace.log`
- make sure to remove any personal information for privacy reasons.
BY SUBMITTING A TICKET, YOU HEREBY AGREE TO COMPLY WITH THESE TERMS.
FAILURE TO DO SO MAY RESULT IN YOUR TICKET BEING DECLARED VOID.
***END OF NOTICE*** --- REMOVE THIS NOTICE AFTER READING!

View File

@ -11,7 +11,7 @@ defaults:
shell: bash
env:
BUILD_TYPE: RelWithDebInfo
BUILD_TYPE: Release
jobs:
build:
@ -20,12 +20,12 @@ jobs:
config:
- { name: 'Windows MSVC x86', os: windows-latest, compiler: msvc, arch: x86 }
- { name: 'Windows MSVC x86_64', os: windows-latest, compiler: msvc, arch: x86_64 }
#- { name: 'Windows MinGW x86', os: ubuntu-20.04, compiler: mingw, arch: x86 }
#- { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 }
- { name: 'Windows MinGW x86', os: ubuntu-20.04, compiler: mingw, arch: x86 }
- { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 }
- { name: 'macOS x86_64', os: macos-latest, arch: x86_64 }
- { name: 'macOS ARM', os: macos-latest, arch: arm64 }
- { name: 'Linux x86_64', os: ubuntu-20.04, arch: x86_64 }
#- { name: 'Linux ARM', os: ubuntu-18.04, arch: armhf }
- { name: 'Linux x86_64', os: ubuntu-18.04, arch: x86_64 }
- { name: 'Linux ARM', os: ubuntu-18.04, arch: armhf }
fail-fast: false
name: ${{ matrix.config.name }}
@ -33,7 +33,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3.1.0
uses: actions/checkout@v2
with:
submodules: recursive
@ -59,9 +59,9 @@ jobs:
echo "MinGW cross target: ${mingw_target}"
fi
echo "vswhere-target=${vswhere_target}" >> $GITHUB_OUTPUT
echo "msvc-target=${msvc_target}" >> $GITHUB_OUTPUT
echo "mingw-target=${mingw_target}" >> $GITHUB_OUTPUT
echo "::set-output name=vswhere-target::${vswhere_target}"
echo "::set-output name=msvc-target::${msvc_target}"
echo "::set-output name=mingw-target::${mingw_target}"
- name: Set package identifier
id: package-identify
@ -82,14 +82,14 @@ jobs:
package_ext=".dmg"
else
package_name="${package_name}-Linux-${{ matrix.config.arch }}"
package_ext=".tar.gz"
package_ext=".AppImage"
fi
echo "Package identifier: ${package_name}"
echo "Package file: ${package_name}${package_ext}"
echo "id=${package_name}" >> $GITHUB_OUTPUT
echo "filename=${package_name}${package_ext}" >> $GITHUB_OUTPUT
echo "::set-output name=id::${package_name}"
echo "::set-output name=filename::${package_name}${package_ext}"
- name: Set build cores amount
id: build-cores
@ -102,11 +102,11 @@ jobs:
echo "Amount of cores we can build with: ${amount}"
echo "amount=${amount}" >> $GITHUB_OUTPUT
echo "::set-output name=amount::${amount}"
- name: Setup Toolchain [Windows MSVC]
if: ${{ matrix.config.compiler == 'msvc' }}
uses: vadz/gha-setup-vsdevenv@avoid-deprecation-warnings
uses: seanmiddleditch/gha-setup-vsdevenv@v3
with:
arch: ${{ steps.windows-identify.outputs.vswhere-target }}
@ -128,7 +128,10 @@ jobs:
librtmidi-dev \
libsndfile1-dev \
zlib1g-dev \
libjack-jackd2-dev
libjack-jackd2-dev \
appstream
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" || wget "https://tildearrow.org/storage/furnace/ci/appimagetool-x86_64.AppImage"
chmod +x appimagetool-x86_64.AppImage
- name: Install Dependencies [Linux armhf]
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'armhf' }}
@ -148,6 +151,9 @@ jobs:
libsndfile1-dev:armhf \
zlib1g-dev:armhf \
libjack-jackd2-dev:armhf
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" || wget "https://tildearrow.org/storage/furnace/ci/appimagetool-x86_64.AppImage"
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-armhf" || wget "https://tildearrow.org/storage/furnace/ci/runtime-armhf"
chmod +x appimagetool-x86_64.AppImage
ls /usr/arm-linux-gnueabihf/lib
- name: Configure (System Libraries)
@ -219,9 +225,6 @@ jobs:
fi
elif [ '${{ matrix.config.compiler }}' == 'mingw' ]; then
CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-${{ steps.windows-identify.outputs.mingw-target }}.cmake')
if [ '${{ matrix.config.arch }}' == 'x86' ]; then
CMAKE_EXTRA_ARGS+=('-DSUPPORT_XP=ON')
fi
elif [ '${{ runner.os }}' == 'macOS' ]; then
if [ '${{ matrix.config.arch }}' == 'arm64' ]; then
CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="11.0"' '-DCMAKE_OSX_ARCHITECTURES=arm64')
@ -237,7 +240,6 @@ jobs:
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
-DWARNINGS_ARE_ERRORS=${USE_WAE} \
-DWITH_DEMOS=OFF -DWITH_INSTRUMENTS=OFF -DWITH_WAVETABLES=OFF \
"${CMAKE_EXTRA_ARGS[@]}"
- name: Build
@ -272,11 +274,7 @@ jobs:
cp -v ../LICENSE LICENSE.txt
cp -v ../README.md README.txt
cp -vr ../papers ../${binPath}/furnace.exe ./
if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then
cp -v ../${binPath}/furnace.pdb ./
fi
sha256sum ../${binPath}/furnace.exe > checksum.txt
cp -vr ../{papers,demos,instruments} ../${binPath}/furnace.exe ./
popd
@ -295,41 +293,28 @@ jobs:
# strip -s build/furnace
#fi
mkdir -p target/furnace
make -C ${PWD}/build DESTDIR=${PWD}/target/furnace install
pushd target/furnace
cp -v ../../res/logo.png .DirIcon
cd usr
mv bin/furnace ..
rmdir bin
rm -r share/applications
rm -r share/doc
rm -r share/icons
rm -r share/licenses
rm -r share/metainfo
rmdir share
cd ..
cp ../../LICENSE .
cp ../../README.md .
cp -r ../../papers papers
rmdir usr
mkdir -p target/furnace.AppDir
make -C ${PWD}/build DESTDIR=${PWD}/target/furnace.AppDir install
pushd target
pushd furnace.AppDir
cp -v usr/share/{icons/hicolor/1024x1024/apps/furnace.png,applications/furnace.desktop} ./
ln -s furnace.png .DirIcon
mv -v usr/share/metainfo/{furnace.appdata,org.tildearrow.furnace.metainfo}.xml
cp -v ../../res/AppRun ./
popd
cd target
tar -zcv -f ../${{ steps.package-identify.outputs.filename }} furnace
if [ '${{ matrix.config.arch }}' == 'armhf' ]; then
../appimagetool-x86_64.AppImage --runtime-file=../runtime-armhf furnace.AppDir
else
../appimagetool-x86_64.AppImage furnace.AppDir
fi
mv Furnace-*.AppImage ../${{ steps.package-identify.outputs.filename }}
popd
- name: Upload artifact
if: ${{ github.repository == 'tildearrow/furnace' && github.ref_name == 'master' }}
uses: actions/upload-artifact@v3.1.1
uses: actions/upload-artifact@v3
with:
name: ${{ steps.package-identify.outputs.id }}
path: ${{ steps.package-identify.outputs.filename }}

7
.gitignore vendored
View File

@ -6,7 +6,6 @@ release/
t/
winbuild/
win32build/
xpbuild/
macbuild/
linuxbuild/
*.swp
@ -16,8 +15,6 @@ test/songs/
test/delta/
test/result/
test/assert_delta
android/local.properties
android/.idea/
android/.gradle/
android/app/build/
android/app/.cxx/
@ -25,7 +22,3 @@ android/app/.cxx/
CMakeSettings.json
CMakePresets.json
extern/imgui_patched/examples/
src/asm/68k/amigatest/*.bin
src/asm/68k/amigatest/player
res/binary_to_compressed_c
res/binary_to_compressed_c.exe

8
.gitmodules vendored
View File

@ -1,7 +1,10 @@
[submodule "extern/Nuked-OPN2"]
path = extern/Nuked-OPN2
url = https://github.com/nukeykt/Nuked-OPN2
[submodule "extern/SDL"]
path = extern/SDL
url = https://github.com/libsdl-org/SDL.git
branch = release-2.28.x
branch = 2.0.22
[submodule "extern/libsndfile"]
path = extern/libsndfile
url = https://github.com/libsndfile/libsndfile.git
@ -12,6 +15,3 @@
[submodule "extern/adpcm"]
path = extern/adpcm
url = https://github.com/superctr/adpcm
[submodule "extern/portaudio"]
path = extern/portaudio
url = https://github.com/PortAudio/portaudio.git

View File

@ -27,7 +27,6 @@ include(TestBigEndian)
if (ANDROID)
set(USE_RTMIDI_DEFAULT OFF)
set(WITH_PORTAUDIO_DEFAULT OFF)
set(USE_BACKWARD_DEFAULT OFF)
find_library(TERMUX rt)
if (TERMUX)
@ -35,7 +34,6 @@ if (ANDROID)
endif()
else()
set(USE_RTMIDI_DEFAULT ON)
set(WITH_PORTAUDIO_DEFAULT ON)
if (WIN32 OR APPLE)
set(USE_BACKWARD_DEFAULT ON)
else()
@ -56,47 +54,21 @@ else()
set(WITH_JACK_DEFAULT OFF)
endif()
set(WITH_RENDER_SDL_DEFAULT ON)
if (APPLE)
set(WITH_RENDER_OPENGL_DEFAULT OFF)
else()
set(WITH_RENDER_OPENGL_DEFAULT ON)
endif()
if (WIN32)
set(WITH_RENDER_DX11_DEFAULT ON)
else()
set(WITH_RENDER_DX11_DEFAULT OFF)
endif()
if (ANDROID)
set(USE_GLES_DEFAULT ON)
else()
set(USE_GLES_DEFAULT OFF)
endif()
option(BUILD_GUI "Build the tracker (disable to build only a headless player)" ${BUILD_GUI_DEFAULT})
option(USE_RTMIDI "Build with MIDI support using RtMidi." ${USE_RTMIDI_DEFAULT})
option(USE_SDL2 "Build with SDL2. Required to build with GUI." ${USE_SDL2_DEFAULT})
option(USE_SNDFILE "Build with libsndfile. Required in order to work with audio files." ${USE_SNDFILE_DEFAULT})
option(USE_BACKWARD "Use backward-cpp to print a backtrace on crash/abort." ${USE_BACKWARD_DEFAULT})
option(WITH_JACK "Whether to build with JACK support. Auto-detects if JACK is available" ${WITH_JACK_DEFAULT})
option(WITH_PORTAUDIO "Whether to build with PortAudio for audio output." ${WITH_PORTAUDIO_DEFAULT})
option(WITH_RENDER_SDL "Whether to build with the SDL_Renderer render backend." ${WITH_RENDER_SDL_DEFAULT})
option(WITH_RENDER_OPENGL "Whether to build with the OpenGL render backend." ${WITH_RENDER_OPENGL_DEFAULT})
option(WITH_RENDER_DX11 "Whether to build with the DirectX 11 render backend." ${WITH_RENDER_DX11_DEFAULT})
option(USE_GLES "Use OpenGL ES for the OpenGL render backend." ${USE_GLES_DEFAULT})
option(SYSTEM_FFTW "Use a system-installed version of FFTW instead of the vendored one" OFF)
option(SYSTEM_FMT "Use a system-installed version of fmt instead of the vendored one" OFF)
option(SYSTEM_LIBSNDFILE "Use a system-installed version of libsndfile instead of the vendored one" OFF)
option(SYSTEM_PORTAUDIO "Use a system-installed version of PortAudio instead of the vendored one" OFF)
option(SYSTEM_RTMIDI "Use a system-installed version of RtMidi 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(SUPPORT_XP "Build a Windows XP-compatible binary" 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)
option(WITH_WAVETABLES "Install wavetables" ON)
set(DEPENDENCIES_INCLUDE_DIRS "")
@ -118,19 +90,12 @@ set(DEPENDENCIES_LIBRARY_DIRS "")
set(DEPENDENCIES_LINK_OPTIONS "")
set(DEPENDENCIES_LEGACY_LDFLAGS "")
if (BUILD_GUI AND WITH_RENDER_SDL)
if (BUILD_GUI)
set(SYSTEM_SDL_MIN_VER 2.0.18)
else()
set(SYSTEM_SDL_MIN_VER 2.0.0)
endif()
if (WIN32)
# support Windows XP
if (SUPPORT_XP)
add_compile_definitions("_WIN32_WINNT=0x0501")
endif()
endif()
list(APPEND DEPENDENCIES_INCLUDE_DIRS "extern/SAASound/include")
list(APPEND DEPENDENCIES_INCLUDE_DIRS "extern/vgsound_emu-modified")
@ -208,27 +173,6 @@ else()
message(STATUS "Not using libsndfile")
endif()
if (WITH_PORTAUDIO)
if (SYSTEM_PORTAUDIO)
find_package(PkgConfig REQUIRED)
pkg_check_modules(PORTAUDIO REQUIRED portaudio)
list(APPEND DEPENDENCIES_INCLUDE_DIRS ${PORTAUDIO_INCLUDE_DIRS})
list(APPEND DEPENDENCIES_COMPILE_OPTIONS ${PORTAUDIO_CFLAGS_OTHER})
list(APPEND DEPENDENCIES_LIBRARIES ${PORTAUDIO_LIBRARIES})
list(APPEND DEPENDENCIES_LIBRARY_DIRS ${PORTAUDIO_LIBRARY_DIRS})
list(APPEND DEPENDENCIES_LINK_OPTIONS ${PORTAUDIO_LDFLAGS_OTHER})
list(APPEND DEPENDENCIES_LEGACY_LDFLAGS ${PORTAUDIO_LDFLAGS})
message(STATUS "Using system-installed PortAudio")
else()
set(PA_BUILD_SHARED_LIBS OFF CACHE BOOL "Build dynamic library" FORCE)
# don't - Furnace has its own implementation
set(PA_USE_JACK OFF CACHE BOOL "Enable support for JACK Audio Connection Kit" FORCE)
add_subdirectory(extern/portaudio EXCLUDE_FROM_ALL)
list(APPEND DEPENDENCIES_LIBRARIES PortAudio)
message(STATUS "Using vendored PortAudio")
endif()
endif()
if (USE_RTMIDI)
if (SYSTEM_RTMIDI)
find_package(PkgConfig REQUIRED)
@ -308,13 +252,6 @@ if (USE_SDL2)
# If you link SDL statically, you also need to define HAVE_LIBC so it builds with the C runtime that your application uses.
# This should probably go in a FAQ.
set(SDL_LIBC ON CACHE BOOL "Tell SDL that we want it to use our C runtime (required for proper static linking)" FORCE)
# https://github.com/tildearrow/furnace/issues/1237
# enabling this will result in SDL finding the Direct3D headers, forcing _WIN32_WINNT to an undesirable value (which makes the Wine headers define GetTickCount64)
if (SUPPORT_XP)
set(SDL_RENDER_D3D OFF CACHE BOOL "Enable the Direct3D render driver" FORCE)
endif()
add_subdirectory(extern/SDL EXCLUDE_FROM_ALL)
list(APPEND DEPENDENCIES_DEFINES HAVE_SDL2)
list(APPEND DEPENDENCIES_INCLUDE_DIRS extern/SDL/include)
@ -327,9 +264,6 @@ if (USE_SDL2)
if (MSVC)
list(APPEND DEPENDENCIES_COMPILE_OPTIONS "/DHAVE_LIBC")
endif()
if (WIN32)
list(APPEND DEPENDENCIES_LIBRARIES SDL2main)
endif()
message(STATUS "Using vendored SDL2")
endif()
else()
@ -339,12 +273,6 @@ else()
endif()
endif()
if (BUILD_GUI)
if (NOT WITH_RENDER_SDL AND NOT WITH_RENDER_OPENGL AND NOT WITH_RENDER_DX11)
message(FATAL_ERROR "No render backends selected!")
endif()
endif()
set(AUDIO_SOURCES
src/audio/abstract.cpp
src/audio/midi.cpp
@ -370,14 +298,6 @@ else()
message(STATUS "Building without JACK support")
endif()
if (WITH_PORTAUDIO)
list(APPEND AUDIO_SOURCES src/audio/pa.cpp)
message(STATUS "Building with PortAudio")
list(APPEND DEPENDENCIES_DEFINES HAVE_PA)
else()
message(STATUS "Building without PortAudio")
endif()
if (USE_RTMIDI)
list(APPEND AUDIO_SOURCES src/audio/rtmidi.cpp)
message(STATUS "Building with RtMidi")
@ -388,7 +308,6 @@ endif()
set(ENGINE_SOURCES
src/log.cpp
src/baseutils.cpp
src/fileutils.cpp
src/utfutils.cpp
@ -432,7 +351,7 @@ extern/adpcm/yma_codec.c
extern/adpcm/ymb_codec.c
extern/adpcm/ymz_codec.c
extern/opn/ym3438.c
extern/Nuked-OPN2/ym3438.c
extern/Nuked-PSG/ympsg.c
extern/opm/opm.c
extern/Nuked-OPLL/opll.c
@ -441,7 +360,6 @@ src/engine/platform/sound/sn76496.cpp
src/engine/platform/sound/ay8910.cpp
src/engine/platform/sound/saa1099.cpp
src/engine/platform/sound/namco.cpp
src/engine/platform/sound/segapcm.cpp
src/engine/platform/sound/gb/apu.c
src/engine/platform/sound/gb/timing.c
src/engine/platform/sound/pce_psg.cpp
@ -476,7 +394,6 @@ src/engine/platform/sound/c64/wave8580_PST.cc
src/engine/platform/sound/c64/wave8580_P_T.cc
src/engine/platform/sound/c64/wave8580__ST.cc
src/engine/platform/sound/c64_fp/array.cpp
src/engine/platform/sound/c64_fp/Dac.cpp
src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp
src/engine/platform/sound/c64_fp/ExternalFilter.cpp
@ -495,8 +412,6 @@ src/engine/platform/sound/c64_fp/WaveformCalculator.cpp
src/engine/platform/sound/c64_fp/WaveformGenerator.cpp
src/engine/platform/sound/c64_fp/resample/SincResampler.cpp
src/engine/platform/sound/c64_d/dsid.c
src/engine/platform/sound/tia/AudioChannel.cpp
src/engine/platform/sound/tia/Audio.cpp
@ -508,9 +423,6 @@ src/engine/platform/sound/ymfm/ymfm_ssg.cpp
src/engine/platform/sound/lynx/Mikey.cpp
src/engine/platform/sound/pokey/mzpokeysnd.c
src/engine/platform/sound/pokey/AltASAP.cpp
src/engine/platform/sound/qsound.c
src/engine/platform/sound/swan.cpp
@ -521,10 +433,6 @@ src/engine/platform/sound/vic20sound.c
src/engine/platform/sound/ymz280b.cpp
src/engine/platform/sound/vsu.cpp
src/engine/platform/sound/t6w28/T6W28_Apu.cpp
src/engine/platform/sound/rf5c68.cpp
src/engine/platform/sound/oki/msm5232.cpp
@ -532,16 +440,6 @@ src/engine/platform/sound/oki/okim6258.cpp
src/engine/platform/sound/snes/SPC_DSP.cpp
src/engine/platform/sound/ga20/iremga20.cpp
src/engine/platform/sound/sm8521.c
src/engine/platform/sound/d65modified.c
src/engine/platform/sound/ted-sound.c
src/engine/platform/sound/c140_c219.c
src/engine/platform/oplAInterface.cpp
src/engine/platform/ym2608Interface.cpp
src/engine/platform/ym2610Interface.cpp
@ -550,17 +448,12 @@ src/engine/blip_buf.c
src/engine/brrUtils.c
src/engine/safeReader.cpp
src/engine/safeWriter.cpp
src/engine/workPool.cpp
src/engine/cmdStream.cpp
src/engine/cmdStreamOps.cpp
src/engine/config.cpp
src/engine/configEngine.cpp
src/engine/dispatchContainer.cpp
src/engine/engine.cpp
src/engine/export.cpp
src/engine/fileOps.cpp
src/engine/fileOpsIns.cpp
src/engine/fileOpsSample.cpp
src/engine/filter.cpp
src/engine/instrument.cpp
src/engine/macroInt.cpp
@ -571,11 +464,9 @@ src/engine/song.cpp
src/engine/sysDef.cpp
src/engine/wavetable.cpp
src/engine/waveSynth.cpp
src/engine/wavOps.cpp
src/engine/vgmOps.cpp
src/engine/zsmOps.cpp
src/engine/zsm.cpp
src/engine/platform/abstract.cpp
src/engine/platform/genesis.cpp
src/engine/platform/genesisext.cpp
@ -610,43 +501,23 @@ src/engine/platform/pcspkr.cpp
src/engine/platform/segapcm.cpp
src/engine/platform/qsound.cpp
src/engine/platform/x1_010.cpp
src/engine/platform/pokey.cpp
src/engine/platform/lynx.cpp
src/engine/platform/su.cpp
src/engine/platform/swan.cpp
src/engine/platform/t6w28.cpp
src/engine/platform/vb.cpp
src/engine/platform/vera.cpp
src/engine/platform/zxbeeper.cpp
src/engine/platform/zxbeeperquadtone.cpp
src/engine/platform/bubsyswsg.cpp
src/engine/platform/n163.cpp
src/engine/platform/pet.cpp
src/engine/platform/pokemini.cpp
src/engine/platform/pong.cpp
src/engine/platform/vic20.cpp
src/engine/platform/vrc6.cpp
src/engine/platform/es5506.cpp
src/engine/platform/scc.cpp
src/engine/platform/ymz280b.cpp
src/engine/platform/namcowsg.cpp
src/engine/platform/rf5c68.cpp
src/engine/platform/snes.cpp
src/engine/platform/k007232.cpp
src/engine/platform/ga20.cpp
src/engine/platform/sm8521.cpp
src/engine/platform/pv1000.cpp
src/engine/platform/k053260.cpp
src/engine/platform/ted.cpp
src/engine/platform/c140.cpp
src/engine/platform/pcmdac.cpp
src/engine/platform/dummy.cpp
src/engine/export/abstract.cpp
src/engine/export/amigaValidation.cpp
src/engine/effect/abstract.cpp
src/engine/effect/dummy.cpp
)
if (USE_SNDFILE)
@ -668,15 +539,12 @@ extern/imgui_patched/imgui.cpp
extern/imgui_patched/imgui_draw.cpp
extern/imgui_patched/imgui_tables.cpp
extern/imgui_patched/imgui_widgets.cpp
extern/imgui_patched/backends/imgui_impl_sdl2.cpp
extern/imgui_patched/backends/imgui_impl_sdlrenderer.cpp
extern/imgui_patched/backends/imgui_impl_sdl.cpp
extern/imgui_patched/misc/cpp/imgui_stdlib.cpp
extern/igfd/ImGuiFileDialog.cpp
src/gui/plot_nolerp.cpp
src/gui/render.cpp
src/gui/render/abstract.cpp
src/gui/font_exo.cpp
src/gui/font_liberationSans.cpp
src/gui/font_mononoki.cpp
@ -686,30 +554,16 @@ src/gui/font_proggyClean.cpp
src/gui/font_ptMono.cpp
src/gui/font_unifont.cpp
src/gui/font_icon.cpp
src/gui/font_furicon.cpp
src/gui/fonts.cpp
src/gui/image_icon.cpp
src/gui/image_talogo.cpp
src/gui/image_tachip.cpp
src/gui/image_logo.cpp
src/gui/image_wordmark.cpp
src/gui/image_introbg.cpp
src/gui/image_pat.cpp
src/gui/image.cpp
src/gui/debug.cpp
src/gui/fileDialog.cpp
src/gui/intConst.cpp
src/gui/guiConst.cpp
src/gui/introTune.cpp
src/gui/about.cpp
src/gui/channels.cpp
src/gui/chanOsc.cpp
src/gui/clock.cpp
src/gui/compatFlags.cpp
src/gui/cursor.cpp
src/gui/dataList.cpp
@ -719,11 +573,8 @@ src/gui/editing.cpp
src/gui/editControls.cpp
src/gui/effectList.cpp
src/gui/findReplace.cpp
src/gui/fmPreview.cpp
src/gui/gradient.cpp
src/gui/grooves.cpp
src/gui/insEdit.cpp
src/gui/intro.cpp
src/gui/log.cpp
src/gui/mixer.cpp
src/gui/midiMap.cpp
@ -736,79 +587,38 @@ src/gui/piano.cpp
src/gui/presets.cpp
src/gui/regView.cpp
src/gui/sampleEdit.cpp
src/gui/scaling.cpp
src/gui/settings.cpp
src/gui/songInfo.cpp
src/gui/songNotes.cpp
src/gui/speed.cpp
src/gui/spoiler.cpp
src/gui/stats.cpp
src/gui/subSongs.cpp
src/gui/sysConf.cpp
src/gui/sysEx.cpp
src/gui/sysManager.cpp
src/gui/sysPartNumber.cpp
src/gui/sysPicker.cpp
src/gui/tutorial.cpp
src/gui/util.cpp
src/gui/waveEdit.cpp
src/gui/volMeter.cpp
src/gui/gui.cpp
)
if (WIN32 AND NOT SUPPORT_XP)
if (WIN32 OR APPLE)
list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_common.cpp)
endif()
if (WIN32)
list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_win.cpp)
endif()
if (APPLE)
list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_common.cpp)
list(APPEND GUI_SOURCES src/gui/macstuff.m)
list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_cocoa.mm)
endif()
if (WITH_RENDER_SDL)
list(APPEND GUI_SOURCES src/gui/render/renderSDL.cpp)
list(APPEND GUI_SOURCES extern/imgui_patched/backends/imgui_impl_sdlrenderer2.cpp)
list(APPEND DEPENDENCIES_DEFINES HAVE_RENDER_SDL)
message(STATUS "UI render backend: SDL_Renderer")
endif()
if (WITH_RENDER_OPENGL)
list(APPEND GUI_SOURCES src/gui/render/renderGL.cpp)
list(APPEND GUI_SOURCES extern/imgui_patched/backends/imgui_impl_opengl3.cpp)
list(APPEND DEPENDENCIES_DEFINES HAVE_RENDER_GL)
if (USE_GLES)
list(APPEND DEPENDENCIES_DEFINES USE_GLES)
list(APPEND DEPENDENCIES_DEFINES IMGUI_IMPL_OPENGL_ES2)
endif()
if (WIN32)
list(APPEND DEPENDENCIES_LIBRARIES opengl32)
elseif(USE_GLES)
list(APPEND DEPENDENCIES_LIBRARIES GLESv2)
else()
list(APPEND DEPENDENCIES_LIBRARIES GL)
endif()
message(STATUS "UI render backend: OpenGL")
endif()
if (WITH_RENDER_DX11)
if (WIN32)
if (SUPPORT_XP)
message(FATAL_ERROR "SUPPORT_XP is on. cannot enable DirectX 11 backend.")
else()
list(APPEND GUI_SOURCES src/gui/render/renderDX11.cpp)
list(APPEND GUI_SOURCES extern/imgui_patched/backends/imgui_impl_dx11.cpp)
list(APPEND DEPENDENCIES_DEFINES HAVE_RENDER_DX11)
list(APPEND DEPENDENCIES_LIBRARIES d3d11)
message(STATUS "UI render backend: DirectX 11")
endif()
else()
message(FATAL_ERROR "DirectX 11 render backend only for Windows!")
endif()
endif()
if (NOT WIN32 AND NOT APPLE)
list(APPEND GUI_SOURCES src/gui/icon.c)
CHECK_INCLUDE_FILE(sys/io.h SYS_IO_FOUND)
CHECK_INCLUDE_FILE(linux/input.h LINUX_INPUT_FOUND)
CHECK_INCLUDE_FILE(linux/kd.h LINUX_KD_FOUND)
@ -858,6 +668,7 @@ if (BUILD_GUI)
list(APPEND USED_SOURCES ${GUI_SOURCES})
list(APPEND DEPENDENCIES_INCLUDE_DIRS
extern/imgui_patched
extern/imgui_conf
extern/imgui_patched/backends
extern/IconFontCppHeaders
extern/igfd
@ -878,11 +689,11 @@ if (WIN32)
if (NOT MSVC)
list(APPEND DEPENDENCIES_LIBRARIES -static)
endif()
elseif (APPLE)
endif()
if (APPLE)
find_library(COCOA Cocoa REQUIRED)
list(APPEND DEPENDENCIES_LIBRARIES ${COCOA})
else()
list(APPEND DEPENDENCIES_LIBRARIES dl)
endif()
if (NOT MSVC)
@ -914,22 +725,22 @@ endif()
string(REPLACE ";" " " WARNING_FLAGS_STRING "${WARNING_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS_STRING}")
if (WARNINGS_ARE_ERRORS)
message(STATUS
message(WARNING
"Treating all warnings in furnace's C++ code as errors! "
"Please report any errors you encounter on the bug tracker."
)
endif()
if(ANDROID AND NOT TERMUX)
add_library(furnace SHARED ${USED_SOURCES})
elseif(WIN32)
if (MSVC)
add_executable(furnace WIN32 ${USED_SOURCES})
elseif(ANDROID AND NOT TERMUX)
add_library(furnace SHARED ${USED_SOURCES})
else()
add_executable(furnace ${USED_SOURCES})
endif()
target_include_directories(furnace SYSTEM PRIVATE ${DEPENDENCIES_INCLUDE_DIRS})
target_compile_definitions(furnace PRIVATE ${DEPENDENCIES_DEFINES})
target_compile_definitions(furnace PRIVATE ${DEPENDENCIES_DEFINES} IMGUI_USER_CONFIG="imconfig_fur.h")
target_compile_options(furnace PRIVATE ${DEPENDENCIES_COMPILE_OPTIONS})
target_link_libraries(furnace PRIVATE ${DEPENDENCIES_LIBRARIES})
if (PKG_CONFIG_FOUND AND (SYSTEM_FMT OR SYSTEM_LIBSNDFILE OR SYSTEM_ZLIB OR SYSTEM_SDL2 OR SYSTEM_RTMIDI OR WITH_JACK))
@ -951,8 +762,7 @@ if (NOT ANDROID OR TERMUX)
install(TARGETS furnace RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(FILES res/furnace.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
install(FILES res/furnace.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
install(DIRECTORY doc DESTINATION ${CMAKE_INSTALL_DOCDIR})
install(DIRECTORY papers DESTINATION ${CMAKE_INSTALL_DOCDIR}/other)
install(DIRECTORY papers DESTINATION ${CMAKE_INSTALL_DOCDIR})
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/furnace)
if (WITH_DEMOS)
install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
@ -960,9 +770,6 @@ if (NOT ANDROID OR TERMUX)
if (WITH_INSTRUMENTS)
install(DIRECTORY instruments DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
endif()
if (WITH_WAVETABLES)
install(DIRECTORY wavetables DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
endif()
foreach(num 16 32 64 128 256 512)
set(res ${num}x${num})
install(FILES res/icon.iconset/icon_${res}.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${res}/apps)

View File

@ -29,7 +29,6 @@ the coding style is described here:
- no spaces in operations except for `||` and `&&`
- no space between variable name and assignment
- space between macro in string literals
- space after comment delimiter
- C++ pointer style: `void* variable` rather than `void *variable`
- indent switch cases
- preprocessor directives not intended
@ -77,21 +76,16 @@ additional guidelines:
- I will run a test suite to make sure this is the case.
- if something breaks, you might want to add a compatibility flag (this requires changing the format though).
- do not use `#pragma once`.
- do not memcmp() structs.
- on a switch block, **always** put `default` last and not in any other position.
- I have fear of some C/C++ compilers ignoring the rest of cases upon hitting default.
## Do NOT Force-Push after submitting Pull Request
if you do so, your pull request will be closed.
## Demo Songs
just put your demo song in `demos/`! be noted there are some guidelines:
- avoid Nintendo song covers.
- avoid big label song covers.
- low effort compositions/covers may not be accepted at all.
- avoid poor quality songs.
# Finishing

179
README.md
View File

@ -1,19 +1,17 @@
# Furnace (chiptune tracker)
# Furnace Tracker
![screenshot](papers/screenshot3.png)
![screenshot](papers/screenshot2.png)
the biggest multi-system chiptune tracker ever made!
[downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [Unix/Linux packages](#packages) | [FAQ](#frequently-asked-questions)
[downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [FAQ](#frequently-asked-questions)
---
## downloads
check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux.
check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage).
for other operating systems, you may [build the source](#developer-info).
[see here](https://nightly.link/tildearrow/furnace/workflows/build/master) for the latest unstable build.
[see here](https://nightly.link/tildearrow/furnace/workflows/build/master) for unstable developer builds.
## features
@ -38,20 +36,13 @@ for other operating systems, you may [build the source](#developer-info).
- TI SN76489 used in Sega Master System and BBC Micro
- PC Speaker
- Philips SAA1099 used in SAM Coupé
- OKI MSM5232 used in some arcade boards
- sample chips:
- SNES
- Amiga
- SegaPCM - all 16 channels
- Capcom QSound
- Yamaha YMZ280B (PCMD8)
- Ricoh RF5C68 used in Sega CD and FM Towns
- OKI MSM6258 and MSM6295
- Konami K007232
- Konami K053260
- Irem GA20
- Ensoniq ES5506
- Namco C140
- wavetable chips:
- HuC6280 used in PC Engine
- Konami Bubble System WSG
@ -59,7 +50,6 @@ for other operating systems, you may [build the source](#developer-info).
- Namco arcade chips (WSG/C15/C30)
- WonderSwan
- Seta/Allumer X1-010
- Sharp SM8521 used in Tiger Game.com
- NES (Ricoh 2A03/2A07), with additional expansion sound support:
- Konami VRC6
- Konami VRC7
@ -70,52 +60,42 @@ for other operating systems, you may [build the source](#developer-info).
- Family Noraebang (OPLL)
- SID (6581/8580) used in Commodore 64
- Mikey used in Atari Lynx
- ZX Spectrum beeper
- SFX-like engine
- QuadTone engine
- Pokémon Mini
- ZX Spectrum beeper (SFX-like engine)
- Commodore PET
- TED used in Commodore Plus/4
- Casio PV-1000
- TIA used in Atari 2600
- POKEY used in Atari 8-bit computers
- Game Boy
- Virtual Boy
- modern/fantasy:
- Commander X16 VERA
- tildearrow Sound Unit
- Generic PCM DAC
- mix and match sound chips!
- over 200 ready to use presets from computers, game consoles and arcade boards...
- ...or create your own - up to 32 of them or a total of 128 channels!
- DefleMask compatibility
- loads .dmf modules from all versions (beta 1 to 1.1.9)
- loads .dmf modules from all versions (beta 1 to 1.1.3)
- saves .dmf modules - both modern and legacy
- Furnace doubles as a module downgrader
- loads/saves .dmp instruments and .dmw wavetables as well
- clean-room design (guesswork and ABX tests only, no decompilation involved)
- some bug/quirk implementation for increased playback accuracy through compatibility flags
- bug/quirk implementation for increased playback accuracy through compatibility flags
- VGM export
- ZSM export for Commander X16
- modular layout that you may adapt to your needs
- audio file export - entire song, per chip or per channel
- audio file export - entire song, per system or per channel
- quality emulation cores (Nuked, MAME, SameBoy, Mednafen PCE, NSFplay, puNES, reSID, Stella, SAASound, vgsound_emu and ymfm)
- wavetable synthesizer
- available on wavetable chips
- create complex sounds with ease - provide up to two wavetables, select and effect and let go!
- MIDI input support
- [Fractal Sound](https://gitlab.com/Natsumi/Fractal-Sound) support!
- the game-ready Sega Genesis/Mega Drive sound driver!
- compose your songs in Furnace using the Fractal Sound presets and then use them in your games with Fractal!
- additional features:
- FM macros!
- negative octaves
- advanced arp macros
- arbitrary pitch samples
- sample loop points
- SSG envelopes and ADPCM-B in Neo Geo
- pitchable OPLL drums
- full duty/cutoff range in C64
- full 16-channel SegaPCM
- ability to change tempo mid-song
- decimal tempo/tick rate
- multiple sub-songs in a module
- per-channel oscilloscope with waveform centering
- built-in sample editor
@ -126,20 +106,18 @@ for other operating systems, you may [build the source](#developer-info).
---
# quick references
- **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, the [official Revolt](https://rvlt.gg/GRPS6tmc) or the [official Discord server](https://discord.gg/EfrwT2wq7z).
- **help**: check out the [documentation](doc/README.md). it's about 90% complete.
- **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z).
- **help**: check out the [documentation](papers/doc/README.md). it's mostly incomplete, but has details on effects.
## packages
## unofficial packages
[![Packaging status](https://repology.org/badge/vertical-allrepos/furnace.svg)](https://repology.org/project/furnace/versions)
[![Packaging status](https://repology.org/badge/tiny-repos/furnace.svg)](https://repology.org/project/furnace/versions)
some people have provided packages for Unix/Unix-like distributions. here's a list.
- **Arch Linux**: [furnace](https://archlinux.org/packages/extra/x86_64/furnace/) is in the official repositories.
- **FreeBSD**: [a package in ports](https://www.freshports.org/audio/furnace/) is available courtesy of ehaupt (warning: 0.5.8!).
- **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608.
- **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari.
- **Void Linux**: [furnace](https://github.com/void-linux/void-packages/tree/master/srcpkgs/furnace) is available in the official repository.
- **Arch Linux**: [furnace-git is in the AUR.](https://aur.archlinux.org/packages/furnace-git) thank you Essem!
- **FreeBSD**: [a package in ports](https://www.freshports.org/audio/furnace/) is available courtesy of ehaupt.
- **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608.
- **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari.
---
# developer info
@ -153,9 +131,7 @@ if you can't download these artifacts (because GitHub requires you to be logged
## dependencies
- CMake
- Git (for cloning the repository)
- JACK (optional, macOS/Linux only)
- a C/C++ compiler (e.g. Visual Studio or MinGW on Windows, Xcode (the command-line tools are enough) on macOS or GCC on Linux)
if building under Windows or macOS, no additional dependencies are required.
otherwise, you may also need the following:
@ -164,7 +140,6 @@ otherwise, you may also need the following:
- libx11
- libasound
- libGL
- any other libraries which may be used by SDL
some Linux distributions (e.g. Ubuntu or openSUSE) will require you to install the `-dev` versions of these.
@ -193,32 +168,10 @@ from the developer tools command prompt:
mkdir build
cd build
cmake ..
```
then open the solution file in Visual Studio and build.
alternatively, do:
```
msbuild ALL_BUILD.vcxproj
```
### Windows using MinGW
setting up MinGW is a bit more complicated. two benefits are a faster, hotter Furnace, and Windows XP support.
however, one huge drawback is lack of backtrace support, so you'll have to use gdb when diagnosing a crash.
```
mkdir build
cd build
cmake -G "MinGW Makefiles" ..
mingw32-make
```
you may use "MSYS Makefiles" instead, depending on how you installed MinGW.
### macOS, Linux and other Unix/Unix-like
### macOS and Linux
```
mkdir build
@ -226,16 +179,7 @@ cd build
cmake ..
make
```
on macOS you may do the following instead:
```
mkdir build
cd build
cmake -G Xcode ..
```
...and then load the project on Xcode or type `xcodebuild`.
Alternatively, build scripts are provided in the `scripts/` folder in the root of the repository.
### CMake options
@ -252,34 +196,18 @@ Available options:
| `USE_SNDFILE` | `ON` | Build with libsndfile (required in order to work with audio files) |
| `USE_BACKWARD` | `ON` | Use backward-cpp to print a backtrace on crash/abort |
| `WITH_JACK` | `ON` if system-installed JACK detected, otherwise `OFF` | Whether to build with JACK support. Auto-detects if JACK is available |
| `WITH_PORTAUDIO` | `ON` | Whether to build with PortAudio. |
| `SYSTEM_FFTW` | `OFF` | Use a system-installed version of FFTW instead of the vendored one |
| `SYSTEM_FMT` | `OFF` | Use a system-installed version of fmt instead of the vendored one |
| `SYSTEM_LIBSNDFILE` | `OFF` | Use a system-installed version of libsndfile instead of the vendored one |
| `SYSTEM_RTMIDI` | `OFF` | Use a system-installed version of RtMidi instead of the vendored one |
| `SYSTEM_ZLIB` | `OFF` | Use a system-installed version of zlib instead of the vendored one |
| `SYSTEM_SDL2` | `OFF` | Use a system-installed version of SDL2 instead of the vendored one |
| `SUPPORT_XP` | `OFF` | Build a Windows XP-compatible binary |
| `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` |
| `WITH_WAVETABLES` | `ON` | Install wavetables on `make install` |
## CMake Error
if it says something about a missing subdirectory in `extern`, then either:
1. you didn't set up submodules, or
2. you downloaded the source as a .zip or .tar.gz. don't do this.
if 1, you may run `git submodule update --init --recursive`. this will initialize submodules.
if 2, clone this repo.
## console usage
(note: if on Windows, type `furnace.exe` instead, or `Debug\furnace.exe` on MSVC)
```
./furnace
```
@ -298,60 +226,58 @@ this will play a compatible file.
this will play a compatible file and enable the commands view.
**note that console mode may not work correctly on Windows. you may have to quit using the Task Manager.**
**note that these commands only actually work in Linux environments. on other command lines, such as Windows' Command Prompt, or MacOS Terminal, it may not work correctly.**
---
# frequently asked questions
> woah! 50 sound chips?! I can't believe it!
yup, it's real.
> where's the manual?
see [papers/](papers/doc/README.md). it's kind of incomplete, but at least the systems (sound chips) section is there.
> it doesn't open under macOS!
this is due to Apple's application signing policy. a workaround is to right click on the Furnace app icon and select Open.
> it says "Furnace" is damaged and can't be opened!
> how do I use C64 absolute filter/duty?
**as of Monterey, this workaround no longer works (especially on ARM).** yeah, Apple has decided to be strict on the matter.
if you happen to be on that version (or later), use this workaround instead (on a Terminal):
on Instrument Editor in the C64 tab there are two options to toggle these.
also provided are two effects:
```
xattr -d com.apple.quarantine /path/to/Furnace.app
```
- `3xxx`: set fine duty.
- `4xxx`: set fine cutoff. `xxx` range is 000-7ff.
additionally, you can change the cutoff and/or duty as a macro inside an instrument by clicking the `absolute cutoff macro` and/or `absolute duty macro` checkbox at the bottom of the instrument. (for the filter, you also need to click the checkbox that says `volume macro is cutoff macro`.)
(replace /path/to/ with the path where Furnace.app is located)
> how do I use PCM on a PCM-capable system?
you may need to log out and/or reboot after doing this.
two possibilities:
- the recommended way is by creating the "Sample" type instrument and assigning a sample to it.
- otherwise you may employ the DefleMask-compatible method, using `17xx` effect.
> where's the manual?
> my .dmf song sounds very odd at a certain point
it is in [doc/](doc/README.md).
file a bug report. use the Issues page. it's probably another playback inaccuracy.
> is there a tutorial?
> my .dmf song sounds correct, but it doesn't in DefleMask
sadly, the in-program tutorial isn't ready yet. however, [a video tutorial is available on YouTube](https://youtube.com/playlist?list=PLCELB6AsTZUnwv0PC5AAGHjvg47F44YQ1), made by Spinning Square Waves.
> I've lost my song!
Furnace keeps backups of the songs you've worked on before. go to **file > restore backup**.
> .spc export?
**not yet!** coming in 0.7 though, eventually...
> ROM export?
**not yet!** coming in 0.7 though, eventually...
> my .dmf song sounds odd at a certain point
Furnace's .dmf compatibility isn't perfect and it's mostly because DefleMask does things different.
file a bug report **here**. it still is a playback inaccuracy.
> my song sounds terrible after saving as .dmf!
you should only save as .dmf if you're really sure, because the DefleMask format has several limitations. save in Furnace song format instead (.fur).
the DefleMask format has several limitations. save in Furnace song format instead (.fur).
> how do I solo channels?
right click on the channel name.
---
# footnotes
copyright (C) 2021-2023 tildearrow and contributors.
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.
@ -360,5 +286,4 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY
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.
Furnace is NOT affiliated with Delek or DefleMask in any form, regardless of its ability to load and save the .dmf, .dmp and .dmw file formats.
additionally, Furnace does not intend to replace DefleMask, nor any other program.
despite the fact this program works with the .dmf file format, it is NOT affiliated with Delek or DefleMask in any way, nor it is a replacement for the original program.

19
TODO.md
View File

@ -1,12 +1,11 @@
# to-do for 0.6pre7
# to-do for 0.6pre2
- tutorial?
- ease-of-use improvements... ideas:
- preset compat flags
- maybe reduced set of presets for the sake of simplicity
- a more preferable highlight/drag system
- some speed/intuitive workflow improvements that go a long way
- make .pdf manual out of doc/
- break compatibility if it relieves complexity
- multi-key binds
- POKEY
- Pokémon Mini
- Virtual Boy
- T6W28
- (maybe) YM2612 CSM (no DualPCM)
- port presets to new format
- bug fixes
- (maybe) ExtCh FM macros?
- (maybe) advanced linear arpeggio? (run arp+slide simultaneously)

View File

@ -15,11 +15,11 @@ android {
}
minSdkVersion 21
targetSdkVersion 26
versionCode 169
versionName "0.6pre9"
versionCode 113
versionName "dev113"
externalNativeBuild {
cmake {
arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static", "-DWARNINGS_ARE_ERRORS=ON"
arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static"
// abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
abiFilters 'arm64-v8a'
}

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.tildearrow.furnace"
android:versionCode="169"
android:versionName="0.6pre9"
android:versionCode="113"
android:versionName="dev113"
android:installLocation="auto">
<!-- OpenGL ES 2.0 -->

View File

@ -186,7 +186,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
// Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead
// of TRANSPORT_LE. Let's force ourselves to connect low energy.
private BluetoothGatt connectGatt(boolean managed) {
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
if (Build.VERSION.SDK_INT >= 23) {
try {
return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);
} catch (Exception e) {
@ -429,7 +429,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
}
});
}
}
}
else if (newState == 0) {
mIsConnected = false;
}

View File

@ -170,7 +170,7 @@ public class HIDDeviceManager {
Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol());
Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount());
// Get endpoint details
// Get endpoint details
for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
{
UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
@ -248,11 +248,7 @@ public class HIDDeviceManager {
0x1689, // Razer Onza
0x1949, // Lab126, Inc.
0x1bad, // Harmonix
0x20d6, // PowerA
0x24c6, // PowerA
0x2c22, // Qanba
0x2dc8, // 8BitDo
0x9886, // ASTRO Gaming
};
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
@ -273,16 +269,12 @@ public class HIDDeviceManager {
final int XB1_IFACE_SUBCLASS = 71;
final int XB1_IFACE_PROTOCOL = 208;
final int[] SUPPORTED_VENDORS = {
0x044f, // Thrustmaster
0x045e, // Microsoft
0x0738, // Mad Catz
0x0e6f, // PDP
0x0f0d, // Hori
0x10f5, // Turtle Beach
0x1532, // Razer Wildcat
0x20d6, // PowerA
0x24c6, // PowerA
0x2dc8, // 8BitDo
0x2e24, // Hyperkin
};
@ -357,13 +349,13 @@ public class HIDDeviceManager {
private void initializeBluetooth() {
Log.d(TAG, "Initializing Bluetooth");
if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ &&
if (Build.VERSION.SDK_INT <= 30 &&
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH");
return;
}
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) {
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18)) {
Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE");
return;
}
@ -528,7 +520,7 @@ public class HIDDeviceManager {
for (HIDDevice device : mDevicesById.values()) {
device.setFrozen(frozen);
}
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
@ -577,7 +569,7 @@ public class HIDDeviceManager {
try {
final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31
int flags;
if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
if (Build.VERSION.SDK_INT >= 31) {
flags = FLAG_MUTABLE;
} else {
flags = 0;

View File

@ -52,7 +52,7 @@ class HIDDeviceUSB implements HIDDevice {
@Override
public String getSerialNumber() {
String result = null;
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
if (Build.VERSION.SDK_INT >= 21) {
try {
result = mDevice.getSerialNumber();
}
@ -74,7 +74,7 @@ class HIDDeviceUSB implements HIDDevice {
@Override
public String getManufacturerName() {
String result = null;
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
if (Build.VERSION.SDK_INT >= 21) {
result = mDevice.getManufacturerName();
}
if (result == null) {
@ -86,7 +86,7 @@ class HIDDeviceUSB implements HIDDevice {
@Override
public String getProductName() {
String result = null;
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
if (Build.VERSION.SDK_INT >= 21) {
result = mDevice.getProductName();
}
if (result == null) {

View File

@ -29,7 +29,6 @@ public class SDL {
// This function stores the current activity (SDL or not)
public static void setContext(Context context) {
SDLAudioManager.setContext(context);
mContext = context;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,5 @@
package org.libsdl.app;
import android.content.Context;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
@ -11,67 +8,34 @@ import android.media.MediaRecorder;
import android.os.Build;
import android.util.Log;
import java.util.Arrays;
public class SDLAudioManager {
public class SDLAudioManager
{
protected static final String TAG = "SDLAudio";
protected static AudioTrack mAudioTrack;
protected static AudioRecord mAudioRecord;
protected static Context mContext;
private static final int[] NO_DEVICES = {};
private static AudioDeviceCallback mAudioDeviceCallback;
public static void initialize() {
mAudioTrack = null;
mAudioRecord = null;
mAudioDeviceCallback = null;
if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)
{
mAudioDeviceCallback = new AudioDeviceCallback() {
@Override
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
Arrays.stream(addedDevices).forEach(deviceInfo -> addAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
}
@Override
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
Arrays.stream(removedDevices).forEach(deviceInfo -> removeAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
}
};
}
}
public static void setContext(Context context) {
mContext = context;
if (context != null) {
registerAudioDeviceCallback();
}
}
public static void release(Context context) {
unregisterAudioDeviceCallback(context);
}
// Audio
protected static String getAudioFormatString(int audioFormat) {
switch (audioFormat) {
case AudioFormat.ENCODING_PCM_8BIT:
return "8-bit";
case AudioFormat.ENCODING_PCM_16BIT:
return "16-bit";
case AudioFormat.ENCODING_PCM_FLOAT:
return "float";
default:
return Integer.toString(audioFormat);
case AudioFormat.ENCODING_PCM_8BIT:
return "8-bit";
case AudioFormat.ENCODING_PCM_16BIT:
return "16-bit";
case AudioFormat.ENCODING_PCM_FLOAT:
return "float";
default:
return Integer.toString(audioFormat);
}
}
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
int channelConfig;
int sampleSize;
int frameSize;
@ -79,14 +43,14 @@ public class SDLAudioManager {
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz");
/* On older devices let's use known good settings */
if (Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
if (Build.VERSION.SDK_INT < 21) {
if (desiredChannels > 2) {
desiredChannels = 2;
}
}
/* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */
if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) {
if (Build.VERSION.SDK_INT < 22) {
if (sampleRate < 8000) {
sampleRate = 8000;
} else if (sampleRate > 48000) {
@ -95,7 +59,7 @@ public class SDLAudioManager {
}
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */);
int minSDKVersion = (isCapture ? 23 : 21);
if (Build.VERSION.SDK_INT < minSDKVersion) {
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
}
@ -156,7 +120,7 @@ public class SDLAudioManager {
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
break;
case 8:
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
if (Build.VERSION.SDK_INT >= 23) {
channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
} else {
Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround");
@ -237,10 +201,6 @@ public class SDLAudioManager {
return null;
}
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId));
}
mAudioRecord.startRecording();
}
@ -264,10 +224,6 @@ public class SDLAudioManager {
return null;
}
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId));
}
mAudioTrack.play();
}
@ -282,73 +238,11 @@ public class SDLAudioManager {
return results;
}
private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
.findFirst()
.orElse(null);
} else {
return null;
}
}
private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
.findFirst()
.orElse(null);
} else {
return null;
}
}
private static void registerAudioDeviceCallback() {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null);
}
}
private static void unregisterAudioDeviceCallback(Context context) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);
}
}
/**
* This method is called by SDL using JNI.
*/
public static int[] getAudioOutputDevices() {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
} else {
return NO_DEVICES;
}
}
/**
* This method is called by SDL using JNI.
*/
public static int[] getAudioInputDevices() {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
} else {
return NO_DEVICES;
}
}
/**
* This method is called by SDL using JNI.
*/
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames);
}
/**
@ -360,11 +254,6 @@ public class SDLAudioManager {
return;
}
if (android.os.Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
Log.e(TAG, "Attempted to make an incompatible audio call with uninitialized audio! (floating-point output is supported since Android 5.0 Lollipop)");
return;
}
for (int i = 0; i < buffer.length;) {
int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);
if (result > 0) {
@ -437,22 +326,18 @@ public class SDLAudioManager {
/**
* This method is called by SDL using JNI.
*/
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames);
}
/** This method is called by SDL using JNI. */
public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
return 0;
} else {
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
}
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
}
/** This method is called by SDL using JNI. */
public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
if (Build.VERSION.SDK_INT < 23) {
return mAudioRecord.read(buffer, 0, buffer.length);
} else {
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
@ -461,7 +346,7 @@ public class SDLAudioManager {
/** This method is called by SDL using JNI. */
public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
if (Build.VERSION.SDK_INT < 23) {
return mAudioRecord.read(buffer, 0, buffer.length);
} else {
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
@ -506,9 +391,4 @@ public class SDLAudioManager {
}
public static native int nativeSetupJNI();
public static native void removeAudioDevice(boolean isCapture, int deviceId);
public static native void addAudioDevice(boolean isCapture, int deviceId);
}

View File

@ -24,7 +24,7 @@ public class SDLControllerManager
public static native int nativeAddJoystick(int device_id, String name, String desc,
int vendor_id, int product_id,
boolean is_accelerometer, int button_mask,
int naxes, int axis_mask, int nhats, int nballs);
int naxes, int nhats, int nballs);
public static native int nativeRemoveJoystick(int device_id);
public static native int nativeAddHaptic(int device_id, String name);
public static native int nativeRemoveHaptic(int device_id);
@ -42,7 +42,7 @@ public class SDLControllerManager
public static void initialize() {
if (mJoystickHandler == null) {
if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {
if (Build.VERSION.SDK_INT >= 19) {
mJoystickHandler = new SDLJoystickHandler_API19();
} else {
mJoystickHandler = new SDLJoystickHandler_API16();
@ -50,7 +50,7 @@ public class SDLControllerManager
}
if (mHapticHandler == null) {
if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {
if (Build.VERSION.SDK_INT >= 26) {
mHapticHandler = new SDLHapticHandler_API26();
} else {
mHapticHandler = new SDLHapticHandler();
@ -168,32 +168,6 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
arg1Axis = MotionEvent.AXIS_GAS;
}
// Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ.
// This is because the usual pairing are:
// - AXIS_X + AXIS_Y (left stick).
// - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers).
// - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers).
// This sorts the axes in the above order, which tends to be correct
// for Xbox-ish game pads that have the right stick on RX/RY and the
// triggers on Z/RZ.
//
// Gamepads that don't have AXIS_Z/AXIS_RZ but use
// AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this.
//
// References:
// - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input
// - https://www.kernel.org/doc/html/latest/input/gamepad.html
if (arg0Axis == MotionEvent.AXIS_Z) {
arg0Axis = MotionEvent.AXIS_RZ - 1;
} else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) {
--arg0Axis;
}
if (arg1Axis == MotionEvent.AXIS_Z) {
arg1Axis = MotionEvent.AXIS_RZ - 1;
} else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) {
--arg1Axis;
}
return arg0Axis - arg1Axis;
}
}
@ -236,7 +210,7 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
mJoysticks.add(joystick);
SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
getVendorId(joystickDevice), getProductId(joystickDevice), false,
getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, 0);
getButtonMask(joystickDevice), joystick.axes.size(), joystick.hats.size()/2, 0);
}
}
}
@ -281,21 +255,23 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
@Override
public boolean handleMotionEvent(MotionEvent event) {
int actionPointerIndex = event.getActionIndex();
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_MOVE) {
SDLJoystick joystick = getJoystick(event.getDeviceId());
if (joystick != null) {
for (int i = 0; i < joystick.axes.size(); i++) {
InputDevice.MotionRange range = joystick.axes.get(i);
/* Normalize the value to -1...1 */
float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
SDLControllerManager.onNativeJoy(joystick.device_id, i, value);
}
for (int i = 0; i < joystick.hats.size() / 2; i++) {
int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));
int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));
SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);
if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
int actionPointerIndex = event.getActionIndex();
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_MOVE) {
SDLJoystick joystick = getJoystick(event.getDeviceId());
if (joystick != null) {
for (int i = 0; i < joystick.axes.size(); i++) {
InputDevice.MotionRange range = joystick.axes.get(i);
/* Normalize the value to -1...1 */
float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
SDLControllerManager.onNativeJoy(joystick.device_id, i, value);
}
for (int i = 0; i < joystick.hats.size() / 2; i++) {
int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));
int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));
SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);
}
}
}
}
@ -317,9 +293,6 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
public int getVendorId(InputDevice joystickDevice) {
return 0;
}
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
return -1;
}
public int getButtonMask(InputDevice joystickDevice) {
return -1;
}
@ -337,43 +310,6 @@ class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
return joystickDevice.getVendorId();
}
@Override
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
// For compatibility, keep computing the axis mask like before,
// only really distinguishing 2, 4 and 6 axes.
int axis_mask = 0;
if (ranges.size() >= 2) {
// ((1 << SDL_GAMEPAD_AXIS_LEFTX) | (1 << SDL_GAMEPAD_AXIS_LEFTY))
axis_mask |= 0x0003;
}
if (ranges.size() >= 4) {
// ((1 << SDL_GAMEPAD_AXIS_RIGHTX) | (1 << SDL_GAMEPAD_AXIS_RIGHTY))
axis_mask |= 0x000c;
}
if (ranges.size() >= 6) {
// ((1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))
axis_mask |= 0x0030;
}
// Also add an indicator bit for whether the sorting order has changed.
// This serves to disable outdated gamecontrollerdb.txt mappings.
boolean have_z = false;
boolean have_past_z_before_rz = false;
for (InputDevice.MotionRange range : ranges) {
int axis = range.getAxis();
if (axis == MotionEvent.AXIS_Z) {
have_z = true;
} else if (axis > MotionEvent.AXIS_Z && axis < MotionEvent.AXIS_RZ) {
have_past_z_before_rz = true;
}
}
if (have_z && have_past_z_before_rz) {
// If both these exist, the compare() function changed sorting order.
// Set a bit to indicate this fact.
axis_mask |= 0x8000;
}
return axis_mask;
}
@Override
public int getButtonMask(InputDevice joystickDevice) {
int button_mask = 0;
@ -626,6 +562,8 @@ class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
switch ( event.getSource() ) {
case InputDevice.SOURCE_JOYSTICK:
case InputDevice.SOURCE_GAMEPAD:
case InputDevice.SOURCE_DPAD:
return SDLControllerManager.handleJoystickMotionEvent(event);
case InputDevice.SOURCE_MOUSE:
@ -755,6 +693,8 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
switch ( event.getSource() ) {
case InputDevice.SOURCE_JOYSTICK:
case InputDevice.SOURCE_GAMEPAD:
case InputDevice.SOURCE_DPAD:
return SDLControllerManager.handleJoystickMotionEvent(event);
case InputDevice.SOURCE_MOUSE:
@ -809,7 +749,7 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
@Override
public boolean supportsRelativeMouse() {
return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */);
return (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27));
}
@Override
@ -819,7 +759,7 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
@Override
public boolean setRelativeMouseEnabled(boolean enabled) {
if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) {
if (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)) {
if (enabled) {
SDLActivity.getContentView().requestPointerCapture();
} else {

View File

@ -1,405 +0,0 @@
package org.libsdl.app;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
/**
SDLSurface. This is what we draw on, so we need to know when it's created
in order to do anything useful.
Because of this, that's where we set up the SDL thread
*/
public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
View.OnKeyListener, View.OnTouchListener, SensorEventListener {
// Sensors
protected SensorManager mSensorManager;
protected Display mDisplay;
// Keep track of the surface size to normalize touch events
protected float mWidth, mHeight;
// Is SurfaceView ready for rendering
public boolean mIsSurfaceReady;
// Startup
public SDLSurface(Context context) {
super(context);
getHolder().addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
setOnKeyListener(this);
setOnTouchListener(this);
mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
setOnGenericMotionListener(SDLActivity.getMotionListener());
// Some arbitrary defaults to avoid a potential division by zero
mWidth = 1.0f;
mHeight = 1.0f;
mIsSurfaceReady = false;
}
public void handlePause() {
enableSensor(Sensor.TYPE_ACCELEROMETER, false);
}
public void handleResume() {
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
setOnKeyListener(this);
setOnTouchListener(this);
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
}
public Surface getNativeSurface() {
return getHolder().getSurface();
}
// Called when we have a valid drawing surface
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.v("SDL", "surfaceCreated()");
SDLActivity.onNativeSurfaceCreated();
}
// Called when we lose the surface
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.v("SDL", "surfaceDestroyed()");
// Transition to pause, if needed
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
SDLActivity.handleNativeState();
mIsSurfaceReady = false;
SDLActivity.onNativeSurfaceDestroyed();
}
// Called when the surface is resized
@Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
Log.v("SDL", "surfaceChanged()");
if (SDLActivity.mSingleton == null) {
return;
}
mWidth = width;
mHeight = height;
int nDeviceWidth = width;
int nDeviceHeight = height;
try
{
if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) {
DisplayMetrics realMetrics = new DisplayMetrics();
mDisplay.getRealMetrics( realMetrics );
nDeviceWidth = realMetrics.widthPixels;
nDeviceHeight = realMetrics.heightPixels;
}
} catch(Exception ignored) {
}
synchronized(SDLActivity.getContext()) {
// In case we're waiting on a size change after going fullscreen, send a notification.
SDLActivity.getContext().notifyAll();
}
Log.v("SDL", "Window size: " + width + "x" + height);
Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate());
SDLActivity.onNativeResize();
// Prevent a screen distortion glitch,
// for instance when the device is in Landscape and a Portrait App is resumed.
boolean skip = false;
int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
if (mWidth > mHeight) {
skip = true;
}
} else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
if (mWidth < mHeight) {
skip = true;
}
}
// Special Patch for Square Resolution: Black Berry Passport
if (skip) {
double min = Math.min(mWidth, mHeight);
double max = Math.max(mWidth, mHeight);
if (max / min < 1.20) {
Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
skip = false;
}
}
// Don't skip in MultiWindow.
if (skip) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
if (SDLActivity.mSingleton.isInMultiWindowMode()) {
Log.v("SDL", "Don't skip in Multi-Window");
skip = false;
}
}
}
if (skip) {
Log.v("SDL", "Skip .. Surface is not ready.");
mIsSurfaceReady = false;
return;
}
/* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
SDLActivity.onNativeSurfaceChanged();
/* Surface is ready */
mIsSurfaceReady = true;
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
SDLActivity.handleNativeState();
}
// Key events
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
return SDLActivity.handleKeyEvent(v, keyCode, event, null);
}
// Touch events
@Override
public boolean onTouch(View v, MotionEvent event) {
/* Ref: http://developer.android.com/training/gestures/multi.html */
int touchDevId = event.getDeviceId();
final int pointerCount = event.getPointerCount();
int action = event.getActionMasked();
int pointerFingerId;
int i = -1;
float x,y,p;
/*
* Prevent id to be -1, since it's used in SDL internal for synthetic events
* Appears when using Android emulator, eg:
* adb shell input mouse tap 100 100
* adb shell input touchscreen tap 100 100
*/
if (touchDevId < 0) {
touchDevId -= 1;
}
// 12290 = Samsung DeX mode desktop mouse
// 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN
// 0x2 = SOURCE_CLASS_POINTER
if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) {
int mouseButton = 1;
try {
Object object = event.getClass().getMethod("getButtonState").invoke(event);
if (object != null) {
mouseButton = (Integer) object;
}
} catch(Exception ignored) {
}
// We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
// if we are. We'll leverage our existing mouse motion listener
SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();
x = motionListener.getEventX(event);
y = motionListener.getEventY(event);
SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());
} else {
switch(action) {
case MotionEvent.ACTION_MOVE:
for (i = 0; i < pointerCount; i++) {
pointerFingerId = event.getPointerId(i);
x = event.getX(i) / mWidth;
y = event.getY(i) / mHeight;
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_DOWN:
// Primary pointer up/down, the index is always zero
i = 0;
/* fallthrough */
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_POINTER_DOWN:
// Non primary pointer up/down
if (i == -1) {
i = event.getActionIndex();
}
pointerFingerId = event.getPointerId(i);
x = event.getX(i) / mWidth;
y = event.getY(i) / mHeight;
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
break;
case MotionEvent.ACTION_CANCEL:
for (i = 0; i < pointerCount; i++) {
pointerFingerId = event.getPointerId(i);
x = event.getX(i) / mWidth;
y = event.getY(i) / mHeight;
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
}
break;
default:
break;
}
}
return true;
}
// Sensor events
public void enableSensor(int sensortype, boolean enabled) {
// TODO: This uses getDefaultSensor - what if we have >1 accels?
if (enabled) {
mSensorManager.registerListener(this,
mSensorManager.getDefaultSensor(sensortype),
SensorManager.SENSOR_DELAY_GAME, null);
} else {
mSensorManager.unregisterListener(this,
mSensorManager.getDefaultSensor(sensortype));
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// TODO
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
// Since we may have an orientation set, we won't receive onConfigurationChanged events.
// We thus should check here.
int newOrientation;
float x, y;
switch (mDisplay.getRotation()) {
case Surface.ROTATION_90:
x = -event.values[1];
y = event.values[0];
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;
break;
case Surface.ROTATION_270:
x = event.values[1];
y = -event.values[0];
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;
break;
case Surface.ROTATION_180:
x = -event.values[0];
y = -event.values[1];
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;
break;
case Surface.ROTATION_0:
default:
x = event.values[0];
y = event.values[1];
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;
break;
}
if (newOrientation != SDLActivity.mCurrentOrientation) {
SDLActivity.mCurrentOrientation = newOrientation;
SDLActivity.onNativeOrientationChanged(newOrientation);
}
SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
y / SensorManager.GRAVITY_EARTH,
event.values[2] / SensorManager.GRAVITY_EARTH);
}
}
// Captured pointer events for API 26.
public boolean onCapturedPointerEvent(MotionEvent event)
{
int action = event.getActionMasked();
float x, y;
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
case MotionEvent.ACTION_HOVER_MOVE:
case MotionEvent.ACTION_MOVE:
x = event.getX(0);
y = event.getY(0);
SDLActivity.onNativeMouse(0, action, x, y, true);
return true;
case MotionEvent.ACTION_BUTTON_PRESS:
case MotionEvent.ACTION_BUTTON_RELEASE:
// Change our action value to what SDL's code expects.
if (action == MotionEvent.ACTION_BUTTON_PRESS) {
action = MotionEvent.ACTION_DOWN;
} else { /* MotionEvent.ACTION_BUTTON_RELEASE */
action = MotionEvent.ACTION_UP;
}
x = event.getX(0);
y = event.getY(0);
int button = event.getButtonState();
SDLActivity.onNativeMouse(button, action, x, y, true);
return true;
}
return false;
}
}

View File

@ -1,41 +1,7 @@
package org.tildearrow.furnace;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.widget.Toast;
import org.libsdl.app.SDLActivity;
public class MainActivity extends SDLActivity {
static final int TA_FILE_REQUEST=1000;
static final int TA_FILE_SAVE_REQUEST=1001;
public void showFileDialog() {
Intent picker=new Intent(Intent.ACTION_GET_CONTENT);
picker.setType("*/*");
picker=Intent.createChooser(picker,"test");
startActivityForResult(picker,TA_FILE_REQUEST);
}
public void showSaveFileDialog() {
Intent picker=new Intent(Intent.ACTION_CREATE_DOCUMENT);
picker.addCategory(Intent.CATEGORY_OPENABLE);
picker.setType("*/*");
startActivityForResult(picker,TA_FILE_SAVE_REQUEST);
}
@Override protected void onActivityResult(int request, int result, Intent intent) {
super.onActivityResult(request,result,intent);
if (request==TA_FILE_REQUEST) {
if (result==RESULT_OK) {
Uri path=intent.getData();
Context context=getApplicationContext();
Toast toast=Toast.makeText(context,path.toString(),Toast.LENGTH_SHORT);
toast.show();
}
}
}
public class MainActivity extends SDLActivity
{
}

BIN
demos/C64 junk.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/Coconut_Mall.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/FDS TEST.fur Normal file

Binary file not shown.

BIN
demos/FEDMS.fur Normal file

Binary file not shown.

BIN
demos/GranularFurn.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/Lagrange_Point.fur Normal file

Binary file not shown.

Binary file not shown.

View File

@ -6,13 +6,10 @@ these demo songs are not under the GPL. all rights are reserved to the original
# submit demo songs!
contact me or send a pull request if you want your song to be added to this collection. be noted we have three rules:
contact me or send a pull request if you want your song to be added to this collection. be noted we have two rules:
- Nintendo covers are frowned upon
- big label music covers also are discouraged
- low effort compositions/covers may not be accepted at all.
make sure to put your demo song under the respective category.
tildearrow also accepts demo songs in the .dmf format as well as the .fur format.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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