Merge branch 'master' into force-critical-input-trickle

This commit is contained in:
tildearrow 2023-07-12 00:04:03 -05:00
commit 09a3da50eb
70 changed files with 1547 additions and 228 deletions

2
.gitignore vendored
View File

@ -15,6 +15,8 @@ test/songs/
test/delta/
test/result/
test/assert_delta
android/local.properties
android/.idea/
android/.gradle/
android/app/build/
android/app/.cxx/

View File

@ -805,7 +805,6 @@ if (BUILD_GUI)
list(APPEND USED_SOURCES ${GUI_SOURCES})
list(APPEND DEPENDENCIES_INCLUDE_DIRS
extern/imgui_patched
extern/imgui_conf
extern/imgui_patched/backends
extern/IconFontCppHeaders
extern/igfd
@ -877,7 +876,7 @@ else()
endif()
target_include_directories(furnace SYSTEM PRIVATE ${DEPENDENCIES_INCLUDE_DIRS})
target_compile_definitions(furnace PRIVATE ${DEPENDENCIES_DEFINES} IMGUI_USER_CONFIG="imconfig_fur.h")
target_compile_definitions(furnace PRIVATE ${DEPENDENCIES_DEFINES})
target_compile_options(furnace PRIVATE ${DEPENDENCIES_COMPILE_OPTIONS})
target_link_libraries(furnace PRIVATE ${DEPENDENCIES_LIBRARIES})
if (PKG_CONFIG_FOUND AND (SYSTEM_FMT OR SYSTEM_LIBSNDFILE OR SYSTEM_ZLIB OR SYSTEM_SDL2 OR SYSTEM_RTMIDI OR WITH_JACK))

View File

@ -9,7 +9,9 @@ the biggest multi-system chiptune tracker ever made!
---
## downloads
check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage).
check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux.
for other operating systems, you may [build the source](#developer-info).
[see here](https://nightly.link/tildearrow/furnace/workflows/build/master) for the latest unstable build.
@ -79,6 +81,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a
- modern/fantasy:
- Commander X16 VERA
- tildearrow Sound Unit
- Generic PCM DAC
- mix and match sound chips!
- over 200 ready to use presets from computers, game consoles and arcade boards...
- ...or create your own - up to 32 of them or a total of 128 channels!
@ -90,6 +93,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a
- clean-room design (guesswork and ABX tests only, no decompilation involved)
- some bug/quirk implementation for increased playback accuracy through compatibility flags
- VGM export
- ZSM export for Commander X16
- modular layout that you may adapt to your needs
- audio file export - entire song, per chip or per channel
- quality emulation cores (Nuked, MAME, SameBoy, Mednafen PCE, NSFplay, puNES, reSID, Stella, SAASound, vgsound_emu and ymfm)
@ -120,11 +124,11 @@ 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, 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.
- **help**: check out the [documentation](doc/README.md). it's about 80% complete.
## packages
[![Packaging status](https://repology.org/badge/tiny-repos/furnace.svg)](https://repology.org/project/furnace/versions)
[![Packaging status](https://repology.org/badge/vertical-allrepos/furnace.svg)](https://repology.org/project/furnace/versions)
some people have provided packages for Unix/Unix-like distributions. here's a list.
@ -156,6 +160,7 @@ otherwise, you may also need the following:
- libx11
- libasound
- libGL
- any other libraries which may be used by SDL
some Linux distributions (e.g. Ubuntu or openSUSE) will require you to install the `-dev` versions of these.
@ -255,6 +260,17 @@ Available options:
| `WITH_INSTRUMENTS` | `ON` | Install demo instruments on `make install` |
| `WITH_WAVETABLES` | `ON` | Install wavetables on `make install` |
## CMake Error
if it says something about a missing subdirectory in `extern`, then either:
1. you didn't set up submodules, or
2. you downloaded the source as a .zip or .tar.gz. don't do this.
if 1, you may run `git submodule update --init --recursive`. this will initialize submodules.
if 2, clone this repo.
## console usage
(note: if on Windows, type `furnace.exe` instead, or `Debug\furnace.exe` on MSVC)
@ -289,7 +305,7 @@ this is due to Apple's application signing policy. a workaround is to right clic
> 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):
if you happen to be on that version (or later), use this workaround instead (on a Terminal):
```
xattr -d com.apple.quarantine /path/to/Furnace.app
@ -301,7 +317,7 @@ 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.
it is in [doc/](doc/README.md).
> is there a tutorial?

View File

@ -1,4 +1,4 @@
# to-do for 0.6pre6
# to-do for 0.6pre7
- tutorial?
- ease-of-use improvements... ideas:

View File

@ -15,8 +15,8 @@ android {
}
minSdkVersion 21
targetSdkVersion 26
versionCode 158
versionName "0.6pre5"
versionCode 162
versionName "0.6pre7"
externalNativeBuild {
cmake {
arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static", "-DWARNINGS_ARE_ERRORS=ON"

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="158"
android:versionName="0.6pre5"
android:versionCode="162"
android:versionName="0.6pre7"
android:installLocation="auto">
<!-- OpenGL ES 2.0 -->

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/sms/FlowOfSN7.fur Normal file

Binary file not shown.

View File

@ -36,6 +36,7 @@ advanced topics:
other topics:
- [settings](../2-interface/settings.md)
- [UI components](components.md)
- [global keyboard shortcuts](keyboard.md)
- [basic mode](basic-mode.md)

View File

@ -8,6 +8,7 @@ the menu bar allows you to select five menus: file, edit, settings, window and h
- **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.
@ -29,12 +30,14 @@ the menu bar allows you to select five menus: file, edit, settings, window and h
- 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).
@ -42,6 +45,7 @@ the menu bar allows you to select five menus: file, edit, settings, window and h
- **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:
@ -49,6 +53,7 @@ the menu bar allows you to select five menus: file, edit, settings, window and h
- 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
@ -131,6 +136,7 @@ it's not really useful, unless you're a developer and want to use a command stre
- **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.
@ -147,11 +153,16 @@ it's not really useful, unless you're a developer and want to use a command stre
- 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.
@ -163,17 +174,22 @@ it's not really useful, unless you're a developer and want to use a command stre
- **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
@ -183,7 +199,7 @@ it's not really useful, unless you're a developer and want to use a command stre
- **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.
- **settings...**: opens the Settings window. these are detailed in [settings.md].
# window
@ -202,9 +218,11 @@ it's not really useful, unless you're a developer and want to use a command stre
- **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.
- **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.

387
doc/2-interface/settings.md Normal file
View File

@ -0,0 +1,387 @@
# settings
settings are saved when clicking the **OK** button at the bottom of the dialog.
# General
- **Workspace layout**
- **Import**: reads a .ini layout file.
- **Export**: writes current layout to a .ini file.
- **Reset**: resets layout to default.
- **Initial system**: the system of chips loaded on starting Furnace.
- **Current system**: sets current chips as default.
- **Randomize**: set default to a random system.
- this will not choose a random system at each start.
- **Reset to defaults**: sets default to "Sega Genesis/Mega Drive".
- **Name**: name for the default system. may be set to any text.
- system configuration: same as in the [chip manager](../8-advanced/chip-manager.md) and [mixer](../8-advanced/mixer.md).
- **Play intro on start-up:**
- **No**: skips intro entirely.
- **Short**: shows silent title screen briefly.
- **Full (short when loading song)**: shows animated musical intro unless started with a song (command line, double-clicking a .fur file, etc.)
- **Full (always)**: always shows animated musical intro.
- **When creating new song**:
- **Display system preset selector**
- **Start with initial system**
- **Double-click time (seconds)**: maximum time between mouse clicks to recognize them as a double-click.
- **Toggle channel solo on:** select which interactions with a channel header will toggle solo for that channel.
- **Push value when overwriting instead of clearing it**: in the order list and pattern editors, typing into an already-filled value will shift digits instead of starting fresh.
- if off: moving the cursor onto the value `A5` and typing a "B" results in `0B`.
- if on: with the cursor on the value `A5` and typing a "B" results in `5B`.
- **Move cursor up on backspace-delete**
- **Move cursor by edit step on delete**
- **Change current instrument when changing instrument column (absorb)**
- **Delete effect value when deleting effect**
- **Change order when scrolling outside of pattern bounds**:
- if off, the pattern edit cursor will stay locked within the current order.
- if on, moving the cursor past the edge of the previous or next order will move to that order.
- **Move cursor by edit step on insert (push)**
- **Move cursor to end of clipboard content when pasting**
- **Don't scroll when moving cursor**
- **Double click selects entire column**
- **Allow docking editors**
- **Don't raise pattern editor on click**
- **Focus pattern editor when selecting instrument**
- **Restart song when changing chip properties**
- **Use system file picker**: use native OS file dialog instead of Furnace's.
- **Only allow window movement when clicking on title bar**
- **Enable event delay**
- may cause issues with high-polling-rate mice when previewing notes.
- **Power-saving mode**
- saves power by lowering the frame rate to 2fps when idle.
- may cause issues under Mesa drivers!
- **Disable threaded input (restart after changing!)**
- threaded input processes key presses for note preview on a separate thread (on supported platforms), which reduces latency.
- however, crashes have been reported when threaded input is on. enable this option if that is the case.
- **Remember window position**
- remembers the window's last position on start-up.
- **New instruments are blank**
- **Save unused patterns**
- **Compress when saving**
- use zlib to compress saved songs.
- **Cursor follows current order when moving it**
- applies when playback is stopped.
- **Audio export loop/fade out time:**
- **Set to these values on start-up:**
- **Loops**: number of additional times to play through `0Bxx` song loop.
- **Fade out (seconds)**: length of fade out after final loop.
- **Remember last values**
- **Note preview behavior:**
- **Never**
- **When cursor is in Note column**
- **When cursor is in Note column or not in edit mode**
- **Always**
- **Wrap pattern cursor horizontally:**
- **No**
- **Yes**
- **Yes, and move to next/prev row**
- **Wrap pattern cursor vertically:**
- **No**
- **Yes**
- **Yes, and move to next/prev pattern**
- **Cursor movement keys behavior:**
- **Move by one**
- **Move by Edit Step**
- **Effect input cursor behavior:**
- **Move down**
- **Move to effect value (otherwise move down)**
- **Move to effect value/next effect and wrap around**
- **Allow dragging selection:**
- **No**
- **Yes**
- **Yes (while holding Ctrl only)**
# Audio/MIDI
- **Backend**: select SDL or JACK for audio output.
- only appears on Linux, or MacOS compiled with JACK support
- **Device**: audio device for playback.
- **Sample rate**
- **Outputs**: select number of audio outputs created, up to 16.
- only appears when Backend is JACK.
- **Channels**: number of output channels to use.
- **Buffer size**: size of buffer in both samples and milliseconds.
- **Quality**: selects quality of resampling. low quality reduces CPU load.
- **Metronome volume**
- **Low-latency mode (experimental!)**: reduces latency by running the engine faster than the tick rate. useful for live playback/jam mode.
- _warning:_ experimental! may produce glitches. only enable if your buffer size is small (10ms or less).
- **Force mono audio**
- **Software clipping**: clips output to nominal range (-1.0 to 1.0) before passing it to the audio device.
- this avoids activating Windows' built-in limiter.
- **want:** displays requested audio configuration.
- **got:** displays actual audio configuration returned by audio backend.
- **MIDI input**
- **MIDI output**
- **MIDI input settings**
- **Note input**
- **Velocity input**
- **Map MIDI channels to direct channels**
- **Map Yamaha FM voice data to instruments**
- **Program change is instrument selection**
- **Value input style**:
- **Disabled/custom**
- **Two octaves (0 is C-4, F is D#5)**
- **Raw (note number is value)**
- **Two octaves alternate (lower keys are 0-9, upper keys are A-F)**
- **Use dual control change (one for each nibble)**
- **CC of upper nibble**
- **CC of lower nibble**
- **Use 14-bit control change**
- **MSB CC**
- **LSB CC**
- **Use single control change**
- **Control**
- **Per-column control change**
- **Instrument**\
**Volume**\
**Effect `x` type**\
**Effect `x` value**
- **Disabled/custom**
- **Use dual control change (one for each nibble)**
- **CC of upper nibble**
- **CC of lower nibble**
- **Use 14-bit control change**
- **MSB CC**
- **LSB CC**
- **Use single control change (imprecise)**
- **Control**
- **Volume curve**
- **Actions:**
- **`+`** button: adds a new action.
- window-with-arrow button: new action with learning! press a button or move a slider/knob/something on your device.
- each action has the following:
- **Type**
- **Channel**
- **Note/Control**
- **Velocity/Value**
- **Action**
- **Learn**
- **Remove**
- **MIDI output settings**
- **Output mode:**
- **Off (use for TX81Z)**
- **Melodic**
- **Send Program Change**
- **Send MIDI clock**
- **Send MIDI timecode**
- **Timecode frame rate:**
- **Closest to Tick Rate**
- **Film (24fps)**
- **PAL (25fps)**
- **NTSC drop (29.97fps)**
- **NTSC non-drop (30fps)**
# Emulation
- **Arcade/YM2151 core**
- **ymfm**
- **Nuked-OPM**
- **Genesis/YM2612 core**
- **Nuked-OPN2**
- **ymfm**
- **SN76489 core**
- **MAME**
- **Nuked-PSG Mod**
- **NES core**
- **puNES**
- **NSFplay**
- **FDS core**
- **puNES**
- **NSFplay**
- **SID core**
- **reSID**
- **reSIDfp**
- **POKEY core**
- **Atari800 (mzpokeysnd)**
- **ASAP (C++ port)**
- **OPN/OPNA/OPNB cores**
- **ymfm only**
- **Nuked-OPN2 (FM) + ymfm (SSG/ADPCM)**
- **PC Speaker strategy:**
- **evdev SND_TONE**
- **KIOCSOUND on /dev/tty1**
- **/dev/port**
- **KIOCSOUND on standard output**
- **outb()**
- **Sample ROMs:**
- **OPL4 YRW801 path**
- **MultiPCM TG100 path**
- **MultiPCM MU5 path**
# Appearance
- **Render driver**
- **Automatic UI scaling factor**: automatically match the OS's UI scaling.
- **UI scaling factor**: only if "Automatic UI scaling factor" is off.
- **Main font**: if "Custom...", a file path selector will appear beneath.
- **Size**
- **Pattern font**: if "Custom...", a file path selector will appear beneath.
- **Size**
- **Icon size**
- **Display Japanese characters**\
**Display Chinese (Simplified) characters**\
**Display Chinese (Traditional) characters**\
**Display Korean characters**
- only toggle these options if you have enough graphics memory.
- these are a temporary solution until dynamic font atlas is implemented in Dear ImGui.
- **Number of recent files**
- **Pattern view labels:**
- **Note off (3-char)**: default is `OFF`
- **Note release (3-char)**: default is `===`.
- **Macro release (3-char)**: default is `REL`.
- **Empty field (3-char)**: default is `...`.
- **Empty field (2-char)**: default is `..`.
- **Orders row number format:**
- **Decimal**
- **Hexadecimal**
- **Pattern row number format:**
- **Decimal**
- **Hexadecimal**
- **FM parameter names:**
- **Friendly**
- **Technical**
- **Technical (alternate)**
- **Title bar:**
- **Furnace**
- **Song Name - Furnace**
- **file_name.fur - Furnace**
- **/path/to/file.fur - Furnace**
- **Display system name on title bar**
- **Display chip names instead of "multi-system" in title bar**
- **Status bar:**
- **Cursor details**
- **File path**
- **Cursor details or file path**
- **Nothing**
- **Play/edit controls layout:**
- **Classic**
- **Compact**
- **Compact (vertical)**
- **Split**
- **Position of buttons in Orders:**
- **Top**
- **Left**
- **Right**
- **FM parameter editor layout:**
- **Modern**
- **Compact (2x2, classic)**
- **Compact (1x4)**
- **Compact (4x1)**
- **Alternate (2x2)**
- **Alternate (1x4)**
- **Alternate (4x1)**
- **Position of Sustain in FM editor:**
- **Between Decay and Sustain Rate**
- **After Release Rate**
- **Macro editor layout:**
- **Unified**
- **Mobile**
- **Grid**
- **Single (with list)**
- **Single (combo box)**
- **Namco 163 chip name**
- **Channel colors:**
- **Single**
- **Channel type**
- **Instrument type**
- **Channel name colors:**
- **Single**
- **Channel type**
- **Instrument type**
- **Channel style:**
- **Classic**
- **Line**
- **Round**
- **Split button**
- **Square border**
- **Round border**
- **Channel volume bar:**
- **None**
- **Simple**
- **Stereo**
- **Real**
- **Real (stereo)**
- **Channel feedback style:**
- **Off**
- **Note**
- **Volume**
- **Active**
- **Channel font:**
- **Regular**
- **Monospace**
- **Center channel name**
- **Colorize instrument editor using instrument type**
- **Use separate colors for carriers/modulators in FM editor**
- **Unified instrument/wavetable/sample list**
- **Horizontal instrument list**
- **Use standard OPL waveform names**
- **Overflow pattern highlights**
- **Display previous/next pattern**
- **Use German notation**: display `B` notes as `H`, and `A#` notes as `B`.
- **Single-digit effects for 00-0F**
- **Center pattern view**: centers pattern horizontally in view.
- **Unsigned FM detune values**
- **Highlight channel at cursor in Orders**
- **About screen party time**
- _warning:_ may cause epileptic seizures.
- **Use compact wave editor**
- **Use classic macro editor vertical slider**
- **Rounded window corners**
- **Rounded buttons**
- **Rounded menu corners**
- **Borders around widgets**
- **Disable fade-in during start-up**
- **Oscilloscope settings:**
- **Rounded corners**
- **Fill entire window**
- **Waveform goes out of bounds**
- **Border**
- **Pattern view spacing after:**
- **Note**
- **Instrument**
- **Volume**
- **Effect**
- **Effect value**
- **Color scheme**
- **Import**
- **Export**
- **Reset defaults**
- **General**
- **Color scheme type:**
- **Dark**
- **Light**
- **Frame shading**
- several more categories...
# Keyboard
- **Import**
- **Export**
- **Reset defaults**
- several categories of keybinds...
- click on a keybind then enter a key or key combination to change it
- right-click to clear the keybind

View File

@ -7,17 +7,15 @@ however, effects are continuous, which means you only need to type it once and t
## volume
- `0Axy`: **Volume slide.**
- If `x` is 0 then this is a slide down.
- If `y` is 0 then this is a slide up.
- `F8xx`: **Single tick volume slide up.**
- `F9xx`: **Single tick volume slide down.**
- `F3xx`: **Fine volume slide up.** 64× slower than `0Axy`.
- `F4xx`: **Fine volume slide down.** 64× slower than `0Axy`.
- `FAxy`: **Fast volume slide.** 4× faster than `0Axy`.
- If `x` is 0 then this is a slide down.
- If `y` is 0 then this is a slide up.
- If `x` is 0 then this slides volume down by `y` each tick.
- If `y` is 0 then this slides volume up by `x` each tick.
- `FAxy`: **Fast volume slide.** same as `0Axy` above but 4× faster.
- `F3xx`: **Fine volume slide up.** same as `0Ax0` but 64× slower.
- `F4xx`: **Fine volume slide down.** same as `0A0x` but 64× slower.
- `F8xx`: **Single tick volume slide up.** adds `x` to volume on first tick only.
- `F9xx`: **Single tick volume slide down.** subtracts `x` from volume on first tick only.
- `07xy`: **Tremolo.** changes volume to be "wavy" with a sine LFO. `x` is the speed, while `y` is the depth.
- `07xy`: **Tremolo.** changes volume to be "wavy" with a sine LFO. `x` is the speed. `y` is the depth.
- Tremolo is downward only.
- Maximum tremolo depth is -60 volume steps.
@ -29,21 +27,22 @@ however, effects are continuous, which means you only need to type it once and t
- `F1xx`: **Single tick pitch slide up.**
- `F2xx`: **Single tick pitch slide down.**
- `03xx`: **Portamento.** slides the current note's pitch to the specified note.
- `03xx`: **Portamento.** slides the current note's pitch to the specified note. `x` is the slide speed.
- A note _must_ be present for this effect to work.
- `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.
- `EAxx`: **Toggle legato.** while on, notes instantly change the pitch of the currrently playing sound instead of starting it over.
- `00xy`: **Arpeggio.** after using this effect the channel will rapidly switch between semitone values of `note`, `note + x` and `note + y`.
- `E0xx`: **Set arpeggio speed.** this sets the number of ticks between arpeggio values.
- `E0xx`: **Set arpeggio speed.** this sets the number of ticks between arpeggio values. default is 1.
- `04xy`: **Vibrato.** changes pitch to be "wavy" with a sine LFO. `x` is the speed, while `y` is the depth.
- Maximum vibrato depth is ±1 semitone.
- `E3xx`: **Set vibrato direction.** `xx` may be one of the following:
- `00`: Up and down.
- `00`: Up and down. default.
- `01`: Up only.
- `02`: Down only.
- `E4xx`: **Set vibrato range** in 1/16th of a semitone.
- `E4xx`: **Set vibrato range** in 1/16th of a semitone.
## panning
@ -51,15 +50,15 @@ not all chips support these effects.
- `08xy`: **Set panning.** changes stereo volumes independently. `x` is the left channel and `y` is the right one.
- `88xy`: **Set rear panning.** changes rear channel volumes independently. `x` is the rear left channel and `y` is the rear right one.
- `81xx`: **Set volume of left channel** (from `00` to `FF`).
- `82xx`: **Set volume of right channel** (from `00` to `FF`).
- `89xx`: **Set volume of rear left channel** (from `00` to `FF`).
- `8Axx`: **Set volume of rear right channel** (from `00` to `FF`).
- `80xx`: **Set panning (linear).** this effect behaves more like other trackers:
- `00` is left.
- `80` is center.
- `FF` is right.
- `81xx`: **Set volume of left channel** (from `00` to `FF`).
- `82xx`: **Set volume of right channel** (from `00` to `FF`).
- `89xx`: **Set volume of rear left channel** (from `00` to `FF`).
- `8Axx`: **Set volume of rear right channel** (from `00` to `FF`).
## time
@ -67,12 +66,14 @@ not all chips support these effects.
- `0Fxx`: **Set speed 2.** during alternating speeds or a groove, this sets the second speed.
- `Cxxx`: **Set tick rate.** changes tick rate to `xxx` Hz (ticks per second).
- `xxx` may be from `000` to `3ff`.
- `F0xx`: **Set BPM.** changes tick rate according to beats per minute.
- `xxx` may be from `000` to `3FF`.
- `F0xx`: **Set BPM.** changes tick rate according to beats per minute. range is `01` to `FF`.
- `0Bxx`: **Jump to order.** this can be used to loop a song.
- `0Dxx`: **Jump to next pattern.** this can be used to shorten the current order.
- `FFxx`: **Stop song.** stops playback and ends the song.
- `0Bxx`: **Jump to order.** `x` is the order to play after the current row.
- this marks the end of a loop with order `x` as the loop start.
- `0Dxx`: **Jump to next pattern.** skips the current row and remainder of current order.
- this can be used to shorten the current order.
- `FFxx`: **Stop song.** stops playback and ends the song. `x` is ignored.
## note

View File

@ -27,6 +27,7 @@ this is a list of sound chips that Furnace supports, including effects.
- [PC Engine/TurboGrafx-16](pce.md)
- [PC Speaker](pcspkr.md)
- [Philips SAA1099](saa1099.md)
- [Pokémon mini](pokemini.md)
- [Capcom QSound](qsound.md)
- [Ricoh RF5C68](ricoh.md)
- [SegaPCM](segapcm.md)

View File

@ -2,7 +2,7 @@
the Nintendo Game Boy is one of the most successful portable game systems ever made.
with stereo sound, two pulse channels, a wave channel and a noise one it packed some serious punch.
with stereo sound, two pulse channels, a wave channel and a noise channel, it packed some serious punch.
# effects
@ -20,3 +20,9 @@ with stereo sound, two pulse channels, a wave channel and a noise one it packed
- `y` is the shift.
- set to `0` to disable it.
- `14xx`: **set sweep direction.** `0` is up and `1` is down.
# links
- [Gameboy sound hardware](https://gbdev.gg8.se/wiki/articles/Gameboy_sound_hardware) - detailed technical information
- [GameBoy Sound Table](http://www.devrs.com/gb/files/sndtab.html) - note frequency table

View File

@ -0,0 +1,7 @@
# Pokémon Mini
the Pokémon Mini is a ridiculously small handheld system from 2001. its single pulse channel has only three volume steps (full, half, and off)... but variable pulse width.
# effects
none.

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

View File

@ -1,16 +1,53 @@
# oscilloscope (per channel)
The "Oscilloscope (per channel)" dialog shows an individual oscilloscope for each channel during playback.
the "Oscilloscope (per channel)" dialog shows an individual oscilloscope for each channel during playback.
![oscilloscope per-channel configuration view](chanosc.png)
Right-clicking within the view will change it to the configuration view shown above:
- **Columns**: Sets the number of columns the view will be split into.
- **Size (ms)**: Sets what length of audio is visible in each oscilloscope.
- **Center waveform**: Does its best to latch to the channel's note frequency and centers the display.
- **Gradient**: (document this)
- The color selector sets the waveform color. Right-clicking on it pops up an option dialog:
- Select between the square selector and the color wheel selector.
- **Alpha bar**: Adds a transparency selector.
- The boxes below that are for selecting colors numerically by red-green-blue-alpha, hue-saturation-value-alpha, and HTML-style RGBA in hex.
- The OK button returns from options view to regular.
right-clicking within the view will change it to the configuration view shown above:
- **Columns**: arranges oscilloscopes into this many columns.
- **Size (ms)**: sets what length of audio is visible in each oscilloscope.
- **Center waveform**: does its best to latch the waveform to the channel's note frequency and centers the display.
- **Gradient**: see below.
- the color selector sets the waveform color. right-clicking on it pops up an option dialog:
- select between the square selector and the color wheel selector.
- **Alpha bar**: adds a transparency selector.
- the boxes below that are for selecting colors numerically by red-green-blue-alpha, hue-saturation-value-alpha, and HTML-style RGBA in hex.
- **Text format**: this string determins what text is shown in the top-left of each oscilloscope. it can be any text, and the following shortcodes will be replaced with information about the channel:
- `%c`: channel name
- `%C`: channel short name
- `%d`: channel number (starting from 0)
- `%D`: channel number (starting from 1)
- `%i`: instrument name
- `%I`: instrument number (decimal)
- `%x`: instrument number (hex)
- `%s`: chip name
- `%S`: chip ID
- `%v`: volume (decimal)
- `%V`: volume (percentage)
- `%b`: volume (hex)
- `%%`: percent sign
- The OK button returns from options view to the oscilloscopes.
## gradient
![oscilloscope per-channel gradient configuration view](chanosc-gradient.png)
in this mode, the color selector is replaced by a square field onto which circular "stops" can be placed. each stop adds a soft circle of color. the resulting image is used to look up the oscilloscope color as determined by each axis.
- right-click to place a stop.
- left-click on a stop to change its color. the color selector is the same as above, with two additions:
- **Distance**: the size of the circle.
- **Spread**: the size of the solid center of the circle. increasing it fills more of the circle with the target color.
- **Background**: sets background color for entire field.
- **X Axis**: determines what the horizontal maps to, from left to right.\
**Y Axis**: determines what the vertical maps to, from bottom to top. these can be set to the following:
- **None (0%)**: stays at the left or bottom.
- **None (50%)**: stays at the center.
- **None (100%)**: stays at the right or top.
- **Frequency**: changes color with note frequency.
- **Volume**: changes color with volume.
- **Channel**: changes color based on channel number.
- **Brightness**: {{document this}}
- **Note Trigger**: changes color when a new note is played.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 113 KiB

View File

@ -524,7 +524,15 @@ _wreaddir_r(
entry->d_off = 0;
entry->d_reclen = sizeof (struct _wdirent);
#ifdef _WIN64
entry->dwin_size = ((size_t)datap->nFileSizeHigh<<32) | datap->nFileSizeLow;
#else
if (datap->nFileSizeHigh) {
entry->dwin_size = 0xffffffff;
} else {
entry->dwin_size = datap->nFileSizeLow;
}
#endif
entry->dwin_mtime = datap->ftLastWriteTime;
/* Set result address */
@ -817,7 +825,15 @@ readdir_r(
entry->d_off = 0;
entry->d_reclen = sizeof (struct dirent);
#ifdef _WIN64
entry->dwin_size = ((size_t)datap->nFileSizeHigh<<32) | datap->nFileSizeLow;
#else
if (datap->nFileSizeHigh) {
entry->dwin_size = 0xffffffff;
} else {
entry->dwin_size = datap->nFileSizeLow;
}
#endif
entry->dwin_mtime = datap->ftLastWriteTime;
} else {

View File

@ -339,17 +339,22 @@ static void ImGui_ImplDX11_CreateFontsTexture()
subResource.SysMemPitch = desc.Width * 4;
subResource.SysMemSlicePitch = 0;
bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture);
IM_ASSERT(pTexture != nullptr);
// Create texture view
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
ZeroMemory(&srvDesc, sizeof(srvDesc));
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = desc.MipLevels;
srvDesc.Texture2D.MostDetailedMip = 0;
bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &bd->pFontTextureView);
pTexture->Release();
if (pTexture != nullptr) {
// Create texture view
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
ZeroMemory(&srvDesc, sizeof(srvDesc));
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = desc.MipLevels;
srvDesc.Texture2D.MostDetailedMip = 0;
bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &bd->pFontTextureView);
pTexture->Release();
} else {
bd->pFontTextureView=NULL;
bd->pFontSampler=NULL;
return;
}
}
// Store our identifier
@ -361,9 +366,9 @@ static void ImGui_ImplDX11_CreateFontsTexture()
D3D11_SAMPLER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
desc.MipLODBias = 0.f;
desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
desc.MinLOD = 0.f;
@ -609,13 +614,15 @@ void ImGui_ImplDX11_Shutdown()
IM_DELETE(bd);
}
void ImGui_ImplDX11_NewFrame()
bool ImGui_ImplDX11_NewFrame()
{
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplDX11_Init()?");
if (!bd->pFontSampler)
ImGui_ImplDX11_CreateDeviceObjects();
return bd->pFontSampler!=NULL;
}
//--------------------------------------------------------------------------------------------------------

View File

@ -19,7 +19,7 @@ struct ID3D11DeviceContext;
IMGUI_IMPL_API bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context);
IMGUI_IMPL_API void ImGui_ImplDX11_Shutdown();
IMGUI_IMPL_API void ImGui_ImplDX11_NewFrame();
IMGUI_IMPL_API bool ImGui_ImplDX11_NewFrame();
IMGUI_IMPL_API void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data);
// Use if you want to reset your rendering device without losing Dear ImGui state.

View File

@ -199,15 +199,18 @@
#define IMGUI_IMPL_OPENGL_MAY_HAVE_EXTENSIONS
#endif
#include "../../../src/ta-log.h"
// [Debugging]
//#define IMGUI_IMPL_OPENGL_DEBUG
#ifdef IMGUI_IMPL_OPENGL_DEBUG
#include <stdio.h>
#define GL_CALL(_CALL) do { _CALL; GLenum gl_err = glGetError(); if (gl_err != 0) fprintf(stderr, "GL error 0x%x returned from '%s'.\n", gl_err, #_CALL); } while (0) // Call with error check
#define GL_CALL(_CALL) do { _CALL; GLenum gl_err = glGetError(); if (gl_err != 0) logE("GL error 0x%x returned from '%s'.\n", gl_err, #_CALL); } while (0) // Call with error check
#else
#define GL_CALL(_CALL) _CALL // Call without error check
#endif
#define GL_CALL_FALSE(_CALL) _CALL; { GLenum gl_err = glGetError(); if (gl_err != 0) { logW("GL error 0x%x returned from '%s'.\n", gl_err, #_CALL); return false; } }
// OpenGL Data
struct ImGui_ImplOpenGL3_Data
{
@ -324,7 +327,7 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version)
#endif
#ifdef IMGUI_IMPL_OPENGL_DEBUG
printf("GL_MAJOR_VERSION = %d\nGL_MINOR_VERSION = %d\nGL_VENDOR = '%s'\nGL_RENDERER = '%s'\n", major, minor, (const char*)glGetString(GL_VENDOR), (const char*)glGetString(GL_RENDERER)); // [DEBUG]
logD("\nGL_VENDOR = '%s'\nGL_RENDERER = '%s'\n", (const char*)glGetString(GL_VENDOR), (const char*)glGetString(GL_RENDERER)); // [DEBUG]
#endif
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET
@ -389,13 +392,14 @@ void ImGui_ImplOpenGL3_Shutdown()
IM_DELETE(bd);
}
void ImGui_ImplOpenGL3_NewFrame()
bool ImGui_ImplOpenGL3_NewFrame()
{
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplOpenGL3_Init()?");
if (!bd->ShaderHandle)
ImGui_ImplOpenGL3_CreateDeviceObjects();
return ImGui_ImplOpenGL3_CreateDeviceObjects();
return true;
}
static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object)
@ -674,14 +678,20 @@ bool ImGui_ImplOpenGL3_CreateFontsTexture()
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
GLint last_texture;
GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture));
GL_CALL(glGenTextures(1, &bd->FontTexture));
GL_CALL(glBindTexture(GL_TEXTURE_2D, bd->FontTexture));
// clear errors
glGetError();
GL_CALL_FALSE(glGenTextures(1, &bd->FontTexture));
GL_CALL_FALSE(glBindTexture(GL_TEXTURE_2D, bd->FontTexture));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
#ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES
GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
#endif
GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
glGetError();
GL_CALL_FALSE(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
// Store our identifier
io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture);
@ -918,7 +928,9 @@ bool ImGui_ImplOpenGL3_CreateDeviceObjects()
glGenBuffers(1, &bd->VboHandle);
glGenBuffers(1, &bd->ElementsHandle);
ImGui_ImplOpenGL3_CreateFontsTexture();
bool whatReturn=true;
if (!ImGui_ImplOpenGL3_CreateFontsTexture()) whatReturn=false;
// Restore modified GL state
glBindTexture(GL_TEXTURE_2D, last_texture);
@ -927,7 +939,7 @@ bool ImGui_ImplOpenGL3_CreateDeviceObjects()
glBindVertexArray(last_vertex_array);
#endif
return true;
return whatReturn;
}
void ImGui_ImplOpenGL3_DestroyDeviceObjects()

View File

@ -29,7 +29,7 @@
// Backend API
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = nullptr);
IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame();
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_NewFrame();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data);
// (Optional) Called by Init/NewFrame/Shutdown

View File

@ -52,9 +52,7 @@ Index of this file:
// Configuration file with compile-time options
// (edit imconfig.h or '#define IMGUI_USER_CONFIG "myfilename.h" from your build system')
#ifdef IMGUI_USER_CONFIG
#include IMGUI_USER_CONFIG
#endif
#include "imconfig_fur.h"
#include "imconfig.h"
#ifndef IMGUI_DISABLE

View File

@ -32,6 +32,9 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
the format versions are:
- 162: Furnace 0.6pre7
- 161: Furnace 0.6pre6
- 160: Furnace dev160
- 159: Furnace dev159
- 158: Furnace 0.6pre5
- 157: Furnace dev157
@ -307,6 +310,7 @@ size | description
| - 0xc9: M114S - 16 channels
| - 0xca: ZX Spectrum (beeper, QuadTone engine) - 5 channels
| - 0xcb: Casio PV-1000 - 3 channels
| - 0xcc: K053260 - 4 channels
| - 0xde: YM2610B extended - 19 channels
| - 0xe0: QSound - 19 channels
| - 0xfc: Pong - 1 channel

404
papers/multiplayer.md Normal file
View File

@ -0,0 +1,404 @@
# multiplayer protocol
this is a concept! it has not been implemented yet!
the Furnace protocol is described here.
# information
all numbers are little-endian.
the following fields may be found in "size":
- `f` indicates a floating point number.
- `STR` is a UTF-8 zero-terminated string.
- `CFG` is the same as STR, but contains a config.
- `???` is an array of variable size.
- `S??` is an array of `STR`s.
- `1??` is an array of bytes.
- `2??` is an array of shorts.
- `4??` is an array of ints.
two player IDs are reserved:
- 0: system
- 1: host (console)
two usernames are reserved:
- SYSTEM
- HOST
some characters are not allowed in usernames: 0x00-0x1f, `@`, 0x7f-0x9f, 0xd800-0xdfff, 0xfeff, 0xfffe and 0xffff.
# header
```
size | description
-----|------------------------------------
3 | "fur" header
1 | packet type
4 | sequence number
```
the sequence number always starts at 0.
# client to server packets (init)
## 0x00: keep-alive
this packet keeps a connection alive.
the server shall respond with a packet of type 0x00 (keep-alive).
if the client does not receive any packets during 30 seconds, it will disconnect from the server.
likewise, if the server does not receive any packets during 30 seconds, it will disconnect the client.
## 0x01: start connection
```
size | description
-----|------------------------------------
1 | reason
| - 0: information
| - 1: join
3 | padding
4 | client version
STR | host name (may be blank)
```
after sending, you will receive a packet of type 0x01 (information), 0x02 (disconnect) or 0x03 (authenticate).
## 0x02: disconnect
```
size | description
-----|------------------------------------
STR | reason
```
## 0x03: auth response
```
size | description
-----|------------------------------------
1 | type
| - 0: open
| - 1: password
| - 2: token
--- | **open response**
STR | username
--- | **password response**
1 | password type
| - 0: plain text
| - 1: SHA-512
STR | account name
??? | password
--- | **token response**
STR | token
```
# server to client packets (init)
## 0x00: keep-alive
this packet keeps a connection alive. it is a response to a client's keep-alive packet.
## 0x01: information
```
size | description
-----|------------------------------------
4 | server version
2 | online players
| - if it is 65535, this information is concealed.
2 | maximum players
| - 0 means unlimited.
STR | server version (string)
STR | server name
STR | server description
STR | project name
```
the client may send a 0x00 (keep-alive) packet after receiving this one within 5 seconds.
connection is then closed.
## 0x02: disconnect
```
size | description
-----|------------------------------------
STR | reason
```
after being sent, the connection is closed.
## 0x03: authenticate
```
size | description
-----|------------------------------------
1 | authentication type
| - 0: open
| - 1: password
| - 2: token
```
## 0x04: authentication success
```
size | description
-----|------------------------------------
4 | player ID
STR | username
CFG | properties
```
# client to server packets (session)
## 0x10: request project
the client may only send this once every minute.
## 0x11: participate
```
size | description
-----|------------------------------------
1 | status
| - 0: spectate
| - 1: join
```
## 0x12: send chat message
```
size | description
-----|------------------------------------
STR | message
```
## 0x13: send command
```
size | description
-----|------------------------------------
STR | command
2 | number of arguments
S?? | arguments
```
## 0x14: get player list
no other information required.
## 0x15: project submission request
no other information required
## 0x16: project submission information
```
size | description
-----|------------------------------------
4 | project size
32 | SHA-256 sum of project
STR | project name
```
this is followed by several 0x17 (project data) packets representing a Furnace song. see [format.md](format.md) for more information.
## 0x17: project submission data
```
size | description
-----|------------------------------------
4 | offset
4 | length
??? | data...
```
the client will send a packet with project size as offset and 0 as length to indicate end of data.
the server subsequently loads the project.
# server to client packets (session)
## 0x10: project information
```
size | description
-----|------------------------------------
4 | project size
32 | SHA-256 sum of project
STR | project name
```
this is followed by several 0x13 (project data) packets representing a Furnace song. see [format.md](format.md) for more information.
## 0x11: project data
```
size | description
-----|------------------------------------
4 | offset
4 | length
??? | data...
```
the server will send a packet with project size as offset and 0 as length to indicate end of data.
the client subsequently loads the project.
## 0x12: participate status
```
size | description
-----|------------------------------------
1 | status
| - 0: denied
| - 1: allowed
```
## 0x13: message
```
size | description
-----|------------------------------------
4 | player ID
8 | time (seconds)
4 | time (nanoseconds)
4 | message ID
1 | type
| - 0: chat, public
| - 1: chat, private
| - 2: notification, info
| - 3: notification, warning
| - 4: notification, urgent
STR | message
```
## 0x14: system message
```
size | description
-----|------------------------------------
STR | message
```
## 0x15: chat message edited
```
size | description
-----|------------------------------------
4 | message ID
STR | message
| - an empty message means deleted.
```
## 0x16: player list
```
size | description
-----|------------------------------------
2 | number of players
--- | **player entry** (×players)
4 | ID
2 | latency (ms)
1 | participating?
| - 0: no
| - 1: yes
1 | status
| - 0: normal
| - 1: away
| - 2: busy
STR | name
STR | IP address
| - if empty, then server is not disclosing IP addresses.
```
this is sent after receiving 0x14 (get player list).
## 0x17: project submission request status
```
size | description
-----|------------------------------------
1 | status
| - 0: denied
| - 1: allowed
```
this is sent after a project submission request is accepted.
if the status is 1, the client shall submit a project.
## 0x18: project submission complete
```
size | description
-----|------------------------------------
1 | status
| - 0: error
| - 1: success
STR | additional information
```
## 0x19: player joined
```
size | description
-----|------------------------------------
4 | ID
STR | name
```
## 0x1a: player left
```
size | description
-----|------------------------------------
4 | ID
```
# client to server packets (project)
## 0x20: request orders
## 0x21: request instrument
## 0x22: request wavetable
## 0x23: request sample
## 0x24: request patterns
## 0x25: request sub-song
## 0x26: request song info
## 0x27: request asset list
## 0x28: request patchbay
## 0x29: request grooves
## 0x30: alter orders
```
size | description
-----|------------------------------------
4 | transaction ID
```
## 0x31: alter instrument
## 0x32: alter wavetable
## 0x33: alter sample
## 0x34: alter pattern
## 0x35: alter sub-song
## 0x36: alter song info
## 0x37: alter asset list
## 0x38: alter patchbay
## 0x39: alter grooves
## 0x3a: alter chips
## 0x3b: alter chip settings
# server to client packets (project)
## 0x20: orders
## 0x21: instrument
## 0x22: wavetable
## 0x23: sample
## 0x24: pattern
## 0x25: sub-song
## 0x26: song info
## 0x27: asset list
## 0x28: patchbay
## 0x29: grooves
## 0x30: transaction response
```
size | description
-----|------------------------------------
1 | status
| - 0: error
| - 1: success
| - 2: success but request again
STR | additional information
```
# client to server packets (interact)
## 0x40: engine command
## 0x41: playback
# server to client packets (interact)
## 0x40: engine command
## 0x41: playback
# client to server packets (extension)
# server to client packets (extension)

View File

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

View File

@ -18,6 +18,9 @@
<p>
it also offers DefleMask compatibility, allowing you to import your songs and even export them back for interoperability.
</p>
<p>
<b>rationale for intense profanity:</b> the tracker itself is clean, but a few demo songs and instruments contain a small amount of strong language.
</p>
</description>
<content_rating type="oars-1.0">

View File

@ -15,7 +15,7 @@ fi
cd win32build
# TODO: potential Arch-ism?
i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" -DBUILD_SHARED_LIBS=OFF -DSUPPORT_XP=ON .. || exit 1
i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" -DBUILD_SHARED_LIBS=OFF -DSUPPORT_XP=ON -DWITH_RENDER_DX11=OFF .. || exit 1
make -j8 || exit 1
cd ..

View File

@ -127,14 +127,20 @@ bool TAAudioSDL::init(TAAudioDesc& request, TAAudioDesc& response) {
ac.callback=taSDLProcess;
ac.userdata=this;
ai=SDL_OpenAudioDevice(request.deviceName.empty()?NULL:request.deviceName.c_str(),0,&ac,&ar,SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
ai=SDL_OpenAudioDevice(request.deviceName.empty()?NULL:request.deviceName.c_str(),0,&ac,&ar,0);
if (ai==0) {
logE("could not open audio device: %s",SDL_GetError());
return false;
}
const char* backendName=SDL_GetCurrentAudioDriver();
desc.deviceName=request.deviceName;
desc.name="";
if (backendName==NULL) {
desc.name="";
} else {
desc.name=backendName;
}
desc.rate=ar.freq;
desc.inChans=0;
desc.outChans=ar.channels;

View File

@ -288,6 +288,9 @@ struct DivRegWrite {
* - x is the instance ID
* - 0xffffxx04: switch sample bank
* - for use in VGM export
* - 0xffffxx05: set sample position
* - xx is the instance ID
* - data is the sample position
* - 0xffffffff: reset
*/
unsigned int addr;

View File

@ -4586,6 +4586,15 @@ bool DivEngine::initAudioBackend() {
}
}
#ifdef HAVE_SDL2
if (audioEngine==DIV_AUDIO_SDL) {
String audioDriver=getConfString("sdlAudioDriver","");
if (!audioDriver.empty()) {
SDL_SetHint("SDL_HINT_AUDIODRIVER",audioDriver.c_str());
}
}
#endif
lowQuality=getConfInt("audioQuality",0);
forceMono=getConfInt("forceMono",0);
clampSamples=getConfInt("clampSamples",0);
@ -4594,7 +4603,7 @@ bool DivEngine::initAudioBackend() {
midiOutClock=getConfInt("midiOutClock",0);
midiOutTime=getConfInt("midiOutTime",0);
midiOutTimeRate=getConfInt("midiOutTimeRate",0);
midiOutProgramChange = getConfInt("midiOutProgramChange",0);
midiOutProgramChange=getConfInt("midiOutProgramChange",0);
midiOutMode=getConfInt("midiOutMode",DIV_MIDI_MODE_NOTE);
if (metroVol<0.0f) metroVol=0.0f;
if (metroVol>2.0f) metroVol=2.0f;

View File

@ -54,8 +54,10 @@
#define EXTERN_BUSY_BEGIN_SOFT e->softLocked=true; e->isBusy.lock();
#define EXTERN_BUSY_END e->isBusy.unlock(); e->softLocked=false;
#define DIV_VERSION "dev160"
#define DIV_ENGINE_VERSION 160
#define DIV_UNSTABLE
#define DIV_VERSION "dev163"
#define DIV_ENGINE_VERSION 163
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
@ -489,7 +491,7 @@ class DivEngine {
void processRow(int i, bool afterDelay);
void nextOrder();
void nextRow();
void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, size_t bankOffset, bool directStream);
void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream);
// returns true if end of song.
bool nextTick(bool noAccum=false, bool inhibitLowLat=false);
bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal);
@ -570,6 +572,7 @@ class DivEngine {
float oscSize;
int oscReadPos, oscWritePos;
int tickMult;
int lastNBIns, lastNBOuts, lastNBSize;
std::atomic<size_t> processTime;
void runExportThread();
@ -1252,6 +1255,9 @@ class DivEngine {
oscReadPos(0),
oscWritePos(0),
tickMult(1),
lastNBIns(0),
lastNBOuts(0),
lastNBSize(0),
processTime(0),
yrw801ROM(NULL),
tg100ROM(NULL),

View File

@ -64,6 +64,41 @@ const char** DivPlatformC64::getRegisterSheet() {
return regCheatSheetSID;
}
short DivPlatformC64::runFakeFilter(unsigned char ch, int in) {
if (!(regPool[0x17]&(1<<ch))) {
if (regPool[0x18]&0x80 && ch==2) return 0;
float fin=in;
fin*=(float)(regPool[0x18]&15)/20.0f;
return CLAMP(fin,-32768,32767);
}
// taken from dSID
float fin=in;
float fout=0;
float ctf=fakeCutTable[((regPool[0x15]&7)|(regPool[0x16]<<3))&0x7ff];
float reso=(sidIs6581?
((regPool[0x17]>0x5F)?8.0/(float)(regPool[0x17]>>4):1.41):
(pow(2,((float)(4-(float)(regPool[0x17]>>4))/8)))
);
float tmp=fin+fakeBand[ch]*reso+fakeLow[ch];
if (regPool[0x18]&0x40) {
fout-=tmp;
}
tmp=fakeBand[ch]-tmp*ctf;
fakeBand[ch]=tmp;
if (regPool[0x18]&0x20) {
fout-=tmp;
}
tmp=fakeLow[ch]+tmp*ctf;
fakeLow[ch]=tmp;
if (regPool[0x18]&0x10) {
fout+=tmp;
}
fout*=(float)(regPool[0x18]&15)/20.0f;
return CLAMP(fout,-32768,32767);
}
void DivPlatformC64::acquire(short** buf, size_t len) {
int dcOff=(sidCore)?0:sid->get_dc(0);
for (size_t i=0; i<len; i++) {
@ -92,18 +127,18 @@ void DivPlatformC64::acquire(short** buf, size_t len) {
sid_fp->clock(4,&buf[0][i]);
if (++writeOscBuf>=4) {
writeOscBuf=0;
oscBuf[0]->data[oscBuf[0]->needle++]=(sid_fp->lastChanOut[0]-dcOff)>>5;
oscBuf[1]->data[oscBuf[1]->needle++]=(sid_fp->lastChanOut[1]-dcOff)>>5;
oscBuf[2]->data[oscBuf[2]->needle++]=(sid_fp->lastChanOut[2]-dcOff)>>5;
oscBuf[0]->data[oscBuf[0]->needle++]=runFakeFilter(0,(sid_fp->lastChanOut[0]-dcOff)>>5);
oscBuf[1]->data[oscBuf[1]->needle++]=runFakeFilter(1,(sid_fp->lastChanOut[1]-dcOff)>>5);
oscBuf[2]->data[oscBuf[2]->needle++]=runFakeFilter(2,(sid_fp->lastChanOut[2]-dcOff)>>5);
}
} else {
sid->clock();
buf[0][i]=sid->output();
if (++writeOscBuf>=16) {
writeOscBuf=0;
oscBuf[0]->data[oscBuf[0]->needle++]=(sid->last_chan_out[0]-dcOff)>>5;
oscBuf[1]->data[oscBuf[1]->needle++]=(sid->last_chan_out[1]-dcOff)>>5;
oscBuf[2]->data[oscBuf[2]->needle++]=(sid->last_chan_out[2]-dcOff)>>5;
oscBuf[0]->data[oscBuf[0]->needle++]=runFakeFilter(0,(sid->last_chan_out[0]-dcOff)>>5);
oscBuf[1]->data[oscBuf[1]->needle++]=runFakeFilter(1,(sid->last_chan_out[1]-dcOff)>>5);
oscBuf[2]->data[oscBuf[2]->needle++]=runFakeFilter(2,(sid->last_chan_out[2]-dcOff)>>5);
}
}
}
@ -540,6 +575,8 @@ void DivPlatformC64::reset() {
for (int i=0; i<3; i++) {
chan[i]=DivPlatformC64::Channel();
chan[i].std.setEngine(parent);
fakeLow[i]=0;
fakeBand[i]=0;
}
if (sidCore==2) {
@ -613,6 +650,24 @@ void DivPlatformC64::setFlags(const DivConfig& flags) {
keyPriority=flags.getBool("keyPriority",true);
testAD=((flags.getInt("testAttack",0)&15)<<4)|(flags.getInt("testDecay",0)&15);
testSR=((flags.getInt("testSustain",0)&15)<<4)|(flags.getInt("testRelease",0)&15);
// init fake filter table
// taken from dSID
double cutRatio=-2.0*3.14*(sidIs6581?(((double)oscBuf[0]->rate/44100.0)*(20000.0/256.0)):(12500.0/256.0))/(double)oscBuf[0]->rate;
for (int i=0; i<2048; i++) {
double c=(double)i/8.0+0.2;
if (sidIs6581) {
if (c<24) {
c=2.0*sin(771.78/(double)oscBuf[0]->rate);
} else {
c=(44100.0/(double)oscBuf[0]->rate)-1.263*(44100.0/(double)oscBuf[0]->rate)*exp(c*cutRatio);
}
} else {
c=1-exp(c*cutRatio);
}
fakeCutTable[i]=c;
}
}
int DivPlatformC64::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {

View File

@ -56,6 +56,9 @@ class DivPlatformC64: public DivDispatch {
Channel chan[3];
DivDispatchOscBuffer* oscBuf[3];
bool isMuted[3];
float fakeLow[3];
float fakeBand[3];
float fakeCutTable[2048];
struct QueuedWrite {
unsigned char addr;
unsigned char val;
@ -80,6 +83,8 @@ class DivPlatformC64: public DivDispatch {
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
inline short runFakeFilter(unsigned char ch, int in);
void acquire_classic(short* bufL, short* bufR, size_t start, size_t len);
void acquire_fp(short* bufL, short* bufR, size_t start, size_t len);

View File

@ -130,14 +130,15 @@ class DivPlatformOPN: public DivPlatformFMBase {
unsigned char freqH, freqL;
int portaPauseFreq;
signed char konCycles;
bool mask;
bool mask, hardReset;
OPNOpChannel():
SharedChannel<int>(0),
freqH(0),
freqL(0),
portaPauseFreq(0),
konCycles(0),
mask(true) {}
mask(true),
hardReset(false) {}
};
struct OPNOpChannelStereo: public OPNOpChannel {

View File

@ -322,7 +322,7 @@ void DivPlatformGB::tick(bool sysTick) {
rWrite(16+i*5+4,((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((chan[i].soundLen<64)<<6));
} else {
rWrite(16+i*5+3,(2048-chan[i].freq)&0xff);
rWrite(16+i*5+4,(((2048-chan[i].freq)>>8)&7)|((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((chan[i].soundLen<63)<<6));
rWrite(16+i*5+4,(((2048-chan[i].freq)>>8)&7)|((chan[i].keyOn||(chan[i].keyOff && i!=2))?0x80:0x00)|((chan[i].soundLen<63)<<6));
}
if (enoughAlready) { // more compat garbage
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63)));
@ -397,6 +397,14 @@ int DivPlatformGB::dispatch(DivCommand c) {
chan[c.chan].vol=chan[c.chan].envVol;
chan[c.chan].outVol=chan[c.chan].envVol;
}
} else if (chan[c.chan].softEnv && c.chan!=2) {
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
chan[c.chan].envVol=chan[c.chan].outVol;
}
chan[c.chan].envLen=0;
chan[c.chan].envDir=1;
chan[c.chan].soundLen=64;
}
if (c.chan==2 && chan[c.chan].softEnv) {
chan[c.chan].soundLen=64;
@ -466,7 +474,9 @@ int DivPlatformGB::dispatch(DivCommand c) {
if (c.chan!=2) break;
chan[c.chan].wave=c.value;
ws.changeWave1(chan[c.chan].wave);
chan[c.chan].keyOn=true;
if (chan[c.chan].active) {
chan[c.chan].keyOn=true;
}
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_PERIODIC(c.value2);
@ -672,8 +682,6 @@ void DivPlatformGB::setFlags(const DivConfig& flags) {
CHECK_CUSTOM_CLOCK;
rate=chipClock/16;
for (int i=0; i<4; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
oscBuf[i]->rate=rate;
}
}
@ -684,6 +692,12 @@ int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, const DivConfig
skipRegisterWrites=false;
model=GB_MODEL_DMG_B;
gb=new GB_gameboy_t;
for (int i=0; i<4; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
reset();
return 4;

View File

@ -63,7 +63,7 @@ void DivPlatformGenesis::processDAC(int iRate) {
for (int i=5; i<7; i++) {
if (chan[i].dacSample!=-1) {
DivSample* s=parent->getSample(chan[i].dacSample);
if (!isMuted[i] && s->samples>0) {
if (!isMuted[i] && s->samples>0 && chan[i].dacPos<s->samples) {
if (parent->song.noOPN2Vol) {
chan[i].dacOutput=s->data8[chan[i].dacDirection?(s->samples-chan[i].dacPos-1):chan[i].dacPos];
} else {
@ -110,7 +110,7 @@ void DivPlatformGenesis::processDAC(int iRate) {
chan[5].dacPeriod+=chan[5].dacRate;
if (chan[5].dacPeriod>=iRate) {
DivSample* s=parent->getSample(chan[5].dacSample);
if (s->samples>0) {
if (s->samples>0 && chan[5].dacPos<s->samples) {
if (!isMuted[5]) {
if (chan[5].dacReady && writes.size()<16) {
int sample;
@ -122,8 +122,6 @@ void DivPlatformGenesis::processDAC(int iRate) {
urgentWrite(0x2a,(unsigned char)sample+0x80);
chan[5].dacReady=false;
}
} else {
urgentWrite(0x2a,0x80);
}
chan[5].dacPos++;
if (!chan[5].dacDirection && (s->isLoopable() && chan[5].dacPos>=(unsigned int)s->loopEnd)) {
@ -595,6 +593,7 @@ void DivPlatformGenesis::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
if (ch>6) return;
if (ch<6) {
if (ch==5) immWrite(0x2a,0x80);
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[ch]|opOffs[j];
DivInstrumentFM::Operator& op=chan[ch].state.op[j];
@ -702,7 +701,11 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
addWrite(0xffff0003,chan[c.chan].dacDirection);
}
}
chan[c.chan].dacPos=0;
if (chan[c.chan].setPos) {
chan[c.chan].setPos=false;
} else {
chan[c.chan].dacPos=0;
}
chan[c.chan].dacPeriod=0;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
@ -925,6 +928,12 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
if (dumpWrites) addWrite(0xffff0003,chan[c.chan].dacDirection);
break;
}
case DIV_CMD_SAMPLE_POS:
if (c.chan<5) c.chan=5;
chan[c.chan].dacPos=c.value;
chan[c.chan].setPos=true;
if (dumpWrites) addWrite(0xffff0005,chan[c.chan].dacPos);
break;
case DIV_CMD_LEGATO: {
if (c.chan==csmChan) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);

View File

@ -57,6 +57,7 @@ class DivPlatformGenesis: public DivPlatformOPN {
int dacDelay;
bool dacReady;
bool dacDirection;
bool setPos;
unsigned char sampleBank;
signed char dacOutput;
Channel():
@ -70,6 +71,7 @@ class DivPlatformGenesis: public DivPlatformOPN {
dacDelay(0),
dacReady(true),
dacDirection(false),
setPos(false),
sampleBank(0),
dacOutput(0) {}
};

View File

@ -396,6 +396,9 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
}
break;
}
case DIV_CMD_FM_HARD_RESET:
opChan[ch].hardReset=c.value;
break;
case DIV_CMD_GET_VOLMAX:
return 127;
break;

View File

@ -42,7 +42,7 @@ void DivPlatformPV1000::acquire(short** buf, size_t len) {
short samp=d65010g031_sound_tick(&d65010g031,1);
buf[0][h]=samp;
for (int i=0; i<3; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=d65010g031.out[i]<<1;
oscBuf[i]->data[oscBuf[i]->needle++]=MAX(d65010g031.out[i]<<2,0);
}
}
}

View File

@ -195,9 +195,6 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
chan[c.chan].pcm.sample=-1;
rWrite(0x86+(c.chan<<3),3);
chan[c.chan].macroInit(NULL);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
break;
}
if (c.value!=DIV_NOTE_NULL) {
@ -207,6 +204,16 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
}
chan[c.chan].furnacePCM=true;
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
if (parent->song.newSegaPCM) {
chan[c.chan].chVolL=(chan[c.chan].outVol*chan[c.chan].chPanL)/127;
chan[c.chan].chVolR=(chan[c.chan].outVol*chan[c.chan].chPanR)/127;
rWrite(2+(c.chan<<3),chan[c.chan].chVolL);
rWrite(3+(c.chan<<3),chan[c.chan].chVolR);
}
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {

View File

@ -115,11 +115,17 @@ void dSID_init(struct SID_chip* sid, double clockRate, double samplingRate, int
double prd0 = sid->g.ckr > 9 ? sid->g.ckr : 9;
sid->g.Aprd[0] = prd0;
sid->g.Astp[0] = ceil(prd0 / 9);
for (int i=0; i<3; i++) {
sid->fakeplp[i]=0;
sid->fakepbp[i]=0;
}
}
double dSID_render(struct SID_chip* sid) {
double flin = 0, output = 0;
double wfout = 0;
double step = 0;
for (int chn = 0; chn < 3; chn++) {
struct SIDVOICE *voic = &((struct SIDMEM *) (sid->M))->v[chn];
double pgt = (sid->SIDct->ch[chn].Ast & GAT);
@ -142,7 +148,6 @@ double dSID_render(struct SID_chip* sid) {
if (sid->SIDct->ch[chn].rcnt >= 0x8000)
sid->SIDct->ch[chn].rcnt -= 0x8000;
static double step;
double prd;
if (sid->SIDct->ch[chn].Ast & ATK) {
@ -291,8 +296,6 @@ double dSID_render(struct SID_chip* sid) {
// mostly copypasted from below
double fakeflin = chnout;
double fakeflout = 0;
static double fakeplp[3] = {0};
static double fakepbp[3] = {0};
double ctf = sid->g.ctf_table[((sid->M[0x15]&7)|(sid->M[0x16]<<3))&0x7ff];
double reso;
if (sid->g.model == 8580) {
@ -300,15 +303,15 @@ double dSID_render(struct SID_chip* sid) {
} else {
reso = (sid->M[0x17] > 0x5F) ? 8.0 / (double) (sid->M[0x17] >> 4) : 1.41;
}
double tmp = fakeflin + fakepbp[chn] * reso + fakeplp[chn];
double tmp = fakeflin + sid->fakepbp[chn] * reso + sid->fakeplp[chn];
if (sid->M[0x18] & HP)
fakeflout -= tmp;
tmp = fakepbp[chn] - tmp * ctf;
fakepbp[chn] = tmp;
tmp = sid->fakepbp[chn] - tmp * ctf;
sid->fakepbp[chn] = tmp;
if (sid->M[0x18] & BP)
fakeflout -= tmp;
tmp = fakeplp[chn] + tmp * ctf;
fakeplp[chn] = tmp;
tmp = sid->fakeplp[chn] + tmp * ctf;
sid->fakeplp[chn] = tmp;
if (sid->M[0x18] & LP)
fakeflout += tmp;

View File

@ -81,6 +81,8 @@ struct SID_chip {
uint8_t M[MemLen];
int16_t lastOut[3];
int mute_mask;
double fakeplp[3];
double fakepbp[3];
};
double dSID_render(struct SID_chip* sid);

View File

@ -141,6 +141,7 @@ template<bool IsOpnA>
bool opn_registers_base<IsOpnA>::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask)
{
assert(index < REGISTERS);
if (index >= REGISTERS) return false;
// writes in the 0xa0-af/0x1a0-af region are handled as latched pairs
// borrow unused registers 0xb8-bf/0x1b8-bf as temporary holding locations

View File

@ -548,19 +548,25 @@ void DivPlatformSwan::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
int DivPlatformSwan::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
void DivPlatformSwan::setFlags(const DivConfig& flags) {
chipClock=3072000;
CHECK_CUSTOM_CLOCK;
rate=chipClock/16; // = 192000kHz, should be enough
for (int i=0; i<4; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
oscBuf[i]->rate=rate;
}
}
int DivPlatformSwan::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<4; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
ws=new WSwan();
setFlags(flags);
reset();
return 4;
}

View File

@ -68,6 +68,7 @@ class DivPlatformSwan: public DivDispatch {
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
void setFlags(const DivConfig& flags);
void notifyWaveChange(int wave);
void notifyInsDeletion(void* ins);
int getOutputCount();

View File

@ -19,7 +19,6 @@
#include "vera.h"
#include "../engine.h"
#include "../../ta-log.h"
#include <string.h>
#include <math.h>
@ -40,14 +39,15 @@ extern "C" {
#define rWriteZSMSync(d) {if (dumpWrites) addWrite(68,(d));}
const char* regCheatSheetVERA[]={
"CHxFreq", "00+x*4",
"CHxVol", "02+x*4",
"CHxWave", "03+x*4",
"CHxFreq", "00+x*4",
"CHxVol", "02+x*4",
"CHxWave", "03+x*4",
"AUDIO_CTRL", "40",
"AUDIO_RATE", "41",
"AUDIO_DATA", "42",
"ZSM_SYNC", "44",
"AUDIO_CTRL", "40",
"AUDIO_RATE", "41",
"AUDIO_DATA", "42",
"ZSM_PCM_LOOP_POINT", "43",
"ZSM_SYNC", "44",
NULL
};
@ -230,9 +230,19 @@ void DivPlatformVERA::tick(bool sysTick) {
chan[16].freqChanged=false;
}
// For export, output the entire sample that starts on this tick
if (dumpWrites) {
DivSample* s=parent->getSample(chan[16].pcm.sample);
if (s->samples>0) {
if (s->isLoopable()) {
// Inform the export process of the loop point for this sample
int tmp_ls=(s->loopStart<<1); // for stereo
if (chan[16].pcm.depth16)
tmp_ls<<=1; // for 16 bit
addWrite(67,tmp_ls&0xff);
addWrite(67,(tmp_ls>>8)&0xff);
addWrite(67,(tmp_ls>>16)&0xff);
}
while (true) {
short tmp_l=0;
short tmp_r=0;
@ -258,8 +268,6 @@ void DivPlatformVERA::tick(bool sysTick) {
}
chan[16].pcm.pos++;
if (s->isLoopable() && chan[16].pcm.pos>=(unsigned int)s->loopEnd) {
//chan[16].pcm.pos=s->loopStart;
logI("VERA PCM export: treating looped sample as non-looped");
chan[16].pcm.sample=-1;
break;
}

View File

@ -915,7 +915,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
//printf("\x1b[1;36m%d: extern command %d\x1b[m\n",i,effectVal);
extValue=effectVal;
extValuePresent=true;
dispatchCmd(DivCommand(DIV_CMD_EXTERNAL,effectVal));
dispatchCmd(DivCommand(DIV_CMD_EXTERNAL,i,effectVal));
break;
case 0xef: // global pitch
globalPitch+=(signed char)(effectVal-0x80);
@ -1694,6 +1694,10 @@ void DivEngine::runMidiTime(int totalCycles) {
}
void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size) {
lastNBIns=inChans;
lastNBOuts=outChans;
lastNBSize=size;
if (!size) {
logW("nextBuf called with size 0!");
return;

View File

@ -24,7 +24,7 @@
constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0;
void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, size_t bankOffset, bool directStream) {
void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream) {
unsigned char baseAddr1=isSecond?0xa0:0x50;
unsigned char baseAddr2=isSecond?0x80:0;
unsigned short baseAddr2S=isSecond?0x8000:0;
@ -610,15 +610,35 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
pendingFreq[streamID]=write.val;
} else {
DivSample* sample=song.sample[write.val];
w->writeC(0x95);
w->writeC(streamID);
w->writeS(write.val); // sample number
w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())|(sampleDir[streamID]?0x10:0)); // flags
int pos=sampleOff8[write.val&0xff]+setPos[streamID];
int len=(int)sampleLen8[write.val&0xff]-setPos[streamID];
if (len<0) len=0;
if (setPos[streamID]!=0) {
if (len<=0) {
w->writeC(0x94);
w->writeC(streamID);
} else {
w->writeC(0x93);
w->writeC(streamID);
w->writeI(pos);
w->writeC(1|((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())?0x80:0)|(sampleDir[streamID]?0x10:0)); // flags
w->writeI(len);
}
} else {
w->writeC(0x95);
w->writeC(streamID);
w->writeS(write.val); // sample number
w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())|(sampleDir[streamID]?0x10:0)); // flags
}
if (sample->isLoopable() && !sampleDir[streamID]) {
loopTimer[streamID]=sample->length8;
loopTimer[streamID]=len;
loopSample[streamID]=write.val;
}
playingSample[streamID]=write.val;
setPos[streamID]=0;
}
}
break;
@ -632,16 +652,36 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
loopFreq[streamID]=realFreq;
if (pendingFreq[streamID]!=-1) {
DivSample* sample=song.sample[pendingFreq[streamID]];
w->writeC(0x95);
w->writeC(streamID);
w->writeS(pendingFreq[streamID]); // sample number
w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())|(sampleDir[streamID]?0x10:0)); // flags
int pos=sampleOff8[pendingFreq[streamID]&0xff]+setPos[streamID];
int len=(int)sampleLen8[pendingFreq[streamID]&0xff]-setPos[streamID];
if (len<0) len=0;
if (setPos[streamID]!=0) {
if (len<=0) {
w->writeC(0x94);
w->writeC(streamID);
} else {
w->writeC(0x93);
w->writeC(streamID);
w->writeI(pos);
w->writeC(1|((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())?0x80:0)|(sampleDir[streamID]?0x10:0)); // flags
w->writeI(len);
}
} else {
w->writeC(0x95);
w->writeC(streamID);
w->writeS(pendingFreq[streamID]); // sample number
w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())|(sampleDir[streamID]?0x10:0)); // flags
}
if (sample->isLoopable() && !sampleDir[streamID]) {
loopTimer[streamID]=sample->length8;
loopTimer[streamID]=len;
loopSample[streamID]=pendingFreq[streamID];
}
playingSample[streamID]=pendingFreq[streamID];
pendingFreq[streamID]=-1;
setPos[streamID]=0;
}
break;
}
@ -655,6 +695,41 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
case 3: // set sample direction
sampleDir[streamID]=write.val;
break;
case 5: // set sample pos
setPos[streamID]=write.val;
if (playingSample[streamID]!=-1 && pendingFreq[streamID]==-1) {
// play the sample again
DivSample* sample=song.sample[playingSample[streamID]];
int pos=sampleOff8[playingSample[streamID]&0xff]+setPos[streamID];
int len=(int)sampleLen8[playingSample[streamID]&0xff]-setPos[streamID];
if (len<0) len=0;
if (setPos[streamID]!=0) {
if (len<=0) {
w->writeC(0x94);
w->writeC(streamID);
} else {
w->writeC(0x93);
w->writeC(streamID);
w->writeI(pos);
w->writeC(1|((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())?0x80:0)|(sampleDir[streamID]?0x10:0)); // flags
w->writeI(len);
}
} else {
w->writeC(0x95);
w->writeC(streamID);
w->writeS(playingSample[streamID]); // sample number
w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())|(sampleDir[streamID]?0x10:0)); // flags
}
if (sample->isLoopable() && !sampleDir[streamID]) {
loopTimer[streamID]=len;
loopSample[streamID]=playingSample[streamID];
}
}
break;
}
}
return;
@ -1067,6 +1142,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
int songTick=0;
unsigned int sampleOff8[256];
unsigned int sampleLen8[256];
unsigned int sampleOffSegaPCM[256];
SafeWriter* w=new SafeWriter;
@ -1087,6 +1163,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
bool sampleDir[DIV_MAX_CHANS];
int pendingFreq[DIV_MAX_CHANS];
int playingSample[DIV_MAX_CHANS];
int setPos[DIV_MAX_CHANS];
std::vector<unsigned int> chipVol;
std::vector<DivDelayedWrite> delayedWrites[DIV_MAX_CHIPS];
std::vector<std::pair<int,DivDelayedWrite>> sortedWrites;
@ -1106,6 +1183,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
loopSample[i]=-1;
pendingFreq[i]=-1;
playingSample[i]=-1;
setPos[i]=0;
sampleDir[i]=false;
}
@ -1363,7 +1441,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
willExport[i]=true;
CHIP_VOL(6,1.0);
CHIP_VOL(0x86,1.7);
writeDACSamples=true;
} else if (!(hasOPN&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
@ -1842,6 +1919,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
// initialize sample offsets
memset(sampleOff8,0,256*sizeof(unsigned int));
memset(sampleLen8,0,256*sizeof(unsigned int));
memset(sampleOffSegaPCM,0,256*sizeof(unsigned int));
// write samples
@ -1850,6 +1928,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
DivSample* sample=song.sample[i];
logI("setting seek to %d",sampleSeek);
sampleOff8[i]=sampleSeek;
sampleLen8[i]=sample->length8;
sampleSeek+=sample->length8;
}
@ -1985,9 +2064,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0xc0+i);
w->writeI(writeRF5C68[i]->getSampleMemUsage()+8);
w->writeI(writeRF5C68[i]->getSampleMemCapacity());
w->writeI(0);
w->writeI(writeRF5C68[i]->getSampleMemUsage()+2);
w->writeS(0);
w->write(writeRF5C68[i]->getSampleMem(),writeRF5C68[i]->getSampleMemUsage());
}
if (writeMSM6295[i]!=NULL && writeMSM6295[i]->getSampleMemUsage()>0) {
@ -2241,7 +2319,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
for (int i=0; i<song.systemLen; i++) {
std::vector<DivRegWrite>& writes=disCont[i].dispatch->getRegisterWrites();
for (DivRegWrite& j: writes) {
performVGMWrite(w,song.system[i],j,streamIDs[i],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i],pendingFreq,playingSample,bankOffset[i],directStream);
performVGMWrite(w,song.system[i],j,streamIDs[i],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i],pendingFreq,playingSample,setPos,sampleOff8,sampleLen8,bankOffset[i],directStream);
writeCount++;
}
writes.clear();
@ -2281,7 +2359,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
lastOne=i.second.time;
}
// write write
performVGMWrite(w,song.system[i.first],i.second.write,streamIDs[i.first],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i.first],pendingFreq,playingSample,bankOffset[i.first],directStream);
performVGMWrite(w,song.system[i.first],i.second.write,streamIDs[i.first],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i.first],pendingFreq,playingSample,setPos,sampleOff8,sampleLen8,bankOffset[i.first],directStream);
// handle global Furnace commands
writeCount++;

View File

@ -61,6 +61,8 @@ void DivZSM::init(unsigned int rate) {
pcmRateCache=-1;
pcmCtrlRVCache=-1;
pcmCtrlDCCache=-1;
pcmIsLooped=false;
pcmLoopPointCache=0;
// Channel masks
ymMask=0;
psgMask=0;
@ -166,6 +168,9 @@ void DivZSM::writePCM(unsigned char a, unsigned char v) {
} else if (a==2) { // PCM data
pcmCache.push_back(v);
numWrites++;
} else if (a==3) { // PCM loop point
pcmLoopPointCache=(pcmLoopPointCache>>8)|(v<<16);
pcmIsLooped=true;
}
}
@ -213,7 +218,7 @@ SafeWriter* DivZSM::finish() {
pcmData.clear();
pcmInsts.clear();
} else if (pcmData.size()) { // if exists, write PCM instruments and blob to the end of file
int pcmOff=w->tell();
unsigned int pcmOff=w->tell();
w->writeC('P');
w->writeC('C');
w->writeC('M');
@ -236,10 +241,11 @@ SafeWriter* DivZSM::finish() {
w->writeC((unsigned char)(inst.length>>16)&0xff);
// Feature mask: Lxxxxxxx
// L = Loop enabled
w->writeC(0);
// Loop point (not yet implemented)
w->writeC(0);
w->writeS(0);
w->writeC((unsigned char)inst.isLooped<<7);
// Sample loop point <l m h>
w->writeC((unsigned char)inst.loopPoint&0xff);
w->writeC((unsigned char)(inst.loopPoint>>8)&0xff);
w->writeC((unsigned char)(inst.loopPoint>>16)&0xff);
// Reserved for future use
w->writeS(0);
w->writeS(0);
@ -289,8 +295,8 @@ void DivZSM::flushWrites() {
}
ymwrites.clear();
unsigned int pcmInst=0;
int pcmOff=0;
int pcmLen=0;
unsigned int pcmOff=0;
unsigned int pcmLen=0;
int extCmd0Len=pcmMeta.size()*2;
if (pcmCache.size()) {
// collapse stereo data to mono if both channels are fully identical
@ -312,6 +318,7 @@ void DivZSM::flushWrites() {
}
pcmCache.resize(pcmCache.size()>>1);
pcmCtrlDCCache&=(unsigned char)~0x10; // clear stereo bit
pcmLoopPointCache>>=1; // halve the loop point
}
}
} else { // 8-bit
@ -328,6 +335,7 @@ void DivZSM::flushWrites() {
}
pcmCache.resize(pcmCache.size()>>1);
pcmCtrlDCCache&=(unsigned char)~0x10; // clear stereo bit
pcmLoopPointCache>>=1; // halve the loop point
}
}
}
@ -347,7 +355,7 @@ void DivZSM::flushWrites() {
extCmd0Len+=2;
// search for a matching PCM instrument definition
for (S_pcmInst& inst: pcmInsts) {
if (inst.offset==pcmOff && inst.length==pcmLen && inst.geometry==pcmCtrlDCCache)
if (inst.offset==pcmOff && inst.length==pcmLen && inst.geometry==pcmCtrlDCCache && inst.isLooped==pcmIsLooped && inst.loopPoint==pcmLoopPointCache)
break;
pcmInst++;
}
@ -356,8 +364,12 @@ void DivZSM::flushWrites() {
inst.geometry=pcmCtrlDCCache;
inst.offset=pcmOff;
inst.length=pcmLen;
inst.loopPoint=pcmLoopPointCache;
inst.isLooped=pcmIsLooped;
pcmInsts.push_back(inst);
}
pcmIsLooped=false;
pcmLoopPointCache=0;
}
if (extCmd0Len>63) { // this would be bad, but will almost certainly never happen
logE("ZSM: extCmd 0 exceeded maximum length of 63: %d",extCmd0Len);

View File

@ -46,7 +46,9 @@ enum PSG_STATE { psg_PREV, psg_NEW, psg_STATES };
class DivZSM {
private:
struct S_pcmInst {
int geometry, offset, length;
int geometry;
unsigned int offset, length, loopPoint;
bool isLooped;
};
SafeWriter* w;
int ymState[ym_STATES][256];
@ -54,6 +56,8 @@ class DivZSM {
int pcmRateCache;
int pcmCtrlRVCache;
int pcmCtrlDCCache;
unsigned int pcmLoopPointCache;
bool pcmIsLooped;
std::vector<DivRegWrite> ymwrites;
std::vector<DivRegWrite> pcmMeta;
std::vector<unsigned char> pcmData;

View File

@ -39,6 +39,7 @@ const char* aboutLine[]={
"cam900",
"djtuBIG-MaliceX",
"laoo",
"MooingLemur",
"OPNA2608",
"superctr",
"System64",
@ -58,6 +59,8 @@ const char* aboutLine[]={
"cam900",
"host12prog",
"WindowxDeveloper",
"polluks",
"Electric Keet",
"",
"-- demo songs --",
"0x5066",
@ -79,6 +82,7 @@ const char* aboutLine[]={
"Dippy",
"djtuBIG-MaliceX",
"dumbut",
"Eknous-P",
"ElectricKeet",
"EpicTyphlosion",
"FΛDE",
@ -134,6 +138,7 @@ const char* aboutLine[]={
"fd",
"GENATARi",
"host12prog",
"jvsTSX",
"Lumigado",
"Lunathir",
"plane",

View File

@ -149,6 +149,14 @@ void FurnaceGUI::drawChanOsc() {
ImGui::EndTable();
}
ImGui::Text("Amplitude");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (CWSliderFloat("##COSAmp",&chanOscAmplify,0.0f,2.0f)) {
if (chanOscAmplify<0.0f) chanOscAmplify=0.0f;
if (chanOscAmplify>2.0f) chanOscAmplify=2.0f;
}
ImGui::Checkbox("Gradient",&chanOscUseGrad);
if (chanOscUseGrad) {
@ -506,12 +514,26 @@ void FurnaceGUI::drawChanOsc() {
text+=fmt::sprintf("%d",e->dispatchOfChan[ch]);
break;
}
case 'v':
case 'v': {
DivChannelState* chanState=e->getChanState(ch);
if (chanState==NULL) break;
text+=fmt::sprintf("%d",chanState->volume>>8);
break;
case 'V':
}
case 'V': {
DivChannelState* chanState=e->getChanState(ch);
if (chanState==NULL) break;
int volMax=chanState->volMax>>8;
if (volMax<1) volMax=1;
text+=fmt::sprintf("%d%%",(chanState->volume>>8)/volMax);
break;
case 'b':
}
case 'b': {
DivChannelState* chanState=e->getChanState(ch);
if (chanState==NULL) break;
text+=fmt::sprintf("%.2X",chanState->volume>>8);
break;
}
case '%':
text+='%';
break;

View File

@ -388,6 +388,34 @@ void FurnaceGUI::drawDebug() {
ImGui::Text("System Managed Scale: %d",sysManagedScale);
ImGui::TreePop();
}
if (ImGui::TreeNode("Audio Debug")) {
TAAudioDesc& audioWant=e->getAudioDescWant();
TAAudioDesc& audioGot=e->getAudioDescGot();
ImGui::Text("want:");
ImGui::Text("- name: %s",audioWant.name.c_str());
ImGui::Text("- device name: %s",audioWant.deviceName.c_str());
ImGui::Text("- rate: %f",audioWant.rate);
ImGui::Text("- buffer size: %d",audioWant.bufsize);
ImGui::Text("- fragments: %d",audioWant.fragments);
ImGui::Text("- inputs: %d",audioWant.inChans);
ImGui::Text("- outputs: %d",audioWant.outChans);
ImGui::Text("- format: %d",audioWant.outFormat);
ImGui::Text("got:");
ImGui::Text("- name: %s",audioGot.name.c_str());
ImGui::Text("- device name: %s",audioGot.deviceName.c_str());
ImGui::Text("- rate: %f",audioGot.rate);
ImGui::Text("- buffer size: %d",audioGot.bufsize);
ImGui::Text("- fragments: %d",audioGot.fragments);
ImGui::Text("- inputs: %d",audioGot.inChans);
ImGui::Text("- outputs: %d",audioGot.outChans);
ImGui::Text("- format: %d",audioGot.outFormat);
ImGui::Text("last call to nextBuf(): in %d, out %d, size %d",e->lastNBIns,e->lastNBOuts,e->lastNBSize);
ImGui::TreePop();
}
if (ImGui::TreeNode("Visualizer Debug")) {
if (ImGui::BeginTable("visX",3,ImGuiTableFlags_Borders)) {
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);

View File

@ -3839,6 +3839,7 @@ bool FurnaceGUI::loop() {
ImGui::GetIO().Fonts->Clear();
mainFont=ImGui::GetIO().Fonts->AddFontDefault();
patFont=mainFont;
bigFont=mainFont;
if (rend) rend->destroyFontsTexture();
if (!ImGui::GetIO().Fonts->Build()) {
logE("error again while building font atlas!");
@ -5251,7 +5252,7 @@ bool FurnaceGUI::loop() {
quit=true;
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) {
ImGui::CloseCurrentPopup();
}
break;
@ -5275,7 +5276,7 @@ bool FurnaceGUI::loop() {
displayNew=true;
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) {
ImGui::CloseCurrentPopup();
}
break;
@ -5299,7 +5300,7 @@ bool FurnaceGUI::loop() {
openFileDialog(GUI_FILE_OPEN);
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) {
ImGui::CloseCurrentPopup();
}
break;
@ -5323,7 +5324,7 @@ bool FurnaceGUI::loop() {
openFileDialog(GUI_FILE_OPEN_BACKUP);
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) {
ImGui::CloseCurrentPopup();
}
break;
@ -5354,7 +5355,7 @@ bool FurnaceGUI::loop() {
nextFile="";
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) {
ImGui::CloseCurrentPopup();
nextFile="";
}
@ -5408,7 +5409,7 @@ bool FurnaceGUI::loop() {
syncSettings();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) {
ImGui::CloseCurrentPopup();
}
break;
@ -5495,7 +5496,7 @@ bool FurnaceGUI::loop() {
ImGui::CloseCurrentPopup();
}
if (ImGui::Button("Wait! What am I doing? Cancel!")) {
if (ImGui::Button("Wait! What am I doing? Cancel!") || ImGui::IsKeyPressed(ImGuiKey_Escape)) {
ImGui::CloseCurrentPopup();
}
break;
@ -5646,7 +5647,7 @@ bool FurnaceGUI::loop() {
ImGui::EndDisabled();
ImGui::SameLine();
}
if (ImGui::Button("Cancel")) {
if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) {
for (std::pair<DivInstrument*,bool>& i: pendingIns) {
i.second=false;
}
@ -5718,7 +5719,7 @@ bool FurnaceGUI::loop() {
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
@ -5759,6 +5760,17 @@ bool FurnaceGUI::loop() {
introPos=12.0;
}
#ifdef DIV_UNSTABLE
{
ImDrawList* dl=ImGui::GetForegroundDrawList();
ImVec2 markPos=ImVec2(canvasW-ImGui::CalcTextSize(DIV_VERSION).x-6.0*dpiScale,4.0*dpiScale);
ImVec4 markColor=uiColors[GUI_COLOR_TEXT];
markColor.w=0.67f;
dl->AddText(markPos,ImGui::ColorConvertFloat4ToU32(markColor),DIV_VERSION);
}
#endif
layoutTimeEnd=SDL_GetPerformanceCounter();
// backup trigger
@ -5837,13 +5849,12 @@ bool FurnaceGUI::loop() {
if (outFile!=NULL) {
if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) {
logW("did not write backup entirely: %s!",strerror(errno));
w->finish();
}
fclose(outFile);
} else {
logW("could not save backup: %s!",strerror(errno));
w->finish();
}
w->finish();
// delete previous backup if there are too many
delFirstBackup(backupBaseName);
@ -5967,6 +5978,7 @@ bool FurnaceGUI::loop() {
ImGui::GetIO().Fonts->Clear();
mainFont=ImGui::GetIO().Fonts->AddFontDefault();
patFont=mainFont;
bigFont=mainFont;
if (rend) rend->destroyFontsTexture();
if (!ImGui::GetIO().Fonts->Build()) {
logE("error again while building font atlas!");
@ -6261,18 +6273,18 @@ bool FurnaceGUI::init() {
logV("window size: %dx%d",scrW,scrH);
if (!initRender()) {
if (settings.renderBackend!="SDL" && !settings.renderBackend.empty()) {
settings.renderBackend="";
e->setConf("renderBackend","");
if (settings.renderBackend!="SDL") {
settings.renderBackend="SDL";
e->setConf("renderBackend","SDL");
e->saveConf();
lastError=fmt::sprintf("\r\nthe render backend has been set to a safe value. please restart Furnace.");
lastError=fmt::sprintf("could not init renderer!\r\nthe render backend has been set to a safe value. please restart Furnace.");
} else {
lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError());
if (!settings.renderDriver.empty()) {
settings.renderDriver="";
e->setConf("renderDriver","");
e->saveConf();
lastError=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace.");
lastError+=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace.");
}
}
return false;
@ -6329,6 +6341,19 @@ bool FurnaceGUI::init() {
}
#endif
int numDriversA=SDL_GetNumAudioDrivers();
if (numDriversA<0) {
logW("could not list audio drivers! %s",SDL_GetError());
} else {
for (int i=0; i<numDriversA; i++) {
const char* r=SDL_GetAudioDriver(i);
if (r==NULL) continue;
if (strcmp(r,"disk")==0) continue;
if (strcmp(r,"dummy")==0) continue;
availAudioDrivers.push_back(String(r));
}
}
int numDrivers=SDL_GetNumRenderDrivers();
if (numDrivers<0) {
logW("could not list render drivers! %s",SDL_GetError());
@ -6349,16 +6374,16 @@ bool FurnaceGUI::init() {
if (!rend->init(sdlWin)) {
if (settings.renderBackend!="SDL") {
settings.renderBackend="SDL";
//e->setConf("renderBackend","");
//e->saveConf();
//lastError=fmt::sprintf("\r\nthe render backend has been set to a safe value. please restart Furnace.");
e->setConf("renderBackend","SDL");
e->saveConf();
lastError=fmt::sprintf("could not init renderer!\r\nthe render backend has been set to a safe value. please restart Furnace.");
} else {
lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError());
if (!settings.renderDriver.empty()) {
settings.renderDriver="";
e->setConf("renderDriver","");
e->saveConf();
lastError=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace.");
lastError+=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace.");
}
}
return false;
@ -6408,6 +6433,7 @@ bool FurnaceGUI::init() {
ImGui::GetIO().Fonts->Clear();
mainFont=ImGui::GetIO().Fonts->AddFontDefault();
patFont=mainFont;
bigFont=mainFont;
if (rend) rend->destroyFontsTexture();
if (!ImGui::GetIO().Fonts->Build()) {
logE("error again while building font atlas!");
@ -6653,6 +6679,10 @@ bool FurnaceGUI::finish() {
return true;
}
void FurnaceGUI::requestQuit() {
quit=true;
}
FurnaceGUI::FurnaceGUI():
e(NULL),
renderBackend(GUI_BACKEND_SDL),

View File

@ -1304,6 +1304,7 @@ class FurnaceGUI {
std::deque<String> recentFile;
std::vector<DivInstrumentType> makeInsTypeList;
std::vector<String> availRenderDrivers;
std::vector<String> availAudioDrivers;
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, zsmExportLoop, vgmExportPatternHints;
bool vgmExportDirectStream, displayInsTypeList;
@ -1534,6 +1535,7 @@ class FurnaceGUI {
String macroRelLabel;
String emptyLabel;
String emptyLabel2;
String sdlAudioDriver;
DivConfig initialSys;
Settings():
@ -1686,7 +1688,8 @@ class FurnaceGUI {
noteRelLabel("==="),
macroRelLabel("REL"),
emptyLabel("..."),
emptyLabel2("..") {}
emptyLabel2(".."),
sdlAudioDriver("") {}
} settings;
struct Tutorial {
@ -2303,6 +2306,7 @@ class FurnaceGUI {
bool loop();
bool finish();
bool init();
void requestQuit();
FurnaceGUI();
};

View File

@ -2378,10 +2378,37 @@ void FurnaceGUI::drawInsEdit() {
bool opsAreMutable=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM);
if (ImGui::BeginTabItem("FM")) {
DivInstrumentFM& fmOrigin=(ins->type==DIV_INS_OPLL && ins->fm.opllPreset>0 && ins->fm.opllPreset<16)?opllPreview:ins->fm;
bool isPresent[4];
int isPresentCount=0;
memset(isPresent,0,4*sizeof(bool));
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i]==DIV_SYSTEM_VRC7) {
isPresent[3]=true;
} else if (e->song.system[i]==DIV_SYSTEM_OPLL || e->song.system[i]==DIV_SYSTEM_OPLL_DRUMS) {
isPresent[(e->song.systemFlags[i].getInt("patchSet",0))&3]=true;
}
}
if (!isPresent[0] && !isPresent[1] && !isPresent[2] && !isPresent[3]) {
isPresent[0]=true;
}
for (int i=0; i<4; i++) {
if (isPresent[i]) isPresentCount++;
}
int presentWhich=0;
for (int i=0; i<4; i++) {
if (isPresent[i]) {
presentWhich=i;
break;
}
}
if (ImGui::BeginTable("fmDetails",3,ImGuiTableFlags_SizingStretchSame)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.0);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0);
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.0);
ImGui::TableNextRow();
switch (ins->type) {
case DIV_INS_FM:
@ -2453,14 +2480,14 @@ void FurnaceGUI::drawInsEdit() {
break;
}
case DIV_INS_OPLL: {
bool dc=ins->fm.fms;
bool dm=ins->fm.ams;
bool dc=fmOrigin.fms;
bool dm=fmOrigin.ams;
bool sus=ins->fm.alg;
ImGui::TableNextColumn();
ImGui::BeginDisabled(ins->fm.opllPreset!=0);
P(CWSliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); rightClickable
P(CWSliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&fmOrigin.fb,&_ZERO,&_SEVEN)); rightClickable
if (ImGui::Checkbox(FM_NAME(FM_DC),&dc)) { PARAMETER
ins->fm.fms=dc;
fmOrigin.fms=dc;
}
ImGui::EndDisabled();
ImGui::TableNextColumn();
@ -2469,7 +2496,7 @@ void FurnaceGUI::drawInsEdit() {
}
ImGui::BeginDisabled(ins->fm.opllPreset!=0);
if (ImGui::Checkbox(FM_NAME(FM_DM),&dm)) { PARAMETER
ins->fm.ams=dm;
fmOrigin.ams=dm;
}
ImGui::EndDisabled();
ImGui::TableNextColumn();
@ -2477,30 +2504,6 @@ void FurnaceGUI::drawInsEdit() {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
bool isPresent[4];
int isPresentCount=0;
memset(isPresent,0,4*sizeof(bool));
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i]==DIV_SYSTEM_VRC7) {
isPresent[3]=true;
} else if (e->song.system[i]==DIV_SYSTEM_OPLL || e->song.system[i]==DIV_SYSTEM_OPLL_DRUMS) {
isPresent[(e->song.systemFlags[i].getInt("patchSet",0))&3]=true;
}
}
if (!isPresent[0] && !isPresent[1] && !isPresent[2] && !isPresent[3]) {
isPresent[0]=true;
}
for (int i=0; i<4; i++) {
if (isPresent[i]) isPresentCount++;
}
int presentWhich=0;
for (int i=0; i<4; i++) {
if (isPresent[i]) {
presentWhich=i;
break;
}
}
if (ImGui::BeginCombo("##LLPreset",opllInsNames[presentWhich][ins->fm.opllPreset])) {
if (isPresentCount>1) {
if (ImGui::BeginTable("LLPresetList",isPresentCount)) {
@ -2578,11 +2581,26 @@ void FurnaceGUI::drawInsEdit() {
// update OPLL preset preview
if (ins->fm.opllPreset>0 && ins->fm.opllPreset<16) {
const opll_patch_t* patchROM=OPLL_GetPatchROM(opll_type_ym2413);
const opll_patch_t* patchROM=NULL;
switch (presentWhich) {
case 1:
patchROM=OPLL_GetPatchROM(opll_type_ymf281);
break;
case 2:
patchROM=OPLL_GetPatchROM(opll_type_ym2423);
break;
case 3:
patchROM=OPLL_GetPatchROM(opll_type_ds1001);
break;
default:
patchROM=OPLL_GetPatchROM(opll_type_ym2413);
break;
}
const opll_patch_t* patch=&patchROM[ins->fm.opllPreset-1];
opllPreview.alg=0;
opllPreview.alg=ins->fm.alg;
opllPreview.fb=patch->fb;
opllPreview.fms=patch->dm;
opllPreview.ams=patch->dc;
@ -2604,8 +2622,6 @@ void FurnaceGUI::drawInsEdit() {
}
}
DivInstrumentFM& fmOrigin=(ins->type==DIV_INS_OPLL && ins->fm.opllPreset>0 && ins->fm.opllPreset<16)?opllPreview:ins->fm;
ImGui::BeginDisabled(!willDisplayOps);
if (settings.fmLayout==0) {
int numCols=15;

View File

@ -133,17 +133,20 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
}
ImGui::PopStyleColor();
// for each column
int mustSetXOf=0;
for (int j=0; j<chans; j++) {
// check if channel is not hidden
if (!e->curSubSong->chanShow[j]) {
patChanX[j]=ImGui::GetCursorScreenPos().x;
continue;
}
int chanVolMax=e->getMaxVolumeChan(j);
if (chanVolMax<1) chanVolMax=1;
const DivPattern* pat=patCache[j];
ImGui::TableNextColumn();
patChanX[j]=ImGui::GetCursorScreenPos().x;
for (int k=mustSetXOf; k<=j; k++) {
patChanX[k]=ImGui::GetCursorScreenPos().x;
}
mustSetXOf=j+1;
// selection highlight flags
int sel1XSum=sel1.xCoarse*32+sel1.xFine;
@ -358,7 +361,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
ImGui::PopStyleColor();
}
ImGui::TableNextColumn();
patChanX[chans]=ImGui::GetCursorScreenPos().x;
for (int k=mustSetXOf; k<=chans; k++) {
patChanX[k]=ImGui::GetCursorScreenPos().x;
}
}
void FurnaceGUI::drawPattern() {
@ -371,7 +376,6 @@ void FurnaceGUI::drawPattern() {
if (!patternOpen) return;
bool inhibitMenu=false;
float scrollX=0;
if (e->isPlaying() && followPattern && (!e->isStepping() || pendingStepUpdate)) {
cursor.y=oldRow+((pendingStepUpdate)?1:0);
@ -946,7 +950,6 @@ void FurnaceGUI::drawPattern() {
}
demandScrollX=false;
}
scrollX=ImGui::GetScrollX();
// overflow changes order
// TODO: this is very unreliable and sometimes it can warp you out of the song
@ -1119,6 +1122,8 @@ void FurnaceGUI::drawPattern() {
break;
}
if (!e->curSubSong->chanShow[i.chan]) continue;
for (int j=0; j<num; j++) {
ImVec2 partPos=ImVec2(
off.x+patChanX[i.chan]+fmod(rand(),width),
@ -1175,8 +1180,8 @@ void FurnaceGUI::drawPattern() {
}
if (width>0.1) for (float j=-patChanSlideY[i]; j<ImGui::GetWindowHeight(); j+=width*0.7) {
ImVec2 tMin=ImVec2(off.x+patChanX[i]-scrollX,off.y+j);
ImVec2 tMax=ImVec2(off.x+patChanX[i+1]-scrollX,off.y+j+width*0.6);
ImVec2 tMin=ImVec2(off.x+patChanX[i],off.y+j);
ImVec2 tMax=ImVec2(off.x+patChanX[i+1],off.y+j+width*0.6);
if (ch->portaNote<=ch->note) {
arrowPoints[0]=ImLerp(tMin,tMax,ImVec2(0.1,1.0-0.8));
arrowPoints[1]=ImLerp(tMin,tMax,ImVec2(0.5,1.0-0.0));

View File

@ -290,8 +290,7 @@ void FurnaceGUIRenderDX11::clear(ImVec4 color) {
}
bool FurnaceGUIRenderDX11::newFrame() {
ImGui_ImplDX11_NewFrame();
return true;
return ImGui_ImplDX11_NewFrame();
}
void FurnaceGUIRenderDX11::createFontsTexture() {

View File

@ -233,8 +233,7 @@ void FurnaceGUIRenderGL::clear(ImVec4 color) {
}
bool FurnaceGUIRenderGL::newFrame() {
ImGui_ImplOpenGL3_NewFrame();
return true;
return ImGui_ImplOpenGL3_NewFrame();
}
void FurnaceGUIRenderGL::createFontsTexture() {

View File

@ -807,6 +807,25 @@ void FurnaceGUI::drawSettings() {
}
#endif
if (settings.audioEngine==DIV_AUDIO_SDL) {
ImGui::Text("Driver");
ImGui::SameLine();
if (ImGui::BeginCombo("##SDLADriver",settings.sdlAudioDriver.empty()?"Automatic":settings.sdlAudioDriver.c_str())) {
if (ImGui::Selectable("Automatic",settings.sdlAudioDriver.empty())) {
settings.sdlAudioDriver="";
}
for (String& i: availAudioDrivers) {
if (ImGui::Selectable(i.c_str(),i==settings.sdlAudioDriver)) {
settings.sdlAudioDriver=i;
}
}
ImGui::EndCombo();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("you may need to restart Furnace for this setting to take effect.");
}
}
ImGui::Text("Device");
ImGui::SameLine();
String audioDevName=settings.audioDevice.empty()?"<System default>":settings.audioDevice;
@ -1324,6 +1343,9 @@ void FurnaceGUI::drawSettings() {
#endif
ImGui::EndCombo();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("you may need to restart Furnace for this setting to take effect.");
}
if (curRenderBackend=="SDL") {
if (ImGui::BeginCombo("Render driver",settings.renderDriver.empty()?"Automatic":settings.renderDriver.c_str())) {
if (ImGui::Selectable("Automatic",settings.renderDriver.empty())) {
@ -1336,6 +1358,9 @@ void FurnaceGUI::drawSettings() {
}
ImGui::EndCombo();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("you may need to restart Furnace for this setting to take effect.");
}
}
bool dpiScaleAuto=(settings.dpiScale<0.5f);
@ -2638,6 +2663,7 @@ void FurnaceGUI::syncSettings() {
settings.midiOutDevice=e->getConfString("midiOutDevice","");
settings.c163Name=e->getConfString("c163Name",DIV_C163_DEFAULT_NAME);
settings.renderDriver=e->getConfString("renderDriver","");
settings.sdlAudioDriver=e->getConfString("sdlAudioDriver","");
settings.audioQuality=e->getConfInt("audioQuality",0);
settings.audioBufSize=e->getConfInt("audioBufSize",1024);
settings.audioRate=e->getConfInt("audioRate",44100);
@ -2986,6 +3012,7 @@ void FurnaceGUI::commitSettings() {
e->setConf("midiOutDevice",settings.midiOutDevice);
e->setConf("c163Name",settings.c163Name);
e->setConf("renderDriver",settings.renderDriver);
e->setConf("sdlAudioDriver",settings.sdlAudioDriver);
e->setConf("audioQuality",settings.audioQuality);
e->setConf("audioBufSize",settings.audioBufSize);
e->setConf("audioRate",settings.audioRate);
@ -3174,6 +3201,7 @@ void FurnaceGUI::commitSettings() {
ImGui::GetIO().Fonts->Clear();
mainFont=ImGui::GetIO().Fonts->AddFontDefault();
patFont=mainFont;
bigFont=mainFont;
if (rend) rend->destroyFontsTexture();
if (!ImGui::GetIO().Fonts->Build()) {
logE("error again while building font atlas!");

View File

@ -36,7 +36,10 @@
typedef HRESULT (WINAPI *SPDA)(PROCESS_DPI_AWARENESS);
#else
#include <signal.h>
#include <unistd.h>
struct sigaction termsa;
#endif
#include "cli/cli.h"
@ -356,9 +359,24 @@ void reportError(String what) {
}
#endif
#ifndef _WIN32
#ifdef HAVE_GUI
static void handleTermGUI(int) {
g.requestQuit();
}
#endif
#endif
// TODO: CoInitializeEx on Windows?
// TODO: add crash log
int main(int argc, char** argv) {
// uncomment these if you want Furnace to play in the background on Android.
// not recommended. it lags.
#if defined(HAVE_SDL2) && defined(ANDROID)
//SDL_SetHint(SDL_HINT_ANDROID_BLOCK_ON_PAUSE,"0");
//SDL_SetHint(SDL_HINT_ANDROID_BLOCK_ON_PAUSE_PAUSEAUDIO,"0");
#endif
// Windows console thing - thanks dj.tuBIG/MaliceX
#ifdef _WIN32
@ -646,6 +664,13 @@ int main(int argc, char** argv) {
g.setFileName(fileName);
}
#ifndef _WIN32
sigemptyset(&termsa.sa_mask);
termsa.sa_flags=0;
termsa.sa_handler=handleTermGUI;
sigaction(SIGTERM,&termsa,NULL);
#endif
g.loop();
logI("closing GUI.");
g.finish();