diff --git a/.gitignore b/.gitignore index 8b45bb970..78ce5cb8e 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fb2aa919..caee04a7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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)) diff --git a/README.md b/README.md index 373712390..6ae675deb 100644 --- a/README.md +++ b/README.md @@ -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? diff --git a/TODO.md b/TODO.md index 20c54eb9a..c190afd18 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,4 @@ -# to-do for 0.6pre6 +# to-do for 0.6pre7 - tutorial? - ease-of-use improvements... ideas: diff --git a/android/app/build.gradle b/android/app/build.gradle index 3f3fc8bd8..e19c78a58 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -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" diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 3deb416b9..7f6a769d4 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ diff --git a/demos/arcade/physics_exam.fur b/demos/arcade/physics_exam.fur new file mode 100644 index 000000000..b26e3f6e4 Binary files /dev/null and b/demos/arcade/physics_exam.fur differ diff --git a/demos/ay8930/AY8930Shuffle.fur b/demos/ay8930/AY8930Shuffle.fur new file mode 100644 index 000000000..68931cf09 Binary files /dev/null and b/demos/ay8930/AY8930Shuffle.fur differ diff --git a/demos/genesis/inside_the_computer.fur b/demos/genesis/inside_the_computer.fur new file mode 100644 index 000000000..b26820194 Binary files /dev/null and b/demos/genesis/inside_the_computer.fur differ diff --git a/demos/multichip/invicibility_mmc5_n163_fds.fur b/demos/multichip/invicibility_mmc5_n163_fds.fur new file mode 100644 index 000000000..5faad8f0c Binary files /dev/null and b/demos/multichip/invicibility_mmc5_n163_fds.fur differ diff --git a/demos/nes/invicibility_mmc5_n163_fds.fur b/demos/nes/invicibility_mmc5_n163_fds.fur deleted file mode 100644 index ef9b05bf9..000000000 Binary files a/demos/nes/invicibility_mmc5_n163_fds.fur and /dev/null differ diff --git a/demos/opl/attack_the_barbarian_opl.fur b/demos/opl/attack_the_barbarian_opl.fur new file mode 100644 index 000000000..f0df49f0e Binary files /dev/null and b/demos/opl/attack_the_barbarian_opl.fur differ diff --git a/demos/pce/Warpdrive_Engage.fur b/demos/pce/Warpdrive_Engage.fur new file mode 100644 index 000000000..9bda4c918 Binary files /dev/null and b/demos/pce/Warpdrive_Engage.fur differ diff --git a/demos/sms/FlowOfSN7.fur b/demos/sms/FlowOfSN7.fur new file mode 100644 index 000000000..643686cc0 Binary files /dev/null and b/demos/sms/FlowOfSN7.fur differ diff --git a/doc/2-interface/README.md b/doc/2-interface/README.md index 2ff1ebfc9..6de22e929 100644 --- a/doc/2-interface/README.md +++ b/doc/2-interface/README.md @@ -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) diff --git a/doc/2-interface/menu-bar.md b/doc/2-interface/menu-bar.md index 8fac9e659..822f38897 100644 --- a/doc/2-interface/menu-bar.md +++ b/doc/2-interface/menu-bar.md @@ -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. diff --git a/doc/2-interface/settings.md b/doc/2-interface/settings.md new file mode 100644 index 000000000..06c21f1c9 --- /dev/null +++ b/doc/2-interface/settings.md @@ -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 diff --git a/doc/3-pattern/effects.md b/doc/3-pattern/effects.md index 6becb6c82..df1f85500 100644 --- a/doc/3-pattern/effects.md +++ b/doc/3-pattern/effects.md @@ -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 diff --git a/doc/7-systems/README.md b/doc/7-systems/README.md index d36480746..71285b521 100644 --- a/doc/7-systems/README.md +++ b/doc/7-systems/README.md @@ -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) diff --git a/doc/7-systems/game-boy.md b/doc/7-systems/game-boy.md index 505de282f..eed8241af 100644 --- a/doc/7-systems/game-boy.md +++ b/doc/7-systems/game-boy.md @@ -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 \ No newline at end of file diff --git a/doc/7-systems/pokemini.md b/doc/7-systems/pokemini.md new file mode 100644 index 000000000..142d49658 --- /dev/null +++ b/doc/7-systems/pokemini.md @@ -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. diff --git a/doc/8-advanced/chanosc-gradient.png b/doc/8-advanced/chanosc-gradient.png new file mode 100644 index 000000000..e8e7e73a7 Binary files /dev/null and b/doc/8-advanced/chanosc-gradient.png differ diff --git a/doc/8-advanced/chanosc.md b/doc/8-advanced/chanosc.md index 55e04a19d..f4b79ca75 100644 --- a/doc/8-advanced/chanosc.md +++ b/doc/8-advanced/chanosc.md @@ -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. diff --git a/doc/8-advanced/chanosc.png b/doc/8-advanced/chanosc.png index 42c6be295..7536dad84 100644 Binary files a/doc/8-advanced/chanosc.png and b/doc/8-advanced/chanosc.png differ diff --git a/extern/igfd/dirent/dirent.h b/extern/igfd/dirent/dirent.h index 35d948a6f..7d5c8abec 100644 --- a/extern/igfd/dirent/dirent.h +++ b/extern/igfd/dirent/dirent.h @@ -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 { diff --git a/extern/imgui_patched/backends/imgui_impl_dx11.cpp b/extern/imgui_patched/backends/imgui_impl_dx11.cpp index 862769686..00ecb8e60 100644 --- a/extern/imgui_patched/backends/imgui_impl_dx11.cpp +++ b/extern/imgui_patched/backends/imgui_impl_dx11.cpp @@ -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; } //-------------------------------------------------------------------------------------------------------- diff --git a/extern/imgui_patched/backends/imgui_impl_dx11.h b/extern/imgui_patched/backends/imgui_impl_dx11.h index cee486f56..5f4dd7f99 100644 --- a/extern/imgui_patched/backends/imgui_impl_dx11.h +++ b/extern/imgui_patched/backends/imgui_impl_dx11.h @@ -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. diff --git a/extern/imgui_patched/backends/imgui_impl_opengl3.cpp b/extern/imgui_patched/backends/imgui_impl_opengl3.cpp index 2ff3e2621..d5ef359d6 100644 --- a/extern/imgui_patched/backends/imgui_impl_opengl3.cpp +++ b/extern/imgui_patched/backends/imgui_impl_opengl3.cpp @@ -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 -#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() diff --git a/extern/imgui_patched/backends/imgui_impl_opengl3.h b/extern/imgui_patched/backends/imgui_impl_opengl3.h index 1c7666c81..328a145d0 100644 --- a/extern/imgui_patched/backends/imgui_impl_opengl3.h +++ b/extern/imgui_patched/backends/imgui_impl_opengl3.h @@ -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 diff --git a/extern/imgui_conf/imconfig_fur.h b/extern/imgui_patched/imconfig_fur.h similarity index 100% rename from extern/imgui_conf/imconfig_fur.h rename to extern/imgui_patched/imconfig_fur.h diff --git a/extern/imgui_patched/imgui.h b/extern/imgui_patched/imgui.h index f78011369..8674f9970 100644 --- a/extern/imgui_patched/imgui.h +++ b/extern/imgui_patched/imgui.h @@ -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 diff --git a/papers/format.md b/papers/format.md index bf0b929eb..78d62d489 100644 --- a/papers/format.md +++ b/papers/format.md @@ -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 diff --git a/papers/multiplayer.md b/papers/multiplayer.md new file mode 100644 index 000000000..c2978ee84 --- /dev/null +++ b/papers/multiplayer.md @@ -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) diff --git a/res/Info.plist b/res/Info.plist index 026051de2..2bb0c814e 100644 --- a/res/Info.plist +++ b/res/Info.plist @@ -15,17 +15,17 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString - 0.6pre5 + 0.6pre7 CFBundleName Furnace CFBundlePackageType APPL CFBundleShortVersionString - 0.6pre5 + 0.6pre7 CFBundleSignature ???? CFBundleVersion - 0.6pre5 + 0.6pre7 NSHumanReadableCopyright NSHighResolutionCapable diff --git a/res/furnace.appdata.xml b/res/furnace.appdata.xml index 5271379ab..5055d0df9 100644 --- a/res/furnace.appdata.xml +++ b/res/furnace.appdata.xml @@ -18,6 +18,9 @@

