Merge branch 'master' of https://github.com/tildearrow/furnace into x1_010_bank

This commit is contained in:
cam900 2023-06-29 21:36:29 +09:00
commit b1e2e33f2d
431 changed files with 25341 additions and 13648 deletions

View File

@ -82,7 +82,7 @@ jobs:
package_ext=".dmg"
else
package_name="${package_name}-Linux-${{ matrix.config.arch }}"
package_ext=".AppImage"
package_ext=".tar.gz"
fi
echo "Package identifier: ${package_name}"
@ -128,10 +128,7 @@ jobs:
librtmidi-dev \
libsndfile1-dev \
zlib1g-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
libjack-jackd2-dev
- name: Install Dependencies [Linux armhf]
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'armhf' }}
@ -151,9 +148,6 @@ 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)
@ -243,6 +237,7 @@ 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
@ -300,24 +295,37 @@ jobs:
# strip -s build/furnace
#fi
mkdir -p target/furnace.AppDir
make -C ${PWD}/build DESTDIR=${PWD}/target/furnace.AppDir install
pushd target
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
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
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
cd target
tar -zcv -f ../${{ steps.package-identify.outputs.filename }} furnace
- name: Upload artifact
if: ${{ github.repository == 'tildearrow/furnace' && github.ref_name == 'master' }}

1
.gitmodules vendored
View File

@ -1,6 +1,7 @@
[submodule "extern/SDL"]
path = extern/SDL
url = https://github.com/libsdl-org/SDL.git
branch = release-2.28.x
[submodule "extern/libsndfile"]
path = extern/libsndfile
url = https://github.com/libsndfile/libsndfile.git

View File

@ -54,12 +54,34 @@ 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_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)
@ -92,12 +114,19 @@ set(DEPENDENCIES_LIBRARY_DIRS "")
set(DEPENDENCIES_LINK_OPTIONS "")
set(DEPENDENCIES_LEGACY_LDFLAGS "")
if (BUILD_GUI)
if (BUILD_GUI AND WITH_RENDER_SDL)
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")
@ -278,6 +307,12 @@ 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
@ -556,6 +591,9 @@ 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)
@ -577,13 +615,15 @@ 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_sdlrenderer.cpp
extern/imgui_patched/backends/imgui_impl_sdl.cpp
extern/imgui_patched/backends/imgui_impl_sdl2.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
@ -672,6 +712,47 @@ if (APPLE)
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)
CHECK_INCLUDE_FILE(sys/io.h SYS_IO_FOUND)
CHECK_INCLUDE_FILE(linux/input.h LINUX_INPUT_FOUND)
@ -743,10 +824,6 @@ if (WIN32)
if (NOT MSVC)
list(APPEND DEPENDENCIES_LIBRARIES -static)
endif()
# support Windows XP
if (SUPPORT_XP)
list(APPEND DEPENDENCIES_DEFINES "_WIN32_WINNT=0x0501")
endif()
elseif (APPLE)
find_library(COCOA Cocoa REQUIRED)
list(APPEND DEPENDENCIES_LIBRARIES ${COCOA})
@ -820,7 +897,8 @@ 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 papers DESTINATION ${CMAKE_INSTALL_DOCDIR})
install(DIRECTORY doc DESTINATION ${CMAKE_INSTALL_DOCDIR})
install(DIRECTORY papers DESTINATION ${CMAKE_INSTALL_DOCDIR}/other)
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/furnace)
if (WITH_DEMOS)
install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)

113
README.md
View File