it also offers DefleMask compatibility, allowing you to import your songs and even export them back for interoperability.

+

+ rationale for intense profanity: the tracker itself is clean, but a few demo songs and instruments contain a small amount of strong language. +

diff --git a/scripts/release-win32.sh b/scripts/release-win32.sh index e0c0eec64..fdd0b4c6f 100755 --- a/scripts/release-win32.sh +++ b/scripts/release-win32.sh @@ -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 .. diff --git a/src/audio/sdlAudio.cpp b/src/audio/sdlAudio.cpp index 6d0ceff06..5d07921e9 100644 --- a/src/audio/sdlAudio.cpp +++ b/src/audio/sdlAudio.cpp @@ -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; diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index c458cf9c4..acb7af66e 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -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; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 1aa8188e0..c7b5ac9e0 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -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; diff --git a/src/engine/engine.h b/src/engine/engine.h index aceee308b..d9772cd37 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -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 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), diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index b41a52d5e..5c1521aba 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -64,6 +64,41 @@ const char** DivPlatformC64::getRegisterSheet() { return regCheatSheetSID; } +short DivPlatformC64::runFakeFilter(unsigned char ch, int in) { + if (!(regPool[0x17]&(1<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; iclock(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) { diff --git a/src/engine/platform/c64.h b/src/engine/platform/c64.h index 26b39c804..5f67b956a 100644 --- a/src/engine/platform/c64.h +++ b/src/engine/platform/c64.h @@ -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); diff --git a/src/engine/platform/fmshared_OPN.h b/src/engine/platform/fmshared_OPN.h index e1b5eca7c..8d3d059c4 100644 --- a/src/engine/platform/fmshared_OPN.h +++ b/src/engine/platform/fmshared_OPN.h @@ -130,14 +130,15 @@ class DivPlatformOPN: public DivPlatformFMBase { unsigned char freqH, freqL; int portaPauseFreq; signed char konCycles; - bool mask; + bool mask, hardReset; OPNOpChannel(): SharedChannel(0), freqH(0), freqL(0), portaPauseFreq(0), konCycles(0), - mask(true) {} + mask(true), + hardReset(false) {} }; struct OPNOpChannelStereo: public OPNOpChannel { diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 683f97211..a475b7da6 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -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; diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index e7f1fa153..fcd969726 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -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].dacPossamples) { 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].dacPossamples) { 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); diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index 210fca9d0..c9de0493f 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -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) {} }; diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 08b7a65cc..e0a3e4a6c 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -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; diff --git a/src/engine/platform/pv1000.cpp b/src/engine/platform/pv1000.cpp index bf03df405..0d7d728d0 100644 --- a/src/engine/platform/pv1000.cpp +++ b/src/engine/platform/pv1000.cpp @@ -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); } } } diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 9376bb0cf..20032a1be 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -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 { diff --git a/src/engine/platform/sound/c64_d/dsid.c b/src/engine/platform/sound/c64_d/dsid.c index d01dc7ad7..2ff2dacf8 100644 --- a/src/engine/platform/sound/c64_d/dsid.c +++ b/src/engine/platform/sound/c64_d/dsid.c @@ -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; diff --git a/src/engine/platform/sound/c64_d/dsid.h b/src/engine/platform/sound/c64_d/dsid.h index b02d1870e..b17a51f55 100644 --- a/src/engine/platform/sound/c64_d/dsid.h +++ b/src/engine/platform/sound/c64_d/dsid.h @@ -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); diff --git a/src/engine/platform/sound/ymfm/ymfm_opn.cpp b/src/engine/platform/sound/ymfm/ymfm_opn.cpp index 25d921a95..a8cc198a3 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opn.cpp +++ b/src/engine/platform/sound/ymfm/ymfm_opn.cpp @@ -141,6 +141,7 @@ template bool opn_registers_base::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 diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 209e2fc2c..f2fc6d439 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -548,19 +548,25 @@ void DivPlatformSwan::poke(std::vector& 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; } diff --git a/src/engine/platform/swan.h b/src/engine/platform/swan.h index 148856c06..cff6cc627 100644 --- a/src/engine/platform/swan.h +++ b/src/engine/platform/swan.h @@ -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(); diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index fe34b8bc4..fa0446ca6 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -19,7 +19,6 @@ #include "vera.h" #include "../engine.h" -#include "../../ta-log.h" #include #include @@ -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; } diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 798de87b2..9f8a2f10b 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -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; diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 97c26c569..d30cd959d 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -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 chipVol; std::vector delayedWrites[DIV_MAX_CHIPS]; std::vector> 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& 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++; diff --git a/src/engine/zsm.cpp b/src/engine/zsm.cpp index b0f421114..04bf68b91 100644 --- a/src/engine/zsm.cpp +++ b/src/engine/zsm.cpp @@ -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 + 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); diff --git a/src/engine/zsm.h b/src/engine/zsm.h index 42300cf70..ff497693f 100644 --- a/src/engine/zsm.h +++ b/src/engine/zsm.h @@ -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 ymwrites; std::vector pcmMeta; std::vector pcmData; diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 2aef20b9e..5dadfcf67 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -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", diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index e6306db2d..809f214b7 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -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; diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index b9dc6c423..bfeb9067e 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -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); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index a1a5cb842..6437ecab7 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -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& 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; iinit(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), diff --git a/src/gui/gui.h b/src/gui/gui.h index 267e34a0f..83c5a380a 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1304,6 +1304,7 @@ class FurnaceGUI { std::deque recentFile; std::vector makeInsTypeList; std::vector availRenderDrivers; + std::vector 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(); }; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index f0de1c614..183392612 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -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; isong.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; isong.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; diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index a34a856d8..72d953b7a 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -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