@ -1,17 +1,17 @@
# Furnace (chiptune tracker)
![screenshot](papers/screenshot2.png)
![screenshot](papers/screenshot3.png)
the biggest multi-system chiptune tracker ever made!
[downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [FAQ](#frequently-asked-questions)
[downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [Unix/Linux packages](#packages) | [FAQ](#frequently-asked-questions)
---
## downloads
check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage).
[see here](https://nightly.link/tildearrow/furnace/workflows/build/master) for unstable developer builds.
[see here](https://nightly.link/tildearrow/furnace/workflows/build/master) for the latest unstable build.
## features
@ -66,9 +66,12 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a
- Family Noraebang (OPLL)
- SID (6581/8580) used in Commodore 64
- Mikey used in Atari Lynx
- ZX Spectrum beeper (SFX-like engine)
- ZX Spectrum beeper
- SFX-like engine
- QuadTone engine
- Pokémon Mini
- Commodore PET
- Casio PV-1000
- TIA used in Atari 2600
- POKEY used in Atari 8-bit computers
- Game Boy
@ -80,7 +83,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a
- 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.7)
- loads .dmf modules from all versions (beta 1 to 1.1.9)
- saves .dmf modules - both modern and legacy
- Furnace doubles as a module downgrader
- loads/saves .dmp instruments and .dmw wavetables as well
@ -116,18 +119,19 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a
---
# quick references
- **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z).
- **help**: check out the [documentation](papers/doc/README.md). it's mostly incomplete, but has details on effects.
- **discussion**: 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 incomplete though.
## unofficial packages
## packages
[![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/community/x86_64/furnace/) is now in the community repo!
- **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.
- **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.
---
# developer info
@ -141,7 +145,9 @@ 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:
@ -178,10 +184,32 @@ 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
```
### macOS and Linux
### 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
```
mkdir build
@ -189,7 +217,16 @@ cd build
cmake ..
make
```
Alternatively, build scripts are provided in the `scripts/` folder in the root of the repository.
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`.
### CMake options
@ -220,6 +257,8 @@ Available options:
## console usage
(note: if on Windows, type `furnace.exe` instead, or `Debug\furnace.exe` on MSVC)
```
./furnace
```
@ -238,23 +277,17 @@ this will play a compatible file.
this will play a compatible file and enable the commands view.
**note that these commands only actually work in Linux environments. on other command lines, such as Windows' Command Prompt, or MacOS Terminal, it may not work correctly.**
**note that console mode may not work correctly on Windows. you may have to quit using the Task Manager.**
---
# 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 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!
**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, use this workaround instead (on a Terminal):
@ -266,24 +299,25 @@ xattr -d com.apple.quarantine /path/to/Furnace.app
you may need to log out and/or reboot after doing this.
> where's the manual?
see [doc/](doc/README.md). it's kind of incomplete though.
> is there a tutorial?
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...
> how do I use C64 absolute filter/duty?
> ROM export?
on Instrument Editor in the C64 tab there are two options to toggle these.
also provided are two effects:
- `3xxx`: set fine duty.
- `4xxx`: set fine cutoff. `xxx` range is 000-7ff.
additionally, you can change the cutoff and/or duty as a macro inside an instrument by clicking the `absolute cutoff macro` and/or `absolute duty macro` checkbox at the bottom of the instrument. (for the filter, you also need to click the checkbox that says `volume macro is cutoff macro`.)
> how do I use PCM on a PCM-capable chip?
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.
**not yet!** coming in 0.7 though, eventually...
> my .dmf song sounds odd at a certain point
@ -293,10 +327,6 @@ Furnace's .dmf compatibility isn't perfect and it's mostly because DefleMask doe
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).
> how do I solo channels?
right click on the channel name.
---
# footnotes
@ -309,4 +339,5 @@ 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.
despite the fact this program works with the .dmf, .dmp and .dmw file formats (besides its native .fur format), it is NOT affiliated with Delek or DefleMask in any way, nor it is a replacement for the original program.
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.

12
TODO.md
View File

@ -1,16 +1,12 @@
# to-do for 0.6pre5
# to-do for 0.6pre6
- tutorial
- tutorial?
- ease-of-use improvements... ideas:
- preset compat flags
- setting to toggle the Choose a System screen on new project
- 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
- Had a hard time finding the docs on github and in Furnace's folder.
- make .pdf manual out of papers/doc/
- you're going too fast; please slow down
- break compatibility if it relieves complexity
- ins/wave/sample organization (folders and all)
- make .pdf manual out of doc/
- break compatibility if it relieves complexity
- multi-key binds
- bug fixes

View File

@ -15,11 +15,11 @@ android {
}
minSdkVersion 21
targetSdkVersion 26
versionCode 143
versionName "0.6pre4"
versionCode 158
versionName "0.6pre5"
externalNativeBuild {
cmake {
arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static"
arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static", "-DWARNINGS_ARE_ERRORS=ON"
// 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="143"
android:versionName="0.6pre4"
android:versionCode="158"
android:versionName="0.6pre5"
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) {
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
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);
@ -251,6 +251,8 @@ public class HIDDeviceManager {
0x20d6, // PowerA
0x24c6, // PowerA
0x2c22, // Qanba
0x2dc8, // 8BitDo
0x9886, // ASTRO Gaming
};
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
@ -271,14 +273,16 @@ 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 */
0x2dc8, // 8BitDo
0x2e24, // Hyperkin
};
@ -353,13 +357,13 @@ public class HIDDeviceManager {
private void initializeBluetooth() {
Log.d(TAG, "Initializing Bluetooth");
if (Build.VERSION.SDK_INT <= 30 &&
if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ &&
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)) {
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) {
Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE");
return;
}
@ -524,7 +528,7 @@ public class HIDDeviceManager {
for (HIDDevice device : mDevicesById.values()) {
device.setFrozen(frozen);
}
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
@ -573,7 +577,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) {
if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
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) {
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
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) {
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
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) {
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
result = mDevice.getProductName();
}
if (result == null) {

View File

@ -29,6 +29,7 @@ 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,5 +1,8 @@
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;
@ -8,34 +11,67 @@ import android.media.MediaRecorder;
import android.os.Build;
import android.util.Log;
public class SDLAudioManager
{
import java.util.Arrays;
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) {
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
int channelConfig;
int sampleSize;
int frameSize;
@ -43,14 +79,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) {
if (Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
if (desiredChannels > 2) {
desiredChannels = 2;
}
}
/* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */
if (Build.VERSION.SDK_INT < 22) {
if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) {
if (sampleRate < 8000) {
sampleRate = 8000;
} else if (sampleRate > 48000) {
@ -59,7 +95,7 @@ public class SDLAudioManager
}
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
int minSDKVersion = (isCapture ? 23 : 21);
int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */);
if (Build.VERSION.SDK_INT < minSDKVersion) {
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
}
@ -120,7 +156,7 @@ public class SDLAudioManager
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
break;
case 8:
if (Build.VERSION.SDK_INT >= 23) {
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
} else {
Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround");
@ -201,6 +237,10 @@ public class SDLAudioManager
return null;
}
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId));
}
mAudioRecord.startRecording();
}
@ -224,6 +264,10 @@ public class SDLAudioManager
return null;
}
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId));
}
mAudioTrack.play();
}
@ -238,11 +282,73 @@ 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[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames);
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);
}
/**
@ -254,6 +360,11 @@ 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) {
@ -326,18 +437,22 @@ public class SDLAudioManager
/**
* This method is called by SDL using JNI.
*/
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames);
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
}
/** This method is called by SDL using JNI. */
public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_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);
}
}
/** This method is called by SDL using JNI. */
public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
if (Build.VERSION.SDK_INT < 23) {
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
return mAudioRecord.read(buffer, 0, buffer.length);
} else {
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
@ -346,7 +461,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) {
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
return mAudioRecord.read(buffer, 0, buffer.length);
} else {
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
@ -391,4 +506,9 @@ 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 nhats, int nballs);
int naxes, int axis_mask, 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) {
if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {
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) {
if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {
mHapticHandler = new SDLHapticHandler_API26();
} else {
mHapticHandler = new SDLHapticHandler();
@ -168,6 +168,32 @@ 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;
}
}
@ -210,7 +236,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(), joystick.hats.size()/2, 0);
getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, 0);
}
}
}
@ -291,6 +317,9 @@ 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;
}
@ -308,6 +337,43 @@ 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;
@ -743,7 +809,7 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
@Override
public boolean supportsRelativeMouse() {
return (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27));
return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */);
}
@Override
@ -753,7 +819,7 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
@Override
public boolean setRelativeMouseEnabled(boolean enabled) {
if (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)) {
if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) {
if (enabled) {
SDLActivity.getContentView().requestPointerCapture();
} else {

View File

@ -0,0 +1,405 @@
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;
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
demos/misc/QSound_smile.fur Normal file

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.

BIN
demos/x16/TFV_Rise.fur Normal file

Binary file not shown.

BIN
demos/x16/richca.fur Normal file

Binary file not shown.

21
doc/1-intro/README.md Normal file
View File

@ -0,0 +1,21 @@
# introduction
Furnace is a tool which allows you to create music using sound chips ("chiptune"), most from the 8/16-bit era.
it has a large selection of features and sound chips. from the NES, SNES and Genesis to ES5506, VIC-20 or even Arcade, Furnace has most likely covered your target with many presets to choose from.
every chip is emulated using many emulation cores, therefore the sound that Furnace produces is authentic to that of real hardware.
## hexadecimal
Furnace uses hexadecimal (abbreviated as "hex") numbers frequently. see [this guide](hex.md) for a crash course.
## interface
Furnace uses a music tracker interface. think of a table with music notes written on it. then that table scrolls up and plays the notes.
due to its nature of being feature-packed, it may be technical and somewhat difficult to get around. therefore we added a basic mode, which hides several advanced features.
it also has a flexible windowing system which you may move around and organize.
see [2-interface](../2-interface/README.md) and [3-pattern](../3-pattern/README.md) for more information.

97
doc/1-intro/hex.md Normal file
View File

@ -0,0 +1,97 @@
# hexadecimal
the hexadecimal numeral system differs from the decimal system by having 16 digits rather than 10:
```
hex| decimal
---|---------
0 | 0
1 | 1
2 | 2
3 | 3
4 | 4
5 | 5
6 | 6
7 | 7
8 | 8
9 | 9
A | 10
B | 11
C | 12
D | 13
E | 14
F | 15
```
when there is more than one digit, these are multiplied by 16, 256, 4096 and so on rather than 10, 100, 1000:
```
hex | decimal
----|---------
00 | 0
04 | 4
08 | 8
0F | 15
10 | 16
11 | 17
12 | 18
13 | 19
20 | 32
30 | 48
40 | 64
```
# hex to decimal
for example, take hexadecimal number `AA`:
```
2nd digit -\ /- 1st digit
A A
16^1*10 = 16*10 = 160 + 10 = 170
```
now for hexadecimal number `4C5F`:
```
3rd digit -\ /- 2nd digit
4th digit -\ | | /- 1st digit
4 C 5 F
| | | |
| | | 15 = 15 = 15 +
| | \16^1*5 = 16 * 5 = 80
| \--- 16^2*12 = 256 * 12 = 3072
\--------- 16^3*4 = 4096 * 4 = 16384
-------
= 19551
```
# decimal to hex
if it's less than 16, just memorize the table at the top of this document.
otherwise find the power of 16 that is closest to the number you want to convert, but no larger than the number.
then divide, and take the remainder.
divide the remainder with the previous power of 16, until the divider is 1.
for example, for the decimal number `220`:
```
220 ÷ 16 = 13 (r = 12) D
12 ÷ 1 = 12 (stop here) C
= DC
```
now for decimal number `69420`:
```
69420 ÷ 65536 = 1 (r = 3884) 1
3884 ÷ 4096 = 0 (r = 3884) 0
3884 ÷ 256 = 15 (r = 44) F
44 ÷ 16 = 2 (r = 12) 2
12 ÷ 1 = 12 (stop here) C
= 10F2C
```

41
doc/2-interface/README.md Normal file
View File

@ -0,0 +1,41 @@
# interface
the Furnace user interface is where the job gets done.
the default layout of Furnace is depicted below.
![interface](interface1.png)
primary topics:
- [menu bar](menu-bar.md)
- [play/edit controls](play-edit-controls.md)
- [instrument/wavetable/sample list](asset-list.md)
- [song information](song-info.md)
- [pattern view](../3-pattern/README.md)
- [instrument editor](../4-instrument/README.md)
- [wavetable editor](../5-wave/README.md)
- [sample editor](../6-sample/README.md)
advanced topics:
- [mixer](../8-advanced/mixer.md)
- [grooves](../8-advanced/grooves.md)
- [channels](../8-advanced/channels.md)
- [pattern manager](../8-advanced/pat-manager.md)
- [chip manager](../8-advanced/chip-manager.md)
- [compatibility flags](../8-advanced/compat-flags.md)
- [song comments](../8-advanced/comments.md)
- [piano/input pad](../8-advanced/piano.md)
- [oscilloscope](../8-advanced/osc.md)
- [oscilloscope (per channel)](../8-advanced/chanosc.md)
- [clock](../8-advanced/clock.md)
- [register view](../8-advanced/regview.md)
- [log viewer](../8-advanced/log-viewer.md)
- [statistics](../8-advanced/stats.md)
other topics:
- [UI components](components.md)
- [global keyboard shortcuts](keyboard.md)
- [basic mode](basic-mode.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

View File

@ -0,0 +1,38 @@
# instrument list
![instruments window](instruments.png)
Buttons from left to right:
- **Add**: Creates a new, default instrument.
- **Duplicate**: Duplicates the currently selected instrument.
- **Open**: Brings up a file dialog to load a file as a new instrument at the end of the list.
- **Save**: Brings up a file dialog to save the currently selected instrument.
- **Toggle folders/standard view**: Enables (and disables) folder view, explained below.
- **Move up**: Moves the currently selected instrument up in the list. Pattern data will automatically be adjusted to match.
- **Move down**: Same, but downward.
- **Delete**: Deletes the currently selected instrument. Pattern data will be adjusted to use the next available instrument in the list.
## folder view
![instruments window in folder view](instruments-folder.png)
In folder view, the "Move up" and "Move down buttons disappear and a new one appears:
- **New folder**: Creates a new folder.
Instruments may be dragged from folder to folder and even rearranged within folders without changing their associated numbers.
Right-clicking on a folder allows one to rename or delete it. Deleting a folder does not remove the instruments in it.
# wavetable list
![wavetables window](wavetables.png)
Everything from the instrument list applies here also, with one major difference: Moving waves around with the buttons will change their associated numbers in the list but _not_ in pattern or instrument data. Be careful!
# sample list
![samples window](samples.png)
Everything from the wavetables list applies here also, with the addition of two buttons:
- **Preview**: Plays the selected sample at its default note.
- **Stop preview**: Stops the sample playback.

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

View File

@ -4,14 +4,14 @@ the user interface consists of several components. this paper describes some of
## windows
TODO: image
![window](window.png)
windows may be moved, collapsed, closed or even docked around the workspace.
to move a window, press and hold the mouse button while on title bar or any empty space on it.
then drag your mouse, and release it to stop moving.
to resize a window, drag any of the bottom corners (marked by triangular tabs).
to resize a window, drag the bottom right corner (marked by a triangular tab) or the borders.
to collapse a window, click on the triangle in the title bar.
clicking again expands it.
@ -24,33 +24,40 @@ windows may be docked, which comes in handy.
to dock a window, drag it from its title bar to another location in the workspace or to the location of another window.
while dragging, an overlay with five options will appear, allowing you to select where and how to dock that window.
while dragging, an overlay with some options will appear, allowing you to select where and how to dock that window.
the options are:
```
UP
LEFT CENTER RIGHT
DOWN
```
![docking options](docking.png)
drag your mouse cursor to any of the options to dock the window.
if you drag the window to `CENTER`, the window will be maximized to cover the workspace (if you do this on the workspace), or it will appear as another tab (if you do this on a window).
if you drag to the sides (marked with blue text), the window will cover that side of the workspace.
if you drag it to a window or empty space (marked with yellow text), five docking positions will appear.
if you drag the window to the center of another window, it will appear as another tab.
if you drag the window to the center of empty space, the window will cover aforementioned empty space.
otherwise the window will be split in two, with the first half covered by the window you docked and the second half covered by the other window.
![tab1](tab1.png)
when a window is docked, its title bar turns into a tab bar, and the function provided by the "collapse" triangle at the top left changes.
if this triangle is clicked, a menu will appear with a single option: "Hide tab bar".
![tab2](tab2.png)
if this triangle is clicked, a menu will appear with a list of tabs, or a single option if there's only one tab: "Hide tab bar".
selecting this option will hide the tab bar of that window.
![tab3](tab3.png)
to bring it back, click on the top left corner.
to undock a window, drag its tab away from where it is docked. then it will be floating again.
## text fields
TODO: image
text fields are able to hold... text.
click on a text field to start editing, and click away to stop editing.
@ -60,24 +67,18 @@ the following keyboard shortcuts work while on a text field:
- `Ctrl-X`: cut
- `Ctrl-C`: copy
- `Ctrl-V`: paste
- `Ctrl-Z`: undo
- `Ctrl-Y`: redo
- `Ctrl-A`: select all
(replace Ctrl with Command on macOS)
## number input fields
TODO: image
these work similar to text fields, but you may only input numbers.
they also usually have two buttons which allow you to increase/decrease the amount when clicked (and rapidly do so when click-holding).
## sliders
TODO: image
sliders are used for controlling values in a quick manner by being dragged.
alternatively, right-clicking or Ctrl-clicking or a slider (Command-click on macOS) will turn it into a number input field for a short period of time, allowing you to input fine values.

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
doc/2-interface/docking.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

226
doc/2-interface/menu-bar.md Normal file
View File

@ -0,0 +1,226 @@
# menu bar
the menu bar allows you to select five menus: file, edit, settings, window and help.
# file
- **new...**: create a new song.
- **open...**: opens the file picker, allowing you to select a song to open.
- **open recent**: contains a list of the songs you've opened before.
- **clear history**: this option erases the file history.
- **save**: saves the current song.
- opens the file picker if this is a new song, or a backup.
- **save as...**: opens the file picker, allowing you to save the song under a different name.
- **save as .dmf (1.1.3+)...**: opens the file picker, allowing you to save your song as a .dmf which is compatible with DefleMask 1.1.3 onwards.
- this will only work with the systems mentioned in the next option, plus:
- Sega Master System (with FM expansion)
- NES + Konami VRC7
- Famicom Disk System
- only use this option if you really need it. there are features which DefleMask does not support, like some effects and FM macros, so these will be lost.
- **save as .dmf (1.0/legacy)...**: opens the file picker, allowing you to save your song as a .dmf which is compatible with DefleMask Legacy (0.12) or 1.0.
- this will only work on the following systems:
- Sega Genesis/Mega Drive (YM2612 + SN76489)
- Sega Genesis/Mega Drive (YM2612 + SN76489, extended channel 3)
- Sega Master System
- Game Boy
- PC Engine
- NES
- Commodore 64
- Arcade (YM2151 + SegaPCM 5-channel compatibility)
- Neo Geo CD (DefleMask 1.0+)
- only use this option if you really need it. there are features which DefleMask does not support, like some effects and FM macros, so these will be lost.
- **export audio...**: export your song to a .wav file. see next section for more details.
- **export VGM...**: export your song to a .vgm file. see next section for more details.
- **export ZSM...**: export your song to a .zsm file. see next section for more details.
- only available when there's a YM2151 and/or VERA.
- **export command stream...**: export song data to a command stream file. see next section for more details.
- this option is for developers.
- **add chip...**: add a chip to the current song.
- **configure chip...**: set a chip's parameters.
- for a list of parameters, see [7-systems](../7-systems/README.md).
- **change chip...**: change a chip to another.
- **Preserve channel positions**: enable this option to make sure Furnace does not auto-arrange/delete channels to compensate for differing channel counts. this can be useful for doing ports, e.g. from Genesis to PC-98.
- **remove chip...**: remove a chip.
- **Preserve channel positions**: same thing as above.
- **restore backup**: restore a previously saved backup.
- Furnace keeps up to 5 backups of a song.
- the backup directory is located in:
- Windows: `%USERPROFILE%\AppData\Roaming\furnace\backups`
- macOS: `~/Library/Application Support/Furnace/backups`
- Linux/other: `~/.config/furnace/backups`
- this directory grows in size as you use Furnace. remember to delete old backups periodically to save space.
- **exit**: I think you know what this does.
## export audio
this option allows you to export your song in .wav format. I know I know, no .mp3 or .ogg export yet, but you can use a converter.
there are two parameters:
- **Loops**: sets the number of times the song will loop.
- does not have effect if the song ends with `FFxx` effect.
- **Fade out (seconds)**: sets the fade out time when the song is over.
- does not have effect if the song ends with `FFxx` effect.
and three export choices:
- **one file**: exports your song to one .wav file.
- **multiple files (one per chip)**: exports the output of each chip to .wav files.
- **multiple files (one per channel)**: exports the output of each channel to .wav files.
- useful for usage with a channel visualizer such as corrscope.
## export VGM
this option allows exporting to a VGM (Video Game Music) file. these can be played back with VGMPlay (for example).
the following settings exist:
- **format version**: sets the VGM format version to use.
- versions under 1.70 do not support per-chip volumes, and therefore will ignore the Mixer completely.
- other versions may not support all chips.
- use this option if you need to export for a quirky player or parser.
- for example, RYMCast is picky with format versions. if you're going to use this player, select 1.60.
- **loop**: writes loop. if disabled, the resulting file won't loop.
- **loop trail**: this option allows you to set how much of the song is written after it loops.
- the reason this exists is to work around a VGM format limitation in where post-loop state isn't recorded at all.
- this may change the song length as it appears on a player.
- **auto-detect**: detect how much to write automatically.
- **add one loop**: add one more loop.
- **custom**: allows you to specify how many ticks to add.
- `0` is effectively none, disabling loop trail completely.
- this option will not appear if the loop modality isn't set to None as there wouldn't be a need to.
- **chips to export**: select which chips are going to be exported.
- due to VGM format limitations, you can only select up to two of each chip type.
- some chips will not be available, either because VGM doesn't support these yet, or because you selected an old format version.
- **add pattern change hints**: this option adds a "hint" when a pattern change occurs. only useful if you're a developer.
- the format of the "hint" data block that gets written is: `67 66 FE ll ll ll ll 01 oo rr pp pp pp ...`
- ll: length, a 32-bit little-endian number
- oo: order
- rr: initial row (a 0Dxx effect is able to select a different row)
- pp: pattern index (one per channel)
- **direct stream mode**: this option allows DualPCM to work. don't use this for other chips.
- may or may not play well with hardware VGM players.
click on **click to export** to begin exporting.
## export ZSM
ZSM (ZSound Music) is a format designed for the Commander X16 to allow hardware playback.
it may contain data for either YM2151 or VERA chips.
Calliope is one of the programs that supports playback of ZSM files.
the following settings are available:
- **Tick Rate (Hz)**: select the tick rate the song will run at.
- I suggest you use the same rate as the song's.
- apparently ZSM doesn't support changing the rate mid-song.
- **loop**: enables loop. if disabled, the song won't loop.
click on **Begin Export** to... you know.
## export command stream
this option exports a text or binary file which contains a dump of the internal command stream produced when playing the song.
it's not really useful, unless you're a developer and want to use a command stream dump for some reason (e.g. writing a hardware sound driver).
- **export (binary)**: exports in Furnace's own command stream format (FCS). see `export-tech.md` in `papers/` for details.
- **export (text)**: exports the command stream as a text file. only useful for analysis, really.
# edit
- **undo**: reverts the last action.
- **redo**: repeats what you undid previously.
- **cut**: moves the current selection in the pattern view to clipboard.
- **copy**: copies the current selection in the pattern view to clipboard.
- **paste**: inserts the clipboard's contents in the cursor position.
- **paste special...**: variants of the paste feature.
- **paste mix**: inserts the clipboard's contents in the cursor position, but does not erase the occupied region.
- **paste mix (background)**: does the same thing as paste mix, but doesn't alter content which is already there.
- **paste with ins (foreground)**: same thing as paste mix, but changes the instrument.
- **paste with ins (background)**: same thing as paste mix (background), but changes the instrument.
- **paste flood**: inserts the clipboard's contents in the cursor position, and repeats until it hits the end of a pattern.
- **paste overflow**: paste, but it will keep pasting even if it runs over another pattern.
- **delete**: clears the contents in the selection.
- **select all**: changes the selection so it covers a larger area.
- if the selection is wide, it will select the rows in a column.
- if the selection is tall, it will select the entire column.
- if a column is already selected, it will select the entire channel.
- if a channel is already selected, it will select the entire pattern.
- **operation mask**: this is an advanced feature. see [this page](../3-pattern/opmask.md) for more information.
- **input latch**: this is an advanced feature. see [this page](../3-pattern/inputlatch.md) for more information.
- **note/octave up/down**: transposes notes in the current selection.
- **values up/down**: changes values in the current selection by ±1 or ±16.
- **transpose**: transpose notes or change values by a specific amount.
- **interpolate**: fills in gaps in the selection by interpolation between values.
- **change instrument**: changes the instrument number in a selection.
- **gradient/fade**: replace the selection with a "gradient" that goes from the beginning of the selection to the end.
- does not affect the note column.
- **Nibble mode**: when enabled, the fade will be per-nibble (0 to F) rather than per-value (00 to FF).
- use for effects like `04xy` (vibrato).
- **scale**: scales values in the selection by a specific amount.
- use to change volume in a selection for example.
- **randomize**: replaces the selection with random values.
- does not affect the note column.
- **invert values**: `00` becomes `FF`, `01` becomes `FE`, `02` becomes `FD` and so on.
- **flip selection**: flips the selection so it is backwards.
- **collapse/expand amount**: allows you to specify how much to collapse/expand in the next options.
- **collapse**: shrinks the selected contents.
- **expand**: expands the selected contents.
- **collapse pattern**: same as collapse, but affects the entire pattern.
- **expand pattern**: same as expand, but affects the entire pattern.
- **collapse song**: same as collapse, but affects the entire song.
- it also changes speeds and pattern length to compensate.
- **expand song**: same as expand, but affects the entire song.
- it also changes speeds and pattern length to compensate.
- **find/replace**: opens the Find/Replace window. see [this page](../3-pattern/find-replace.md) for more information.
- **clear**: allows you to mass-delete things like songs, instruments and the like.
# settings
- **full screen**: expands the Furnace window so it covers your screen.
- **lock layout**: prevents you from dragging/resizing docked windows, or docking more.
- **basic mode**: toggles [Basic Mode](basic-mode.md).
- **visualizer**: toggles pattern view particle effects when the song plays.
- **reset layout**: resets the workspace to its defaults.
- **settings...**: opens the Settings window.
# window
- **song information**: shows/hides the Song Information window.
- **subsongs**: shows/hides the Subsongs window.
- **speed**: shows/hides the Speed window.
- **instruments**: shows/hides the instrument list.
- **wavetables**: shows/hides the wavetable list.
- **samples**: shows/hides the sample list.
- **orders**: shows/hides the Orders window.
- **pattern**: shows/hides the pattern view.
- **mixer**: shows/hides the Mixer window.
- **grooves**: shows/hides the Grooves window.
- **channels**: shows/hides the Channels window.
- **pattern manager**: shows/hides the Pattern Manager window.
- **chip manager**: shows/hides the Chip Manager window.
- **compatibility flags**: shows/hides the Compatibility Flags window.
- **song comments**: shows/hides the Song Comments window.
- **instrument editor**: shows/hides the Instrument Editor.
- **wavetable editor**: shows/hides the Wavetable Editor.
- **sample editor**: shows/hides the Sample Editor.
- **play/edit controls**: shows/hides the Play/Edit Controls.
- **piano/input pad**: shows/hides the Piano/Input Pad window.
- **oscilloscope (master)**: shows/hides the oscilloscope.
- **oscilloscope (per-channel)**: shows/hides the per-channel oscilloscope.
- **volume meter**: shows/hides the volume meter.
- **clock**: shows/hides the clock.
- **register view**: shows/hides the Register View window.
- **log viewer**: shows/hides the log Viewer.
- **statistics**: shows/hides the Statistics window.
# help
- **effect list**: displays the effect list.
- **debug menu**: this menu contains various debug utilities.
- unless you are working with the Furnace codebase, it's not useful.
- **inspector**: this options opens the Dear ImGui Metrics/Debugger window.
- unless you are working with the Furnace codebase, it's not useful.
- **panic**: this resets all chips while the song is playing, effectively silencing everything.
- **about...**: displays the About screen.

View File

@ -0,0 +1,37 @@
# play/edit controls
The "Play/Edit Controls" are used to control playback and change parameters of the pattern view.
- ![](control-play.png) **Play**: Plays from cursor position.
- ![](control-stop.png) **Stop**: Stops all playback.
- ![](control-play-pattern.png) **Play from the beginning of this pattern**: Plays from the start of current pattern.
- ![](control-play-repeat.png) **Repeat from the beginning of this pattern**: Repeats current pattern from its start.
- ![](control-step.png) **Step one row**: Plays only the row at cursor position and moves down one.
- ![](control-edit.png) **Edit**: Toggles edit mode. If off, nothing can be edited in the pattern window. (Great for playing around on the keyboard!)
- ![](control-metronome.png) **Metronome**: Toggles the metronome, which only sounds during playback.
- ![](control-repeat.png) **Repeat pattern**: Toggles pattern repeat. During playback while this is on, the current pattern will play over and over instead of following the order list.
- **Poly**: Turns on polyphony for previewing notes. Toggles to **Mono** for monophony (one note at a time only).
- **Octave**: Sets current input octave.
- **Step**: Sets edit step. If this is 1, entering a note or effect will move to the next row. If this is a larger number, rows will be skipped. If this is 0, the cursor will stay in place.
- **Follow orders**: If on, the selected order in the orders window will follow the song during playback.
- **Follow pattern**: If on, the cursor will follow playback and the song will scroll by as it plays.
## layouts
The layout can be changed in Settings > Appearance to one of these:
### classic
![classic play/edit controls](controls-classic.png)
### compact
![compact play/edit controls](controls-compact.png)
### compact (vertical)
![compact vertical play/edit controls](controls-vertical.png)
### split
![split play and edit controls](controls-split.png)

BIN
doc/2-interface/samples.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -0,0 +1,49 @@
# song info
- **Name**: The track's title.
- **Author**: List of contributors to a song. If the song is a cover of someone else's track, it's customary to list their name first, followed by `[cv. YourName]`.
- **Album**: The associated album name, the name of the game the song is from, or whatever.
- **System**: The game console or computer the track is designed for. This is automatically set when creating a new tune, but it can be changed to anything one wants. The **Auto** button will provide a guess based on the chips in use.
All of this metadata will be included in a VGM export. This isn't the case for a WAV export, however.
**Tuning (A-4)**: Set tuning based on the note A-4, which should be 440 in most cases. Opening an Amiga MOD will set it to 436 for hardware compatibility.
# subsongs
This window allows one to create **subsongs** multiple individual songs within a single file. Each song has its own order list and patterns, but all songs within a file share the same chips, samples, and so forth.
- The drop-down box selects the current subsong.
- The **`+`** button adds a new subsong.
- The **``** button permanently deletes the current subsong (unless it's the only one).
- **Name**: Title of the current subsong.
- The box at the bottom can store any arbitrary text, like a separate "Comments" box for the current subsong.
# speed
There are multiple ways to set the tempo of a song.
**Tick Rate**: The frequency of ticks per second, thus the rate at which notes and effects are processed.
- All values are allowed for all chips, though most chips have hardware limitations that mean they should stay at either 60 (approximately NTSC) or 50 (exactly PAL).
- Clicking the Tick Rate button switches to a more traditional **Base Tempo** BPM setting.
**Speed**: The number of ticks per row.
- Clicking the "Speed" button changes to more complex modes covered in the [grooves] page.
**Virtual Tempo**: Simulates any arbitrary tempo without altering the tick rate. It does this by adding or skipping ticks to approximate the tempo. The two numbers represent a ratio applied to the actual tick rate. Example:
- Set tick rate to 150 BPM (60 Hz) and speed to 6.
- Set the first virtual tempo number (numerator) to 200.
- Set the second virtual tempo number (denominator) to 150.
- The track will play at 200 BPM.
- The ratio doesn't have to match BPM numbers. Set the numerator to 4 and the denominator to 5, and the virtual BPM becomes 150 × 4/5 = 120.
**Divider**: Changes the effective tick rate. A tick rate of 60Hz and a divisor of 6 will result in ticks lasting a tenth of a second each!
**Highlight**: Sets the pattern row highlights:
- The first value represents the number of rows per beat.
- The second value represents the number of rows per measure.
- These don't have to line up with the music's actual beats and measures. Set them as preferred for tracking. _Note:_ These values are used for the metronome and calculating BPM.
**Pattern Length**: The length of each pattern in rows. This affects all patterns in the song, and every pattern must be the same length. (Individual patterns can be cut short by `0Bxx`, `0Dxx`, and `FFxx` commands.)
**Song Length**: How many orders are in the order list. Decreasing it will hide the orders at the bottom. Increasing it will restore those orders; increasing it further will add new orders of all `00` patterns.

BIN
doc/2-interface/tab1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
doc/2-interface/tab2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
doc/2-interface/tab3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
doc/2-interface/window.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
doc/3-pattern/channels.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@ -3,74 +3,74 @@
most of the effect numbers are from ProTracker/FastTracker 2.
however, effects are continuous, which means you only need to type it once and then stop it with an effect value of `00`.
- `00xy`: arpeggio. after using this effect the channel will rapidly switch between `note`, `note+x` and `note+y`.
- `01xx`: slide up.
- `02xx`: slide down.
- `03xx`: note portamento.
- **`00xy`**: arpeggio. after using this effect the channel will rapidly switch between `note`, `note+x` and `note+y`.
- **`01xx`**: slide up.
- **`02xx`**: slide down.
- **`03xx`**: note portamento.
- a note must be present for this effect to work.
- `04xy`: vibrato. `x` is the speed, while `y` is the depth.
- **`04xy`**: vibrato. `x` is the speed, while `y` is the depth.
- maximum vibrato depth is ±1 semitone.
- `07xy`: tremolo. `x` is the speed, while `y` is the depth.
- **`07xy`**: tremolo. `x` is the speed, while `y` is the depth.
- maximum tremolo depth is -60 volume steps.
- `08xy`: set panning. `x` is the left channel and `y` is the right one.
- **`08xy`**: set panning. `x` is the left channel and `y` is the right one.
- not all chips support this effect.
- `80xx`: set panning (linear). this effect behaves more like other trackers:
- **`80xx`**: set panning (linear). this effect behaves more like other trackers:
- `00` is left.
- `80` is center.
- `FF` is right.
- not all chips support this effect.
- `81xx`: set volume of left channel (from `00` to `FF`).
- **`81xx`**: set volume of left channel (from `00` to `FF`).
- not all chips support this effect.
- `82xx`: set volume of right channel (from `00` to `FF`).
- **`82xx`**: set volume of right channel (from `00` to `FF`).
- not all chips support this effect.
- `09xx`: set speed 1.
- `0Axy`: volume slide.
- **`09xx`**: set speed 1.
- **`0Axy`**: volume slide.
- if `x` is 0 then this is a slide down.
- if `y` is 0 then this is a slide up.
- `0Bxx`: jump to pattern.
- `0Cxx`: retrigger note every `xx` ticks.
- **`0Bxx`**: jump to pattern.
- **`0Cxx`**: retrigger note every `xx` ticks.
- this effect is not continuous.
- `0Dxx`: jump to next pattern.
- `0Fxx`: set speed 2.
- **`0Dxx`**: jump to next pattern.
- **`0Fxx`**: set speed 2.
- `9xxx`: set sample position to `xxx`\*0x100.
- **`9xxx`**: set sample position to `xxx`\*0x100.
- not all chips support this effect.
- `Cxxx`: change song Hz.
- **`Cxxx`**: change song Hz.
- `xxx` may be from `000` to `3ff`.
- `E0xx`: set arpeggio tick.
- **`E0xx`**: set arpeggio tick.
- this sets the number of ticks between arpeggio values.
- `E1xy`: note slide up. `x` is the speed, while `y` is how many semitones to slide up.
- `E2xy`: note slide down. `x` is the speed, while `y` is how many semitones to slide down.
- `E3xx`: set vibrato direction. `xx` may be one of the following:
- **`E1xy`**: note slide up. `x` is the speed, while `y` is how many semitones to slide up.
- **`E2xy`**: note slide down. `x` is the speed, while `y` is how many semitones to slide down.
- **`E3xx`**: set vibrato direction. `xx` may be one of the following:
- `00`: up and down.
- `01`: up only.
- `02`: down only.
- `E4xx`: set vibrato range in 1/16th of a semitone.
- `E5xx`: set pitch. `80` is 0 cents.
- **`E4xx`**: set vibrato range in 1/16th of a semitone.
- **`E5xx`**: set pitch. `80` is 0 cents.
- range is ±1 semitone.
- `EAxx`: toggle legato.
- `EBxx`: set sample bank.
- **`EAxx`**: toggle legato.
- **`EBxx`**: set sample bank.
- does not apply on Amiga.
- `ECxx`: note off after `xx` ticks.
- `EDxx`: delay note by `xx` ticks.
- `EExx`: send external command.
- **`ECxx`**: note off after `xx` ticks.
- **`EDxx`**: delay note by `xx` ticks.
- **`EExx`**: send external command.
- this effect is currently incomplete.
- `F0xx`: change song Hz by BPM value.
- `F1xx`: single tick slide up.
- `F2xx`: single tick slide down.
- `F3xx`: fine volume slide up (64x slower than `0Axy`).
- `F4xx`: fine volume slide down (64x slower than `0Axy`).
- `F5xx`: disable macro.
- **`F0xx`**: change song Hz by BPM value.
- **`F1xx`**: single tick slide up.
- **`F2xx`**: single tick slide down.
- **`F3xx`**: fine volume slide up (64x slower than `0Axy`).
- **`F4xx`**: fine volume slide down (64x slower than `0Axy`).
- **`F5xx`**: disable macro.
- see macro table at the end of this document for possible values.
- `F6xx`: enable macro.
- `F8xx`: single tick volume slide up.
- `F9xx`: single tick volume slide down.
- `FAxy`: fast volume slide (4x faster than `0Axy`).
- **`F6xx`**: enable macro.
- **`F8xx`**: single tick volume slide up.
- **`F9xx`**: single tick volume slide down.
- **`FAxy`**: fast volume slide (4x faster than `0Axy`).
- if `x` is 0 then this is a slide down.
- if `y` is 0 then this is a slide up.
- `FFxx`: end of song/stop playback.
- **`FFxx`**: end of song/stop playback.
additionally each chip has its own effects. [click here for more details](../7-systems/README.md).

BIN
doc/3-pattern/keyboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
doc/3-pattern/pattern.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

16
doc/4-instrument/8930.md Normal file
View File

@ -0,0 +1,16 @@
# AY8930 instrument editor
AY8930 instrument editor consists of these macros.
- **Volume**: volume sequence
- **Arpeggio**: pitch in half-steps
- **Noise Freq**: AY8930 noise generator frequency sequence
- **Waveform**: selector of sound type: pulse wave tone, noise or envelope generator
- **Pitch**: fine pitch
- **Phase Reset**: trigger restart of waveform
- **Duty**: duty cycle of a pulse wave sequence
- **Envelope**: allows shaping an envelope
- **AutoEnv Num**: sets the envelope to the channel's frequency multiplied by numerator
- **AutoEnv Den**: sets the envelope to the channel's frequency divided by denominator
- **Noise AND Mask**: alters the shape/frequency of the noise generator, allowing to produce various interesting sound effects and even PWM phasing
- **Noise OR Mask**: see above

View File

@ -0,0 +1,99 @@
# instrument list
![instrument list](list.png)
click on an instrument to select it.
double-click to open the instrument editor.
# instrument editor
every instrument can be renamed and have its type changed.
depending on the instrument type, there are currently 13 different types of an instrument editor:
- [FM synthesis](fm.md) - for use with YM2612, YM2151 and FM block portion of YM2610.
- [Standard](standard.md) - for use with NES and Sega Master System's PSG sound source and its derivatives.
- [Game Boy](game-boy.md) - for use with Game Boy APU.
- [PC Engine / TurboGrafx-16](pce.md) - for use with PC Engine's wavetable synthesizer.
- [WonderSwan](wonderswan.md) - for use with WonderSwan's wavetable synthesizer.
- [AY8930](8930.md) - for use with Microchip AY8930 E-PSG sound source.
- [Commodore 64](c64.md) - for use with Commodore 64 SID.
- [SAA1099](saa.md) - for use with Philips SAA1099 PSG sound source.
- [TIA](tia.md) - for use with Atari 2600 chip.
- [AY-3-8910](ay8910.md) - for use with AY-3-8910 PSG sound source and SSG portion in YM2610.
- [Amiga / sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM, X1-010 and PC Engine's sample playback mode.
- [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console.
- [VERA](vera.md) - for use with Commander X16 VERA.
- [Seta/Allumer X1-010](x1_010.md) - for use with Wavetable portion in Seta/Allumer X1-010.
- [Konami SCC / Bubble System WSG](scc.md) - for use with Konami SCC and Wavetable portion in Bubble System's sound hardware.
- [Namco 163](n163.md) - for use with Namco 163.
- [Konami VRC6](vrc6.md) - for use with VRC6's PSG sound source.
# macros
Macros are incredibly versatile tools for automating instrument parameters.
After creating an instrument, open the Instrument Editor and select the "Macros" tab. There may be multiple macro tabs to control individual FM operators and such.
![macro view](macroview.png)
The very first numeric entry sets the visible width of the bars in sequence-type macros. The scrollbar affects the view of all macros at once. There's a matching scrollbar at the bottom underneath all the macros.
Each macro has two buttons on the left.
- Macro type (explained below).
- Timing editor, which pops up a small dialog:
- Step Length (ticks): Determines how many ticks pass before each change of value.
- Delay: Delays the start of the macro until this many ticks have passed.
## macro types
Every macro can be defined though one of three methods, selectable with the leftmost button under the macro type label:
- ![](macro-button-seq.png) **Sequence:** Displayed as a bar graph, this is a sequence of numeric values.
- ![](macro-button-ADSR.png) **ADSR:** This is a traditional ADSR envelope, defined by the rate of increase and decrease of value over time.
- ![](macro-button-LFO.png) **LFO:** The Low Frequency Oscillator generates a repeating wave of values.
Some macros are "bitmap" style. They represent a number of "bits" that can be toggled individually, and the values listed represent the sum of which bits are turned on.
### sequence
![sequence macro editor](macro-seq.png)
The number between the macro type label and the macro type button is the macro length in steps. The `-` and `+` buttons change the length of the macro. Start out by adding at least a few steps.
The values of the macro can be drawn in the "bar graph box". Just beneath the box is shorter bar graph.
- Click to set the start point of a loop; the end point is the last value or release point. Right-click to remove the loop.
- Shift-click to set the release point. When played, the macro will hold here until the note is released. Right-click to remove the release point.
Finally, the sequence of values can be directly edited in the text box at the bottom.
- The loop start is entered as a `|`.
- The release point is entered as a `/`.
- In arpeggio macros, a value starting with a `@` is an absolute note (instead of a relative shift). No matter the note played, `@` values will be played at that exact note. This is especially useful for noise instruments with preset periods.
### ADSR
![ADSR macro editor](macro-ADSR.png)
- **Bottom** and **Top** determine the range of outputs generated by the macro. (Bottom can be larger than Top to invert the envelope!) All outputs will be between these two values.
- Attack, Decay, Sustain, SusDecay, and Release accept inputs between 0 to 255. These are scaled to the distance between Bottom and Top.
- **Attack** is how much the value moves toward Top with each tick.
- **Hold** sets how many ticks to stay at Top before Decay.
- **Decay** is how much the value moves to the Sustain level.
- **Sustain** is how far from Bottom the value stays while the note is held.
- **SusTime** is how many ticks to stay at Sustain until SusDecay.
- **SusDecay** is how much the value moves toward Bottom with each tick while the note is held.
- **Release** is how much the value moves toward Bottom with each tick after the note is released.
![macro ADSR chart](macro-ADSRchart.png)
### LFO
![LFO macro editor](macro-LFO.png)
- **Bottom** and **Top** determine the range of values generated by the macro. (Bottom can be larger than Top to invert the waveform!)
- **Speed** is how quickly the values change the frequency of the oscillator.
- **Phase** is which part of the waveform the macro will start at, measured in 1/1024 increments.
- **Shape** is the waveform used. Triangle is the default, and Saw and Square are exactly as they say.

17
doc/4-instrument/amiga.md Normal file
View File

@ -0,0 +1,17 @@
# Amiga/PCM sound sourceinstrument editor
The PCM instrument editor consists of a sample selector and several macros:
# Amiga/sample
- **Initial sample**: specifies which sample should be assigned to the instrument, or the first one in the sequence
# Macros
- **Volume**: volume sequence WARNING: it works only on Amiga system, as of version 0.5.5!!
- **Arpeggio**: pitch sequence
- **Waveform**: sample sequence
- **Panning (left)**: output level for left channel
- **Panning (right)**: output level for right channel
- **Pitch**: fine pitch
- **Phase Reset**: trigger restart of waveform

View File

@ -0,0 +1,13 @@
# AY-3-8910 instrument editor
The AY-3-8910 instrument editor consists of these macros.
- **Volume**: volume levels sequence
- **Arpeggio**: pitch sequence
- **Noise Freq**: AY-3-8910 noise generator frequency sequence
- **Waveform**: selector of sound type - square wave tone, noise or envelope generator
- **Pitch**: fine pitch
- **Phase Reset**: trigger restart of waveform
- **Envelope**: allows shaping an envelope
- **AutoEnv Num**: sets the envelope to the channel's frequency multiplied by numerator
- **AutoEnv Den**: sets the envelope to the channel's frequency multiplied by denominator

35
doc/4-instrument/c64.md Normal file
View File

@ -0,0 +1,35 @@
# C64 SID instrument editor
The C64 instrument editor consists of two tabs: "C64" to control various parameters of sound channels, and "Macros" containing several macros.
## C64
- **Waveform**: allows selecting a waveform. NOTE: more than one waveform can be selected at once, logical AND mix of waves will be produced, with an exception of a noise waveform, it can't be mixed.
- **Attack**: determines the rising time for the sound. The bigger the value, the slower the attack. (0-15 range)
- **Decay**: Determines the diminishing time for the sound. The higher the value, the longer the decay. It's the initial amplitude decay rate. (0-15 range)
- **Sustain**: Sets the volume level at which the sound stops decaying and holds steady. (0-15 range)
- **Release**: Determines the rate at which the sound disappears after KEY-OFF. The higher the value, the longer the release. (0-15 range)
- **Ring Modulation**: enables the ring modulation affecting the instrument.
- **Duty**: specifies the width of a pulse wave. (0-4095 range)
- **Oscillator Sync**: enables the oscillator hard sync. As one oscillator finishes a cycle, it resets the period of another oscillator, forcing the latter to have the same base frequency. This can produce a harmonically rich sound, the timbre of which can be altered by varying the synced oscillator's frequency.
- **Enable filter**: enables analogue filter affecting the instrument
- **Initialize filter**: initializes the filter with the specified parameters:
- **Cutoff**: defines the "intensity" of a filter, to put in in layman terms (0-2047 range)
- **Resonance**: defines an additional controlled amplification of that cutoff frequency, creating a secondary peak forms and colors the original pitch. (0-15 range)
- **Filter mode**: determined the filter mode NOTE: SID's filter is multi-mode, you can mix different modes together (like low and high-pass filters at once) CH3-OFF disables the channel 3, for no reason whatsoever lmao
- **Volume Macro is Cutoff Macro**: turns a volume macro in a macros tab into a filter cutoff macro.
- **Absolute Cutoff Macro**: changes the behaviour of a cutoff macro from the old-style, compatible to much more define-able.
- **Absolute Duty Macro**: changes the behaviour of a duty cycle macro from the old-style, compatible to much more definable.
- **Don't test/gate before new note**: Don't reset the envelope to zero when a new note starts. (Read "Test/Gate" below for more info.)
## Macros
- **Volume**: volume sequence (WARNING: Volume sequence is global for ALL three channels!!)
- **Arpeggio**: pitch sequence
- **Duty**: pulse duty cycle sequence
- **Waveform**: select the waveform used by instrument
- **Pitch**: fine pitch
- **Filter mode**: select the filter mode/sequence
- **Resonance**: filter resonance sequence
- **Special**: ring and oscillator sync selector
- **Test/Gate**: When on, the TEST bit resets and locks Oscillator 1 at zero until cleared. The GATE bit controls Oscillator 1's envelope: Gate on runs through the envelope's attack, delay, and sustain; Gate off is envelope release.

47
doc/4-instrument/fm.md Normal file
View File

@ -0,0 +1,47 @@
# FM synthesis instrument editor
FM editor is divided into 7 tabs:
- **FM**: for controlling the basic parameters of FM sound source.
- **Macros (FM)**: for macros controlling algorithm, feedback and LFO
- **Macros (OP1)**: for macros controlling FM paramets of operator 1
- **Macros (OP2)**: for macros controlling FM paramets of operator 2
- **Macros (OP3)**: for macros controlling FM paramets of operator 3
- **Macros (OP4)**: for macros controlling FM paramets of operator 4
- **Macros**: for miscellaneous macros controlling volume, argeggio and YM2151 noise generator.
## FM
FM synthesizers Furnace supports are four-operator, meaning it takes four oscillators to produce a single sound. Each operator is controlled by a dozen sliders:
- **Attack Rate (AR)**: determines the rising time for the sound. The bigger the value, the faster the attack. (0-31 range)
- **Decay Rate (DR)**: Determines the diminishing time for the sound. The higher the value, the shorter the decay. It's the initial amplitude decay rate. (0-31 range)
- **Secondary Decay Rate (DR2)/Sustain Rate (SR)**: Determines the diminishing time for the sound. The higher the value, the shorter the decay. This is the long "tail" of the sound that continues as long as the key is depressed. (0-31 range)
- **Release Rate (RR)**: Determines the rate at which the sound disappears after KEY-OFF. The higher the value, the shorter the release. (0-15 range)
- **Sustain Level(SL)**: Determines the point at which the sound ceases to decay and changes to a sound having a constant level. The sustain level is expressed as a fraction of the maximum level. (0-15 range)
- **Total Level (TL)**: Represents the envelopes highest amplitude, with 0 being the largest and 127 (decimal) the smallest. A change of one unit is about 0.75 dB.
- **Envelope Scale (KSR)**: A parameter that determines the degree to which the envelope execution speed increases according to the pitch. (0-3 range)
- **Frequency Multiplier (MULT)**: Determines the operator frequency in relation to the pitch. (0-15 range)
- **Fine Detune (DT)**: Shifts the pitch a little (0-7 range)
- **Coarse Detune (DT2)**: Shifts the pitch by tens of cents (0-3 range) WARNING: this parameter affects only YM2151 sound source!!!
- **Hardware Envelope Generator (SSG-EG)**: Executes the built-in envelope, inherited from AY-3-8910 PSG. Speed of execution is controlled via Decay Rate. WARNING: this parameter affects only YM2610/YM2612 sound source!!!
- **Algorithm (AL)**: Determines how operators are connected to each other. (0-7 range)
- **Feedback (FB)**: Determines the amount of signal whick operator 1 returns to itself. (0-7 range)
- **Amplitude Modulation (AM)**: Makes the operator affected by LFO.
- **LFO Frequency Sensitivity**: Determines the amount of LFO frequency changes. (0-7 range)
- **LFO Amplitude Sensitivity (AM)**: Determines the amount of LFO frequency changes. (0-3 range)
## Macros
Macros define the sequence of values passed to the given parameter. Via macro, aside previously mentioned parameters, the following can be controlled:
- **LFO Frequency**
- **LFO Waveform**: _WARNING:_ this parameter affects only YM2151 sound source!
- **Amplitude Modulation Depth**: _WARNING:_ this parameter affects only YM2151 sound source!
- **Frequency Modulation Depth**: _WARNING:_ this parameter affects only YM2151 sound source!
- **Arpeggio Macro**: Pitch change sequence in semitones. Two modes are available:
- **Absolute** (default): Executes the pitch with absolute change based on the pitch of the actual note.
- **Fixed**: Executes at the pitch specified in the sequence regardless of the note pitch.
- **Noise Frequency**: specifies the noise frequency in noise mode of YM2151's Channel 8 Operator 4 special mode.
Looping: You can loop the execution of part of a sequence. Left-click anywhere on the Loop line at the bottom of the editor to create a loop. You can move the start and end points of the loop by dragging both ends of the loop. Rigkt-click to remove the loop.

View File

@ -0,0 +1,25 @@
# Game Boy instrument editor
GB instrument editor consists of two tabs: one controlling envelope of sound channels and macro tab containing several macros.
## Game Boy
- **Use software envelope**: switch to volume macro instead of envelope
- **Initialize envelope on every note**: forces a volume reset on each new note
- **Volume**: initial channel volume (range 0-15)
- **Length**: envelope decay/attack duration (range 0-7)
- **Sound Length**: cuts off sound after specified length, overriding the Length value
- **Up and Down radio buttons**: Up makes the envelope an attack, down makes it decay. _Note:_ For envelope attack to have any effect, start at a lower volume!
- **Hardware Sequence**: (document this)
## Macros
- **Volume**: volume sequence. _Note:_ This only appears if "Use software envelope" is checked.
- **Arpeggio**: pitch in half-steps
- **Duty/Noise**: pulse wave duty cycle or noise mode sequence
- **Waveform**: ch3 wavetable sequence
- **Panning**: output for left and right channels
- **Pitch**: fine pitch
- **Phase Reset**: trigger restart of waveform

BIN
doc/4-instrument/list.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

View File

@ -1,15 +1,18 @@
# Atari Lynx instrument editor
Atari Lynx instrument editor consists of only three macros:
Atari Lynx instrument editor consists of these macros:
- [Volume] - volume sequence
- [Arpeggio] - pitch sequencer
- [Duty/Int] - bit pattern for LFSR taps and integration.
- **Volume**: volume sequence
- **Arpeggio**: pitch in half-steps
- **Duty/Int**: bit pattern for LFSR taps and integration
- **Panning (left)**: output level for left channel
- **Panning (right)**: output level for right channel
- **Pitch**: fine pitch
- **Phase Reset**: trigger restart of waveform
## Audio generation description
Atari Lynx to generate sound uses 12-bit linear feedback shift register with configurable tap. Nine separate bits can be enable to be the source of feedback.
Namely bits 0, 1, 2, 3, 4, 5, 7, 10 and 11. To generate ANY sound at least one bit MUST be enable.
Atari Lynx generates sound using a 12-bit linear feedback shift register with configurable tap. Nine separate bits can be enabled to be the source of feedback: 0, 1, 2, 3, 4, 5, 7, 10 and 11. To generate _any_ sound at least one bit _must_ be enabled.
### Square wave

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

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