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 a5e91fb00..0128941c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -455,6 +455,8 @@ src/engine/platform/sound/c64_fp/WaveformCalculator.cpp src/engine/platform/sound/c64_fp/WaveformGenerator.cpp src/engine/platform/sound/c64_fp/resample/SincResampler.cpp +src/engine/platform/sound/c64_d/dsid.c + src/engine/platform/sound/tia/AudioChannel.cpp src/engine/platform/sound/tia/Audio.cpp @@ -586,6 +588,7 @@ src/engine/platform/k007232.cpp src/engine/platform/ga20.cpp src/engine/platform/sm8521.cpp src/engine/platform/pv1000.cpp +src/engine/platform/k053260.cpp src/engine/platform/pcmdac.cpp src/engine/platform/dummy.cpp @@ -803,7 +806,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 @@ -860,7 +862,7 @@ endif() string(REPLACE ";" " " WARNING_FLAGS_STRING "${WARNING_FLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS_STRING}") if (WARNINGS_ARE_ERRORS) - message(WARNING + message(STATUS "Treating all warnings in furnace's C++ code as errors! " "Please report any errors you encounter on the bug tracker." ) @@ -875,7 +877,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/CONTRIBUTING.md b/CONTRIBUTING.md index a3973f40a..f8a9d74ca 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -81,6 +81,10 @@ additional guidelines: - on a switch block, **always** put `default` last and not in any other position. - I have fear of some C/C++ compilers ignoring the rest of cases upon hitting default. +## Do NOT Force-Push after submitting Pull Request + +if you do so, your pull request will be closed. + ## Demo Songs just put your demo song in `demos/`! be noted there are some guidelines: 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/Destiny_Islands_Irem_M92.fur b/demos/arcade/Destiny_Islands_Irem_M92.fur new file mode 100644 index 000000000..36d158ecf Binary files /dev/null and b/demos/arcade/Destiny_Islands_Irem_M92.fur differ diff --git a/demos/arcade/Some_Creatures_SegaPCM.fur b/demos/arcade/Some_Creatures_SegaPCM.fur new file mode 100644 index 000000000..5578b8775 Binary files /dev/null and b/demos/arcade/Some_Creatures_SegaPCM.fur differ 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/gameboy/finger.fur b/demos/gameboy/finger.fur new file mode 100644 index 000000000..eb1917a34 Binary files /dev/null and b/demos/gameboy/finger.fur differ diff --git a/demos/genesis/Shovel_Knight_Title.fur b/demos/genesis/Shovel_Knight_Title.fur new file mode 100644 index 000000000..f16e971b6 Binary files /dev/null and b/demos/genesis/Shovel_Knight_Title.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/msx/Striking_Towards_Opposition.fur b/demos/msx/Striking_Towards_Opposition.fur new file mode 100644 index 000000000..40dee3801 Binary files /dev/null and b/demos/msx/Striking_Towards_Opposition.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/Fly_to_the_Leaden_Sky_OPL3.fur b/demos/opl/Fly_to_the_Leaden_Sky_OPL3.fur new file mode 100644 index 000000000..4546a270e Binary files /dev/null and b/demos/opl/Fly_to_the_Leaden_Sky_OPL3.fur 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/demos/snes/Cosmic_Warehouse.fur b/demos/snes/Cosmic_Warehouse.fur new file mode 100644 index 000000000..f5c277d64 Binary files /dev/null and b/demos/snes/Cosmic_Warehouse.fur differ diff --git a/demos/x16/Cafe - 010 Editor 2.0kg.fur b/demos/x16/Cafe - 010 Editor 2.0kg.fur index 6533a3846..ef1c9fea3 100644 Binary files a/demos/x16/Cafe - 010 Editor 2.0kg.fur and b/demos/x16/Cafe - 010 Editor 2.0kg.fur differ diff --git a/demos/x16/Shades of Blue.fur b/demos/x16/Shades of Blue.fur new file mode 100644 index 000000000..6d6e5a510 Binary files /dev/null and b/demos/x16/Shades of Blue.fur differ diff --git a/demos/x16/her11.fur b/demos/x16/her11.fur index 1cfabac19..06c4870f8 100644 Binary files a/demos/x16/her11.fur and b/demos/x16/her11.fur differ diff --git a/demos/x16/keygen19.fur b/demos/x16/keygen19.fur new file mode 100644 index 000000000..26d04ea82 Binary files /dev/null and b/demos/x16/keygen19.fur differ diff --git a/doc/1-intro/README.md b/doc/1-intro/README.md index c21ad355b..34658ece3 100644 --- a/doc/1-intro/README.md +++ b/doc/1-intro/README.md @@ -12,7 +12,7 @@ Furnace uses hexadecimal (abbreviated as "hex") numbers frequently. see [this gu ## interface -Furnace uses a music tracker interface. think of a table with music notes written on it. then that table scrolls up and plays the notes. +Furnace uses a music tracker interface. think of a table with music notes written on it. then that table scrolls up and plays the notes. even experienced tracker musicians might benefit from a quick review of [tracker concepts and terms](concepts.md) before using Furnace. due to its nature of being feature-packed, it may be technical and somewhat difficult to get around. therefore we added a basic mode, which hides several advanced features. diff --git a/doc/1-intro/concepts.md b/doc/1-intro/concepts.md new file mode 100644 index 000000000..130e2ae83 --- /dev/null +++ b/doc/1-intro/concepts.md @@ -0,0 +1,36 @@ +# concepts and terms + +- A **module** is a file for a tracker that contains at least one **song**. +- Each Furnace module involves at least one **[chip](../7-systems/README.md)**, an emulation of a specific audio processor. + +## tracking + +The **[pattern view](../3-pattern/README.md)** is like a spreadsheet that displays the following: +- Each labeled column represents a **channel** of sound provided by the chips in use. +- Each **note** starts a sound playing. Within a channel, only one note can play at a time. +- Each note is assigned an **[instrument](../4-instrument/README.md)** which describes what it will sound like. +- An **effect** is a command that changes some aspect of playback. It can alter note pitch, volume, timing, and more. +- An instrument **macro** is an automated sequence of effects that applies to every note of that instrument. + +## structure + +The **order list** is a smaller spreadsheet showing the overall song structure. +- A song is made up of a list of **orders**. +- An **order** is a set of numbered **patterns** used for each channel. +- Each channel has its own unique list of patterns. +- Each pattern contains note and effect data for that channel only. +- Patterns may be used multiple times in the order list. Changing a pattern's data in one order will affect the same pattern used in other orders. + +## time + +- Each pattern is made of the same number of **rows** as seen in the tracker view. +- During playback, Each row lasts a number of **ticks** determined by its **speed** value. +- A tick is the smallest measure of time to which all note, effect, and macro times are quantized. + +## sound + +Different chips have different capabilities. Even within the same chip, each channel may have its own ways of making sound. +- Some channels use one or more waveform **generators** (sine, square, noise...) to build up a sound. +- Of special note are **[FM (frequency modulation)](../4-instrument/fm.md)** channels, which use a number of generators called **operators** that can interact to make very complex sounds. +- Some channels use **[samples](../6-sample/README.md)** - recordings of sounds, often with defined loop points to allow a note to sustain. +- Some channels use **[wavetables](../5-wave/README.md)**, which are like very short samples of fixed length that automatically loop. \ No newline at end of file diff --git a/doc/1-intro/hex.md b/doc/1-intro/hex.md index 6139ca87f..16a28bc24 100644 --- a/doc/1-intro/hex.md +++ b/doc/1-intro/hex.md @@ -95,3 +95,24 @@ now for decimal number `69420`: = 10F2C ``` + +# hex-decimal table + + hex | `0` | `1` | `2` | `3` | `4` | `5` | `6` | `7` | `8` | `9` | `A` | `B` | `C` | `D` | `E` | `F` +-----:|----:|----:|----:|----:|----:|----:|----:|----:|----:|----:|----:|----:|----:|----:|----:|----: + `00` | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 + `10` | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 + `20` | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 + `30` | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 + `40` | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 + `50` | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 + `60` | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 + `70` | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 + `80` | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 + `90` | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 + `A0` | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 + `B0` | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 + `C0` | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 + `D0` | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 + `E0` | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 + `F0` | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 diff --git a/doc/2-interface/README.md b/doc/2-interface/README.md index 2ff1ebfc9..cb1588dde 100644 --- a/doc/2-interface/README.md +++ b/doc/2-interface/README.md @@ -9,6 +9,7 @@ the default layout of Furnace is depicted below. primary topics: - [menu bar](menu-bar.md) +- [order list](order-list.md) - [play/edit controls](play-edit-controls.md) - [instrument/wavetable/sample list](asset-list.md) - [song information](song-info.md) @@ -36,6 +37,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/basic-mode.md b/doc/2-interface/basic-mode.md index a8b0402ff..c764d9de2 100644 --- a/doc/2-interface/basic-mode.md +++ b/doc/2-interface/basic-mode.md @@ -1,6 +1,6 @@ # basic mode -Furnace comes with a "basic mode" that can be toggled through the "settings" menu. it disables certain features in Furnace that may look intimidating or confusing for newcomers. if you find that a certain feature of furnace is missing, see if this setting is enabled or not. +Furnace comes with a "basic mode" that can be toggled through the "settings" menu. it disables certain features in Furnace that may look intimidating or confusing for newcomers. if you find that a certain feature of Furnace is missing, see if this setting is enabled or not. among the features that cannot be accessed in this mode are: * file menu: diff --git a/doc/2-interface/menu-bar.md b/doc/2-interface/menu-bar.md index 8fac9e659..b53aa5f78 100644 --- a/doc/2-interface/menu-bar.md +++ b/doc/2-interface/menu-bar.md @@ -1,6 +1,6 @@ # menu bar -the menu bar allows you to select five menus: file, edit, settings, window and help. +the menu bar allows you to select from five menus: file, edit, settings, window and help. # file @@ -8,6 +8,7 @@ the menu bar allows you to select five menus: file, edit, settings, window and h - **open...**: opens the file picker, allowing you to select a song to open. - **open recent**: contains a list of the songs you've opened before. - **clear history**: this option erases the file history. + - **save**: saves the current song. - opens the file picker if this is a new song, or a backup. - **save as...**: opens the file picker, allowing you to save the song under a different name. @@ -29,12 +30,14 @@ the menu bar allows you to select five menus: file, edit, settings, window and h - Arcade (YM2151 + SegaPCM 5-channel compatibility) - Neo Geo CD (DefleMask 1.0+) - only use this option if you really need it. there are features which DefleMask does not support, like some effects and FM macros, so these will be lost. + - **export audio...**: export your song to a .wav file. see next section for more details. - **export VGM...**: export your song to a .vgm file. see next section for more details. - **export ZSM...**: export your song to a .zsm file. see next section for more details. - only available when there's a YM2151 and/or VERA. - **export command stream...**: export song data to a command stream file. see next section for more details. - this option is for developers. + - **add chip...**: add a chip to the current song. - **configure chip...**: set a chip's parameters. - for a list of parameters, see [7-systems](../7-systems/README.md). @@ -42,6 +45,7 @@ the menu bar allows you to select five menus: file, edit, settings, window and h - **Preserve channel positions**: enable this option to make sure Furnace does not auto-arrange/delete channels to compensate for differing channel counts. this can be useful for doing ports, e.g. from Genesis to PC-98. - **remove chip...**: remove a chip. - **Preserve channel positions**: same thing as above. + - **restore backup**: restore a previously saved backup. - Furnace keeps up to 5 backups of a song. - the backup directory is located in: @@ -49,6 +53,7 @@ the menu bar allows you to select five menus: file, edit, settings, window and h - macOS: `~/Library/Application Support/Furnace/backups` - Linux/other: `~/.config/furnace/backups` - this directory grows in size as you use Furnace. remember to delete old backups periodically to save space. + - **exit**: I think you know what this does. ## export audio @@ -131,6 +136,7 @@ it's not really useful, unless you're a developer and want to use a command stre - **undo**: reverts the last action. - **redo**: repeats what you undid previously. + - **cut**: moves the current selection in the pattern view to clipboard. - **copy**: copies the current selection in the pattern view to clipboard. - **paste**: inserts the clipboard's contents in the cursor position. @@ -147,11 +153,16 @@ it's not really useful, unless you're a developer and want to use a command stre - if the selection is tall, it will select the entire column. - if a column is already selected, it will select the entire channel. - if a channel is already selected, it will select the entire pattern. -- **operation mask**: this is an advanced feature. see [this page](../3-pattern/opmask.md) for more information. -- **input latch**: this is an advanced feature. see [this page](../3-pattern/inputlatch.md) for more information. + +- **operation mask**: toggles which columns will be affected by the listed operations. [more information here.](../8-advanced/opmask.md) +- **input latch**: determines which data are placed along with a note. [more information here.](../8-advanced/inputlatch.md) + - **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/expand amount**: allows you to specify how much to collapse/expand in the next two menu items. - **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. + +- **find/replace**: shows [the Find/Replace window](../8-advanced/find-replace.md). + - **clear**: allows you to mass-delete things like songs, instruments and the like. # settings @@ -183,44 +199,52 @@ 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...**: shows the Settings window. these are detailed in [settings.md]. # window -- **song information**: shows/hides the Song Information window. -- **subsongs**: shows/hides the Subsongs window. -- **speed**: shows/hides the Speed window. -- **instruments**: shows/hides the instrument list. -- **wavetables**: shows/hides the wavetable list. -- **samples**: shows/hides the sample list. -- **orders**: shows/hides the Orders window. -- **pattern**: shows/hides the pattern view. -- **mixer**: shows/hides the Mixer window. -- **grooves**: shows/hides the Grooves window. -- **channels**: shows/hides the Channels window. -- **pattern manager**: shows/hides the Pattern Manager window. -- **chip manager**: shows/hides the Chip Manager window. -- **compatibility flags**: shows/hides the Compatibility Flags window. -- **song comments**: shows/hides the Song Comments window. -- **instrument editor**: shows/hides the Instrument Editor. -- **wavetable editor**: shows/hides the Wavetable Editor. -- **sample editor**: shows/hides the Sample Editor. -- **play/edit controls**: shows/hides the Play/Edit Controls. -- **piano/input pad**: shows/hides the Piano/Input Pad window. -- **oscilloscope (master)**: shows/hides the oscilloscope. -- **oscilloscope (per-channel)**: shows/hides the per-channel oscilloscope. -- **volume meter**: shows/hides the volume meter. -- **clock**: shows/hides the clock. -- **register view**: shows/hides the Register View window. -- **log viewer**: shows/hides the log Viewer. -- **statistics**: shows/hides the Statistics window. +all these menu items show or hide their associated windows. + +- [song information](song-info.md) +- [subsongs](song-info.md) +- [speed](song-info.md) +- [instruments](../4-instrument/README.md) +- [wavetables](../5-wave/README.md) +- [samples](../6-sample/README.md) +- [orders](order-list.md) +- [pattern](../3-pattern/README.md) +- [mixer](mixer.md) +- [grooves](grooves.md) +- [channels](channels.md) +- [pattern manager](pat-manager.md) +- [chip manager](chip-manager.md) +- [compatibility flags](compat-flags.md) +- [song comments](comments.md) + +- [piano](piano.md) +- [oscilloscope](osc.md) +- [oscilloscopes (per-channel)](chanosc.md) +- [clock](clock.md) +- [register view](regview.md) +- [log viewer](log-viewer.md) +- [stats](stats.md) # help - **effect list**: displays the effect list. - **debug menu**: this menu contains various debug utilities. - unless you are working with the Furnace codebase, it's not useful. -- **inspector**: this options opens the Dear ImGui Metrics/Debugger window. +- **inspector**: this option shows the Dear ImGui Metrics/Debugger window. - unless you are working with the Furnace codebase, it's not useful. - **panic**: this resets all chips while the song is playing, effectively silencing everything. - **about...**: displays the About screen. + +at the end of the menu bar, more information may be shown: +- during editing, information about the data under the cursor will be shown here: + - note or note modifier. + - instrument number and name. + - volume in decimal, hex, and percentage. + - effect type and description. +- during playback, the current values of the following will be listed:\ + speed/groove @ tick rate (BPM) | order | row | elapsed time. +- if any changes or edits have been made but not yet saved, "modified" will appear. diff --git a/doc/2-interface/order-list.md b/doc/2-interface/order-list.md new file mode 100644 index 000000000..b838d811b --- /dev/null +++ b/doc/2-interface/order-list.md @@ -0,0 +1,29 @@ +# order list + +the order list is a playlist for patterns. + +![order list](order-list.png) + +along the top are the available channels. their abbreviations can be set in the [channels window](../8-advanced/channels.md). the highlighted channel follows the channel the pattern view cursor is in. + +along the left are the order numbers. these are referenced with the `0Bxx` command. the highlighted row follows the order the pattern view cursor is in. + +each entry in the table is the pattern that will play during that order. these can be changed according to the order edit mode. + +hovering over a pattern number will pop up a tooltip showing the name of that pattern, if it has one. + +The buttons are as follows: +- **Add new order**. +- **Remove order**. +- **Duplicate order**: adds a new order with patterns matching the selected one directly below it. right-click to "deep clone"; this copies all patterns involved to new ones. +- **Move order up**: swaps the selected order with the one above it. +- **Move order down**: swaps the selected order with the one below it. +- **Duplicate order at end of song**: same as "Duplicate order" except the new order is added at the bottom of the list. +- **Order change mode**: selects how much of the order will change with an edit. only applies if "Order edit mode" is set to "Click to change". + - **one**: only current channel's pattern will change. + - **entire row**: all patterns in the order will change. +- **Order edit mode**: selects the method of changing orders. + - **Click to change**: a click will add one to the pattern number. a right-click will subtract one. + - **Select and type (don't scroll)**: select a pattern and type. + - **Select and type (scroll horizontally)**: as above, but after entering two digits, the cursor moves to the next channel. + - **Select and type (scroll vertically)**: as above, but after entering two digits, the cursor moves to the next order. diff --git a/doc/2-interface/order-list.png b/doc/2-interface/order-list.png new file mode 100644 index 000000000..e3ead8c06 Binary files /dev/null and b/doc/2-interface/order-list.png differ 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/2-interface/song-info.md b/doc/2-interface/song-info.md index 5ba682a21..7919e7bec 100644 --- a/doc/2-interface/song-info.md +++ b/doc/2-interface/song-info.md @@ -11,7 +11,7 @@ All of this metadata will be included in a VGM export. This isn't the case for a # subsongs -This window allows one to create **subsongs** – multiple individual songs within a single file. Each song has its own order list and patterns, but all songs within a file share the same chips, samples, and so forth. +This window allows one to create **subsongs** - multiple individual songs within a single file. Each song has its own order list and patterns, but all songs within a file share the same chips, samples, and so forth. - The drop-down box selects the current subsong. - The **`+`** button adds a new subsong. diff --git a/doc/3-pattern/effects.md b/doc/3-pattern/effects.md index c7a54fd49..df1f85500 100644 --- a/doc/3-pattern/effects.md +++ b/doc/3-pattern/effects.md @@ -1,78 +1,99 @@ # effect list -most of the effect numbers are from ProTracker/FastTracker 2. -however, effects are continuous, which means you only need to type it once and then stop it with an effect value of `00`. +most of the effect numbers are from ProTracker / FastTracker 2. -- **`00xy`**: arpeggio. after using this effect the channel will rapidly switch between `note`, `note+x` and `note+y`. -- **`01xx`**: slide up. -- **`02xx`**: slide down. -- **`03xx`**: note portamento. - - a note must be present for this effect to work. -- **`04xy`**: vibrato. `x` is the speed, while `y` is the depth. - - maximum vibrato depth is ±1 semitone. -- **`07xy`**: tremolo. `x` is the speed, while `y` is the depth. - - maximum tremolo depth is -60 volume steps. -- **`08xy`**: set panning. `x` is the left channel and `y` is the right one. - - not all chips support this effect. -- **`80xx`**: set panning (linear). this effect behaves more like other trackers: +however, effects are continuous, which means you only need to type it once and then stop it with an effect value of `00` or no effect value at all. + +## volume + +- `0Axy`: **Volume slide.** + - 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. `y` is the depth. + - Tremolo is downward only. + - Maximum tremolo depth is -60 volume steps. + +## pitch + +- `E5xx`: **Set pitch.** `00` is -1 semitone, `80` is base pitch, `FF` is nearly +1 semitone. +- `01xx`: **Pitch slide up.** +- `02xx`: **Pitch slide down.** +- `F1xx`: **Single tick pitch slide up.** +- `F2xx`: **Single tick pitch slide down.** + +- `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. 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. default. + - `01`: Up only. + - `02`: Down only. +- `E4xx`: **Set vibrato range** in 1/16th of a semitone. + +## panning + +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. - - not all chips support this effect. -- **`81xx`**: set volume of left channel (from `00` to `FF`). - - not all chips support this effect. -- **`82xx`**: set volume of right channel (from `00` to `FF`). - - not all chips support this effect. -- **`09xx`**: set speed 1. -- **`0Axy`**: volume slide. - - if `x` is 0 then this is a slide down. - - if `y` is 0 then this is a slide up. -- **`0Bxx`**: jump to pattern. -- **`0Cxx`**: retrigger note every `xx` ticks. - - this effect is not continuous. -- **`0Dxx`**: jump to next pattern. -- **`0Fxx`**: set speed 2. -- **`9xxx`**: set sample position to `xxx`\*0x100. - - not all chips support this effect. +## time -- **`Cxxx`**: change song Hz. - - `xxx` may be from `000` to `3ff`. +- `09xx`: **Set speed/groove.** if no grooves are defined, this sets speed. If alternating speeds are active, this sets the first speed. +- `0Fxx`: **Set speed 2.** during alternating speeds or a groove, this sets the second speed. -- **`E0xx`**: set arpeggio tick. - - this sets the number of ticks between arpeggio values. -- **`E1xy`**: note slide up. `x` is the speed, while `y` is how many semitones to slide up. -- **`E2xy`**: note slide down. `x` is the speed, while `y` is how many semitones to slide down. -- **`E3xx`**: set vibrato direction. `xx` may be one of the following: - - `00`: up and down. - - `01`: up only. - - `02`: down only. -- **`E4xx`**: set vibrato range in 1/16th of a semitone. -- **`E5xx`**: set pitch. `80` is 0 cents. - - range is ±1 semitone. -- **`EAxx`**: toggle legato. -- **`EBxx`**: set sample bank. - - does not apply on Amiga. -- **`ECxx`**: note off after `xx` ticks. -- **`EDxx`**: delay note by `xx` ticks. -- **`EExx`**: send external command. - - this effect is currently incomplete. -- **`F0xx`**: change song Hz by BPM value. -- **`F1xx`**: single tick slide up. -- **`F2xx`**: single tick slide down. -- **`F3xx`**: fine volume slide up (64x slower than `0Axy`). -- **`F4xx`**: fine volume slide down (64x slower than `0Axy`). -- **`F5xx`**: disable macro. - - see macro table at the end of this document for possible values. -- **`F6xx`**: enable macro. -- **`F8xx`**: single tick volume slide up. -- **`F9xx`**: single tick volume slide down. -- **`FAxy`**: fast volume slide (4x faster than `0Axy`). - - if `x` is 0 then this is a slide down. - - if `y` is 0 then this is a slide up. -- **`FFxx`**: end of song/stop playback. +- `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. range is `01` to `FF`. -additionally each chip has its own effects. [click here for more details](../7-systems/README.md). +- `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 + +- `0Cxx`: **Retrigger.** repeats current note every `xx` ticks. + - This effect is not continuous; it must be entered on every row. +- `ECxx`: **Note cut.** ends current note after `xx` ticks. For FM instruments, it's equivalent to a "key off". +- `EDxx`: **Note delay.** delays note by `x` ticks. + +## other + +- `9xxx`: **Set sample position.** jumps current sample to position `xxx \* 0x100`. + - Not all chips support this effect. +- `EBxx`: **Set sample bank.** + - Does not apply on Amiga. +- `EExx`: **Send external command.** + - This effect is currently incomplete. +- `F5xx`: **Disable macro.** see macro table at the end of this document for possible values. +- `F6xx`: **Enable macro.** + +additionally, [each chip has its own effects](../7-systems/README.md). ## macro table @@ -98,8 +119,8 @@ ID | macro 11 | extra 6 12 | extra 7 13 | extra 8 ----|----------------------------- -20 | **operator 1 macros** - AM +| | **operator 1 macros** +20 | AM 21 | AR 22 | DR 23 | MULT @@ -119,10 +140,9 @@ ID | macro 31 | VIB 32 | WS 33 | KSR ----|----------------------------- -40 | operator 2 macros -60 | operator 3 macros -80 | operator 4 macros +40 | **operator 2 macros** +60 | **operator 3 macros** +80 | **operator 4 macros** the interpretation of duty, wave and extra macros depends on chip/instrument type: diff --git a/doc/4-instrument/README.md b/doc/4-instrument/README.md index 8857cf2ec..bd00b6b85 100644 --- a/doc/4-instrument/README.md +++ b/doc/4-instrument/README.md @@ -1,99 +1,114 @@ -# instrument list - -![instrument list](list.png) - -click on an instrument to select it. - -double-click to open the instrument editor. - -# instrument editor - -every instrument can be renamed and have its type changed. - -depending on the instrument type, there are currently 13 different types of an instrument editor: - -- [FM synthesis](fm.md) - for use with YM2612, YM2151 and FM block portion of YM2610. -- [Standard](standard.md) - for use with NES and Sega Master System's PSG sound source and its derivatives. -- [Game Boy](game-boy.md) - for use with Game Boy APU. -- [PC Engine / TurboGrafx-16](pce.md) - for use with PC Engine's wavetable synthesizer. -- [WonderSwan](wonderswan.md) - for use with WonderSwan's wavetable synthesizer. -- [AY8930](8930.md) - for use with Microchip AY8930 E-PSG sound source. -- [Commodore 64](c64.md) - for use with Commodore 64 SID. -- [SAA1099](saa.md) - for use with Philips SAA1099 PSG sound source. -- [TIA](tia.md) - for use with Atari 2600 chip. -- [AY-3-8910](ay8910.md) - for use with AY-3-8910 PSG sound source and SSG portion in YM2610. -- [Amiga / sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM, X1-010 and PC Engine's sample playback mode. -- [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console. -- [VERA](vera.md) - for use with Commander X16 VERA. -- [Seta/Allumer X1-010](x1_010.md) - for use with Wavetable portion in Seta/Allumer X1-010. -- [Konami SCC / Bubble System WSG](scc.md) - for use with Konami SCC and Wavetable portion in Bubble System's sound hardware. -- [Namco 163](n163.md) - for use with Namco 163. -- [Konami VRC6](vrc6.md) - for use with VRC6's PSG sound source. - - - -# macros - -Macros are incredibly versatile tools for automating instrument parameters. - -After creating an instrument, open the Instrument Editor and select the "Macros" tab. There may be multiple macro tabs to control individual FM operators and such. - -![macro view](macroview.png) - -The very first numeric entry sets the visible width of the bars in sequence-type macros. The scrollbar affects the view of all macros at once. There's a matching scrollbar at the bottom underneath all the macros. - -Each macro has two buttons on the left. -- Macro type (explained below). -- Timing editor, which pops up a small dialog: - - Step Length (ticks): Determines how many ticks pass before each change of value. - - Delay: Delays the start of the macro until this many ticks have passed. - -## macro types - -Every macro can be defined though one of three methods, selectable with the leftmost button under the macro type label: - -- ![](macro-button-seq.png) **Sequence:** Displayed as a bar graph, this is a sequence of numeric values. -- ![](macro-button-ADSR.png) **ADSR:** This is a traditional ADSR envelope, defined by the rate of increase and decrease of value over time. -- ![](macro-button-LFO.png) **LFO:** The Low Frequency Oscillator generates a repeating wave of values. - -Some macros are "bitmap" style. They represent a number of "bits" that can be toggled individually, and the values listed represent the sum of which bits are turned on. - -### sequence - -![sequence macro editor](macro-seq.png) - -The number between the macro type label and the macro type button is the macro length in steps. The `-` and `+` buttons change the length of the macro. Start out by adding at least a few steps. - -The values of the macro can be drawn in the "bar graph box". Just beneath the box is shorter bar graph. -- Click to set the start point of a loop; the end point is the last value or release point. Right-click to remove the loop. -- Shift-click to set the release point. When played, the macro will hold here until the note is released. Right-click to remove the release point. - -Finally, the sequence of values can be directly edited in the text box at the bottom. -- The loop start is entered as a `|`. -- The release point is entered as a `/`. -- In arpeggio macros, a value starting with a `@` is an absolute note (instead of a relative shift). No matter the note played, `@` values will be played at that exact note. This is especially useful for noise instruments with preset periods. - -### ADSR - -![ADSR macro editor](macro-ADSR.png) - -- **Bottom** and **Top** determine the range of outputs generated by the macro. (Bottom can be larger than Top to invert the envelope!) All outputs will be between these two values. -- Attack, Decay, Sustain, SusDecay, and Release accept inputs between 0 to 255. These are scaled to the distance between Bottom and Top. -- **Attack** is how much the value moves toward Top with each tick. -- **Hold** sets how many ticks to stay at Top before Decay. -- **Decay** is how much the value moves to the Sustain level. -- **Sustain** is how far from Bottom the value stays while the note is held. -- **SusTime** is how many ticks to stay at Sustain until SusDecay. -- **SusDecay** is how much the value moves toward Bottom with each tick while the note is held. -- **Release** is how much the value moves toward Bottom with each tick after the note is released. - -![macro ADSR chart](macro-ADSRchart.png) - -### LFO - -![LFO macro editor](macro-LFO.png) - -- **Bottom** and **Top** determine the range of values generated by the macro. (Bottom can be larger than Top to invert the waveform!) -- **Speed** is how quickly the values change – the frequency of the oscillator. -- **Phase** is which part of the waveform the macro will start at, measured in 1/1024 increments. -- **Shape** is the waveform used. Triangle is the default, and Saw and Square are exactly as they say. +# instrument list + +![instrument list](list.png) + +click on an instrument to select it. + +double-click to open the instrument editor. + +# instrument editor + +every instrument can be renamed and have its type changed. + +depending on the instrument type, there are many different types of instrument editor: + +- [FM synthesis](fm.md) - for use with YM2612, YM2151 and FM block portion of YM2610. +- [PSG](psg.md) - for use with TI SN76489 and derivatives like Sega Master System's PSG. +- [NES](nes.md) - for use with NES. +- [Game Boy](game-boy.md) - for use with Game Boy APU. +- [PC Engine / TurboGrafx-16](pce.md) - for use with PC Engine's wavetable synthesizer. +- [WonderSwan](wonderswan.md) - for use with WonderSwan's wavetable synthesizer. +- [AY8930](8930.md) - for use with Microchip AY8930 E-PSG sound source. +- [Commodore 64](c64.md) - for use with Commodore 64 SID. +- [SAA1099](saa.md) - for use with Philips SAA1099 PSG sound source. +- [TIA](tia.md) - for use with Atari 2600 chip. +- [AY-3-8910](ay8910.md) - for use with AY-3-8910 PSG sound source and SSG portion in YM2610. +- [Amiga / sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM, X1-010 and PC Engine's sample playback mode. +- [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console. +- [VERA](vera.md) - for use with Commander X16 VERA. +- [Seta/Allumer X1-010](x1_010.md) - for use with Wavetable portion in Seta/Allumer X1-010. +- [Konami SCC / Bubble System WSG](scc.md) - for use with Konami SCC and Wavetable portion in Bubble System's sound hardware. +- [Namco 163](n163.md) - for use with Namco 163. +- [Konami VRC6](vrc6.md) - for use with VRC6's PSG sound source. +- [SNES](snes.md) - for use with SNES S-APU. +- [Casio PV-1000](pv1000.md) - for use with Casio PV-1000. + + +# macros + +Macros are incredibly versatile tools for automating instrument parameters. + +After creating an instrument, open the Instrument Editor and select the "Macros" tab. There may be multiple macro tabs to control individual FM operators and such. + +![macro view](macroview.png) + +The very first numeric entry sets the visible width of the bars in sequence-type macros. The scrollbar affects the view of all macros at once. There's a matching scrollbar at the bottom underneath all the macros. + +Each macro has two buttons on the left. +- Macro type (explained below). +- Timing editor, which pops up a small dialog: + - Step Length (ticks): Determines how many ticks pass before each change of value. + - Delay: Delays the start of the macro until this many ticks have passed. + +## macro types + +Every macro can be defined though one of three methods, selectable with the leftmost button under the macro type label: + +- ![](macro-button-seq.png) **Sequence:** displayed as a bar graph, this is a sequence of numeric values. +- ![](macro-button-ADSR.png) **ADSR:** this is a traditional ADSR envelope, defined by the rate of increase and decrease of value over time. +- ![](macro-button-LFO.png) **LFO:** the Low Frequency Oscillator generates a repeating wave of values. + +Some macros are "bitmap" style. They represent a number of "bits" that can be toggled individually, and the values listed represent the sum of which bits are turned on. + +### sequence + +![sequence macro editor](macro-seq.png) + +The number between the macro type label and the macro type button is the macro length in steps. The `-` and `+` buttons change the length of the macro. Start out by adding at least a few steps. + +The values of the macro can be drawn in the "bar graph box". Just beneath the box is shorter bar graph. +- Click to set the start point of a loop; the end point is the last value or release point. Right-click to remove the loop. +- Shift-click to set the release point. When played, the macro will hold here until the note is released. Right-click to remove the release point. + +Finally, the sequence of values can be directly edited in the text box at the bottom. +- The loop start is entered as a `|`. +- The release point is entered as a `/`. +- In arpeggio macros, a value starting with a `@` is an absolute note (instead of a relative shift). No matter the note played, `@` values will be played at that exact note. This is especially useful for noise instruments with preset periods. + +### ADSR + +![ADSR macro editor](macro-ADSR.png) + +- **Bottom** and **Top** determine the range of outputs generated by the macro. (Bottom can be larger than Top to invert the envelope!) All outputs will be between these two values. +- Attack, Decay, Sustain, SusDecay, and Release accept inputs between 0 to 255. These are scaled to the distance between Bottom and Top. +- **Attack** is how much the value moves toward Top with each tick. +- **Hold** sets how many ticks to stay at Top before Decay. +- **Decay** is how much the value moves to the Sustain level. +- **Sustain** is how far from Bottom the value stays while the note is held. +- **SusTime** is how many ticks to stay at Sustain until SusDecay. +- **SusDecay** is how much the value moves toward Bottom with each tick while the note is held. +- **Release** is how much the value moves toward Bottom with each tick after the note is released. + +![macro ADSR chart](macro-ADSRchart.png) + +### LFO + +![LFO macro editor](macro-LFO.png) + +- **Bottom** and **Top** determine the range of values generated by the macro. (Bottom can be larger than Top to invert the waveform!) +- **Speed** is how quickly the values change - the frequency of the oscillator. +- **Phase** is which part of the waveform the macro will start at, measured in 1/1024 increments. +- **Shape** is the waveform used. Triangle is the default, and Saw and Square are exactly as they say. + +# samples + +This tab appears for Generic PCM, SNES, Amiga, and other sample-based instruments. + +![](sample-map.png) + +- **Initial Sample**: the sample that the instrument will use. +- **Use wavetable**: instead of samples, use wavetables. this causes the [Wavetables](../5-wave/README.md) tab to appear next to Sample. + - depending on the system and use of the wavetable synthesizer, this may or may not be reproducible on hardware. +- **Use sample map**: assigns a sample to each note. + - samples will be played at their default pitch. + - to set a note's sample, click the list entry in the `#` column then type the number of the sample. diff --git a/doc/4-instrument/amiga.md b/doc/4-instrument/amiga.md index fc76c8667..3d7a606f9 100644 --- a/doc/4-instrument/amiga.md +++ b/doc/4-instrument/amiga.md @@ -1,4 +1,4 @@ -# Amiga/PCM sound sourceinstrument editor +# Amiga/PCM sound source instrument editor The PCM instrument editor consists of a sample selector and several macros: @@ -8,7 +8,7 @@ The PCM instrument editor consists of a sample selector and several macros: # Macros -- **Volume**: volume sequence WARNING: it works only on Amiga system, as of version 0.5.5!! +- **Volume**: volume sequence. _warning:_ it works only on Amiga system, as of version 0.5.5! - **Arpeggio**: pitch sequence - **Waveform**: sample sequence - **Panning (left)**: output level for left channel diff --git a/doc/4-instrument/nes.md b/doc/4-instrument/nes.md new file mode 100644 index 000000000..d8df859af --- /dev/null +++ b/doc/4-instrument/nes.md @@ -0,0 +1,18 @@ +# Standard instrument editor + +The instrument editor for NES consists of these macros: + +- **Volume**: volume. +- **Arpeggio**: pitch in half-steps. +- **Duty**: duty cycle and noise mode. + - pulse duty cycles: + - `0`: 12.5% + - `1`: 25% + - `2`: 50% + - `3`: 75% + - noise modes: + - `0`: long noise. + - `1`: short noise. +- **Panning**: output for left and right channels. +- **Pitch**: fine pitch. +- **Phase Reset**: trigger restart of waveform. \ No newline at end of file diff --git a/doc/4-instrument/psg.md b/doc/4-instrument/psg.md new file mode 100644 index 000000000..2f692af1f --- /dev/null +++ b/doc/4-instrument/psg.md @@ -0,0 +1,14 @@ +# PSG instrument editor + +The instrument editor for PSG (SMS, MSX, and other TI SN76489 derivatives) consists of these macros: + +- **Volume**: volume. +- **Arpeggio**: pitch in half-steps. +- **Duty**: noise mode. + - `0`: short noise, preset frequencies. + - `1`: long noise, preset frequencies. + - `2`: short noise, use channel 3 for frequency. + - `3`: long noise, use channel 3 for frequency. +- **Panning**: output for left and right channels. +- **Pitch**: fine pitch. +- **Phase Reset**: trigger restart of waveform. \ No newline at end of file diff --git a/doc/4-instrument/pv1000.md b/doc/4-instrument/pv1000.md new file mode 100644 index 000000000..5d6a88534 --- /dev/null +++ b/doc/4-instrument/pv1000.md @@ -0,0 +1,7 @@ +# PV-1000 instrument editor + +The instrument editor for the Casio PV-1000 consists of these macros: + +- **Volume**: volume +- **Arpeggio**: pitch in half-steps +- **Pitch**: fine pitch diff --git a/doc/4-instrument/sample-map.png b/doc/4-instrument/sample-map.png new file mode 100644 index 000000000..1d2787122 Binary files /dev/null and b/doc/4-instrument/sample-map.png differ diff --git a/doc/4-instrument/snes.md b/doc/4-instrument/snes.md new file mode 100644 index 000000000..0887e9a6a --- /dev/null +++ b/doc/4-instrument/snes.md @@ -0,0 +1,46 @@ +# SNES instrument editor + +these tabs are unique to the editor for SNES instruments. + + + +# SNES + +**Use envelope** enables the ADSR volume envelope. if it's on: + +- **A**: attack rate. +- **D**: decay rate. +- **S**: sustain level. +- **D2**: decay rate during sustain. +- **R**: release rate. +- **Sustain/release mode**: + - **Direct**: note release acts as note cut. + - **Effective (linear decrease)**: after release, volume lowers by subtractions of 1/64 steps. + - **Effective (exponential decrease)**: after release, volume decays exponentially. see [gain chart](../7-systems/snes.md). + - **Delayed (write R on release)**: after release, waits until A and D have completed before starting exponential decrease. + +if envelope is off, select gain mode as described below. + + + +# Macros + +- **Volume**: volume. +- **Arpeggio**: pitch in half-steps. +- **Noise Freq**: preset frequency of noise generator. +- **Waveform**: waveform. +- **Panning (left)**: output level of left channel. +- **Panning (right)**: output level of right channel. +- **Pitch**: fine pitch. +- **Special**: bitmap of flags. + - invert left: inverts output of left channel. + - invert right: inverts output of right channel. + - pitch mod: modulates pitch using previous channel's output. + - echo: enables echo. + - noise: enables noise generator. +- **Gain**: sets mode and value of gain. + - 0 - 127: direct gain from 0 to 127 + - 128 - 159: linear gain from -0 to -31 + - 160 - 191: exponential gain from -0 to -31 + - 192 - 223: linear gain from +0 to +31 + - 224 - 255: exponential gain from +0 to +31 diff --git a/doc/4-instrument/standard.md b/doc/4-instrument/standard.md deleted file mode 100644 index 2da6e1601..000000000 --- a/doc/4-instrument/standard.md +++ /dev/null @@ -1,10 +0,0 @@ -# Standard instrument editor - -The instrument editor for NES and PSG (SMS, MSX, and such) consists of these macros: - -- **Volume**: volume -- **Arpeggio**: pitch in half-steps -- **Duty**: duty cycle and noise mode for NES channels. _Note:_ This has no effect on Sega Master System. -- **Panning**: output for left and right channels -- **Pitch**: fine pitch -- **Phase Reset**: trigger restart of waveform \ No newline at end of file diff --git a/doc/4-instrument/x1_010.md b/doc/4-instrument/x1_010.md index 2311f74d0..8be27f057 100644 --- a/doc/4-instrument/x1_010.md +++ b/doc/4-instrument/x1_010.md @@ -1,11 +1,11 @@ # X1-010 instrument editor -X1-010 instrument editor consists of 7 macros. +X1-010 instrument editor consists of these macros. - **Volume**: volume levels sequence - **Arpeggio**: pitch sequence -- **Waveform**: spicifies wavetables sequence +- **Waveform**: specifies wavetables sequence - **Envelope Mode**: allows shaping an envelope -- **Envelope**: spicifies envelope shape sequence, it's also wavetable. +- **Envelope**: specifies envelope shape sequence, it's also wavetable. - **Auto envelope numerator**: sets the envelope to the channel's frequency multiplied by numerator - **Auto envelope denominator**: sets the envelope to the channel's frequency divided by denominator diff --git a/doc/5-wave/README.md b/doc/5-wave/README.md index cdf344f0c..e03940588 100644 --- a/doc/5-wave/README.md +++ b/doc/5-wave/README.md @@ -11,7 +11,7 @@ Furnace's wavetable editor features multiple ways of creating desired waveform s - **Exponent**: Powers the waveform in the mathematical sense of the word (^2, ^3 and so on) - **XOR Point**: Determines the point where the waveform gets negated. - _TODO:_ amplitude/phase part -- **FM** For creating the waveform with frequency modulation synthesis principles: One can set carrier/modulation levels, frquency multiplier, connection between operators and FM waveforms of these operators. +- **FM** for creating the waveform with frequency modulation synthesis principles: One can set carrier/modulation levels, frquency multiplier, connection between operators and FM waveforms of these operators. - **WaveTools**: Allows user to fine-tune the waveform: scale said waveform in both X and Y axes, smoothen, amplify, normalize, convert to signed/unisgned, invert or even randomize the wavetable. ## wavetable synthesizer diff --git a/doc/6-sample/README.md b/doc/6-sample/README.md index 2f4fe3cd4..637884370 100644 --- a/doc/6-sample/README.md +++ b/doc/6-sample/README.md @@ -6,7 +6,7 @@ In Furnace, these samples can be generated by importing a .wav (think of it as a ## supported chips -as of Furnace 0.6, the following sound chips have sample support: +the following sound chips have sample support: - NES/Ricoh 2A03 (with DPCM support and only on channel 5) - Sega Genesis/YM2612 (channel 6 only) @@ -32,6 +32,7 @@ as of Furnace 0.6, the following sound chips have sample support: - Ensoniq OTTO/ES5506 - Yamaha PCMD8/YMZ280B - MMC5 (last channel only) +- SNES/S-DSP ## compatible sample mode @@ -46,7 +47,7 @@ use of this mode is discouraged in favor of Sample type instruments. due to limitations in some of those sound chips, some restrictions exist: -- Amiga: sample lengths and loop will be set to an even number, and your sample can't be longer than 131070. +- Amiga: maximum frequency is 31,469Hz, but anything over 28,867 will sound glitchy on hardware. sample lengths and loop will be set to an even number, and your sample can't be longer than 131070. - NES: if on DPCM mode, only a limited selection of frequencies is available, and loop position isn't supported (only entire sample). - SegaPCM: your sample can't be longer than 65535, and the maximum frequency is 31.25KHz. - QSound: your sample can't be longer than 65535, and the loop length shall not be greater than 32767. @@ -61,12 +62,77 @@ furthermore, many of these chips have a limited amount of sample memory. check m # the sample editor -You can actually tweak your samples in Furnace's sample editor, which can be accessed by clicking on `window` (at the top of the screen) then clicking on `sample editor`. +you can actually tweak your samples in Furnace's sample editor, which can be accessed by clicking on `window` (at the top of the screen) then clicking on `sample editor`. -In there, you can modify certain data pertaining to your sample, such as the: +the changes you make will be applied as soon as you've committed them to your sample, but they can be undone and redone, just like text. + +in there, you can modify certain data pertaining to your sample, such as the: - volume of the sample in percentage, where 100% is the current level of the sample (note that you can distort it if you put it too high) - the sample rate. - what frequencies to filter, along with filter level/sweep and resonance options (much like the C64) - and many more. -The changes you make will be applied as soon as you've committed them to your sample, but they can be undone and redoed, just like text. +![sample editor](sample-editor.png) + +- top-left drop-down box: sample slot. +- **Open**: replaces current sample. +- **Save**: saves current sample to disk. +- **Name**: name in sample list. +- button to left of **Info**: collapses and expands the info bar. +- **Type**: sample format. only 8-bit and 16-bit PCM samples are editable. selecting a format converts the sample data. +- **BRR emphasis**: boosts higher frequencies to compensate for the SNES low-pass filter. should not be enabled for BRR-type samples. + +- **Rate**: switches to normal rate values. +- **Compat Rate**: switches to DefleMask-compatible rate values for sample mapping. +- **Hz**: base frequency of sample played at `C-4`. +- **Note**: note corresponding to Hz. +- **Fine**: fine tuning. ranges from -64 to 63, which maps to -1 to almost +1 semitone. + +- **Loop**: enable or disable sample loop. only on supported chips. +- **Mode**: direction of loop. backward and ping pong loops are only natively available on some chips; on others, loop will be automatically unrolled as needed. +- **Start**: start of loop. +- **End**: end of loop. + +- **Chips:** set assignment to chips and sample banks. + - sample will only be uploaded to selected chips. + - columns correspond to chips in use. + - rows correspond to sample banks. + +![sample editor button bar](sample-editor-buttons.png) + +- **Edit mode: Select**: cursor selects portion of sample. +- **Edit mode: Draw**: cursor draws over wave. +- **Resize**: stretches sample. pops up a dialog to type new length. +- **Resample**: stretches sample. pops up a dialog box: + - **Rate**: new sample rate. + - **0.5x**: halves sample rate. + - **==**: returns to original sample rate. + - **2.0x**: doubles sample rate. + - **Factor**: multiplier of original sample rate. + - **Filter**: selects interpolation filter for resampling. +- **Undo**: undoes previous edit. +- **Redo**: redoes undone edit. +- **Amplify**: changes amplitude of selection. pops up a dialog to type amount. +- **Normalize**: adjusts amplitude of selection to maximum without clipping. +- **Fade in**: ramp amplitude of selection from 0 to original. +- **Fade in**: ramp amplitude of selection from original to 0. +- **Insert silence**: inserts silence. pops up a dialog to type length. +- **Apply silence**: reduces amplitude of selection to 0. +- **Delete**: removes selection. +- **Trim**: removes all but selection. +- **Reverse**: reverses direction of selection. +- **Invert**: flips selection "vertically". +- **Signed/unsigned exchange**: reinterprets selection data as being of the opposite sign. if a sample sounds fine elsewhere but is distorted on import, it may have been interpreted as signed when it should be unsigned, or vice versa; this will correct that. +- **Apply filter**: filters the selection. pops up a dialog box: + - **From**: filter cutoff frequency at start of selection. + - **To**: filter cutoff frequency at end of selection. + - **Resonance**: emphasizes frequencies around filter cutoff. + - **Power**: number of times resonance is applied. + - **Low-pass**: amount to attenuate everything above cutoff. + - **Band-pass**: amount to attenuate everything outside cutoff. + - **High-pass**: amount to attenuate everything below cutoff. +- **Preview sample**: plays sample at base frequency. +- **Stop sample preview**: stops preview. +- **Create instrument from sample**: creates a new instrument with its initial sample set to the current sample. +- **Zoom**: shows and sets sample view zoom level. +- **Zoom mode**: switches between "Auto" (entire sample fits in window) and "100%" (each horizontal pixel represents one sample point). diff --git a/doc/6-sample/sample-editor-buttons.png b/doc/6-sample/sample-editor-buttons.png new file mode 100644 index 000000000..7d2351db9 Binary files /dev/null and b/doc/6-sample/sample-editor-buttons.png differ diff --git a/doc/6-sample/sample-editor.png b/doc/6-sample/sample-editor.png new file mode 100644 index 000000000..1086e43d4 Binary files /dev/null and b/doc/6-sample/sample-editor.png differ diff --git a/doc/7-systems/README.md b/doc/7-systems/README.md index 4101aa958..71285b521 100644 --- a/doc/7-systems/README.md +++ b/doc/7-systems/README.md @@ -6,6 +6,7 @@ this is a list of sound chips that Furnace supports, including effects. - [AY-3-8910](ay8910.md) - [Microchip AY8930](ay8930.md) - [Bubble System WSG](bubblesystem.md) +- [Casio PV-1000](pv1000.md) - [Commodore 64](c64.md) - [Commodore PET](pet.md) - [Commodore VIC-20](vic20.md) @@ -26,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/amiga.md b/doc/7-systems/amiga.md index 907b79fed..ee197a90a 100644 --- a/doc/7-systems/amiga.md +++ b/doc/7-systems/amiga.md @@ -4,12 +4,20 @@ a computer with a desktop OS, lifelike graphics and 4 channels of PCM sound in 1 in this very computer music trackers were born... +imported MOD files use this chip, and will set A-4 tuning to 436. + # effects -- **`10xx`**: toggle low-pass filter. `0` turns it off and `1` turns it on. -- **`11xx`**: toggle amplitude modulation with the next channel. +- `10xx`: **toggle low-pass filter.** `0` turns it off and `1` turns it on. +- `11xx`: **toggle amplitude modulation with the next channel.** - does not work on the last channel. -- **`12xx`**: toggle period (frequency) modulation with the next channel. +- `12xx`: **toggle period (frequency) modulation with the next channel.** - does not work on the last channel. -- **`13xx`**: change wave. +- `13xx`: **change wave.** - only works when "Mode" is set to "Wavetable" in the instrument. + +# info + +- the maximum rate for sample playback is technically 31469Hz but anything higher than 28867Hz will sound glitchy on hardware. +- sample lengths and loop will be set to an even number. +- samples can't be longer than 131070. \ No newline at end of file diff --git a/doc/7-systems/ay8910.md b/doc/7-systems/ay8910.md index 4b4724e72..0b9d759a4 100644 --- a/doc/7-systems/ay8910.md +++ b/doc/7-systems/ay8910.md @@ -6,41 +6,52 @@ it is a 3-channel square/noise/envelope sound generator. the chip's powerful sou the AY-3-8914 variant was used in Intellivision, which is pretty much an AY with 4 level envelope volume per channel and different register format. +as of Furnace 0.6pre7, AY-3-8910 supports software sample playback, where all 3 channels can play 4-bit PCM samples (at the cost of a very high CPU usage) + # effects -- **`20xx`**: set channel mode. `xx` may be one of the following: - - `00`: square - - `01`: noise - - `02`: square and noise - - `03`: envelope - - `04`: envelope and square - - `05`: envelope and noise - - `06`: envelope and square and noise - - `07`: nothing -- **`21xx`**: set noise frequency. `xx` is a value between 00 and 1F. -- **`22xy`**: set envelope mode. - - `x` sets the envelope shape, which may be one of the following: - - `0: \___` decay - - `4: /___` attack once - - `8: \\\\` saw - - `9: \___` decay - - `A: \/\/` inverse obelisco - - `B: \¯¯¯` decay once - - `C: ////` inverse saw - - `D: /¯¯¯` attack - - `E: /\/\` obelisco - - `F: /___` attack once +- `20xx`: **set channel mode.** + - `0`: square + - `1`: noise + - `2`: square and noise + - `3`: envelope + - `4`: envelope and square + - `5`: envelope and noise + - `6`: envelope and square and noise + - `7`: nothing +- `21xx`: **set noise frequency.** range is `0` to `1F`. +- `22xy`: **set envelope mode.** + - `x` sets the envelope shape: + - `0`: `\___` decay + - `4`: `/___` attack once + - `8`: `\\\\` saw + - `9`: `\___` decay + - `A`: `\/\/` inverse obelisco + - `B`: `\¯¯¯` decay once + - `C`: `////` inverse saw + - `D`: `/¯¯¯` attack + - `E`: `/\/\` obelisco + - `F`: `/___` attack once - if `y` is 1 then the envelope will affect this channel. -- **`23xx`**: set envelope period low byte. -- **`24xx`**: set envelope period high byte. -- **`25xx`**: slide envelope period up. -- **`26xx`**: slide envelope period down. -- **`29xy`**: enable auto-envelope mode. +- `23xx`: **set envelope period low byte.** +- `24xx`: **set envelope period high byte.** +- `25xx`: **slide envelope period up.** +- `26xx`: **slide envelope period down.** +- `29xy`: **enable auto-envelope mode.** - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. - `x` is the numerator. - `y` is the denominator. - if `x` or `y` are 0 this will disable auto-envelope mode. -- **`2Exx`**: write to I/O port A. +- `2Exx`: **write to I/O port A.** - this changes the port's mode to "write". make sure you have connected something to it. -- **`2Fxx`**: write to I/O port B. +- `2Fxx`: **write to I/O port B.** - this changes the port's mode to "write". make sure you have connected something to it. + +# chip config +## AY derivative modes + +AY-3-810 was an absurdly popular chip that was blessed with many third-party clones, licensed or not. + +- the AY-3-8914 variant was used in Intellivision, which is pretty much an 8910 with 4 level envelope volume per channel and different register format. +- Yamaha YM2149 was an AY-3-8910 clone released in 1983. it's almost identical to AY with minor differences being: higher hardware envelope step resolution (16 vs 32), half-clock mode when voltage level is low, much stronger DC offset and cleaner, but softer output. +- Sunsoft 5B is YM2149 clone with half-clock mode forced on. diff --git a/doc/7-systems/ay8930.md b/doc/7-systems/ay8930.md index dda2e7584..1a926ebce 100644 --- a/doc/7-systems/ay8930.md +++ b/doc/7-systems/ay8930.md @@ -3,52 +3,54 @@ a backwards-compatible successor to the AY-3-8910, with increased volume resolution, duty cycle control, three envelopes and highly configurable noise generator. sadly, this soundchip has only ever observed minimal success, and has remained rather obscure since. -it is best known for being used in the Covox Sound Master, which didn't sell well either. It also observed very minimal success in Merit's CRT-250 machines, but only as a replacement for the AY-3-8910. +it is best known for being used in the Covox Sound Master, which didn't sell well either. it also observed very minimal success in Merit's CRT-250 machines, but only as a replacement for the AY-3-8910. emulation of this chip in Furnace is now complete thanks to community efforts and hardware testing, which an MSX board called Darky has permitted. +as of Furnace 0.6pre7, AY8930 supports software PCM, where all 3 channels can play 5-bit PCM samples (at the cost of a very high CPU usage) + # effects -- **`12xx`**: set channel duty cycle. `xx` is a value between 00 and 08. - - `00`: 3.125% - - `01`: 6.25% - - `02`: 12.5% - - `03`: 25% - - `04`: 50% - - `05`: 75% - - `06`: 87.5% - - `07`: 93.75% - - `08`: 96.875% -- **`20xx`**: set channel mode. `xx` may be one of the following: - - `00`: square - - `01`: noise - - `02`: square and noise - - `03`: envelope - - `04`: envelope and square - - `05`: envelope and noise - - `06`: envelope and square and noise - - `07`: nothing -- **`21xx`**: set noise frequency. `xx` is a value between 00 and FF. -- **`22xy`**: set envelope mode. +- `12xx`: **set channel duty cycle.** + - `0`: 3.125% + - `1`: 6.25% + - `2`: 12.5% + - `3`: 25% + - `4`: 50% + - `5`: 75% + - `6`: 87.5% + - `7`: 93.75% + - `8`: 96.875% +- `20xx`: **set channel mode.** `xx` may be one of the following: + - `0`: square + - `1`: noise + - `2`: square and noise + - `3`: envelope + - `4`: envelope and square + - `5`: envelope and noise + - `6`: envelope and square and noise + - `7`: nothing +- `21xx`: **set noise frequency.** `xx` is a value between `00` and `FF`. +- `22xy`: **set envelope mode.** - `x` sets the envelope shape, which may be one of the following: - - `0: \___` decay - - `4: /___` attack once - - `8: \\\\` saw - - `9: \___` decay - - `A: \/\/` inverse obelisco - - `B: \¯¯¯` decay once - - `C: ////` inverse saw - - `D: /¯¯¯` attack - - `E: /\/\` obelisco - - `F: /___` attack once + - `0`: `\___` decay + - `4`: `/___` attack once + - `8`: `\\\\` saw + - `9`: `\___` decay + - `A`: `\/\/` inverse obelisco + - `B`: `\¯¯¯` decay once + - `C`: `////` inverse saw + - `D`: `/¯¯¯` attack + - `E`: `/\/\` obelisco + - `F`: `/___` attack once - if `y` is 1 then the envelope will affect this channel. -- **`23xx`**: set envelope period low byte. -- **`24xx`**: set envelope period high byte. -- **`25xx`**: slide envelope period up. -- **`26xx`**: slide envelope period down. -- **`27xx`**: set noise AND mask. -- **`28xx`**: set noise OR mask. -- **`29xy`**: enable auto-envelope mode. +- `23xx`: **set envelope period low byte.** +- `24xx`: **set envelope period high byte.** +- `25xx`: **slide envelope period up.** +- `26xx`: **slide envelope period down.** +- `27xx`: **set noise AND mask.** +- `28xx`: **set noise OR mask.** +- `29xy`: **enable auto-envelope mode.** - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. - `x` is the numerator. - `y` is the denominator. diff --git a/doc/7-systems/bubblesystem.md b/doc/7-systems/bubblesystem.md index 1a904b9d8..c04aa098d 100644 --- a/doc/7-systems/bubblesystem.md +++ b/doc/7-systems/bubblesystem.md @@ -6,8 +6,8 @@ however, the K005289 is just part of the logic used for pitch and wavetable ROM waveform select and volume control are tied with single AY-3-8910 IO for both channels. another AY-3-8910 IO is used for reading sound hardware status. -Furnace emulates this configuration as a "chip" with 32x16 wavetables. +Furnace emulates this configuration as a "chip" with 32×16 wavetables. # effects -- **`10xx`**: change wave. +- `10xx`: **change wave.** diff --git a/doc/7-systems/c64.md b/doc/7-systems/c64.md index 4b0f26f12..7627f3d9b 100644 --- a/doc/7-systems/c64.md +++ b/doc/7-systems/c64.md @@ -8,7 +8,7 @@ two versions of aforementioned chip exist - 6581 (original chip) and 8580 (impro # effects -- **`10xx`**: change wave. the following values are accepted: +- `10xx`: **change wave.** the following values are accepted: - `00`: nothing - `01`: triangle - `02`: saw @@ -18,14 +18,14 @@ two versions of aforementioned chip exist - 6581 (original chip) and 8580 (impro - `06`: pulse and saw - `07`: pulse and triangle and saw - `08`: noise -- **`11xx`**: set coarse cutoff. `xx` may be a value between 00 to 64. - - **this effect only exists for compatibility reasons, and its use is discouraged.** +- `11xx`: **set coarse cutoff.** `xx` may be a value between `00` and `64`. + - _this effect only exists for compatibility reasons, and its use is discouraged._ - use effect `4xxx` instead. -- **`12xx`**: set coarse duty cycle. `xx` may be a value between 00 to 64. - - **this effect only exists for compatibility reasons, and its use is discouraged.** +- `12xx`: **set coarse duty cycle.** `xx` may be a value between `00` and `64`. + - _this effect only exists for compatibility reasons, and its use is discouraged._ - use effect `3xxx` instead. -- **`13xx`**: set resonance. `xx` may be a value between 00 and 0F. -- **`14xx`**: set filter mode. the following values are accepted: +- `13xx`: **set resonance.** `xx` may be a value between `00` and `0F`. +- `14xx`: **set filter mode.** the following values are accepted: - `00`: filter off - `01`: low pass - `02`: band pass @@ -34,26 +34,26 @@ two versions of aforementioned chip exist - 6581 (original chip) and 8580 (impro - `05`: band reject/stop/notch - `06`: high+band pass - `07`: all pass -- **`15xx`**: set envelope reset time. +- `15xx`: **set envelope reset time.** - this is the amount of ticks the channel turns off before a note occurs in order to reset the envelope safely. - if `xx` is 0 or higher than the song speed, the envelope will not reset. -- **`1Axx`**: disable envelope reset for this channel. -- **`1Bxy`**: reset cutoff: +- `1Axx`: **disable envelope reset for this channel.** +- `1Bxy`: **reset cutoff**: - if `x` is not 0: on new note - if `y` is not 0: now - this effect is not necessary if the instrument's cutoff macro is absolute. -- **`1Cxy`**: reset duty cycle: +- `1Cxy`: **reset duty cycle**: - if `x` is not 0: on new note - if `y` is not 0: now - this effect is not necessary if the instrument's duty macro is absolute. -- **`1Exy`**: change additional parameters. +- `1Exy`: **change additional parameters.** - `x` may be one of the following: - - `0`: attack (`y` from 0 to F) - - `1`: decay (`y` from 0 to F) - - `2`: sustain (`y` from 0 to F) - - `3`: release (`y` from 0 to F) - - `4`: ring modulation (`y` is 0 or 1) - - `5`: oscillator sync (`y` is 0 or 1) - - `6`: disable channel 3 (`y` is 0 or 1) -- **`3xxx`**: set duty cycle. `xxx` range is 000-FFF -- **`4xxx`**: set cutoff. `xxx` range is 000-7FF. + - `0`: attack (`y` from `0` to `F`) + - `1`: decay (`y` from `0` to `F`) + - `2`: sustain (`y` from `0` to `F`) + - `3`: release (`y` from `0` to `F`) + - `4`: ring modulation (`y` is `0` or `1`) + - `5`: oscillator sync (`y` is `0` or `1`) + - `6`: disable channel 3 (`y` is `0` or `1`) +- `3xxx`: **set duty cycle.** `xxx` range is `000` to `FFF`. +- `4xxx`: **set cutoff.** `xxx` range is `000` to `7FF`. diff --git a/doc/7-systems/es5506.md b/doc/7-systems/es5506.md index 759fdde50..436aca0c7 100644 --- a/doc/7-systems/es5506.md +++ b/doc/7-systems/es5506.md @@ -1,41 +1,41 @@ # Ensoniq ES5506 (OTTO) -Sample-based synthesis chip used in a bunch of Taito arcade machines and PC sound cards like Soundscape Elite. A variant of it was the heart of the well-known Gravis Ultrasound. +sample-based synthesis chip used in a bunch of Taito arcade machines and PC sound cards like Soundscape Elite. a variant of it was the heart of the well-known Gravis Ultrasound. -it supports a whooping 32 channels of 16-bit PCM and: +it supports a whopping 32 channels of 16-bit PCM and: -- Real time digital filters -- Frequency interpolation -- Loop start and stop positions for each voice (bidirectional and reverse looping) -- Internal volume multiplication and stereo panning -- Hardware support for envelopes +- real time digital filters +- frequency interpolation +- loop start and stop positions for each voice (bidirectional and reverse looping) +- internal volume multiplication and stereo panning +- hardware support for envelopes # effects -- **`10xx`**: set waveform. -- **`11xx`**: set filter mode (0-3) -- **`120x`**: set pause (bit 0). Pauses the sample until the bit is unset, where it will then resume where it left off. -- **`14xx`**: set filter coefficient K1 low byte. -- **`15xx`**: set filter coefficient K1 high byte. -- **`16xx`**: set filter coefficient K2 low byte. -- **`17xx`**: set filter coefficient K2 high byte. -- **`18xx`**: set filter coefficient K1 slide up. -- **`19xx`**: set filter coefficient K1 slide down. -- **`1Axx`**: set filter coefficient K2 slide up. -- **`1Bxx`**: set filter coefficient K2 slide down. -- **`20xx`**: set envelope count. -- **`22xx`**: set envelope left volume ramp. -- **`23xx`**: set envelope right volume ramp. -- **`24xx`**: set envelope filter coefficient K1 ramp. -- **`25xx`**: set envelope filter coefficient K1 ramp (slower). -- **`26xx`**: set envelope filter coefficient K2 ramp. -- **`27xx`**: set envelope filter coefficient K2 ramp (slower). -- **`3xxx`**: set coarse filter coefficient K1. -- **`4xxx`**: set coarse filter coefficient K2. -- **`81xx`**: set panning (left channel). -- **`82xx`**: set panning (right channel). -- **`88xx`**: set panning (rear channels). -- **`89xx`**: set panning (rear left channel). -- **`8Axx`**: set panning (rear right channel). -- **`9xxx`**: set sample offset (x256). -- **`DFxx`**: set sample playback direction. +- `10xx`: **set waveform.** +- `11xx`: **set filter mode.** values are `0` through `3`. +- `120x`: **set pause (bit 0).** pauses the sample until the bit is unset; it will then resume where it left off. +- `14xx`: **set filter coefficient K1 low byte.** +- `15xx`: **set filter coefficient K1 high byte.** +- `16xx`: **set filter coefficient K2 low byte.** +- `17xx`: **set filter coefficient K2 high byte.** +- `18xx`: **set filter coefficient K1 slide up.** +- `19xx`: **set filter coefficient K1 slide down.** +- `1Axx`: **set filter coefficient K2 slide up.** +- `1Bxx`: **set filter coefficient K2 slide down.** +- `20xx`: **set envelope count.** +- `22xx`: **set envelope left volume ramp.** +- `23xx`: **set envelope right volume ramp.** +- `24xx`: **set envelope filter coefficient K1 ramp.** +- `25xx`: **set envelope filter coefficient K1 ramp (slower).** +- `26xx`: **set envelope filter coefficient K2 ramp.** +- `27xx`: **set envelope filter coefficient K2 ramp (slower).** +- `3xxx`: **set coarse filter coefficient K1.** +- `4xxx`: **set coarse filter coefficient K2.** +- `81xx`: **set panning (left channel).** +- `82xx`: **set panning (right channel).** +- `88xx`: **set panning (rear channels).** +- `89xx`: **set panning (rear left channel).** +- `8Axx`: **set panning (rear right channel).** +- `9xxx`: **set sample offset.** resets sample position to `xxx * 0x100`. +- `DFxx`: **set sample playback direction.** diff --git a/doc/7-systems/fds.md b/doc/7-systems/fds.md index 83e8538b2..8105d107f 100644 --- a/doc/7-systems/fds.md +++ b/doc/7-systems/fds.md @@ -1,20 +1,20 @@ # Famicom Disk System -the Famicom Disk System is an expansion device for the Famicom (known as NES outside Japan), a popular console from the '80's. +the Famicom Disk System is an expansion device for the Famicom (known as NES outside Japan), a popular console from the '80s. as it name implies, it allowed people to play games on specialized floppy disks that could be rewritten on vending machines, therefore reducing the cost of ownership and manufacturing. it also offers an additional 6-bit, 64-byte wavetable sound channel with (somewhat limited) FM capabilities, which is what Furnace supports. # effects -- **`10xx`**: change wave. -- **`11xx`**: set modulation depth. -- **`12xy`**: set modulation speed high byte and toggle on/off. - - `x` is the toggle. a value of 1 turns on the modulator. +- `10xx`: **change wave.** +- `11xx`: **set modulation depth.** +- `12xy`: **set modulation speed high byte and toggle on/off.** + - `x` is the toggle. a value of `1` turns on the modulator. - `y` is the speed. -- **`13xx`**: set modulation speed low byte. -- **`14xx`**: set modulator position. -- **`15xx`**: set modulator wave. +- `13xx`: **set modulation speed low byte.** +- `14xx`: **set modulator position.** +- `15xx`: **set modulator wave.** - `xx` points to a wavetable. it should (preferably) have a height of 7 with the values mapping to: - 0: +0 - 1: +1 diff --git a/doc/7-systems/game-boy.md b/doc/7-systems/game-boy.md index 16b6c1496..eed8241af 100644 --- a/doc/7-systems/game-boy.md +++ b/doc/7-systems/game-boy.md @@ -2,17 +2,27 @@ 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 -- **`10xx`**: change wave. -- **`11xx`**: set noise length. `xx` may be one of: - - 0: long - - 1: short -- **`12xx`**: set duty cycle (from 0 to 3). -- **`13xy`**: setup sweep (pulse channels only). +- `10xx`: **change wave.** +- `11xx`: **set noise length.** + - `0`: long + - `1`: short +- `12xx`: **set duty cycle.** + - `0`: 12.5% + - `1`: 25% + - `2`: 50% + - `3`: 75% +- `13xy`: **setup sweep.** pulse channels only. - `x` is the time. - `y` is the shift. - - set to 0 to disable it. -- **`14xx`**: set sweep direction. 0 is up and 1 is down. + - 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/genesis.md b/doc/7-systems/genesis.md index fd7fc56df..5215fb101 100644 --- a/doc/7-systems/genesis.md +++ b/doc/7-systems/genesis.md @@ -6,32 +6,56 @@ this console is powered by two sound chips: the [Yamaha YM2612](ym2612.md) and [ # effects -- **`10xy`**: set LFO parameters. +- `10xy`: **set LFO parameters.** - `x` toggles the LFO. - `y` sets its speed. -- **`11xx`**: set feedback of channel. -- **`12xx`**: set operator 1 level. -- **`13xx`**: set operator 2 level. -- **`14xx`**: set operator 3 level. -- **`15xx`**: set operator 4 level. -- **`16xy`**: set multiplier of operator. +- `11xx`: **set feedback of channel.** +- `12xx`: **set operator 1 level.** +- `13xx`: **set operator 2 level.** +- `14xx`: **set operator 3 level.** +- `15xx`: **set operator 4 level.** +- `16xy`: **set multiplier of operator.** - `x` is the operator (1-4). - `y` is the multiplier. -- **`17xx`**: enable PCM channel. +- `17xx`: **enable PCM channel.** - this only works on channel 6. - - **this effect is there for compatibility reasons** - it is otherwise recommended to use Sample type instruments (which automatically enable PCM mode when used). -- **`18xx`**: toggle extended channel 3 mode. - - 0 disables it and 1 enables it. + - _this effect is here for compatibility reasons!_ it is otherwise recommended to use Sample type instruments (which automatically enable PCM mode when used). +- `18xx`: **toggle extended channel 3 mode.** + - `0` disables it and `1` enables it. - only in extended channel 3 chip. -- **`19xx`**: set attack of all operators. -- **`1Axx`**: set attack of operator 1. -- **`1Bxx`**: set attack of operator 2. -- **`1Cxx`**: set attack of operator 3. -- **`1Dxx`**: set attack of operator 4. -- **`20xy`**: set PSG noise mode. +- `19xx`: **set attack of all operators.** +- `1Axx`: **set attack of operator 1.** +- `1Bxx`: **set attack of operator 2.** +- `1Cxx`: **set attack of operator 3.** +- `1Dxx`: **set attack of operator 4.** +- `20xy`: **set PSG noise mode.** - `x` controls whether to inherit frequency from PSG channel 3. - - 0: use one of 3 preset frequencies (C: A-2; C#: A-3; D: A-4). - - 1: use frequency of PSG channel 3. + - `0`: use one of 3 preset frequencies (`C`: A-2; `C#`: A-3; `D`: A-4). + - `1`: use frequency of PSG channel 3. - `y` controls whether to select noise or thin pulse. - - 0: thin pulse. - - 1: noise. + - `0`: thin pulse. + - `1`: noise. + + + +# system modes + +## extended channel 3 + +in ExtCh mode, channel 3 is split into one column for each of its four operators. feedback and LFO levels are shared. the frequency of each operator may be controlled independently with notes and effects. this can be used for more polyphony or more complex sounds. + +all four operators are still combined according to the algorithm in use. for example, algorithm 7 acts as four independent sine waves. algorithm 4 acts as two independent 2op sounds. even with algorithm 0, placing a note in any operator triggers that operator alone. + +## CSM + +CSM is short for "Composite Sinusoidal Modeling". CSM works by sending key-on and key-off commands to channel 3 at a specific frequency, controlled by the added "CSM Timer" channel. this can be used to create vocal formants (speech synthesis!) or other complex effects. + +CSM is beyond the scope of this documentation. for more information, see this [brief SSG-EG and CSM video tutorial](https://www.youtube.com/watch?v=IKOR0TUlnWU). + +## DualPCM + +[info here.](ym2612.md) + +## Sega CD + +this isn't a mode so much as a chip configuration. it adds the [Ricoh RF5C68](ricoh.md) found in the Sega CD add-on, providing 8 channels of PCM. diff --git a/doc/7-systems/k007232.md b/doc/7-systems/k007232.md index adf7d95fb..f5571e1e2 100644 --- a/doc/7-systems/k007232.md +++ b/doc/7-systems/k007232.md @@ -2,10 +2,10 @@ a 2-channel PCM sound chip from Konami which was used in some of their 1986-1990 arcade boards. -Its sample format is unique; the topmost bit is the end marker, and the low 7 bits are used for generating sound (unsigned format). +its sample format is unique; the topmost bit is the end marker, and the low 7 bits are used for generating sound (unsigned format). -It has 7 bit digital output per each channel and no volume register on chip, so it needs external logic to control channel volume. +it has 7 bit digital output per each channel and no volume register on chip, so it needs external logic to control channel volume. # effects -- Nothing for now +- nothing for now. diff --git a/doc/7-systems/lynx.md b/doc/7-systems/lynx.md index 7c46ec445..24f32634f 100644 --- a/doc/7-systems/lynx.md +++ b/doc/7-systems/lynx.md @@ -15,6 +15,5 @@ the Atari Lynx has a 6502-based CPU with a sound part (this chip is known as MIK # effects -- **`3xxx`**: Load LFSR (0 to FFF). - - this is a bitmask. +- `3xxx`: **load LFSR.** this is a bitmask with values ranging from `000` to `FFF`. - for it to work, duty macro in instrument editor must be set to some value. without it LFSR will not be fed with any bits. diff --git a/doc/7-systems/mmc5.md b/doc/7-systems/mmc5.md index ef3e31404..a838dfe88 100644 --- a/doc/7-systems/mmc5.md +++ b/doc/7-systems/mmc5.md @@ -8,5 +8,5 @@ additionally, it offers an 8-bit DAC which can be used to play samples. only one # effects -- **`12xx`**: set duty cycle or noise mode of channel. - - may be 0-3 for the pulse channels. +- `12xx`: **set duty cycle or noise mode of channel.** + - may be `0` through `3` for the pulse channels. diff --git a/doc/7-systems/msm5232.md b/doc/7-systems/msm5232.md index 7c5ffa6be..0f28cb786 100644 --- a/doc/7-systems/msm5232.md +++ b/doc/7-systems/msm5232.md @@ -16,11 +16,11 @@ Furnace implements this chip in a way that allows the following features: # effects -- **`10xy`**: set group control. +- `10xy`: **set group control.** - `x` sets sustain mode. - `y` is a 4-bit mask which toggles overtones. -- **`11xx`**: set noise mode. -- **`12xx`**: set group attack (0 to 5). +- `11xx`: **set noise mode.** +- `12xx`: **set group attack.** range is `0` to `5`. - only in internal (capacitor-based) envelope mode. -- **`13xx`**: set group decay (0 to 11). +- `13xx`: **set group decay.** range is `0` to `11`. - only in internal (capacitor-based) envelope mode. diff --git a/doc/7-systems/msm6258.md b/doc/7-systems/msm6258.md index a71aa9507..a7b8a21fd 100644 --- a/doc/7-systems/msm6258.md +++ b/doc/7-systems/msm6258.md @@ -1,7 +1,20 @@ # OKI MSM6258 a single-channel ADPCM sound source developed by OKI. it allows max sample rate of 15.6 KHz... with no variable pitch. most prominent use of this chip was Sharp X68000 computer, where it was paired with Yamaha YM2151. +Furnace's implementation is MSM6258V, a CPU driven variant that is unlimited by amount of sample data, being able to be fed from the system's RAM. # effects ... +# chip config + +## chip clock rates + +MSM6258 is an extremely basic ADPCM sound codec. it has no variable frequency rate; it depends on clock rate of a chip itself. Furnace supports following rates: + +| clock rate | sampling rate | +|--------------------|---------------| +| 4 MHz | 7812 Hz | +| 4.096 MHz | 8000 Hz | +| 8 MHz | 15625 Hz | +| 8.192 MHz | 16000 Hz | diff --git a/doc/7-systems/msm6295.md b/doc/7-systems/msm6295.md index 1faf15664..c130854ea 100644 --- a/doc/7-systems/msm6295.md +++ b/doc/7-systems/msm6295.md @@ -1,7 +1,33 @@ # OKI MSM6295 -an upgrade from 6258 - it provides 4 ADPCM channels, at max 32 KHz (still no variable pitch though). between late 80s and late 90s, it was one of the most common, if not THE most common soundchip used in arcade machines (Capcom, Toaplan, Kaneko, Atari, Tecmo, the list can go on and on...) +an upgrade from 6258 - it provides 4 ADPCM channels, at max 32 KHz (still no variable pitch though). between late '80s and late '90s, it was one of the most common, if not _the_ most common soundchip used in arcade machines (Capcom, Toaplan, Kaneko, Atari, Tecmo, the list can go on and on...). Without bankswitching, the chip supports 256kB of sample RAM and can hold up to 127 samples at once. # effects -- **`20xx`**: set chip output rate. +- `20xx`: **set chip output rate.** + +# chip config +## chip clock rates +like MSM6258, MSM295 is an extremely basic ADPCM sound codec. it has no variable frequency rate, it depends on clock rate of a chip itself. Furnace supports following rates: + +| clock rate | sampling rate | +|--------------------|---------------| +| 1 MHz | 7576 Hz | +| 1.02 MHz | 7727 Hz | +| 1.056 MHz | 8000 Hz | +| 1.193 MHz | 9038 Hz | +| 0.89 MHz | 6742 Hz | +| 0.875 MHz | 6629 Hz | +| 0.9375 MHz | 7102 Hz | +| 1.5 MHz | 11364 Hz | +| 1.79 MHz | 13561 Hz | +| 2 MHz | 15152Hz | +| 2.112 MHz | 16000 Hz | +| 3 MHz | 22728 Hz | +| 3.58 MHz | 27122 Hz | +| 4 MHz | 30304 Hz | +| 4.224 MHz | 32000 Hz | + +## chip clock divisor + +MSM6295 clock rate could be divided by 132 (resulting sample rates above), or by 165. To get a clock rate using divisor of 165, formula is clock rate (in Hz) / 165. Example: 1 MHz MSM6295 in 165 divisor mode results in output rate of 6060 Hz. diff --git a/doc/7-systems/n163.md b/doc/7-systems/n163.md index 50b2bbd11..cb5ff5304 100644 --- a/doc/7-systems/n163.md +++ b/doc/7-systems/n163.md @@ -13,20 +13,29 @@ Furnace supports loading waveforms into RAM and waveform playback simultaneously you must load waveform to RAM first for playback, as its load behavior auto-updates when every waveform changes. both waveform playback and load command work independently per each channel columns. -(Global) commands don't care about the channel columns for work commands and its load behavior is independent with per-channel column load commands. +global commands don't care about the channel columns for work commands and its load behavior is independent with per-channel column load commands. # effects -- **`10xx`**: set waveform for playback. -- **`11xx`**: set waveform position in RAM for playback (single nibble unit). -- **`12xx`**: set waveform length in RAM for playback (04 to FC, 4 nibble unit). -- **`130x`**: set playback waveform update behavior (0: off, bit 0: update now, bit 1: update when every waveform is changed). -- **`14xx`**: set waveform for load to RAM. -- **`15xx`**: set waveform position for load to RAM (single nibble unit). -- **`16xx`**: set waveform length for load to RAM (04 to FC, 4 nibble unit). -- **`170x`**: set waveform load behavior (0: off, bit 0: load now, bit 1: load when every waveform is changed). -- **`180x`**: set channel limit (0 to 7, x + 1). -- **`20xx`**: (Global) set waveform for load to RAM. -- **`21xx`**: (Global) set waveform position for load to RAM (single nibble unit). -- **`22xx`**: (Global) set waveform length for load to RAM (04 to FC, 4 nibble unit). -- **`230x`**: (Global) set waveform load behavior (0: off, bit 0: load now, bit 1: load when every waveform is changed). +- `10xx`: **set waveform for playback.** +- `11xx`: **set waveform position in RAM for playback.** single nibble unit. +- `12xx`: **set waveform length in RAM for playback.** `04` to `FC`, 4 nibble unit. +- `130x`: **set playback waveform update behavior.** + - `0`: off. + - bit 0: update now. + - bit 1: update when every waveform is changed. +- `14xx`: **set waveform for load to RAM.** +- `15xx`: **set waveform position for load to RAM.** single nibble unit. +- `16xx`: **set waveform length for load to RAM.** `04` to `FC`, 4 nibble unit. +- `170x`: **set waveform load behavior.** + - `0`: off. + - bit 0: load now. + - bit 1: load when every waveform is changed. +- `180x`: **set channel limit.** range is `0` to `7`; 1 is added to get results of 1 through 8. +- `20xx`: **globally set waveform for load to RAM.** +- `21xx`: **globally set waveform position for load to RAM.** single nibble unit. +- `22xx`: **globally set waveform length for load to RAM.** `04` to `FC`, 4 nibble unit. +- `230x`: **globally set waveform load behavior.** + - `0`: off. + - bit 0: load now. + - bit 1: load when every waveform is changed. diff --git a/doc/7-systems/namco.md b/doc/7-systems/namco.md index 12d9ec9ef..54d8b964f 100644 --- a/doc/7-systems/namco.md +++ b/doc/7-systems/namco.md @@ -6,5 +6,5 @@ everything starts with Namco WSG, which is a simple 3-channel wavetable with no # effects -- **`10xx`**: change waveform. -- **`11xx`**: toggle noise mode (WARNING: only on C30). +- `10xx`: **change waveform.** +- `11xx`: **toggle noise mode.** _warning:_ only on C30. diff --git a/doc/7-systems/nes.md b/doc/7-systems/nes.md index 9292a1de5..c3ccb7064 100644 --- a/doc/7-systems/nes.md +++ b/doc/7-systems/nes.md @@ -6,30 +6,37 @@ also known as Famicom. it is a five-channel sound generator: first two channels # effects -- **`11xx`**: write to delta modulation counter. - - this may be used to attenuate the triangle and noise channels. +- `11xx`: **write to delta modulation counter.** range is `00` to `7F`. + - this may be used to attenuate the triangle and noise channels; at `7F`, they will be at about 57% volume. - will not work if a sample is playing. -- **`12xx`**: set duty cycle or noise mode of channel. - - may be 0-3 for the pulse channels and 0-1 for the noise channel. -- **`13xy`**: setup sweep up. +- `12xx`: **set duty cycle or noise mode of channel.** + - may be `0` to `3` for the pulse channels: + - `0`: 12.5% + - `1`: 25% + - `2`: 50% + - `3`: 75% + - may be `0` or `1` for the noise channel: + - `0`: long (15-bit LFSR, 32767-step) + - `1`: short (9-bit LFSR, 93-step) +- `13xy`: **setup sweep up.** - `x` is the time. - `y` is the shift. - - set to 0 to disable it. -- **`14xy`**: setup sweep down. + - set to `0` to disable it. +- `14xy`: **setup sweep down.** - `x` is the time. - `y` is the shift. - - set to 0 to disable it. -- **`15xx`**: set envelope mode. - - `0`: envelope + length counter (volume represents envelope duration). - - `1`: length counter (volume represents output volume). - - `2`: looping envelope (volume represents envelope duration). - - `3`: constant volume (default; volume represents output volume). - - pulse and noise channels only. + - set to `0` to disable it. +- `15xx`: **set envelope mode.** + - `0`: envelope + length counter. volume represents envelope duration. + - `1`: length counter. volume represents output volume. + - `2`: looping envelope. volume represents envelope duration. + - `3`: constant volume. default value. volume represents output volume. + - Pulse and noise channels only. - you may need to apply a phase reset (using the macro) to make the envelope effective. -- **`16xx`**: set length counter. +- `16xx`: **set length counter.** - see table below for possible values. - this will trigger phase reset. -- **`17xx`**: set frame counter mode. +- `17xx`: **set frame counter mode.** - `0`: 4-step. - NTSC: 120Hz sweeps and lengths; 240Hz envelope. - PAL: 100Hz sweeps and lengths; 200Hz envelope. @@ -38,73 +45,137 @@ also known as Famicom. it is a five-channel sound generator: first two channels - NTSC: 96Hz sweeps and lengths; 192Hz envelope. - PAL: 80Hz sweeps and lengths; 160Hz envelope. - Dendy: 95.1Hz sweeps and lengths; 190.2Hz envelope. -- **`18xx`**: set PCM channel mode. +- `18xx`: **set PCM channel mode.** - `00`: PCM (software). - `01`: DPCM (hardware). - - when in DPCM mode, samples will sound muffled (due to its nature), availables pitches are limited and loop point is ignored. -- **`19xx`**: set triangle linear counter. + - when in DPCM mode, samples will sound muffled (due to its nature), availables pitches are limited, and loop point is ignored. +- `19xx`: **set triangle linear counter.** - `00` to `7F` set the counter. - `80` and higher halt it. -- **`20xx`**: set DPCM frequency. +- `20xx`: **set DPCM frequency.** - only works in DPCM mode. - see table below for possible values. -# DPCM frequency table +# tables -val | NTSC | PAL -----|-----------|----------- - 00 | 4181.7Hz | 4177.4Hz - 01 | 4709.9Hz | 4696.6Hz - 02 | 5264.0Hz | 5261.4Hz - 03 | 5593.0Hz | 5579.2Hz - 04 | 6257.9Hz | 6023.9Hz - 05 | 7046.3Hz | 7044.9Hz - 06 | 7919.3Hz | 7917.2Hz - 07 | 8363.4Hz | 8397.0Hz - 08 | 9419.9Hz | 9446.6Hz - 09 | 11186.1Hz | 11233.8Hz - 0A | 12604.0Hz | 12595.5Hz - 0B | 13982.6Hz | 14089.9Hz - 0C | 16884.6Hz | 16965.4Hz - 0D | 21306.8Hz | 21315.5Hz - 0E | 24858.0Hz | 25191.0Hz - 0F | 33143.9Hz | 33252.1Hz +## short noise frequencies (NTSC) -# length counter table +note | arpeggio | fundamental | MIDI note | pitch +:---- | -------: | ----------: | --------: | :---------- +`C-0` | @0 | 4.7 Hz | -9.47 | `d_1` + 53¢ +`C#0` | @1 | 9.5 Hz | 2.53 | `D-0` + 53¢ +`D-0` | @2 | 18.9 Hz | 14.55 | `D-1` + 55¢ +`D#0` | @3 | 25.3 Hz | 19.53 | `G-1` + 53¢ +`E-0` | @4 | 37.9 Hz | 26.55 | `D-2` + 55¢ +`F-0` | @5 | 50.6 Hz | 31.57 | `G-2` + 57¢ +`F#0` | @6 | 75.8 Hz | 38.55 | `D-3` + 55¢ +`G-0` | @7 | 95.3 Hz | 42.51 | `F#3` + 51¢ +`G#0` | @8 | 120.3 Hz | 46.55 | `A#3` + 55¢ +`A-0` | @9 | 150.4 Hz | 50.41 | `D-4` + 41¢ +`A#0` | @10 | 200.5 Hz | 55.39 | `G-4` + 39¢ +`B-0` | @11 | 300.7 Hz | 62.41 | `D-5` + 41¢ +`C-1` | @12 | 601.4 Hz | 74.41 | `D-6` + 41¢ +`C#1` | @13 | 1202.8 Hz | 86.41 | `D-7` + 41¢ +`D-1` | @14 | 2405.6 Hz | 98.41 | `D-8` + 41¢ +`D#1` | @15 | 4811.2 Hz | 110.41 | `D-9` + 41¢ -val | raw | NTSC | PAL | Dendy | NTSC 5-step | PAL 5-step | Dendy 5-step -----|-----|-------|-------|-------|-------------|------------|-------------- - 00 | 10 | 83ms | 100ms | 84ms | 104ms | 125ms | 105ms - 01 | 254 | 2.1s | 2.5s | 2.1s | 2.6s | 3.2s | 2.7s - 02 | 20 | 166ms | 200ms | 168ms | 208ms | 250ms | 210ms - 03 | 2 | 17ms | 20ms | 17ms | 21ms | 25ms | 21ms - 04 | 40 | 333ms | 400ms | 336ms | 417ms | 500ms | 421ms - 05 | 4 | 33ms | 40ms | 34ms | 42ms | 50ms | 42ms - 06 | 80 | 667ms | 800ms | 673ms | 833ms | 1.0s | 841ms - 07 | 6 | 50ms | 60ms | 50ms | 63ms | 75ms | 63ms - 08 | 160 | 1.3s | 1.6s | 1.3s | 1.7s | 2.0s | 1.7s - 09 | 8 | 67ms | 80ms | 67ms | 83ms | 100ms | 84ms - 0A | 60 | 500ms | 600ms | 505ms | 625ms | 750ms | 631ms - 0B | 10 | 83ms | 100ms | 84ms | 104ms | 125ms | 105ms - 0C | 14 | 117ms | 140ms | 118ms | 146ms | 175ms | 147ms - 0D | 12 | 100ms | 120ms | 101ms | 125ms | 150ms | 126ms - 0E | 26 | 217ms | 260ms | 219ms | 271ms | 325ms | 273ms - 0F | 14 | 117ms | 140ms | 118ms | 145ms | 175ms | 147ms - 10 | 12 | 100ms | 120ms | 101ms | 125ms | 150ms | 126ms - 11 | 16 | 133ms | 160ms | 135ms | 167ms | 200ms | 168ms - 12 | 24 | 200ms | 240ms | 202ms | 250ms | 300ms | 252ms - 13 | 18 | 150ms | 180ms | 151ms | 188ms | 225ms | 189ms - 14 | 48 | 400ms | 480ms | 404ms | 500ms | 600ms | 505ms - 15 | 20 | 167ms | 200ms | 168ms | 208ms | 250ms | 210ms - 16 | 96 | 800ms | 960ms | 807ms | 1.0s | 1.2s | 1.0s - 17 | 22 | 183ms | 220ms | 185ms | 229ms | 275ms | 231ms - 18 | 192 | 1.6s | 1.9s | 1.6s | 2.0s | 2.4s | 2.0s - 19 | 24 | 200ms | 240ms | 202ms | 250ms | 300ms | 252ms - 1A | 72 | 600ms | 720ms | 606ms | 750ms | 900ms | 757ms - 1B | 26 | 217ms | 260ms | 219ms | 271ms | 325ms | 273ms - 1C | 16 | 133ms | 160ms | 135ms | 167ms | 200ms | 168ms - 1D | 28 | 233ms | 280ms | 235ms | 292ms | 350ms | 294ms - 1E | 32 | 267ms | 320ms | 269ms | 333ms | 400ms | 336ms - 1F | 30 | 250ms | 300ms | 252ms | 313ms | 375ms | 315ms +reference: [NESdev](https://www.nesdev.org/wiki/APU_Noise) + +## length counter table + + + + + +value | raw | NTSC | PAL | Dendy | NTSC 5-step | PAL 5-step | Dendy 5-step +-----:|----:|------:|------:|------:|------------:|-----------:|-------------: + `03` | 2 | 17ms | 20ms | 17ms | 21ms | 25ms | 21ms + `05` | 4 | 33ms | 40ms | 34ms | 42ms | 50ms | 42ms + `07` | 6 | 50ms | 60ms | 50ms | 63ms | 75ms | 63ms + `09` | 8 | 67ms | 80ms | 67ms | 83ms | 100ms | 84ms + `00` | 10 | 83ms | 100ms | 84ms | 104ms | 125ms | 105ms + `0B` | 10 | 83ms | 100ms | 84ms | 104ms | 125ms | 105ms + `0D` | 12 | 100ms | 120ms | 101ms | 125ms | 150ms | 126ms + `10` | 12 | 100ms | 120ms | 101ms | 125ms | 150ms | 126ms + `0C` | 14 | 117ms | 140ms | 118ms | 146ms | 175ms | 147ms + `0F` | 14 | 117ms | 140ms | 118ms | 145ms | 175ms | 147ms + `1C` | 16 | 133ms | 160ms | 135ms | 167ms | 200ms | 168ms + `11` | 16 | 133ms | 160ms | 135ms | 167ms | 200ms | 168ms + `13` | 18 | 150ms | 180ms | 151ms | 188ms | 225ms | 189ms + `02` | 20 | 166ms | 200ms | 168ms | 208ms | 250ms | 210ms + `15` | 20 | 167ms | 200ms | 168ms | 208ms | 250ms | 210ms + `17` | 22 | 183ms | 220ms | 185ms | 229ms | 275ms | 231ms + `12` | 24 | 200ms | 240ms | 202ms | 250ms | 300ms | 252ms + `19` | 24 | 200ms | 240ms | 202ms | 250ms | 300ms | 252ms + `0E` | 26 | 217ms | 260ms | 219ms | 271ms | 325ms | 273ms + `1B` | 26 | 217ms | 260ms | 219ms | 271ms | 325ms | 273ms + `1D` | 28 | 233ms | 280ms | 235ms | 292ms | 350ms | 294ms + `1F` | 30 | 250ms | 300ms | 252ms | 313ms | 375ms | 315ms + `1E` | 32 | 267ms | 320ms | 269ms | 333ms | 400ms | 336ms + `04` | 40 | 333ms | 400ms | 336ms | 417ms | 500ms | 421ms + `14` | 48 | 400ms | 480ms | 404ms | 500ms | 600ms | 505ms + `0A` | 60 | 500ms | 600ms | 505ms | 625ms | 750ms | 631ms + `1A` | 72 | 600ms | 720ms | 606ms | 750ms | 900ms | 757ms + `06` | 80 | 667ms | 800ms | 673ms | 833ms | 1.0s | 841ms + `16` | 96 | 800ms | 960ms | 807ms | 1.0s | 1.2s | 1.0s + `08` | 160 | 1.3s | 1.6s | 1.3s | 1.7s | 2.0s | 1.7s + `18` | 192 | 1.6s | 1.9s | 1.6s | 2.0s | 2.4s | 2.0s + `01` | 254 | 2.1s | 2.5s | 2.1s | 2.6s | 3.2s | 2.7s reference: [NESdev](https://www.nesdev.org/wiki/APU_Length_Counter) + +## DPCM frequency table + +value | NTSC | PAL +------|----------:|----------: + `00` | 4181.7Hz | 4177.4Hz + `01` | 4709.9Hz | 4696.6Hz + `02` | 5264.0Hz | 5261.4Hz + `03` | 5593.0Hz | 5579.2Hz + `04` | 6257.9Hz | 6023.9Hz + `05` | 7046.3Hz | 7044.9Hz + `06` | 7919.3Hz | 7917.2Hz + `07` | 8363.4Hz | 8397.0Hz + `08` | 9419.9Hz | 9446.6Hz + `09` | 11186.1Hz | 11233.8Hz + `0A` | 12604.0Hz | 12595.5Hz + `0B` | 13982.6Hz | 14089.9Hz + `0C` | 16884.6Hz | 16965.4Hz + `0D` | 21306.8Hz | 21315.5Hz + `0E` | 24858.0Hz | 25191.0Hz + `0F` | 33143.9Hz | 33252.1Hz diff --git a/doc/7-systems/opl.md b/doc/7-systems/opl.md index 3f9e9c0cf..5f7034cb9 100644 --- a/doc/7-systems/opl.md +++ b/doc/7-systems/opl.md @@ -16,65 +16,65 @@ afterwards everyone moved to Windows and software mixed PCM streaming... # effects -- 10xx: set AM depth. the following values are accepted: - - 0: 1dB (shallow) - - 1: 4.8dB (deep) +- `10xx`: **set AM depth.** the following values are accepted: + - `0`: 1dB (shallow) + - `1`: 4.8dB (deep) - this effect applies to all channels. -- **`11xx`**: set feedback of channel. -- **`12xx`**: set operator 1 level. -- **`13xx`**: set operator 2 level. -- **`14xx`**: set operator 3 level. +- `11xx`: **set feedback of channel.** +- `12xx`: **set operator 1 level.** +- `13xx`: **set operator 2 level.** +- `14xx`: **set operator 3 level.** - only in 4-op mode (OPL3). -- **`15xx`**: set operator 4 level. +- `15xx`: **set operator 4 level.** - only in 4-op mode (OPL3). -- **`16xy`**: set multiplier of operator. +- `16xy`: **sSet multiplier of operator.** - `x` is the operator (1-4; last 2 operators only in 4-op mode). - `y` is the multiplier. -- 17xx: set vibrato depth. the following values are accepted: - - 0: normal - - 1: double +- `17xx`: **set vibrato depth.** + - `0`: normal + - `1`: double - this effect applies to all channels. -- **`18xx`**: toggle drums mode. - - 0 disables it and 1 enables it. +- `18xx`: **toggle drums mode.** + - `0` disables it and `1` enables it. - only in drums chip. -- **`19xx`**: set attack of all operators. -- **`1Axx`**: set attack of operator 1. -- **`1Bxx`**: set attack of operator 2. -- **`1Cxx`**: set attack of operator 3. +- `19xx`: **set attack of all operators.** +- `1Axx`: **set attack of operator 1.** +- `1Bxx`: **set attack of operator 2.** +- `1Cxx`: **set attack of operator 3.** - only in 4-op mode (OPL3). -- **`1Dxx`**: set attack of operator 4. +- `1Dxx`: **set attack of operator 4.** - only in 4-op mode (OPL3). -- **`2Axy`**: set waveform of operator. - - `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators". +- `2Axy`: **set waveform of operator.** + - `x` is the operator from 1 to 4; the last 2 operators only work in 4-op mode. a value of `0` means "all operators". - `y` is the value. - only in OPL2 or higher. -- **`30xx`**: enable envelope hard reset. +- `30xx`: **enable envelope hard reset.** - this works by inserting a quick release and tiny delay before a new note. -- **`50xy`**: set AM of operator. - - `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators". +- `50xy`: **set AM of operator.** + - `x` is the operator from 1 to 4; the last 2 operators only work in 4-op mode. a value of `0` means "all operators". - `y` determines whether AM is on. -- **`51xy`**: set SL of operator. - - `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators". +- `51xy`: **set SL of operator.** + - `x` is the operator from 1 to 4; the last 2 operators only work in 4-op mode. a value of `0` means "all operators". - `y` is the value. -- **`52xy`**: set RR of operator. - - `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators". +- `52xy`: **set RR of operator.** + - `x` is the operator from 1 to 4; the last 2 operators only work in 4-op mode. a value of `0` means "all operators". - `y` is the value. -- **`53xy`**: set VIB of operator. - - `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators". +- `53xy`: **set VIB of operator.** + - `x` is the operator from 1 to 4; the last 2 operators only work in 4-op mode. a value of `0` means "all operators". - `y` determines whether VIB is on. -- **`54xy`**: set KSL of operator. - - `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators". +- `54xy`: **set KSL of operator.** + - `x` is the operator from 1 to 4; the last 2 operators only work in 4-op mode. a value of `0` means "all operators". - `y` is the value. -- **`55xy`**: set SUS of operator. - - `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators". +- `55xy`: **set SUS of operator.** + - `x` is the operator from 1 to 4; the last 2 operators only work in 4-op mode. a value of `0` means "all operators". - `y` determines whether SUS is on. -- **`56xx`**: set DR of all operators. -- **`57xx`**: set DR of operator 1. -- **`58xx`**: set DR of operator 2. -- **`59xx`**: set DR of operator 3. +- `56xx`: **set DR of all operators.** +- `57xx`: **set DR of operator 1.** +- `58xx`: **set DR of operator 2.** +- `59xx`: **set DR of operator 3.** - only in 4-op mode (OPL3). -- **`5Axx`**: set DR of operator 4. +- `5Axx`: **set DR of operator 4.** - only in 4-op mode (OPL3). -- **`5Bxy`**: set KSR of operator. - - `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators". +- `5Bxy`: **set KSR of operator.** + - `x` is the operator from 1 to 4; the last 2 operators only work in 4-op mode. a value of `0` means "all operators". - `y` determines whether KSR is on. diff --git a/doc/7-systems/opll.md b/doc/7-systems/opll.md index e4dc3ae37..c5dd4ff6d 100644 --- a/doc/7-systems/opll.md +++ b/doc/7-systems/opll.md @@ -12,56 +12,56 @@ OPLL also spawned a few derivative chips, the best known of these is: the YM2413 is equipped with the following features: - 9 channels of 2 operator FM synthesis -- A drum/percussion mode, replacing the last 3 voices with 5 rhythm channels, with drum mode tones hard-defined in the chip itself, like FM instruments. Only pitch might be altered. +- a drum/percussion mode, replacing the last 3 voices with 5 rhythm channels, with drum mode tones hard-defined in the chip itself, like FM instruments. only pitch might be altered. - - Drum mode works like following: FM channel 7 is for Kick Drum, which is a normal FM channel but routed through mxier twice for 2x volume, like all drum sounds. FM channel 8 splits to Snare Drum and Hi-Hat. Snare Drum is the carrier and it works with a special 1 bit noise generator combined with a square wave, all possible by overriding phase-generator with some different synthesis method. Hi-Hat is the modulator and it works with the noise generator and also the special synthesis. CH9 splits to Top-Cymbal and Tom-Tom, Top-Cymbal is the carrier and only has the special synthesis, while Tom-Tom is basically a 1op wave. - - Special syntheis mentioned already is: 5 square waves are gathered from 4x, 64x and 128x the pitch of channel 8 and 16x and 64x the pitch of channel 9 and they go through a process where 2 HH bits OR'd together, then 1 HH and 1 TC bit OR'd, then the two TC bits OR'd together, and those 3 results get XOR'd. + - drum mode works like following: FM channel 7 is for Kick Drum, which is a normal FM channel but routed through mixer twice for 2× volume, like all drum sounds. FM channel 8 splits to Snare, Drum, and Hi-Hat. Snare Drum is the carrier and it works with a special 1 bit noise generator combined with a square wave, all possible by overriding phase-generator with some different synthesis method. Hi-Hat is the modulator and it works with the noise generator and also the special synthesis. CH9 splits to Top-Cymbal and Tom-Tom, Top-Cymbal is the carrier and only has the special synthesis, while Tom-Tom is basically a 1op wave. + - special synthesis mentioned already is: 5 square waves are gathered from 4×, 64× and 128× the pitch of channel 8 and 16× and 64× the pitch of channel 9 and they go through a process where 2 HH bits OR'd together, then 1 HH and 1 TC bit OR'd, then the two TC bits OR'd together, and those 3 results get XOR'd. - 1 user-definable patch (this patch can be changed throughout the course of the song) - 15 pre-defined patches which can all be used at the same time -- Support for ADSR on both the modulator and the carrier -- Sine and half-sine based FM synthesis +- support for ADSR on both the modulator and the carrier +- sine and half-sine based FM synthesis - 9 octave note control - 4096 different frequencies for channels -- 16 unique volume levels (NOTE: Volume 0 is NOT silent.) -- Modulator and carrier key scaling -- Built-in hardware vibrato support +- 16 unique volume levels (NOTE: volume 0 is NOT silent.) +- modulator and carrier key scaling +- built-in hardware vibrato support # effects -- **`11xx`**: set feedback of channel. -- **`12xx`**: set operator 1 level. -- **`13xx`**: set operator 2 level. -- **`16xy`**: set multiplier of operator. - - `x` is the operator (1 or 2). +- `11xx`: **set feedback of channel.** +- `12xx`: **set operator 1 level.** +- `13xx`: **set operator 2 level.** +- `16xy`: **set multiplier of operator.** + - `x` is the operator, either 1 or 2. - `y` is the multiplier. -- **`18xx`**: toggle drums mode. - - 0 disables it and 1 enables it. +- `18xx`: **toggle drums mode.** + - `0` disables it and `1` enables it. - only in drums chip. -- **`19xx`**: set attack of all operators. -- **`1Axx`**: set attack of operator 1. -- **`1Bxx`**: set attack of operator 2. -- **`50xy`**: set AM of operator. - - `x` is the operator (1-2). a value of 0 means "all operators". +- `19xx`: **set attack of all operators.** +- `1Axx`: **set attack of operator 1.** +- `1Bxx`: **set attack of operator 2.** +- `50xy`: **set AM of operator.** + - `x` is the operator, either 1 or 2. a value of `0` means "all operators". - `y` determines whether AM is on. -- **`51xy`**: set SL of operator. - - `x` is the operator (1-2). a value of 0 means "all operators". +- `51xy`: **set SL of operator.** + - `x` is the operator, either 1 or 2. a value of `0` means "all operators". - `y` is the value. -- **`52xy`**: set RR of operator. - - `x` is the operator (1-2). a value of 0 means "all operators". +- `52xy`: **set RR of operator.** + - `x` is the operator, either 1 or 2. a value of `0` means "all operators". - `y` is the value. -- **`53xy`**: set VIB of operator. - - `x` is the operator (1-2). a value of 0 means "all operators". +- `53xy`: **set VIB of operator.** + - `x` is the operator, either 1 or 2. a value of `0` means "all operators". - `y` determines whether VIB is on. -- **`54xy`**: set KSL of operator. - - `x` is the operator (1-2). a value of 0 means "all operators". +- `54xy`: **set KSL of operator.** + - `x` is the operator, either 1 or 2. a value of `0` means "all operators". - `y` is the value. -- **`55xy`**: set EGT of operator. - - `x` is the operator (1-2). a value of 0 means "all operators". +- `55xy`: **set EGT of operator.** + - `x` is the operator, either 1 or 2. a value of `0` means "all operators". - `y` determines whether EGT is on. -- **`56xx`**: set DR of all operators. -- **`57xx`**: set DR of operator 1. -- **`58xx`**: set DR of operator 2. -- **`5Bxy`**: set KSR of operator. - - `x` is the operator (1-2). a value of 0 means "all operators". +- `56xx`: **set DR of all operators.** +- `57xx`: **set DR of operator 1.** +- `58xx`: **set DR of operator 2.** +- `5Bxy`: **set KSR of operator.** + - `x` is the operator, either 1 or 2. a value of `0` means "all operators". - `y` determines whether KSR is on. diff --git a/doc/7-systems/opz.md b/doc/7-systems/opz.md index f5b79d4c6..1abef5c45 100644 --- a/doc/7-systems/opz.md +++ b/doc/7-systems/opz.md @@ -25,91 +25,91 @@ no plans have been made for TX81Z MIDI passthrough, because: # effects -- **`10xx`**: set noise frequency of channel 8 operator 4. 00 disables noise while 01 to 20 enables it. -- **`11xx`**: set feedback of channel. -- **`12xx`**: set operator 1 level. -- **`13xx`**: set operator 2 level. -- **`14xx`**: set operator 3 level. -- **`15xx`**: set operator 4 level. -- **`16xy`**: set multiplier of operator. +- `10xx`: **set noise frequency of channel 8 operator 4.** `00` disables noise while `01` to `20` enable it. +- `11xx`: **set feedback of channel.** +- `12xx`: **set operator 1 level.** +- `13xx`: **set operator 2 level.** +- `14xx`: **set operator 3 level.** +- `15xx`: **set operator 4 level.** +- `16xy`: **set multiplier of operator.** - `x` is the operator (1-4). - `y` is the multiplier. -- **`17xx`**: set LFO speed. -- **`18xx`**: set LFO waveform. `xx` may be one of the following: +- `17xx`: **set LFO speed.** +- `18xx`: **set LFO waveform.** `xx` may be one of the following: - `00`: saw - `01`: square - `02`: triangle - `03`: noise -- **`19xx`**: set attack of all operators. -- **`1Axx`**: set attack of operator 1. -- **`1Bxx`**: set attack of operator 2. -- **`1Cxx`**: set attack of operator 3. -- **`1Dxx`**: set attack of operator 4. -- **`1Exx`**: set LFO AM depth. -- **`1Fxx`**: set LFO PM depth. -- **`24xx`**: set LFO 2 speed. -- **`25xx`**: set LFO 2 waveform. `xx` may be one of the following: +- `19xx`: **set attack of all operators.** +- `1Axx`: **set attack of operator 1.** +- `1Bxx`: **set attack of operator 2.** +- `1Cxx`: **set attack of operator 3.** +- `1Dxx`: **set attack of operator 4.** +- `1Exx`: **set LFO AM depth.** +- `1Fxx`: **set LFO PM depth.** +- `24xx`: **set LFO 2 speed.** +- `25xx`: **set LFO 2 waveform.** `xx` may be one of the following: - `00`: saw - `01`: square - `02`: triangle - `03`: noise -- **`26xx`**: set LFO 2 AM depth. -- **`27xx`**: set LFO 2 PM depth. -- **`28xy`**: set reverb of operator. - - `x` is the operator (1-4). a value of 0 means "all operators". +- `26xx`: **set LFO 2 AM depth.** +- `27xx`: **set LFO 2 PM depth.** +- `28xy`: **set reverb of operator.** + - `x` is the operator (1-4). a value of `0` means "all operators". - `y` is the value. -- **`2Axy`**: set waveform of operator. - - `x` is the operator (1-4). a value of 0 means "all operators". +- `2Axy`: **set waveform of operator.** + - `x` is the operator (1-4). a value of `0` means "all operators". - `y` is the value. -- **`2Bxy`**: set EG shift of operator. - - `x` is the operator (1-4). a value of 0 means "all operators". +- `2Bxy`: **set EG shift of operator.** + - `x` is the operator (1-4). a value of `0` means "all operators". - `y` is the value. -- **`2Cxy`**: set fine multiplier of operator. - - `x` is the operator (1-4). a value of 0 means "all operators". +- `2Cxy`: **set fine multiplier of operator.** + - `x` is the operator (1-4). a value of `0` means "all operators". - `y` is the value. -- **`2Fxx`**: enable envelope hard reset. +- `2Fxx`: **enable envelope hard reset.** - this works by inserting a quick release and tiny delay before a new note. -- **`3xyy`**: set fixed frequency of operator 1/2. - - `x` is the block (0-7 for operator 1; 8-F for operator 2). +- `3xyy`: **set fixed frequency of operator 1/2.** + - `x` is the block (`0-7` for operator 1; `8-F` for operator 2). - `y` is the frequency. fixed frequency mode will be disabled if this is less than 8. - the actual frequency is: `y*(2^x)`. -- **`4xyy`**: set fixed frequency of operator 3/4. - - `x` is the block (0-7 for operator 3; 8-F for operator 4). +- `4xyy`: **set fixed frequency of operator 3/4.** + - `x` is the block (`0-7` for operator 3; `8-F` for operator 4). - `y` is the frequency. fixed frequency mode will be disabled if this is less than 8. - the actual frequency is: `y*(2^x)`. -- **`50xy`**: set AM of operator. - - `x` is the operator (1-4). a value of 0 means "all operators". +- `50xy`: **set AM of operator.** + - `x` is the operator (1-4). a value of `0` means "all operators". - `y` determines whether AM is on. -- **`51xy`**: set SL of operator. - - `x` is the operator (1-4). a value of 0 means "all operators". +- `51xy`: **set SL of operator.** + - `x` is the operator (1-4). a value of `0` means "all operators". - `y` is the value. -- **`52xy`**: set RR of operator. - - `x` is the operator (1-4). a value of 0 means "all operators". +- `52xy`: **set RR of operator.** + - `x` is the operator (1-4). a value of `0` means "all operators". - `y` is the value. -- **`53xy`**: set DT of operator. - - `x` is the operator (1-4). a value of 0 means "all operators". +- `53xy`: **set DT of operator.** + - `x` is the operator (1-4). a value of `0` means "all operators". - `y` is the value: - - 0: +0 - - 1: +1 - - 2: +2 - - 3: +3 - - 4: -0 - - 5: -3 - - 6: -2 - - 7: -1 -- **`54xy`**: set RS of operator. - - `x` is the operator (1-4). a value of 0 means "all operators". + - `0`: +0 + - `1`: +1 + - `2`: +2 + - `3`: +3 + - `4`: -0 + - `5`: -3 + - `6`: -2 + - `7`: -1 +- `54xy`: **set RS of operator.** + - `x` is the operator (1-4). a value of `0` means "all operators". - `y` is the value. -- **`55xy`**: set DT2 of operator. - - `x` is the operator (1-4). a value of 0 means "all operators". +- `55xy`: **set DT2 of operator.** + - `x` is the operator (1-4). a value of `0` means "all operators". - `y` is the value. -- **`56xx`**: set DR of all operators. -- **`57xx`**: set DR of operator 1. -- **`58xx`**: set DR of operator 2. -- **`59xx`**: set DR of operator 3. -- **`5Axx`**: set DR of operator 4. -- **`5Bxx`**: set D2R/SR of all operators. -- **`5Cxx`**: set D2R/SR of operator 1. -- **`5Dxx`**: set D2R/SR of operator 2. -- **`5Exx`**: set D2R/SR of operator 3. -- **`5Fxx`**: set D2R/SR of operator 4. +- `56xx`: **set DR of all operators.** +- `57xx`: **set DR of operator 1.** +- `58xx`: **set DR of operator 2.** +- `59xx`: **set DR of operator 3.** +- `5Axx`: **set DR of operator 4.** +- `5Bxx`: **set D2R/SR of all operators.** +- `5Cxx`: **set D2R/SR of operator 1.** +- `5Dxx`: **set D2R/SR of operator 2.** +- `5Exx`: **set D2R/SR of operator 3.** +- `5Fxx`: **set D2R/SR of operator 4.** diff --git a/doc/7-systems/pce.md b/doc/7-systems/pce.md index ea7b0a239..80f4af109 100644 --- a/doc/7-systems/pce.md +++ b/doc/7-systems/pce.md @@ -1,22 +1,22 @@ # PC Engine/TurboGrafx-16 a console from NEC that, depending on a region: - attempted to enter the fierce battle between Nintendo and Sega, but because its capabilities are a mix of third and fourth generation, it failed to last long. (US and Europe) - was Nintendo's most fearsome rival, completely defeating Sega Mega Drive and defending itself against Super Famicom (Japan) +- attempted to enter the fierce battle between Nintendo and Sega, but because its capabilities are a mix of third and fourth generation, it failed to last long. (US and Europe) +- was Nintendo's most fearsome rival, completely defeating Sega Mega Drive and defending itself against Super Famicom (Japan) it has 6 wavetable channels and the last two ones also double as noise channels. furthermore, it has some PCM and LFO! # effects -- **`10xx`**: change wave. -- **`11xx`**: toggle noise mode. only available in the last two channels. -- **`12xx`**: setup LFO. the following values are accepted: +- `10xx`: **change wave.** +- `11xx`: **toggle noise mode.** only available in the last two channels. +- `12xx`: **setup LFO.** the following values are accepted: - `00`: LFO disabled. - `01`: LFO enabled, shift 0. - `02`: LFO enabled, shift 4. - `03`: LFO enabled, shift 8. - when LFO is enabled, channel 2 is muted and its output is passed to channel 1's frequency. -- **`13xx`**: set LFO speed. -- **`17xx`**: toggle PCM mode. - - **this effect is there for compatibility reasons** - it is otherwise recommended to use Sample type instruments (which automatically enable PCM mode when used). +- `13xx`: **set LFO speed.** +- `17xx`: **toggle PCM mode.** + - _this effect is here for compatibility reasons_; it is otherwise recommended to use Sample type instruments (which automatically enable PCM mode when used). diff --git a/doc/7-systems/pet.md b/doc/7-systems/pet.md index 8d5406dbf..9b7e3b5bf 100644 --- a/doc/7-systems/pet.md +++ b/doc/7-systems/pet.md @@ -8,4 +8,4 @@ some of these didn't even have sound... # effects -- **`10xx`**: set waveform. `xx` is a bitmask. +- `10xx`: **set waveform.** `xx` is a bitmask. 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/7-systems/pokey.md b/doc/7-systems/pokey.md index cd94652f2..20d75f1ea 100644 --- a/doc/7-systems/pokey.md +++ b/doc/7-systems/pokey.md @@ -4,7 +4,7 @@ a sound and input chip developed by Atari for their 8-bit computers (Atari 400, # effects -- **`10xx`**: set waveform. +- `10xx`: **set waveform.** - 0: harsh noise (poly5+17) - 1: square buzz (poly5) - 2: weird noise (poly4+5) @@ -13,7 +13,7 @@ a sound and input chip developed by Atari for their 8-bit computers (Atari 400, - 5: square - 6: bass (poly4) - 7: buzz (poly4) -- **`11xx`**: set AUDCTL. `xx` is a bitmask. +- `11xx`: **set AUDCTL.** `xx` is a bitmask. - bit 7: 9-bit poly mode. shortens noise. - bit 6: high channel 1 clock (~1.79MHz on NTSC). - overrides 15KHz mode. @@ -32,6 +32,6 @@ a sound and input chip developed by Atari for their 8-bit computers (Atari 400, - filtered output on channel 2 (I suggest you to set channel 4 volume to 0). - use for PWM effects (not automatic!). - bit 0: 15KHz mode. -- **`12xx`**: toggle two-tone mode. +- `12xx`: **toggle two-tone mode.** - when enabled, channel 2 modulates channel 1. I don't know how, but it does. - only on ASAP core. diff --git a/doc/7-systems/pv1000.md b/doc/7-systems/pv1000.md new file mode 100644 index 000000000..43abbfea9 --- /dev/null +++ b/doc/7-systems/pv1000.md @@ -0,0 +1,7 @@ +# Casio PV-1000 + +released only in Japan, this console was pulled after only a few weeks on the market. it has only 3 square waves with 6-bit pitch resolution and no bass. + +# effects + +- `10xx`: **set ring modulation.** amplitude modulation by the previous channel's output. `0` turns it off and `1` turns it on. diff --git a/doc/7-systems/qsound.md b/doc/7-systems/qsound.md index 95bd6124e..07651a721 100644 --- a/doc/7-systems/qsound.md +++ b/doc/7-systems/qsound.md @@ -12,8 +12,8 @@ there are also 3 ADPCM channels. ADPCM samples are fixed to 8012 Hz. # effects -- **`10xx`**: set echo feedback level. +- `10xx`: **set echo feedback level.** - this effect will apply to all channels. -- **`11xx`**: set echo level. -- **`12xx`**: toggle QSound algorithm (on by default). -- **`3xxx`**: set the length of the echo delay buffer. +- `11xx`: **set echo level.** +- `12xx`: **toggle QSound algorithm.** on by default. +- `3xxx`: **set echo delay buffer length.** diff --git a/doc/7-systems/saa1099.md b/doc/7-systems/saa1099.md index 55073716f..6b2624e40 100644 --- a/doc/7-systems/saa1099.md +++ b/doc/7-systems/saa1099.md @@ -1,18 +1,18 @@ # Philips SAA1099 -this was used by the Game Blaster and SAM Coupé. it's pretty similar to the AY-3-8910, but has stereo sound, twice the channels and two envelopes, both of which are highly flexible. The envelopes work like this: +this was used by the Game Blaster and SAM Coupé. it's pretty similar to the AY-3-8910, but has stereo sound, twice the channels and two envelopes, both of which are highly flexible. the envelopes work like this: - an instrument with envelope settings is placed on channel 2 or channel 5 - an instrument that is used as an "envelope output" is placed on channel 3 or channel 6 (you may want to disable wave output on the output channel) # effects -- **`10xy`**: set channel mode. +- `10xy`: **set channel mode.** - `x` toggles noise. - `y` toggles square. - this effect affects either the first 3 or last 3 channels, depending on where it is placed. -- **`11xx`**: set noise frequency. +- `11xx`: **set noise frequency.** - this effect affects either the first 3 or last 3 channels, depending on where it is placed. -- **`12xx`**: setup envelope. this is a bitmask. +- `12xx`: **setup envelope.** this is a bitmask. - bit 7 toggles the envelope. - bit 5 toggles whether to use a fixed frequency or lock to the frequency of channel 2 or 5. - bit 4 sets the envelope resolution. diff --git a/doc/7-systems/scc.md b/doc/7-systems/scc.md index 413d240ff..19a0736c8 100644 --- a/doc/7-systems/scc.md +++ b/doc/7-systems/scc.md @@ -8,4 +8,4 @@ the SCC+ fixes this issue though (while being compatible with SCC games). # effects -- **`10xx`**: change wave. +- `10xx`: **change wave.** diff --git a/doc/7-systems/segapcm.md b/doc/7-systems/segapcm.md index b44ca2505..3272699d3 100644 --- a/doc/7-systems/segapcm.md +++ b/doc/7-systems/segapcm.md @@ -12,6 +12,6 @@ Furnace also has a five channel version of this chip, but it only exists for Def # effects -- **`20xx`**: set PCM frequency. +- `20xx`: **set PCM frequency.** - `xx` is a 256th fraction of 31250Hz. - - this effect exists for mostly DefleMask compatibility - it is otherwise recommended to use Sample type instruments. + - this effect exists mostly for DefleMask compatibility; it is otherwise recommended to use Sample type instruments. diff --git a/doc/7-systems/sm8521.md b/doc/7-systems/sm8521.md index 563aa35cc..a5a6e024c 100644 --- a/doc/7-systems/sm8521.md +++ b/doc/7-systems/sm8521.md @@ -1,23 +1,23 @@ # Sharp SM8521 -The SM8521 is the CPU and sound chip of the Game.com, a handheld console released in 1997 as a competitor to the infamous Nintendo Virtual Boy. +the SM8521 is the CPU and sound chip of the Game.com, a handheld console released in 1997 as a competitor to the infamous Nintendo Virtual Boy. -Ultimately, most of the games for the Game.com ended up being failures in the eyes of reviewers, thus giving the Game.com a pretty bad reputation. This was one of the reasons that the Game.com only ended up selling at least 300,000 units. For these reasons and more, the Game.com ended up being discontinued in 2000. +ultimately, most of the games for the Game.com ended up being failures in the eyes of reviewers, thus giving the Game.com a pretty bad reputation. this was one of the reasons that the Game.com only ended up selling at least 300,000 units. for these reasons and more, the Game.com ended up being discontinued in 2000. -However, for its time, it was a pretty competitively priced system. The Gameboy Color was to be released in a year for $79.95, while the Game.com was released for $69.99, and its later model, the Pocket Pro, was released in mid-1999 for $29.99 due to the Game.com's apparent significant decrease in value. +however, for its time, it was a pretty competitively priced system. the Gameboy Color was to be released in a year for $79.95, while the Game.com was released for $69.99, and its later model, the Pocket Pro, was released in mid-1999 for $29.99 due to the Game.com's apparent significant decrease in value. -In fact, most games never used the wavetable/noise mode of the chip. Sonic Jam, for example, uses a sine wave with a software-controlled volume envelope on the DAC channel (see below for more information on the DAC channel). +in fact, most games never used the wavetable/noise mode of the chip. sonic Jam, for example, uses a sine wave with a software-controlled volume envelope on the DAC channel (see below for more information on the DAC channel). -The sound-related features and quirks of the SM8521 are as follows: +the sound-related features and quirks of the SM8521 are as follows: - 2 4-bit wavetable channels - a noise channel (which can go up to a very high pitch, creating an almost periodic noise sound) - 5-bit volume -- A low bit-depth output (which means it distorts a lot). -- It phase resets when you switch waves +- a low bit-depth output (which means it distorts a lot). +- it phase resets when you switch waves - 12-bit pitch with a wide frequency range -- A software-controlled D/A register that (potentially) requires all other registers to be stopped to play. Due to this, it is currently, it is not implemented in Furnace as of version 0.6pre4. +- a software-controlled D/A register that (potentially) requires all other registers to be stopped to play. due to this, it is currently, it is not implemented in Furnace as of version 0.6pre4. ## effect commands -- **`10xx`**: Set waveform - - `xx` is a value between 0 and 255, that sets the waveform of the channel you place it on. +- `10xx`: **set waveform.** + - `xx` is a value between 0 and 255 that sets the waveform of the channel you place it on. diff --git a/doc/7-systems/sms.md b/doc/7-systems/sms.md index a17f8d229..528de4207 100644 --- a/doc/7-systems/sms.md +++ b/doc/7-systems/sms.md @@ -1,17 +1,31 @@ -# TI SN76489 (e.g. Sega Master System) +# TI SN76489 (e.g. sega Master System) a relatively simple sound chip made by Texas Instruments. a derivative of it is used in Sega's Master System, the predecessor to Genesis. -the original iteration of the SN76489 used in the TI-99/4A computers was clocked at 447 KHz, being able to play as low as 13.670 Hz (A -1). consequently, pitch accuracy for higher notes is compromised. - -on the other hand, the chip was clocked at a much higher speed on Master System and Genesis, which makes it rather poor in the bass range. +the original iteration of the SN76489 used in the TI-99/4A computer, the SN94624, could only produce tones as low as 100Hz, and was clocked at 447 KHz. all later versions (such as the one in the Master System and Genesis) had a clock divider but ran on a faster clock... except for the SN76494, which can play notes as low as 13.670 Hz (A -1). consequently, its pitch accuracy for higher notes is compromised. # effects -- **`20xy`**: set noise mode. +- `20xy`: **set noise mode.** - `x` controls whether to inherit frequency from channel 3. - - 0: use one of 3 preset frequencies (C: A-2; C#: A-3; D: A-4). - - 1: use frequency of channel 3. + - `0`: use one of 3 preset frequencies (C: A-2; C#: A-3; D: A-4). + - `1`: use frequency of channel 3. - `y` controls whether to select noise or thin pulse. - - 0: thin pulse. - - 1: noise. + - `0`: thin pulse. + - `1`: noise. + +# chip config +## SN7 versions +SN7 was extremely popular due to low cost. Therefore, it was cloned and copied to no end, often with minor differences between each other. Furnace supports several of these: +- SN94624, can only produce tones as low as 100Hz, and is clocked at 447 KHz. +- SN76494, which can play notes as low as 13.670 Hz (A -1). It has a different noise feedback and invert masks. +- SN76489, identical to SN94624, just without a clock divider +- SN76489A, identical to 76494, just with a /8 clock divider +- SN76496, literally identical to former. Why is it even here? +- SN76496 with a Atari-like short noise. The chip of many legend and rumours, might be a result of inaccurate emulation. +- Sega Master System VDP version has a different, characteristic noise LFSR. +- Game Gear SN7, identical to the above, but with stereo +- NCR8496, different noise invert masks +- PSSJ3, literally identical to the former, it just swaps "high" and "low" signals in the output, which results in no audible difference + +TODO: all these checkboxes diff --git a/doc/7-systems/snes.md b/doc/7-systems/snes.md index a9a98b894..9888291c6 100644 --- a/doc/7-systems/snes.md +++ b/doc/7-systems/snes.md @@ -1,8 +1,8 @@ -# Super Nintendo Entertainment System (SNES)/Super Famicom +# Super Nintendo Entertainment System (SNES) / Super Famicom the successor to NES to compete with Genesis, packing superior graphics and sample-based audio. -its audio system, developed by Sony, features a DSP chip, SPC700 CPU and 64KB of dedicated SRAM used by both. +its Sony-developed audio system features a DSP chip, SPC700 CPU, and 64KB of dedicated SRAM used by both. this whole system itself is pretty much a separate computer that the main CPU needs to upload its program and samples to. Furnace communicates with the DSP directly and provides a full 64KB of memory. this memory might be reduced excessively on ROM export to make up for playback engine and pattern data. you can go to window > statistics to see how much memory your samples are using. @@ -22,60 +22,156 @@ Furnace also allows the SNES to use wavetables (and the wavetable synthesizer) i # effects -- **`10xx`**: set waveform. -- **`11xx`**: toggle noise mode. -- **`12xx`**: toggle echo on this channel. -- **`13xx`**: toggle pitch modulation. -- **`14xy`**: toggle inverting the left or right channels (x: left, y: right). -- **`15xx`**: set envelope mode. - - 0: ADSR. - - 1: gain (direct). - - 2: linear decrement. - - 3: exponential decrement. - - 4: linear increment. - - 5: bent line (inverse log) increment. -- **`16xx`**: set gain (00 to 7F if direct, 00 to 1F otherwise). -- **`18xx`**: enable echo buffer. -- **`19xx`**: set echo delay - - goes from 0 to F. -- **`1Axx`**: set left echo channel volume. - - this is a signed number. - - 00 to 7F for 0 to 127. - - 80 to FF for -128 to -1. - - setting this to -128 is not recommended as it may cause echo output to overflow and therefore click. -- **`1Bxx`**: set right echo channel volume. - - this is a signed number. - - 00 to 7F for 0 to 127. - - 80 to FF for -128 to -1. - - setting this to -128 is not recommended as it may cause echo output to overflow and therefore click. -- **`1Cxx`**: set echo feedback. - - this is a signed number. - - 00 to 7F for 0 to 127. - - 80 to FF for -128 to -1. - - setting this to -128 is not recommended as it may cause echo output to overflow and therefore click. -- **`1Dxx`**: set noise generator frequency (00 to 1F). -- **`1Exx`**: set left dry/global volume. - - this does not affect echo. -- **`1Fxx`**: set right dry/global volume. - - this does not affect echo. -- **`20xx`**: set attack (0 to F). - - only in ADSR envelope mode. -- **`21xx`**: set decay (0 to 7). - - only in ADSR envelope mode. -- **`22xx`**: set sustain (0 to 7). - - only in ADSR envelope mode. -- **`23xx`**: set release (00 to 1F). - - only in ADSR envelope mode. -- **`30xx`**: set echo filter coefficient 0. -- **`31xx`**: set echo filter coefficient 1. -- **`32xx`**: set echo filter coefficient 2. -- **`33xx`**: set echo filter coefficient 3. -- **`34xx`**: set echo filter coefficient 4. -- **`35xx`**: set echo filter coefficient 5. -- **`36xx`**: set echo filter coefficient 6. -- **`37xx`**: set echo filter coefficient 7. +- `10xx`: **set waveform.** +- `11xx`: **toggle noise mode.** +- `12xx`: **toggle echo on this channel.** +- `13xx`: **toggle pitch modulation.** frequency modulation by the previous channel's output. no effect on channel 1. +- `14xy`: **toggle inverting the left or right channels.** `x` is left, `y` is right. +- `15xx`: **set envelope mode.** see gain chart below for `1` through `5`. + - `0`: ADSR mode. + - `1`: gain (direct). volume holds at one level. + - `2`: linear decrement. volume lowers by subtractions of 1/64. + - `3`: exponential decrement. volume lowers by multiplications of 255/256. + - `4`: linear increment. volume rises by additions of 1/64. + - `5`: bent line (inverse log) increment. volume rises by additions of 1/64 until 3/4, then additions of 1/256. +- `16xx`: **set gain.** `00` to `7F` if direct, `00` to `1F` otherwise. +- `18xx`: **enable echo buffer.** +- `19xx`: **set echo delay.** range is `0` to `F`. +- `1Axx`: **set left echo channel volume.**\ + `1Bxx`: **set right echo channel volume.**\ + `1Cxx`: **set echo feedback.** - all of these are signed numbers. - - 00 to 7F for 0 to 127. - - 80 to FF for -128 to -1. - - make sure the sum of these is between -128 or 127. - - failure to comply may result in overflow and therefore clicking. + - `00` to `7F` for 0 to 127. + - `80` to `FF` for -128 to -1. + - setting these to -128 is not recommended as it may cause echo output to overflow and therefore click. +- `1Dxx`: **set noise generator frequency.** range is `00` to `1F`. see noise frequencies chart below. +- `1Exx`: **set left dry / global volume.**\ + `1Fxx`: **set right dry / global volume.** + - these do not affect echo. +- `20xx`: **set attack.** range is `0` to `F`.\ + `21xx`: **set decay.** range is `0` to `7`.\ + `22xx`: **set sustain.** range is `0` to `7`.\ + `23xx`: **set release.** range is `00` to `1F`. + - these four are only used in ADSR envelope mode. see ADSR chart below. +- `30xx`: **set echo filter coefficient 0.**\ + `31xx`: **set echo filter coefficient 1.**\ + `32xx`: **set echo filter coefficient 2.**\ + `33xx`: **set echo filter coefficient 3.**\ + `34xx`: **set echo filter coefficient 4.**\ + `35xx`: **set echo filter coefficient 5.**\ + `36xx`: **set echo filter coefficient 6.**\ + `37xx`: **set echo filter coefficient 7.** + - all of these are signed numbers. + - `00` to `7F` for 0 to 127. + - `80` to `FF` for -128 to -1. + - _Note:_ Be sure the sum of all coefficients is between -128 and 127. sums outside that may result in overflow and therefore clicking. + - see [SnesLab](https://sneslab.net/wiki/FIR_Filter) for a full explanation and examples. + +# tables + +## ADSR + +| attack | 0→1 time | decay | 1→S time | sustain | ratio | release | S→0 time +| -----: | -------: | ----: | -------: | ------: | :---: | ------: | -------: +| `00` | 4.1s | `00` | 1.2s | `00` | 1/8 | `00` | ∞ +| `01` | 2.5s | `01` | 740ms | `01` | 2/8 | `01` | 38s +| `02` | 1.5s | `02` | 440ms | `02` | 3/8 | `02` | 28s +| `03` | 1.0s | `03` | 290ms | `03` | 4/8 | `03` | 24s +| `04` | 640ms | `04` | 180ms | `04` | 5/8 | `04` | 19s +| `05` | 380ms | `05` | 110ms | `05` | 6/8 | `05` | 14s +| `06` | 260ms | `06` | 74ms | `06` | 7/8 | `06` | 12s +| `07` | 160ms | `07` | 37ms | `07` | 1 | `07` | 9.4s +| `08` | 96ms | | | | | `08` | 7.1s +| `09` | 64ms | | | | | `09` | 5.9s +| `0A` | 40ms | | | | | `0A` | 4.7s +| `0B` | 24ms | | | | | `0B` | 3.5s +| `0C` | 16ms | | | | | `0C` | 2.9s +| `0D` | 10ms | | | | | `0D` | 2.4s +| `0E` | 6ms | | | | | `0E` | 1.8s +| `0F` | 0ms | | | | | `0F` | 1.5s +| | | | | | | `10` | 1.2s +| | | | | | | `11` | 880ms +| | | | | | | `12` | 740ms +| | | | | | | `13` | 590ms +| | | | | | | `14` | 440ms +| | | | | | | `15` | 370ms +| | | | | | | `16` | 290ms +| | | | | | | `17` | 220ms +| | | | | | | `18` | 180ms +| | | | | | | `19` | 150ms +| | | | | | | `1A` | 110ms +| | | | | | | `1B` | 92ms +| | | | | | | `1C` | 74ms +| | | | | | | `1D` | 55ms +| | | | | | | `1E` | 37ms +| | | | | | | `1F` | 18ms + +reference: [Super Famicom Development Wiki](https://wiki.superfamicom.org/spc700-reference#dsp-voice-register:-adsr-1097) + +## gain + +value | linear inc. | bent line inc. | linear dec. | exponent dec. +----: | ----------: | -------------: | ----------: | ------------: + `00` | ∞ | ∞ | ∞ | ∞ + `01` | 4.1s | 7.2s | 4.1s | 38s + `02` | 3.1s | 5.4s | 3.1s | 28s + `03` | 2.6s | 4.6s | 2.6s | 24s + `04` | 2.0s | 3.5s | 2.0s | 19s + `05` | 1.5s | 2.6s | 1.5s | 14s + `06` | 1.3s | 2.3s | 1.3s | 12s + `07` | 1.0s | 1.8s | 1.0s | 9.4s + `08` | 770ms | 1.3s | 770ms | 7.1s + `09` | 640ms | 1.1s | 640ms | 5.9s + `0A` | 510ms | 900ms | 510ms | 4.7s + `0B` | 380ms | 670ms | 380ms | 3.5s + `0C` | 320ms | 560ms | 320ms | 2.9s + `0D` | 260ms | 450ms | 260ms | 2.4s + `0E` | 190ms | 340ms | 190ms | 1.8s + `0F` | 160ms | 280ms | 160ms | 1.5s + `10` | 130ms | 220ms | 130ms | 1.2s + `11` | 96ms | 170ms | 96ms | 880ms + `12` | 80ms | 140ms | 80ms | 740ms + `13` | 64ms | 110ms | 64ms | 590ms + `14` | 48ms | 84ms | 48ms | 440ms + `15` | 40ms | 70ms | 40ms | 370ms + `16` | 32ms | 56ms | 32ms | 290ms + `17` | 24ms | 42ms | 24ms | 220ms + `18` | 20ms | 35ms | 20ms | 180ms + `19` | 16ms | 28ms | 16ms | 150ms + `1A` | 12ms | 21ms | 12ms | 110ms + `1B` | 10ms | 18ms | 10ms | 92ms + `1C` | 8ms | 14ms | 8ms | 74ms + `1D` | 6ms | 11ms | 6ms | 55ms + `1E` | 4ms | 7ms | 4ms | 37ms + `1F` | 2ms | 3.5ms | 2ms | 18ms + +reference: [Super Famicom Development Wiki](https://wiki.superfamicom.org/spc700-reference#dsp-voice-register:-gain-1156) + +## noise frequencies + +value | freq. | value | freq. +----: | -----: | ----: | -------: +`00` | 0 Hz | `10` | 500 Hz +`01` | 16 Hz | `11` | 667 Hz +`02` | 21 Hz | `12` | 800 Hz +`03` | 25 Hz | `13` | 1.0 KHz +`04` | 31 Hz | `14` | 1.3 KHz +`05` | 42 Hz | `15` | 1.6 KHz +`06` | 50 Hz | `16` | 2.0 KHz +`07` | 63 Hz | `17` | 2.7 KHz +`08` | 83 Hz | `18` | 3.2 KHz +`09` | 100 Hz | `19` | 4.0 KHz +`0A` | 125 Hz | `1A` | 5.3 KHz +`0B` | 167 Hz | `1B` | 6.4 KHz +`0C` | 200 Hz | `1C` | 8.0 KHz +`0D` | 250 Hz | `1D` | 10.7 KHz +`0E` | 333 Hz | `1E` | 16 KHz +`0F` | 400 Hz | `1F` | 32 KHz + +reference: [Super Famicom Development Wiki](https://wiki.superfamicom.org/spc700-reference#dsp-register:-flg-1318) + + + +# resources + +- [SNES-format BRR samples](https://www.smwcentral.net/?p=stion&s=brrsamples) at SMW Central diff --git a/doc/7-systems/soundunit.md b/doc/7-systems/soundunit.md index 6bc43d180..5b4838133 100644 --- a/doc/7-systems/soundunit.md +++ b/doc/7-systems/soundunit.md @@ -14,43 +14,43 @@ it has the following capabilities: # effects -- **`10xx`**: set waveform - - 0: pulse wave - - 1: sawtooth - - 2: sine wave - - 3: triangle wave - - 4: noise - - 5: periodic noise - - 6: XOR sine - - 7: XOR triangle -- **`12xx`**: set pulse width (0 to 7F) -- **`13xx`**: set resonance of filter (0 to FF) - - despite what the internal effects list says (0 to F), you can use a resonance value from 0 to FF (255) -- **`14xx`**: set filter mode and ringmod +- `10xx`: **set waveform.** + - `0`: pulse wave + - `1`: sawtooth + - `2`: sine wave + - `3`: triangle wave + - `4`: noise + - `5`: periodic noise + - `6`: XOR sine + - `7`: XOR triangle +- `12xx`: **set pulse width.** range is `0` to `7F`. +- `13xx`: **set resonance of filter.** range is `0` to `FF`. + - despite what the internal effects list says (`0` to `F`), you can use a resonance value from `0` to `FF` (255). +- `14xx`: **set filter mode and ringmod.** - bit 0: ring mod - bit 1: low pass - bit 2: high pass - bit 3: band pass -- **`15xx`**: set frequency sweep period low byte -- **`16xx`**: set frequency sweep period high byte -- **`17xx`**: set volume sweep period low byte -- **`18xx`**: set volume sweep period high byte -- **`19xx`**: set cutoff sweep period low byte -- **`1Axx`**: set cutoff sweep period high byte -- **`1Bxx`**: set frequency sweep boundary -- **`1Cxx`**: set volume sweep boundary -- **`1Dxx`**: set cutoff sweep boundary -- **`1Exx`**: set phase reset period low byte -- **`1Fxx`**: set phase reset period high byte -- **`20xx`**: toggle frequency sweep +- `15xx`: **set frequency sweep period low byte.** +- `16xx`: **set frequency sweep period high byte.** +- `17xx`: **set volume sweep period low byte.** +- `18xx`: **set volume sweep period high byte.** +- `19xx`: **set cutoff sweep period low byte.** +- `1Axx`: **set cutoff sweep period high byte.** +- `1Bxx`: **set frequency sweep boundary.** +- `1Cxx`: **set volume sweep boundary.** +- `1Dxx`: **set cutoff sweep boundary.** +- `1Exx`: **set phase reset period low byte.** +- `1Fxx`: **set phase reset period high byte.** +- `20xx`: **toggle frequency sweep.** - bit 0-6: speed - bit 7: up direction -- **`21xx`**: toggle volume sweep +- `21xx`: **toggle volume sweep.** - bit 0-4: speed - bit 5: up direction - bit 6: loop - bit 7: alternate -- **`22xx`**: toggle cutoff sweep +- `22xx`: **toggle cutoff sweep.** - bit 0-6: speed - bit 7: up direction -- **`4xxx`**: set cutoff (0 to FFF) +- `4xxx`: **set cutoff.** range is `0` to `FFF`. diff --git a/doc/7-systems/t6w28.md b/doc/7-systems/t6w28.md index 346100f02..886ea63b4 100644 --- a/doc/7-systems/t6w28.md +++ b/doc/7-systems/t6w28.md @@ -6,6 +6,6 @@ this chip was used in Neo Geo Pocket. # effects -- **`20xx`**: set noise mode. - - 0: thin pulse. - - 1: noise. +- `20xx`: **set noise mode.** + - `0`: thin pulse. + - `1`: noise. diff --git a/doc/7-systems/tia.md b/doc/7-systems/tia.md index fbfd2edba..b39d31db4 100644 --- a/doc/7-systems/tia.md +++ b/doc/7-systems/tia.md @@ -9,20 +9,20 @@ Furnace isn't complete without this one... # effects -- **`10xx`**: select shape. `xx` may be one of: - - 0: nothing - - 1: buzzy - - 2: low buzzy - - 3: flangy - - 4: square - - 5: square - - 6: pure buzzy - - 7: reedy - - 8: noise - - 9: reedy - - 10: pure buzzy - - 11: nothing - - 12: low square - - 13: low square - - 14: low pure buzzy - - 15: low reedy \ No newline at end of file +- `10xx`: **select shape.** + - `0`: nothing + - `1`: buzzy + - `2`: low buzzy + - `3`: flangy + - `4`: square + - `5`: square + - `6`: pure buzzy + - `7`: reedy + - `8`: noise + - `9`: reedy + - `A`: pure buzzy + - `B`: nothing + - `C`: low square + - `D`: low square + - `E`: low pure buzzy + - `F`: low reedy \ No newline at end of file diff --git a/doc/7-systems/vera.md b/doc/7-systems/vera.md index 8d630129b..e89744a9d 100644 --- a/doc/7-systems/vera.md +++ b/doc/7-systems/vera.md @@ -7,9 +7,11 @@ currently Furnace does not support the PCM channel's stereo mode, though (except # effects -- **`20xx`**: set waveform. the following values are accepted: - - 0: pulse - - 1: saw - - 2: triangle - - 3: noise -- **`22xx`**: set duty cycle. `xx` may go from 0 to 3F. +- `20xx`: **set waveform.** + - `0`: pulse + - `1`: saw + - `2`: triangle + - `3`: noise +- `22xx`: **set duty cycle.** range is `0` to `3F`. +- `EExx`: **ZSM synchronization event.** + - Where `xx` is the event payload. This has no effect in how the music is played in Furnace, but the ZSMKit library for the Commander X16 interprets these events inside ZSM files and optionally triggers a callback routine. This can be used, for instance, to cause game code to respond to beats or at certain points in the music. diff --git a/doc/7-systems/vic20.md b/doc/7-systems/vic20.md index 4d445936d..e5126c182 100644 --- a/doc/7-systems/vic20.md +++ b/doc/7-systems/vic20.md @@ -15,4 +15,4 @@ these channels are not referred as "square" wave channels since a technique to p ## effect commands -- **`10xx`**: Switch waveform (`xx` from `00` to `0F`) +- `10xx`: **switch waveform.** range is `00` to `0F`. diff --git a/doc/7-systems/virtual-boy.md b/doc/7-systems/virtual-boy.md index 9079d1c7b..9ef6c909b 100644 --- a/doc/7-systems/virtual-boy.md +++ b/doc/7-systems/virtual-boy.md @@ -10,34 +10,34 @@ additionally, channel 5 offers a modulation/sweep unit. the former is similar to # effects -- **`10xx`**: set waveform. -- **`11xx`**: set noise length (0 to 7). +- `10xx`: **set waveform.** +- `11xx`: **set noise length.** range is `0` to `7`. - only in the noise channel. -- **`12xy`**: setup envelope. +- `12xy`: **setup envelope.** - `x` determines whether envelope is enabled or not. - - 0: disabled - - 1: enabled - - 3: enabled and loop - - yeah, the value 2 isn't useful. + - `0`: disabled + - `1`: enabled + - `3`: enabled and loop + - yeah, the value `2` isn't useful. - `y` sets the speed and direction. - - 0-7: down - - 8-F: up -- **`13xy`**: setup sweep. + - `0-7`: down + - `8-F`: up +- `13xy`: **setup sweep.** - `x` sets the speed. - - 0 and 8 are "speed 0" - sweep is ineffective. - - `y` sets the shift (0 to 7). - - 8 and higher will mute the channel. + - `0` and `8` are "speed 0" - sweep is ineffective. + - `y` sets the shift (`0` to `7`). + - `8` and higher will mute the channel. - only in channel 5. -- **`14xy`**: setup modulation. +- `14xy`: **setup modulation.** - `x` determines whether it's enabled or not. - 0: disabled - 1: enabled - 3: enabled and loop - 2 isn't useful here either. - `y` sets the speed. - - 0 and 8 are "speed 0" - modulation is ineffective. + - `0` and `8` are "speed 0" - modulation is ineffective. - no, you can't really do Yamaha FM using this. - only in channel 5. -- **`15xx`**: set modulation wave. - - `xx` points to a wavetable. it should have a height of 255. +- `15xx`: **set modulation wave.** + - `xx` points to a wavetable. range is `0` to `FF`. - this is an alternative to setting the modulation wave through the instrument. diff --git a/doc/7-systems/vrc6.md b/doc/7-systems/vrc6.md index e761f90dc..d652cc26c 100644 --- a/doc/7-systems/vrc6.md +++ b/doc/7-systems/vrc6.md @@ -14,5 +14,5 @@ Furnace supports this routine for PCM playback, but it consumes a lot of CPU tim these effects only are effective in the pulse channels. -- **`12xx`**: set duty cycle (0 to 7). -- **`17xx`**: toggle PCM mode. +- `12xx`: **set duty cycle.** range is `0` to `7`. +- `17xx`: **toggle PCM mode.** diff --git a/doc/7-systems/wonderswan.md b/doc/7-systems/wonderswan.md index eb3b47cde..d4bff7c1e 100644 --- a/doc/7-systems/wonderswan.md +++ b/doc/7-systems/wonderswan.md @@ -10,12 +10,12 @@ it has 4 wavetable channels. some of them have additional capabilities: # effects -- **`10xx`**: change wave. -- **`11xx`**: setup noise mode (channel 4 only). +- `10xx`: **change wave**. +- `11xx`: **setup noise mode.** channel 4 only. - 0: disable. - 1-8: enable and set tap preset. -- **`12xx`**: setup sweep period (channel 3 only). +- `12xx`: **setup sweep period.** channel 3 only. - 0: disable. - 1-32: enable and set period. -- **`13xx`**: setup sweep amount (channel 3 only). -- **`17xx`**: toggle PCM mode (channel 2 only). +- `13xx`: **setup sweep amount.** channel 3 only. +- `17xx`: **toggle PCM mode.** channel 2 only. diff --git a/doc/7-systems/x1-010.md b/doc/7-systems/x1-010.md index deae16e7b..5f110aba5 100644 --- a/doc/7-systems/x1-010.md +++ b/doc/7-systems/x1-010.md @@ -1,47 +1,47 @@ # Seta/Allumer X1-010 -A sound chip designed by Seta, mainly used in their own arcade hardware from the late 80s to the early 2000s. -It has 2 output channels, but there is no known hardware taking advantage of stereo sound capabilities. -Later hardware paired this with external bankswitching logic, but this isn't emulated yet. +a sound chip designed by Seta, mainly used in their own arcade hardware from the late 80s to the early 2000s. +it has 2 output channels, but there is no known hardware taking advantage of stereo sound capabilities. +later hardware paired this with external bankswitching logic, but this isn't emulated yet. Allumer rebadged it for their own arcade hardware. -It has 16 channels, which can all be switched between PCM sample or wavetable playback mode. -Wavetable playback needs to paired with envelope, similar to AY PSG, but shapes are stored in RAM and as such are user-definable. +it has 16 channels, which can all be switched between PCM sample or wavetable playback mode. +wavetable playback needs to paired with envelope, similar to AY PSG, but shapes are stored in RAM and as such are user-definable. -In Furnace, this chip can be configured for original arcade mono output or stereo output - it simulates early 'incorrect' emulation on some mono hardware, but it is also based on the assumption that each channel is connected to each output. +in Furnace, this chip can be configured for original arcade mono output or stereo output - it simulates early 'incorrect' emulation on some mono hardware, but it is also based on the assumption that each channel is connected to each output. -# Waveform types +# waveform types -This chip supports 2 types of waveforms, needs to be paired to external 8 KB RAM to access these features: +this chip supports 2 types of waveforms, needs to be paired to external 8 KB RAM to access these features: -One is a signed 8 bit mono waveform, operated like other wavetable based sound systems. -These are stored at the lower half of RAM at common case. +one is a signed 8 bit mono waveform, operated like other wavetable based sound systems. +these are stored at the lower half of RAM at common case. -The other one ("Envelope") is a 4 bit stereo waveform, multiplied with the above and calculates final output, each nibble is used for each output channel. -These are stored at the upper half of RAM at common case. +the other one ("Envelope") is a 4 bit stereo waveform, multiplied with the above and calculates final output, each nibble is used for each output channel. +these are stored at the upper half of RAM at common case. -Both waveforms are 128 bytes (fixed size), freely allocated at each half of RAM except the channel register area: each half can store total 32/31 waveforms at once. -In furnace, you can enable the envelope shape split mode. When it is set, its waveform will be split to the left and right halves for each output. Each max size is 128 bytes, total 256 bytes. +both waveforms are 128 bytes (fixed size), freely allocated at each half of RAM except the channel register area: each half can store total 32/31 waveforms at once. +in Furnace, you can enable the envelope shape split mode. when it is set, its waveform will be split to the left and right halves for each output. each max size is 128 bytes, total 256 bytes. # effects -- **`10xx`**: change wave. -- **`11xx`**: change envelope shape (also wavetable). -- **`17xx`**: toggle PCM mode. -- **`20xx`**: set PCM frequency (1 to FF). -- **`22xx`**: set envelope mode. +- `10xx`: **change wave.** +- `11xx`: **change envelope shape.** also wavetable. +- `17xx`: **toggle PCM mode.** +- `20xx`: **set PCM frequency.** range is `1` to `FF`. + - PCM frequency formula: `step * (clock / 8192)`. + - range is 1.95KHz to 498KHz if the chip clock is 16MHz. +- `22xx`: **set envelope mode.** - bit 0 sets whether envelope will affect this channel. - bit 1 toggles the envelope one-shot mode. when it is set, channel is halted after envelope cycle is finished. - bit 2 toggles the envelope shape split mode. when it is set, envelope shape will be split to left half and right half. - bit 3/5 sets whether the right/left shape will mirror the original one. - bit 4/6 sets whether the right/left output will mirror the original one. -- **`23xx`**: set envelope period. -- **`25xx`**: slide envelope period up. -- **`26xx`**: slide envelope period down. -- **`29xy`**: enable auto-envelope mode. +- `23xx`: **set envelope period.** +- `25xx`: **slide envelope period up.** +- `26xx`: **slide envelope period down.** +- `29xy`: **enable auto-envelope mode.** - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. - `x` is the numerator. - `y` is the denominator. - if `x` or `y` are 0 this will disable auto-envelope mode. - -* PCM frequency: 255 step, formula: `step * (Chip clock / 8192)`; 1.95KHz to 498KHz if Chip clock is 16MHz. diff --git a/doc/7-systems/ym2151.md b/doc/7-systems/ym2151.md index affb8c3a9..f9bd87511 100644 --- a/doc/7-systems/ym2151.md +++ b/doc/7-systems/ym2151.md @@ -1,6 +1,6 @@ # Yamaha YM2151 -the sound chip powering several arcade boards, the Sharp X1/X68000 and the Commander X16. Eight 4-op FM channels, with overpowered LFO and almost unused noise generator. +the sound chip powering several arcade boards, the Sharp X1/X68000 and the Commander X16. eight 4-op FM channels, with overpowered LFO and almost unused noise generator. it also was present on several pinball machines and synthesizers of the era, and later surpassed by the YM2414 (OPZ) present in the world-famous TX81Z. @@ -8,63 +8,63 @@ in most arcade boards the chip was used in combination with a PCM chip, like [Se # effects -- **`10xx`**: set noise frequency of channel 8 operator 4. 00 disables noise while 01 to 20 enables it. -- **`11xx`**: set feedback of channel. -- **`12xx`**: set operator 1 level. -- **`13xx`**: set operator 2 level. -- **`14xx`**: set operator 3 level. -- **`15xx`**: set operator 4 level. -- **`16xy`**: set multiplier of operator. +- `10xx`: **set noise frequency of channel 8 operator 4.** `00` disables noise while `01` to `20` enables it. +- `11xx`: **set feedback of channel.** +- `12xx`: **set operator 1 level.** +- `13xx`: **set operator 2 level.** +- `14xx`: **set operator 3 level.** +- `15xx`: **set operator 4 level.** +- `16xy`: **set multiplier of operator.** - `x` is the operator (1-4). - `y` is the multiplier. -- **`17xx`**: set LFO speed. -- **`18xx`**: set LFO waveform. `xx` may be one of the following: +- `17xx`: **set LFO speed.** +- `18xx`: **set LFO waveform.** - `00`: saw - `01`: square - `02`: triangle - `03`: noise -- **`19xx`**: set attack of all operators. -- **`1Axx`**: set attack of operator 1. -- **`1Bxx`**: set attack of operator 2. -- **`1Cxx`**: set attack of operator 3. -- **`1Dxx`**: set attack of operator 4. -- **`1Exx`**: set LFO AM depth. -- **`1Fxx`**: set LFO PM depth. -- **`30xx`**: enable envelope hard reset. +- `19xx`: **set attack of all operators.** +- `1Axx`: **set attack of operator 1.** +- `1Bxx`: **set attack of operator 2.** +- `1Cxx`: **set attack of operator 3.** +- `1Dxx`: **set attack of operator 4.** +- `1Exx`: **set LFO AM depth.** +- `1Fxx`: **set LFO PM depth.** +- `30xx`: **enable envelope hard reset.** - this works by inserting a quick release and tiny delay before a new note. -- **`50xy`**: set AM of operator. +- `50xy`: **set AM of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` determines whether AM is on. -- **`51xy`**: set SL of operator. +- `51xy`: **set SL of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`52xy`**: set RR of operator. +- `52xy`: **set RR of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`53xy`**: set DT of operator. +- `53xy`: **set DT of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value: - - 0: +0 - - 1: +1 - - 2: +2 - - 3: +3 - - 4: -0 - - 5: -3 - - 6: -2 - - 7: -1 -- **`54xy`**: set RS of operator. + - `0`: +0 + - `1`: +1 + - `2`: +2 + - `3`: +3 + - `4`: -0 + - `5`: -3 + - `6`: -2 + - `7`: -1 +- `54xy`: **set RS of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`55xy`**: set DT2 of operator. +- `55xy`: **set DT2 of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`56xx`**: set DR of all operators. -- **`57xx`**: set DR of operator 1. -- **`58xx`**: set DR of operator 2. -- **`59xx`**: set DR of operator 3. -- **`5Axx`**: set DR of operator 4. -- **`5Bxx`**: set D2R/SR of all operators. -- **`5Cxx`**: set D2R/SR of operator 1. -- **`5Dxx`**: set D2R/SR of operator 2. -- **`5Exx`**: set D2R/SR of operator 3. -- **`5Fxx`**: set D2R/SR of operator 4. +- `56xx`: **set DR of all operators.** +- `57xx`: **set DR of operator 1.** +- `58xx`: **set DR of operator 2.** +- `59xx`: **set DR of operator 3.** +- `5Axx`: **set DR of operator 4.** +- `5Bxx`: **set D2R/SR of all operators.** +- `5Cxx`: **set D2R/SR of operator 1.** +- `5Dxx`: **set D2R/SR of operator 2.** +- `5Exx`: **set D2R/SR of operator 3.** +- `5Fxx`: **set D2R/SR of operator 4.** diff --git a/doc/7-systems/ym2203.md b/doc/7-systems/ym2203.md index 984d32f97..f79f95d24 100644 --- a/doc/7-systems/ym2203.md +++ b/doc/7-systems/ym2203.md @@ -11,91 +11,97 @@ several variants of this chip were released as well, with more features. # effects -- **`11xx`**: set feedback of channel. -- **`12xx`**: set operator 1 level. -- **`13xx`**: set operator 2 level. -- **`14xx`**: set operator 3 level. -- **`15xx`**: set operator 4 level. -- **`16xy`**: set multiplier of operator. - - `x` is the operator (1-4). +- `11xx`: **set feedback of channel.** +- `12xx`: **set operator 1 level.** +- `13xx`: **set operator 2 level.** +- `14xx`: **set operator 3 level.** +- `15xx`: **set operator 4 level.** +- `16xy`: **set multiplier of operator.** + - `x` is the operator from 1 to 4. - `y` is the multiplier. -- **`18xx`**: toggle extended channel 3 mode. - - 0 disables it and 1 enables it. +- `18xx`: **toggle extended channel 3 mode.** + - `0` disables it and `1` enables it. - only in extended channel 3 chip. -- **`19xx`**: set attack of all operators. -- **`1Axx`**: set attack of operator 1. -- **`1Bxx`**: set attack of operator 2. -- **`1Cxx`**: set attack of operator 3. -- **`1Dxx`**: set attack of operator 4. -- **`20xx`**: set SSG channel mode. `xx` may be one of the following: - - `00`: square - - `01`: noise - - `02`: square and noise - - `03`: nothing (apparently) - - `04`: envelope and square - - `05`: envelope and noise - - `06`: envelope and square and noise - - `07`: nothing -- **`21xx`**: set noise frequency. `xx` is a value between 00 and 1F. -- **`22xy`**: set envelope mode. +- `19xx`: **set attack of all operators.** +- `1Axx`: **set attack of operator 1.** +- `1Bxx`: **set attack of operator 2.** +- `1Cxx`: **set attack of operator 3.** +- `1Dxx`: **set attack of operator 4.** +- `20xx`: **set SSG channel mode.** `xx` may be one of the following: + - `0`: square + - `1`: noise + - `2`: square and noise + - `3`: nothing (apparently) + - `4`: envelope and square + - `5`: envelope and noise + - `6`: envelope and square and noise + - `7`: nothing +- `21xx`: **set noise frequency.** `xx` is a value between 00 and 1F. +- `22xy`: **set envelope mode.** - `x` sets the envelope shape, which may be one of the following: - - `0: \___` decay - - `4: /___` attack once - - `8: \\\\` saw - - `9: \___` decay - - `A: \/\/` inverse obelisco - - `B: \¯¯¯` decay once - - `C: ////` inverse saw - - `D: /¯¯¯` attack - - `E: /\/\` obelisco - - `F: /___` attack once + - `0`: `\___` decay + - `4`: `/___` attack once + - `8`: `\\\\` saw + - `9`: `\___` decay + - `A`: `\/\/` inverse obelisco + - `B`: `\¯¯¯` decay once + - `C`: `////` inverse saw + - `D`: `/¯¯¯` attack + - `E`: `/\/\` obelisco + - `F`: `/___` attack once - if `y` is 1 then the envelope will affect this channel. -- **`23xx`**: set envelope period low byte. -- **`24xx`**: set envelope period high byte. -- **`25xx`**: slide envelope period up. -- **`26xx`**: slide envelope period down. -- **`29xy`**: enable SSG auto-envelope mode. +- `23xx`: **set envelope period low byte.** +- `24xx`: **set envelope period high byte.** +- `25xx`: **slide envelope period up.** +- `26xx`: **slide envelope period down.** +- `29xy`: **enable SSG auto-envelope mode.** - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. - `x` is the numerator. - `y` is the denominator. - if `x` or `y` are 0 this will disable auto-envelope mode. -- **`30xx`**: enable envelope hard reset. +- `30xx`: **enable envelope hard reset.** - this works by inserting a quick release and tiny delay before a new note. -- **`50xy`**: set AM of operator. +- `50xy`: **set AM of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` determines whether AM is on. -- **`51xy`**: set SL of operator. +- `51xy`: **set SL of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`52xy`**: set RR of operator. +- `52xy`: **set RR of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`53xy`**: set DT of operator. +- `53xy`: **set DT of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value: - - 0: +0 - - 1: +1 - - 2: +2 - - 3: +3 - - 4: -0 - - 5: -3 - - 6: -2 - - 7: -1 -- **`54xy`**: set RS of operator. + - `0`: +0 + - `1`: +1 + - `2`: +2 + - `3`: +3 + - `4`: -0 + - `5`: -3 + - `6`: -2 + - `7`: -1 +- `54xy`: **set RS of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`55xy`**: set SSG-EG of operator. +- `55xy`: **set SSG-EG of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value (0-8). - values between 0 and 7 set SSG-EG. - value 8 disables it. -- **`56xx`**: set DR of all operators. -- **`57xx`**: set DR of operator 1. -- **`58xx`**: set DR of operator 2. -- **`59xx`**: set DR of operator 3. -- **`5Axx`**: set DR of operator 4. -- **`5Bxx`**: set D2R/SR of all operators. -- **`5Cxx`**: set D2R/SR of operator 1. -- **`5Dxx`**: set D2R/SR of operator 2. -- **`5Exx`**: set D2R/SR of operator 3. -- **`5Fxx`**: set D2R/SR of operator 4. +- `56xx`: **set DR of all operators.** +- `57xx`: **set DR of operator 1.** +- `58xx`: **set DR of operator 2.** +- `59xx`: **set DR of operator 3.** +- `5Axx`: **set DR of operator 4.** +- `5Bxx`: **set D2R/SR of all operators.** +- `5Cxx`: **set D2R/SR of operator 1.** +- `5Dxx`: **set D2R/SR of operator 2.** +- `5Exx`: **set D2R/SR of operator 3.** +- `5Fxx`: **set D2R/SR of operator 4.** + +# system modes +## extended channel 3 +in ExtCh mode, channel 3 is split into one column for each of its four operators and feedback are shared. the frequency of each operator may be controlled independently with notes and effects. this can be used for more polyphony or more complex sounds. + +all four operators are still combined according to the algorithm in use. for example, algorithm 7 acts as four independent sine waves. algorithm 4 acts as two independent 2op sounds. even with algorithm 0, placing a note in any operator triggers that operator alone. diff --git a/doc/7-systems/ym2608.md b/doc/7-systems/ym2608.md index 5f57e5f6a..16f3cc7e1 100644 --- a/doc/7-systems/ym2608.md +++ b/doc/7-systems/ym2608.md @@ -2,32 +2,32 @@ like YM2203, but with twice the FM channels, stereo, an ADPCM channel and built-in drums ("rhythm")! -it was one of the available sound chips for the NEC PC-88VA and PC-98 series of computers. +it was one of the available sound chips for the NEC PC-88VA and later models of PC-98 series of computers. the YM2610 (OPNB) and YM2610B chips are very similar to this one, but the built-in drums have been replaced with 6 sample channels. # effects -- **`10xy`**: set LFO parameters. +- `10xy`: **set LFO parameters.** - `x` toggles the LFO. - `y` sets its speed. -- **`11xx`**: set feedback of channel. -- **`12xx`**: set operator 1 level. -- **`13xx`**: set operator 2 level. -- **`14xx`**: set operator 3 level. -- **`15xx`**: set operator 4 level. -- **`16xy`**: set multiplier of operator. +- `11xx`: **set feedback of channel.** +- `12xx`: **set operator 1 level.** +- `13xx`: **set operator 2 level.** +- `14xx`: **set operator 3 level.** +- `15xx`: **set operator 4 level.** +- `16xy`: **set multiplier of operator.** - `x` is the operator (1-4). - `y` is the multiplier. -- **`18xx`**: toggle extended channel 3 mode. - - 0 disables it and 1 enables it. +- `18xx`: **toggle extended channel 3 mode.** + - `0` disables it and `1` enables it. - only in extended channel 3 chip. -- **`19xx`**: set attack of all operators. -- **`1Axx`**: set attack of operator 1. -- **`1Bxx`**: set attack of operator 2. -- **`1Cxx`**: set attack of operator 3. -- **`1Dxx`**: set attack of operator 4. -- **`20xx`**: set SSG channel mode. `xx` may be one of the following: +- `19xx`: **set attack of all operators.** +- `1Axx`: **set attack of operator 1.** +- `1Bxx`: **set attack of operator 2.** +- `1Cxx`: **set attack of operator 3.** +- `1Dxx`: **set attack of operator 4.** +- `20xx`: **set SSG channel mode.** - `00`: square - `01`: noise - `02`: square and noise @@ -36,66 +36,72 @@ the YM2610 (OPNB) and YM2610B chips are very similar to this one, but the built- - `05`: envelope and noise - `06`: envelope and square and noise - `07`: nothing -- **`21xx`**: set noise frequency. `xx` is a value between 00 and 1F. -- **`22xy`**: set envelope mode. +- `21xx`: **set noise frequency.** range is `0` to `1F`. +- `22xy`: **set envelope mode.** - `x` sets the envelope shape, which may be one of the following: - - `0: \___` decay - - `4: /___` attack once - - `8: \\\\` saw - - `9: \___` decay - - `A: \/\/` inverse obelisco - - `B: \¯¯¯` decay once - - `C: ////` inverse saw - - `D: /¯¯¯` attack - - `E: /\/\` obelisco - - `F: /___` attack once + - `0`: `\___` decay + - `4`: `/___` attack once + - `8`: `\\\\` saw + - `9`: `\___` decay + - `A`: `\/\/` inverse obelisco + - `B`: `\¯¯¯` decay once + - `C`: `////` inverse saw + - `D`: `/¯¯¯` attack + - `E`: `/\/\` obelisco + - `F`: `/___` attack once - if `y` is 1 then the envelope will affect this channel. -- **`23xx`**: set envelope period low byte. -- **`24xx`**: set envelope period high byte. -- **`25xx`**: slide envelope period up. -- **`26xx`**: slide envelope period down. -- **`29xy`**: enable SSG auto-envelope mode. +- `23xx`: **set envelope period low byte.** +- `24xx`: **set envelope period high byte.** +- `25xx`: **slide envelope period up.** +- `26xx`: **slide envelope period down.** +- `29xy`: **enable SSG auto-envelope mode.** - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. - `x` is the numerator. - `y` is the denominator. - if `x` or `y` are 0 this will disable auto-envelope mode. -- **`30xx`**: enable envelope hard reset. +- `30xx`: **enable envelope hard reset.** - this works by inserting a quick release and tiny delay before a new note. -- **`50xy`**: set AM of operator. +- `50xy`: **set AM of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` determines whether AM is on. -- **`51xy`**: set SL of operator. +- `51xy`: **set SL of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`52xy`**: set RR of operator. +- `52xy`: **set RR of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`53xy`**: set DT of operator. +- `53xy`: **set DT of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value: - - 0: +0 - - 1: +1 - - 2: +2 - - 3: +3 - - 4: -0 - - 5: -3 - - 6: -2 - - 7: -1 -- **`54xy`**: set RS of operator. + - `0`: +0 + - `1`: +1 + - `2`: +2 + - `3`: +3 + - `4`: -0 + - `5`: -3 + - `6`: -2 + - `7`: -1 +- `54xy`: **set RS of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`55xy`**: set SSG-EG of operator. +- `55xy`: **set SSG-EG of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value (0-8). - values between 0 and 7 set SSG-EG. - value 8 disables it. -- **`56xx`**: set DR of all operators. -- **`57xx`**: set DR of operator 1. -- **`58xx`**: set DR of operator 2. -- **`59xx`**: set DR of operator 3. -- **`5Axx`**: set DR of operator 4. -- **`5Bxx`**: set D2R/SR of all operators. -- **`5Cxx`**: set D2R/SR of operator 1. -- **`5Dxx`**: set D2R/SR of operator 2. -- **`5Exx`**: set D2R/SR of operator 3. -- **`5Fxx`**: set D2R/SR of operator 4. +- `56xx`: **set DR of all operators.** +- `57xx`: **set DR of operator 1.** +- `58xx`: **set DR of operator 2.** +- `59xx`: **set DR of operator 3.** +- `5Axx`: **set DR of operator 4.** +- `5Bxx`: **set D2R/SR of all operators.** +- `5Cxx`: **set D2R/SR of operator 1.** +- `5Dxx`: **set D2R/SR of operator 2.** +- `5Exx`: **set D2R/SR of operator 3.** +- `5Fxx`: **set D2R/SR of operator 4.** + +# system modes +## extended channel 3 +in ExtCh mode, channel 3 is split into one column for each of its four operators. feedback and LFO levels are shared. the frequency of each operator may be controlled independently with notes and effects. this can be used for more polyphony or more complex sounds. + +all four operators are still combined according to the algorithm in use. for example, algorithm 7 acts as four independent sine waves. algorithm 4 acts as two independent 2op sounds. even with algorithm 0, placing a note in any operator triggers that operator alone. diff --git a/doc/7-systems/ym2610.md b/doc/7-systems/ym2610.md index 022b82e98..02a630b0e 100644 --- a/doc/7-systems/ym2610.md +++ b/doc/7-systems/ym2610.md @@ -2,73 +2,73 @@ originally an arcade board, but SNK shortly adapted it to a rather expensive video game console with the world's biggest cartridges because some people liked the system so much they wanted a home version of it. -its soundchip is a 4-in-1: 4ch 4-op FM, YM2149 (AY-3-8910 clone) and 2 different format ADPCM in a single package! +its soundchip is a 4-in-1: 4ch 4-op FM, YM2149 (AY-3-8910 clone) and [2 different format ADPCM](https://wiki.neogeodev.org/index.php?title=ADPCM) in a single package! # effects -- **`10xy`**: set LFO parameters. +- `10xy`: **set LFO parameters.** - `x` toggles the LFO. - `y` sets its speed. -- **`11xx`**: set feedback of channel. -- **`12xx`**: set operator 1 level. -- **`13xx`**: set operator 2 level. -- **`14xx`**: set operator 3 level. -- **`15xx`**: set operator 4 level. -- **`16xy`**: set multiplier of operator. +- `11xx`: **set feedback of channel.** +- `12xx`: **set operator 1 level.** +- `13xx`: **set operator 2 level.** +- `14xx`: **set operator 3 level.** +- `15xx`: **set operator 4 level.** +- `16xy`: **set multiplier of operator.** - `x` is the operator (1-4). - `y` is the multiplier. -- **`18xx`**: toggle extended channel 2 mode. +- `18xx`: **toggle extended channel 2 mode.** - 0 disables it and 1 enables it. - only in extended channel 2 chip. -- **`19xx`**: set attack of all operators. -- **`1Axx`**: set attack of operator 1. -- **`1Bxx`**: set attack of operator 2. -- **`1Cxx`**: set attack of operator 3. -- **`1Dxx`**: set attack of operator 4. -- **`20xx`**: set SSG channel mode. `xx` may be one of the following: - - `00`: square - - `01`: noise - - `02`: square and noise - - `03`: nothing (apparently) - - `04`: envelope and square - - `05`: envelope and noise - - `06`: envelope and square and noise - - `07`: nothing -- **`21xx`**: set noise frequency. `xx` is a value between 00 and 1F. -- **`22xy`**: set envelope mode. - - `x` sets the envelope shape, which may be one of the following: - - `0: \___` decay - - `4: /___` attack once - - `8: \\\\` saw - - `9: \___` decay - - `A: \/\/` inverse obelisco - - `B: \¯¯¯` decay once - - `C: ////` inverse saw - - `D: /¯¯¯` attack - - `E: /\/\` obelisco - - `F: /___` attack once +- `19xx`: **set attack of all operators.** +- `1Axx`: **set attack of operator 1.** +- `1Bxx`: **set attack of operator 2.** +- `1Cxx`: **set attack of operator 3.** +- `1Dxx`: **set attack of operator 4.** +- `20xx`: **set SSG channel mode.** + - `0`: square + - `1`: noise + - `2`: square and noise + - `3`: nothing (apparently) + - `4`: envelope and square + - `5`: envelope and noise + - `6`: envelope and square and noise + - `7`: nothing +- `21xx`: **set noise frequency.** range is `00` to `1F`. +- `22xy`: **set envelope mode.** + - `x` sets the envelope shape: + - `0`: `\___` decay + - `4`: `/___` attack once + - `8`: `\\\\` saw + - `9`: `\___` decay + - `A`: `\/\/` inverse obelisco + - `B`: `\¯¯¯` decay once + - `C`: `////` inverse saw + - `D`: `/¯¯¯` attack + - `E`: `/\/\` obelisco + - `F`: `/___` attack once - if `y` is 1 then the envelope will affect this channel. -- **`23xx`**: set envelope period low byte. -- **`24xx`**: set envelope period high byte. -- **`25xx`**: slide envelope period up. -- **`26xx`**: slide envelope period down. -- **`29xy`**: enable SSG auto-envelope mode. +- `23xx`: **set envelope period low byte.** +- `24xx`: **set envelope period high byte.** +- `25xx`: **slide envelope period up.** +- `26xx`: **slide envelope period down.** +- `29xy`: **enable SSG auto-envelope mode.** - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. - `x` is the numerator. - `y` is the denominator. - if `x` or `y` are 0 this will disable auto-envelope mode. -- **`30xx`**: enable envelope hard reset. +- `30xx`: **enable envelope hard reset.** - this works by inserting a quick release and tiny delay before a new note. -- **`50xy`**: set AM of operator. +- `50xy`: **set AM of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` determines whether AM is on. -- **`51xy`**: set SL of operator. +- `51xy`: **set SL of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`52xy`**: set RR of operator. +- `52xy`: **set RR of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`53xy`**: set DT of operator. +- `53xy`: **set DT of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value: - 0: +0 @@ -79,21 +79,27 @@ its soundchip is a 4-in-1: 4ch 4-op FM, YM2149 (AY-3-8910 clone) and 2 different - 5: -3 - 6: -2 - 7: -1 -- **`54xy`**: set RS of operator. +- `54xy`: **set RS of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`55xy`**: set SSG-EG of operator. +- `55xy`: **set SSG-EG of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value (0-8). - values between 0 and 7 set SSG-EG. - value 8 disables it. -- **`56xx`**: set DR of all operators. -- **`57xx`**: set DR of operator 1. -- **`58xx`**: set DR of operator 2. -- **`59xx`**: set DR of operator 3. -- **`5Axx`**: set DR of operator 4. -- **`5Bxx`**: set D2R/SR of all operators. -- **`5Cxx`**: set D2R/SR of operator 1. -- **`5Dxx`**: set D2R/SR of operator 2. -- **`5Exx`**: set D2R/SR of operator 3. -- **`5Fxx`**: set D2R/SR of operator 4. +- `56xx`: **set DR of all operators.** +- `57xx`: **set DR of operator 1.** +- `58xx`: **set DR of operator 2.** +- `59xx`: **set DR of operator 3.** +- `5Axx`: **set DR of operator 4.** +- `5Bxx`: **set D2R/SR of all operators.** +- `5Cxx`: **set D2R/SR of operator 1.** +- `5Dxx`: **set D2R/SR of operator 2.** +- `5Exx`: **set D2R/SR of operator 3.** +- `5Fxx`: **set D2R/SR of operator 4.** + +# system modes +## extended channel 2 +in ExtCh mode, channel 2 is split into one column for each of its four operators. feedback and LFO levels are shared. the frequency of each operator may be controlled independently with notes and effects. this can be used for more polyphony or more complex sounds. + +all four operators are still combined according to the algorithm in use. for example, algorithm 7 acts as four independent sine waves. algorithm 4 acts as two independent 2op sounds. even with algorithm 0, placing a note in any operator triggers that operator alone. diff --git a/doc/7-systems/ym2610b.md b/doc/7-systems/ym2610b.md index feeea9f18..2911c2671 100644 --- a/doc/7-systems/ym2610b.md +++ b/doc/7-systems/ym2610b.md @@ -5,26 +5,26 @@ it is backward compatible with the original chip. # effects -- **`10xy`**: set LFO parameters. +- `10xy`: **set LFO parameters.** - `x` toggles the LFO. - `y` sets its speed. -- **`11xx`**: set feedback of channel. -- **`12xx`**: set operator 1 level. -- **`13xx`**: set operator 2 level. -- **`14xx`**: set operator 3 level. -- **`15xx`**: set operator 4 level. -- **`16xy`**: set multiplier of operator. +- `11xx`: **set feedback of channel.** +- `12xx`: **set operator 1 level.** +- `13xx`: **set operator 2 level.** +- `14xx`: **set operator 3 level.** +- `15xx`: **set operator 4 level.** +- `16xy`: **set multiplier of operator.** - `x` is the operator (1-4). - `y` is the multiplier. -- **`18xx`**: toggle extended channel 3 mode. +- `18xx`: **toggle extended channel 3 mode.** - 0 disables it and 1 enables it. - only in extended channel 3 chip. -- **`19xx`**: set attack of all operators. -- **`1Axx`**: set attack of operator 1. -- **`1Bxx`**: set attack of operator 2. -- **`1Cxx`**: set attack of operator 3. -- **`1Dxx`**: set attack of operator 4. -- **`20xx`**: set SSG channel mode. `xx` may be one of the following: +- `19xx`: **set attack of all operators.** +- `1Axx`: **set attack of operator 1.** +- `1Bxx`: **set attack of operator 2.** +- `1Cxx`: **set attack of operator 3.** +- `1Dxx`: **set attack of operator 4.** +- `20xx`: **set SSG channel mode.** - `00`: square - `01`: noise - `02`: square and noise @@ -33,66 +33,72 @@ it is backward compatible with the original chip. - `05`: envelope and noise - `06`: envelope and square and noise - `07`: nothing -- **`21xx`**: set noise frequency. `xx` is a value between 00 and 1F. -- **`22xy`**: set envelope mode. +- `21xx`: **set noise frequency.** range is `00` to `1F`. +- `22xy`: **set envelope mode.** - `x` sets the envelope shape, which may be one of the following: - - `0: \___` decay - - `4: /___` attack once - - `8: \\\\` saw - - `9: \___` decay - - `A: \/\/` inverse obelisco - - `B: \¯¯¯` decay once - - `C: ////` inverse saw - - `D: /¯¯¯` attack - - `E: /\/\` obelisco - - `F: /___` attack once + - `0`:` \___` decay + - `4`:` /___` attack once + - `8`:` \\\\` saw + - `9`:` \___` decay + - `A`:` \/\/` inverse obelisco + - `B`:` \¯¯¯` decay once + - `C`:` ////` inverse saw + - `D`:` /¯¯¯` attack + - `E`:` /\/\` obelisco + - `F`:` /___` attack once - if `y` is 1 then the envelope will affect this channel. -- **`23xx`**: set envelope period low byte. -- **`24xx`**: set envelope period high byte. -- **`25xx`**: slide envelope period up. -- **`26xx`**: slide envelope period down. -- **`29xy`**: enable SSG auto-envelope mode. +- `23xx`: **set envelope period low byte.** +- `24xx`: **set envelope period high byte.** +- `25xx`: **slide envelope period up.** +- `26xx`: **slide envelope period down.** +- `29xy`: **enable SSG auto-envelope mode.** - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. - `x` is the numerator. - `y` is the denominator. - if `x` or `y` are 0 this will disable auto-envelope mode. -- **`30xx`**: enable envelope hard reset. +- `30xx`: **enable envelope hard reset.** - this works by inserting a quick release and tiny delay before a new note. -- **`50xy`**: set AM of operator. +- `50xy`: **set AM of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` determines whether AM is on. -- **`51xy`**: set SL of operator. +- `51xy`: **set SL of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`52xy`**: set RR of operator. +- `52xy`: **set RR of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`53xy`**: set DT of operator. +- `53xy`: **set DT of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value: - - 0: +0 - - 1: +1 - - 2: +2 - - 3: +3 - - 4: -0 - - 5: -3 - - 6: -2 - - 7: -1 -- **`54xy`**: set RS of operator. + - `0`: +0 + - `1`: +1 + - `2`: +2 + - `3`: +3 + - `4`: -0 + - `5`: -3 + - `6`: -2 + - `7`: -1 +- `54xy`: **set RS of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`55xy`**: set SSG-EG of operator. +- `55xy`: **set SSG-EG of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value (0-8). - values between 0 and 7 set SSG-EG. - value 8 disables it. -- **`56xx`**: set DR of all operators. -- **`57xx`**: set DR of operator 1. -- **`58xx`**: set DR of operator 2. -- **`59xx`**: set DR of operator 3. -- **`5Axx`**: set DR of operator 4. -- **`5Bxx`**: set D2R/SR of all operators. -- **`5Cxx`**: set D2R/SR of operator 1. -- **`5Dxx`**: set D2R/SR of operator 2. -- **`5Exx`**: set D2R/SR of operator 3. -- **`5Fxx`**: set D2R/SR of operator 4. +- `56xx`: **set DR of all operators.** +- `57xx`: **set DR of operator 1.** +- `58xx`: **set DR of operator 2.** +- `59xx`: **set DR of operator 3.** +- `5Axx`: **set DR of operator 4.** +- `5Bxx`: **set D2R/SR of all operators.** +- `5Cxx`: **set D2R/SR of operator 1.** +- `5Dxx`: **set D2R/SR of operator 2.** +- `5Exx`: **set D2R/SR of operator 3.** +- `5Fxx`: **set D2R/SR of operator 4.** + +# system modes +## extended channel 3 +in ExtCh mode, channel 3 is split into one column for each of its four operators. feedback and LFO levels are shared. the frequency of each operator may be controlled independently with notes and effects. this can be used for more polyphony or more complex sounds. + +all four operators are still combined according to the algorithm in use. for example, algorithm 7 acts as four independent sine waves. algorithm 4 acts as two independent 2op sounds. even with algorithm 0, placing a note in any operator triggers that operator alone. diff --git a/doc/7-systems/ym2612.md b/doc/7-systems/ym2612.md index afdb98f36..ddb91b781 100644 --- a/doc/7-systems/ym2612.md +++ b/doc/7-systems/ym2612.md @@ -1,67 +1,68 @@ # Yamaha YM2612 -one of two chips that powered the Sega Genesis. It is a six-channel, four-operator FM synthesizer. Channel #6 can be turned into 8-bit PCM player, that via software mixing, thanks to Z80 sound CPU, can play more than one channel of straight-shot samples at once. As of Furnace 0.6pre5, Furnace offers DualPCM, which allows 2 channels of software-mixed 8-bit PCM samples at 13750 Hz. +one of two chips that powered the Sega Genesis. it is a six-channel, four-operator FM synthesizer. channel #6 can be turned into 8-bit PCM player, that via software mixing, thanks to Z80 sound CPU, can play more than one channel of straight-shot samples at once. +as of Furnace 0.6pre5, Furnace offers DualPCM, a Z80 driver that splits channel 6 into two individual PCM channels with variable pitch. using the console's Z80 processor, these are mixed together in software and streamed to channel 6 in PCM mode with a mix rate of 13750 Hz. VGM export requires the "direct stream mode" option to be enabled, and resulting files will be very large. # effects -- **`10xy`**: set LFO parameters. +- `10xy`: **set LFO parameters.** - `x` toggles the LFO. - `y` sets its speed. -- **`11xx`**: set feedback of channel. -- **`12xx`**: set operator 1 level. -- **`13xx`**: set operator 2 level. -- **`14xx`**: set operator 3 level. -- **`15xx`**: set operator 4 level. -- **`16xy`**: set multiplier of operator. +- `11xx`: **set feedback of channel.** +- `12xx`: **set operator 1 level.** +- `13xx`: **set operator 2 level.** +- `14xx`: **set operator 3 level.** +- `15xx`: **set operator 4 level.** +- `16xy`: **set multiplier of operator.** - `x` is the operator (1-4). - `y` is the multiplier. -- **`17xx`**: enable PCM channel. +- `17xx`: **enable PCM channel.** - this only works on channel 6. -- **`18xx`**: toggle extended channel 3 mode. +- `18xx`: **toggle extended channel 3 mode.** - 0 disables it and 1 enables it. - only in extended channel 3 chip. -- **`19xx`**: set attack of all operators. -- **`1Axx`**: set attack of operator 1. -- **`1Bxx`**: set attack of operator 2. -- **`1Cxx`**: set attack of operator 3. -- **`1Dxx`**: set attack of operator 4. -- **`30xx`**: enable envelope hard reset. +- `19xx`: **set attack of all operators.** +- `1Axx`: **set attack of operator 1.** +- `1Bxx`: **set attack of operator 2.** +- `1Cxx`: **set attack of operator 3.** +- `1Dxx`: **set attack of operator 4.** +- `30xx`: **enable envelope hard reset.** - this works by inserting a quick release and tiny delay before a new note. -- **`50xy`**: set AM of operator. +- `50xy`: **set AM of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` determines whether AM is on. -- **`51xy`**: set SL of operator. +- `51xy`: **set SL of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`52xy`**: set RR of operator. +- `52xy`: **set RR of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`53xy`**: set DT of operator. +- `53xy`: **set DT of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value: - - 0: -3 - - 1: -2 - - 2: -1 - - 3: 0 - - 4: 1 - - 5: 2 - - 6: 3 - - 7: -0 -- **`54xy`**: set RS of operator. + - `0`: -3 + - `1`: -2 + - `2`: -1 + - `3`: 0 + - `4`: 1 + - `5`: 2 + - `6`: 3 + - `7`: -0 +- `54xy`: **set RS of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value. -- **`55xy`**: set SSG-EG of operator. +- `55xy`: **set SSG-EG of operator.** - `x` is the operator (1-4). a value of 0 means "all operators". - `y` is the value (0-8). - values between 0 and 7 set SSG-EG. - value 8 disables it. -- **`56xx`**: set DR of all operators. -- **`57xx`**: set DR of operator 1. -- **`58xx`**: set DR of operator 2. -- **`59xx`**: set DR of operator 3. -- **`5Axx`**: set DR of operator 4. -- **`5Bxx`**: set D2R/SR of all operators. -- **`5Cxx`**: set D2R/SR of operator 1. -- **`5Dxx`**: set D2R/SR of operator 2. -- **`5Exx`**: set D2R/SR of operator 3. -- **`5Fxx`**: set D2R/SR of operator 4. +- `56xx`: **set DR of all operators.** +- `57xx`: **set DR of operator 1.** +- `58xx`: **set DR of operator 2.** +- `59xx`: **set DR of operator 3.** +- `5Axx`: **set DR of operator 4.** +- `5Bxx`: **set D2R/SR of all operators.** +- `5Cxx`: **set D2R/SR of operator 1.** +- `5Dxx`: **set D2R/SR of operator 2.** +- `5Exx`: **set D2R/SR of operator 3.** +- `5Fxx`: **set D2R/SR of operator 4.** diff --git a/doc/7-systems/zxbeep.md b/doc/7-systems/zxbeep.md index 6381814a8..e724e0943 100644 --- a/doc/7-systems/zxbeep.md +++ b/doc/7-systems/zxbeep.md @@ -2,7 +2,10 @@ rather than having a dedicated sound synthesizer, early ZX Spectrum models had one piezo beeper, controlled by Z80 CPU and ULA chip. its capabilities should be on par with an IBM PC speaker... right? -not really - very soon talented programmers found out ways to output much more than one square wave channel. a lot of ZX beeper routines do exist, but as of 0.6 Furnace supports only a Follin/SFX-like engine with 6 channels of narrow pulse wave and click drums. +not really - very soon talented programmers found out ways to output much more than one square wave channel. a lot of ZX beeper routines do exist, but as of 0.6 Furnace supports two engines: + +- a Follin/SFX-like engine with 6 channels of narrow pulse wave and click drums. +- QuadTone: PWM-driven engine with 4ch of pulse wave with freely variable duty cycles and 1-bit PCM drums. # effects diff --git a/doc/8-advanced/README.md b/doc/8-advanced/README.md index b1772007a..16d83afa8 100644 --- a/doc/8-advanced/README.md +++ b/doc/8-advanced/README.md @@ -1,6 +1,12 @@ # advanced -advanced Furnace features, as listed in the `Window` menu. +advanced Furnace features. + +as listed in the "Edit" menu: + +- [find/replace](find-replace.md) + +as listed in the "Window" menu: - [mixer](mixer.md) - [grooves](grooves.md) @@ -10,8 +16,6 @@ advanced Furnace features, as listed in the `Window` menu. - [compatibility flags](compat-flags.md) - [song comments](comments.md) -
- - [piano](piano.md) - [oscilloscope](osc.md) - [oscilloscopes (per-channel)](chanosc.md) diff --git a/doc/8-advanced/chanosc-gradient.png b/doc/8-advanced/chanosc-gradient.png new file mode 100644 index 000000000..26bdb2581 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..77157f1a5 100644 --- a/doc/8-advanced/chanosc.md +++ b/doc/8-advanced/chanosc.md @@ -1,16 +1,54 @@ -# oscilloscope (per channel) +# 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. +- **Amplitude**: scales amplitude for all oscilloscopes. +- **Gradient**: see below. +- the color selector sets the color for all waveforms. 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..2f90435a8 100644 Binary files a/doc/8-advanced/chanosc.png and b/doc/8-advanced/chanosc.png differ diff --git a/doc/8-advanced/find-find.png b/doc/8-advanced/find-find.png new file mode 100644 index 000000000..64f5c0ea3 Binary files /dev/null and b/doc/8-advanced/find-find.png differ diff --git a/doc/8-advanced/find-replace.md b/doc/8-advanced/find-replace.md new file mode 100644 index 000000000..872abb1c3 --- /dev/null +++ b/doc/8-advanced/find-replace.md @@ -0,0 +1,60 @@ +# find/replace + +Furnace has a powerful find-and-replace function that can take the repetitive work out of mass editing. + +# find + +![find dialog](find-find.png) + +all data that can be found within a pattern can be searched for here. + +- a find term contains: + - **Note**: note.\ + **Ins**: instrument.\ + **Volume**: volume.\ + **Effect**: effect type.\ + **Value**: effect value. all of these have the following choices for what data will be found: + - **ignore**: ignore this. + - **equals**: match the given value exactly. + - **not equal**: match everything but the given value. + - **between**: match anything between and including the given values. + - **not between**: match anything outside the given range of values. + - **any**: match all values. + - **none**: match blanks only. + - **-**: remove find term. if only one find term exists, it is cleared. + - **Add effect**: adds another Effect and Value to the term, each set representing additional effects columns. + - **Remove effect**: removes last Effect and Value from the term. +- **+**: adds another find term. +- **Search range**: restricts the find to the whole **Song**, the current **Selection**, or the currently viewed **Pattern**. +- **Confine to channels**: restricts the find to the selected channels and the channels between them. +- **Match effect position**: chooses how the order of effect types and effect values will matter when finding them. + - **No**: no attention is paid to what order the effects appear in. + - **Lax**: matches effects if they appear in the same order as selected above. + - **Strict**: effects may only match in their correponding effects columns. +- **Find**: finds everything that matches the terms and displays it in a list. + - the **order**, **row**, and **channel** columns are as they say. + - the **go** column of buttons will snap the pattern cursor to the location of the find. + +# replace + +![replace dialog](find-replace.png) + +- the replacement term contains: + - **Note**: note.\ + **Ins**: instrument.\ + **Volume**: volume.\ + **Effect**: effect type.\ + **Value**: effect value. all of these have the following choices for how they alter the found data: + - **set**: changes found data to this value. + - **add**: adds this value to the found data. it may be negative for subtraction. notes are calculated in semitones. + - **add (overflow)**: as "add" above, but values will wrap around; for example, adding 13 to `FF` will result in `0C`. + - **scale**: multiply value to this percentage; for example, scaling `1A` by `150` results in `27`. not available for "note". + - **clear**: erases data. + - **Add effect**: adds another Effect and Value to be replaced according to how they were found. + - **Remove effect**: removes last Effect and Value. +- **Effect replace mode**: + - **Replace matches only**: replaces only the effect columns that match. + - **Replace matches, then free spaces**: replaces matched effects; if there are effect columns without data, those will be filled in with the additional effect replacements. + - **Clear effects**: overwrites effect data with replacement effects. + - **Insert in free spaces**: replaces nothing; replacement effects are inserted in free effects columns when available. +- **Replace**: finds everything from the "Find" tab and replaces it as directed. diff --git a/doc/8-advanced/find-replace.png b/doc/8-advanced/find-replace.png new file mode 100644 index 000000000..77f46f6ef Binary files /dev/null and b/doc/8-advanced/find-replace.png differ diff --git a/doc/8-advanced/inputlatch.md b/doc/8-advanced/inputlatch.md new file mode 100644 index 000000000..fd7acf521 --- /dev/null +++ b/doc/8-advanced/inputlatch.md @@ -0,0 +1,11 @@ +# input latch + +![input latch menu item](inputlatch.png) + +input latch determines which data are placed along with a note. as in the pattern view, the columns are note (not changeable), instrument, volume, effect type, and effect value. +- `&&` fills in the currently selected instrument. +- `..` ignores the column. +- all columns (except note) can be reset with a right-click. +- **Set**: sets latch according to the data found at the cursor. +- **Reset**: resets all columns to default (selected instrument, ignore others). +- only the first effect type and effect value may be latched. diff --git a/doc/8-advanced/inputlatch.png b/doc/8-advanced/inputlatch.png new file mode 100644 index 000000000..3100ca4fb Binary files /dev/null and b/doc/8-advanced/inputlatch.png differ diff --git a/doc/8-advanced/opmask.md b/doc/8-advanced/opmask.md new file mode 100644 index 000000000..d0b7240cd --- /dev/null +++ b/doc/8-advanced/opmask.md @@ -0,0 +1,7 @@ +# operation mask + +![operation mask popup](opmask.png) + +the operation mask toggles which columns will be affected by the listed operations. as in the pattern view, the columns are note, instrument, volume, effect types, and effect values. the effect toggles apply to all effect columns. + +click any area to toggle it. a `---` or `--` means the operation will ignore any data in that column. \ No newline at end of file diff --git a/doc/8-advanced/opmask.png b/doc/8-advanced/opmask.png new file mode 100644 index 000000000..fd5c350e7 Binary files /dev/null and b/doc/8-advanced/opmask.png differ diff --git a/extern/igfd/ImGuiFileDialog.cpp b/extern/igfd/ImGuiFileDialog.cpp index 752f41005..2ccc3e9c0 100644 --- a/extern/igfd/ImGuiFileDialog.cpp +++ b/extern/igfd/ImGuiFileDialog.cpp @@ -46,11 +46,11 @@ SOFTWARE. #include #endif // EMSCRIPTEN #ifdef WIN32 - #define stat _stat #define stricmp _stricmp #include #include "dirent/dirent.h" // directly open the dirent file attached to this lib #define PATH_SEP '\\' + #define PATH_SEP_STR "\\" #ifndef PATH_MAX #define PATH_MAX 260 #endif // PATH_MAX @@ -60,6 +60,7 @@ SOFTWARE. #include #include #define PATH_SEP '/' + #define PATH_SEP_STR "/" #endif // defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) #include "imgui.h" @@ -1440,10 +1441,14 @@ namespace IGFD return fileNameExt; } - void IGFD::FileManager::AddFile(const FileDialogInternal& vFileDialogInternal, const std::string& vPath, const std::string& vFileName, const char& vFileType) + void IGFD::FileManager::AddFile(const FileDialogInternal& vFileDialogInternal, const std::string& vPath, const std::string& vFileName, const char& vFileType, void* ent) { auto infos = std::make_shared(); +#ifdef _WIN32 + struct dirent* dent=(struct dirent*)ent; +#endif + infos->filePath = vPath; infos->fileNameExt = vFileName; infos->fileNameExt_optimized = prOptimizeFilenameForSearchOperations(infos->fileNameExt); @@ -1473,6 +1478,24 @@ namespace IGFD } } +#ifdef _WIN32 + SYSTEMTIME systemTime; + SYSTEMTIME localTime; + char timebuf[100]; + + infos->fileSize=dent->dwin_size; + if (FileTimeToSystemTime(&dent->dwin_mtime,&systemTime)==TRUE) { + if (SystemTimeToTzSpecificLocalTime(NULL,&systemTime,&localTime)==TRUE) { + snprintf(timebuf,99,"%d/%.2d/%.2d %.2d:%.2d",localTime.wYear,localTime.wMonth,localTime.wDay,localTime.wHour,localTime.wMinute); + } else { + snprintf(timebuf,99,"%d/%.2d/%.2d %.2d:%.2d",systemTime.wYear,systemTime.wMonth,systemTime.wDay,systemTime.wHour,systemTime.wMinute); + } + infos->fileModifDate=timebuf; + } else { + infos->fileModifDate="???"; + } +#endif + vFileDialogInternal.puFilterManager.prFillFileStyle(infos); prCompleteFileInfos(infos); @@ -1510,9 +1533,9 @@ namespace IGFD for (i = 0; i < n; i++) { struct dirent* ent = files[i]; - std::string where = path + std::string("/") + std::string(ent->d_name); + std::string where = path + std::string(PATH_SEP_STR) + std::string(ent->d_name); char fileType = 0; -#ifdef HAVE_DIRENT_TYPE +#if defined(HAVE_DIRENT_TYPE) || defined(_WIN32) if (ent->d_type != DT_UNKNOWN) { switch (ent->d_type) @@ -1522,6 +1545,9 @@ namespace IGFD case DT_DIR: fileType = 'd'; break; case DT_LNK: +#ifdef _WIN32 + fileType = 'f'; +#else DIR* dirTest = opendir(where.c_str()); if (dirTest == NULL) { @@ -1539,12 +1565,14 @@ namespace IGFD fileType = 'd'; closedir(dirTest); } +#endif break; } } else #endif // HAVE_DIRENT_TYPE { +#ifndef _WIN32 struct stat filestat; if (stat(where.c_str(), &filestat) == 0) { @@ -1557,11 +1585,12 @@ namespace IGFD fileType = 'f'; } } +#endif } auto fileNameExt = ent->d_name; - AddFile(vFileDialogInternal, path, fileNameExt, fileType); + AddFile(vFileDialogInternal, path, fileNameExt, fileType, ent); } for (i = 0; i < n; i++) @@ -1744,6 +1773,12 @@ namespace IGFD //time_t st_mtime; /* time of last modification - not sure out of ntfs */ //time_t st_ctime; /* time of last status change - not sure out of ntfs */ +#ifdef _WIN32 + if (vInfos->fileType != 'd') + { + vInfos->formatedFileSize = prFormatFileSize(vInfos->fileSize); + } +#else std::string fpn; if (vInfos->fileType == 'f' || vInfos->fileType == 'l' || vInfos->fileType == 'd') // file @@ -1778,6 +1813,7 @@ namespace IGFD vInfos->formatedFileSize = prFormatFileSize(vInfos->fileSize); vInfos->fileModifDate="???"; } +#endif } } diff --git a/extern/igfd/ImGuiFileDialog.h b/extern/igfd/ImGuiFileDialog.h index 5ab7f6c84..93db26e9d 100644 --- a/extern/igfd/ImGuiFileDialog.h +++ b/extern/igfd/ImGuiFileDialog.h @@ -883,7 +883,7 @@ namespace IGFD void prRemoveFileNameInSelection(const std::string& vFileName); // selection : remove a file name void prAddFileNameInSelection(const std::string& vFileName, bool vSetLastSelectionFileName); // selection : add a file name void AddFile(const FileDialogInternal& vFileDialogInternal, - const std::string& vPath, const std::string& vFileName, const char& vFileType); // add file called by scandir + const std::string& vPath, const std::string& vFileName, const char& vFileType, void* ent); // add file called by scandir public: FileManager(); diff --git a/extern/igfd/ImGuiFileDialogConfig.h b/extern/igfd/ImGuiFileDialogConfig.h index 4a212b600..8f06588a8 100644 --- a/extern/igfd/ImGuiFileDialogConfig.h +++ b/extern/igfd/ImGuiFileDialogConfig.h @@ -36,8 +36,8 @@ //#define IGFD_KEY_BACKSPACE GLFW_KEY_BACKSPACE // by ex you can quit the dialog by pressing the key excape -//#define USE_DIALOG_EXIT_WITH_KEY -//#define IGFD_EXIT_KEY GLFW_KEY_ESCAPE +#define USE_DIALOG_EXIT_WITH_KEY +#define IGFD_EXIT_KEY ImGuiKey_Escape // widget // filter combobox width diff --git a/extern/igfd/dirent/dirent.h b/extern/igfd/dirent/dirent.h index 0549aa27e..7d5c8abec 100644 --- a/extern/igfd/dirent/dirent.h +++ b/extern/igfd/dirent/dirent.h @@ -237,6 +237,10 @@ struct _wdirent { /* File name */ wchar_t d_name[PATH_MAX+1]; + + /* Windows extensions */ + size_t dwin_size; + FILETIME dwin_mtime; }; typedef struct _wdirent _wdirent; @@ -277,6 +281,10 @@ struct dirent { /* File name */ char d_name[PATH_MAX+1]; + + /* Windows extensions */ + size_t dwin_size; + FILETIME dwin_mtime; }; typedef struct dirent dirent; @@ -516,6 +524,17 @@ _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 */ *result = entry; @@ -806,6 +825,17 @@ 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 { /* @@ -821,6 +851,9 @@ readdir_r( entry->d_ino = 0; entry->d_off = -1; entry->d_reclen = 0; + entry->dwin_size = 0; + entry->dwin_mtime.dwHighDateTime = 0; + entry->dwin_mtime.dwLowDateTime = 0; } 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/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.cpp index bf056b816..59d8af8f7 100644 --- a/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.cpp +++ b/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.cpp @@ -8,18 +8,19 @@ #include "k053260.hpp" -void k053260_core::tick() +void k053260_core::tick(u32 cycle) { m_out[0] = m_out[1] = 0; if (m_ctrl.sound_en()) { for (int i = 0; i < 4; i++) { - m_voice[i].tick(); + m_voice[i].tick(cycle); m_out[0] += m_voice[i].out(0); m_out[1] += m_voice[i].out(1); } } + /* // dac clock (YM3012 format) u8 dac_clock = m_dac.clock(); if (bitfield(++dac_clock, 0, 4) == 0) @@ -34,62 +35,58 @@ void k053260_core::tick() m_dac.set_state(bitfield(dac_state, 0, 2)); } m_dac.set_clock(bitfield(dac_clock, 0, 4)); + */ } -void k053260_core::voice_t::tick() +void k053260_core::voice_t::tick(u32 cycle) { if (m_enable && m_busy) { - bool update = false; // update counter - if (bitfield(++m_counter, 0, 12) == 0) + m_counter += cycle; + if (m_counter >= 0x1000) { if (m_bitpos < 8) { m_bitpos += 8; - m_addr = bitfield(m_addr + 1, 0, 21); + m_addr = m_reverse ? bitfield(m_addr - 1, 0, 21) : bitfield(m_addr + 1, 0, 21); m_remain--; + if (m_remain < 0) // check end flag + { + if (m_loop) + { + m_addr = m_start; + m_remain = m_length; + m_output = 0; + } + else + { + m_busy = false; + } + } } + m_data = m_host.m_intf.read_sample(bitfield(m_addr, 0, 21)); // fetch ROM if (m_adpcm) { - m_bitpos -= 4; - update = true; + m_bitpos -= 4; + const u8 nibble = bitfield(m_data, m_reverse ? (~m_bitpos & 4) : (m_bitpos & 4), 4); // get nibble from ROM + if (nibble) + { + m_output += m_host.adpcm_lut(nibble); + } } else { m_bitpos -= 8; } - m_counter = bitfield(m_pitch, 0, 12); - } - m_data = m_host.m_intf.read_sample(bitfield(m_addr, 0, 21)); // fetch ROM - if (update) - { - const u8 nibble = bitfield(m_data, m_bitpos & 4, 4); // get nibble from ROM - if (nibble) - { - m_adpcm_buf += bitfield(nibble, 3) ? s8(0x80 >> bitfield(nibble, 0, 3)) - : (1 << bitfield(nibble - 1, 0, 3)); - } + m_counter = (m_counter - 0x1000) + bitfield(m_pitch, 0, 12); } - if (m_remain < 0) // check end flag - { - if (m_loop) - { - m_addr = m_start; - m_remain = m_length; - m_adpcm_buf = 0; - } - else - { - m_busy = false; - } - } // calculate output - s32 output = m_adpcm ? m_adpcm_buf : sign_ext(m_data, 8) * s32(m_volume); + s32 output = (m_adpcm ? m_output : sign_ext(m_data, 8)) * s32(m_volume); // use math for now; actually fomula unknown - m_out[0] = (m_pan >= 0) ? s32(output * cos(f64(m_pan) * PI / 180)) : 0; - m_out[1] = (m_pan >= 0) ? s32(output * sin(f64(m_pan) * PI / 180)) : 0; + m_out[0] = (output * m_host.pan_lut(m_pan, 0)) >> 7; + m_out[1] = (output * m_host.pan_lut(m_pan, 1)) >> 7; } else { @@ -172,6 +169,7 @@ void k053260_core::write(u8 address, u8 data) case 0x28: // keyon/off toggle for (int i = 0; i < 4; i++) { + m_voice[i].set_reverse(bitfield(data, 4 + i)); if (bitfield(data, i) && (!m_voice[i].enable())) { // rising edge (keyon) m_voice[i].keyon(); @@ -244,8 +242,9 @@ void k053260_core::voice_t::keyon() m_addr = m_start; m_remain = m_length; m_bitpos = 4; - m_adpcm_buf = 0; - std::fill(m_out.begin(), m_out.end(), 0); + m_data = 0; + m_output = 0; + std::fill_n(m_out, 2, 0); } // key off trigger @@ -259,34 +258,35 @@ void k053260_core::reset() elem.reset(); } - m_intf.write_int(0); + //m_intf.write_int(0); - std::fill(m_host2snd.begin(), m_host2snd.end(), 0); - std::fill(m_snd2host.begin(), m_snd2host.end(), 0); + std::fill_n(m_host2snd, 2, 0); + std::fill_n(m_snd2host, 2, 0); m_ctrl.reset(); - m_dac.reset(); + //m_dac.reset(); - std::fill(m_reg.begin(), m_reg.end(), 0); - std::fill(m_out.begin(), m_out.end(), 0); + std::fill_n(m_reg, 64, 0); + std::fill_n(m_out, 2, 0); } // reset voice void k053260_core::voice_t::reset() { - m_enable = 0; - m_busy = 0; - m_loop = 0; - m_adpcm = 0; - m_pitch = 0; - m_start = 0; - m_length = 0; - m_volume = 0; - m_pan = -1; - m_counter = 0; - m_addr = 0; - m_remain = 0; - m_bitpos = 4; - m_data = 0; - m_adpcm_buf = 0; + m_enable = 0; + m_busy = 0; + m_loop = 0; + m_adpcm = 0; + m_pitch = 0; + m_reverse = 0; + m_start = 0; + m_length = 0; + m_volume = 0; + m_pan = 4; + m_counter = 0; + m_addr = 0; + m_remain = 0; + m_bitpos = 4; + m_data = 0; + m_output = 0; m_out[0] = m_out[1] = 0; } diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.hpp index a8668a0df..9eb81363d 100644 --- a/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.hpp +++ b/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.hpp @@ -25,7 +25,7 @@ class k053260_intf : public vgsound_emu_core virtual u8 read_sample(u32 address) { return 0; } // sample fetch - virtual void write_int(u8 out) {} // timer interrupt + //virtual void write_int(u8 out) {} // timer interrupt }; class k053260_core : public vgsound_emu_core @@ -33,7 +33,19 @@ class k053260_core : public vgsound_emu_core friend class k053260_intf; // k053260 specific interface private: - const int pan_dir[8] = {-1, 0, 24, 35, 45, 55, 66, 90}; // pan direction + const s32 m_pan_lut[8][2] = { + {0x00, 0x00}, + {0x7f, 0x00}, + {0x74, 0x34}, + {0x68, 0x49}, + {0x5a, 0x5a}, + {0x49, 0x68}, + {0x34, 0x74}, + {0x00, 0x7f} + }; // pan LUT + + const s8 m_adpcm_lut[16] = + {0, 1, 2, 4, 8, 16, 32, 64, -128, -64, -32, -16, -8, -4, -2, -1}; // ADPCM LUT class voice_t : public vgsound_emu_core { @@ -47,23 +59,24 @@ class k053260_core : public vgsound_emu_core , m_loop(0) , m_adpcm(0) , m_pitch(0) + , m_reverse(0) , m_start(0) , m_length(0) , m_volume(0) - , m_pan(-1) + , m_pan(4) , m_counter(0) , m_addr(0) , m_remain(0) , m_bitpos(4) , m_data(0) - , m_adpcm_buf(0) + , m_output(0) { - m_out.fill(0); + std::fill_n(m_out, 2, 0); } // internal state void reset(); - void tick(); + void tick(u32 cycle); // accessors void write(u8 address, u8 data); @@ -79,9 +92,14 @@ class k053260_core : public vgsound_emu_core inline void set_adpcm(bool adpcm) { m_adpcm = adpcm ? 1 : 0; } + inline void set_reverse(const bool reverse) + { + m_reverse = reverse ? 1 : 0; + } + inline void length_inc() { m_length = (m_length + 1) & 0xffff; } - inline void set_pan(u8 pan) { m_pan = m_host.pan_dir[pan & 7]; } + inline void set_pan(u8 pan) { m_pan = pan & 7; } // getters inline bool enable() { return m_enable; } @@ -97,22 +115,23 @@ class k053260_core : public vgsound_emu_core private: // registers k053260_core &m_host; - u16 m_enable : 1; // enable flag - u16 m_busy : 1; // busy status - u16 m_loop : 1; // loop flag - u16 m_adpcm : 1; // ADPCM flag - u16 m_pitch : 12; // pitch - u32 m_start = 0; // start position - u16 m_length = 0; // source length - u8 m_volume = 0; // master volume - int m_pan = -1; // master pan - u16 m_counter = 0; // frequency counter - u32 m_addr = 0; // current address - s32 m_remain = 0; // remain for end sample - u8 m_bitpos = 4; // bit position for ADPCM decoding - u8 m_data = 0; // current data - s8 m_adpcm_buf = 0; // ADPCM buffer - std::array m_out; // current output + u16 m_enable : 1; // enable flag + u16 m_busy : 1; // busy status + u16 m_loop : 1; // loop flag + u16 m_adpcm : 1; // ADPCM flag + u16 m_pitch : 12; // pitch + u8 m_reverse : 1; // reverse playback + u32 m_start = 0; // start position + u16 m_length = 0; // source length + u8 m_volume = 0; // master volume + s32 m_pan = 4; // master pan + u16 m_counter = 0; // frequency counter + u32 m_addr = 0; // current address + s32 m_remain = 0; // remain for end sample + u8 m_bitpos = 4; // bit position for ADPCM decoding + u8 m_data = 0; // current data + s8 m_output = 0; // ADPCM buffer + s32 m_out[2]; // current output }; class ctrl_t @@ -152,6 +171,7 @@ class k053260_core : public vgsound_emu_core u8 m_input_en : 2; // Input enable }; + /* class ym3012_t { public: @@ -177,7 +197,9 @@ class k053260_core : public vgsound_emu_core std::array m_in; std::array m_out; }; + */ + /* class dac_t { public: @@ -205,6 +227,7 @@ class k053260_core : public vgsound_emu_core u8 m_clock : 4; // DAC clock (16 clock) u8 m_state : 2; // DAC state (4 state - SAM1, SAM2) }; + */ public: // constructor @@ -213,13 +236,13 @@ class k053260_core : public vgsound_emu_core , m_voice{*this, *this, *this, *this} , m_intf(intf) , m_ctrl(ctrl_t()) - , m_ym3012(ym3012_t()) - , m_dac(dac_t()) + //, m_ym3012(ym3012_t()) + //, m_dac(dac_t()) { - m_host2snd.fill(0); - m_snd2host.fill(0); - m_reg.fill(0); - m_out.fill(0); + std::fill_n(m_host2snd, 2, 0); + std::fill_n(m_snd2host, 2, 0); + std::fill_n(m_reg, 64, 0); + std::fill_n(m_out, 2, 0); } // communications @@ -233,7 +256,7 @@ class k053260_core : public vgsound_emu_core // internal state void reset(); - void tick(); + void tick(u32 cycle); // getters for debug, trackers, etc inline s32 output(u8 ch) { return m_out[ch & 1]; } // output for each channels @@ -245,20 +268,25 @@ class k053260_core : public vgsound_emu_core return (voice < 4) ? m_voice[voice].out(ch & 1) : 0; } + protected: + inline s32 pan_lut(const u8 pan, const u8 out) { return m_pan_lut[pan][out]; } + + inline s32 adpcm_lut(const u8 nibble) { return m_adpcm_lut[nibble]; } + private: - std::array m_voice; + voice_t m_voice[4]; k053260_intf &m_intf; // common memory interface - std::array m_host2snd; - std::array m_snd2host; + u8 m_host2snd[2]; + u8 m_snd2host[2]; ctrl_t m_ctrl; // chip control - ym3012_t m_ym3012; // YM3012 output - dac_t m_dac; // YM3012 interface + //ym3012_t m_ym3012; // YM3012 output + //dac_t m_dac; // YM3012 interface - std::array m_reg; // register pool - std::array m_out; // stereo output + u8 m_reg[64]; // register pool + s32 m_out[2]; // stereo output }; #endif diff --git a/instruments/FM/drums/OPM Power Snare.fui b/instruments/FM/drums/OPM Power Snare.fui new file mode 100644 index 000000000..588465823 Binary files /dev/null and b/instruments/FM/drums/OPM Power Snare.fui differ diff --git a/instruments/other/AY Heavy Kick and Snare.fui b/instruments/other/AY Heavy Kick and Snare.fui new file mode 100644 index 000000000..2bcb7d954 Binary files /dev/null and b/instruments/other/AY Heavy Kick and Snare.fui differ diff --git a/instruments/other/AY Heavy Kick.fui b/instruments/other/AY Heavy Kick.fui new file mode 100644 index 000000000..3f3de6fca Binary files /dev/null and b/instruments/other/AY Heavy Kick.fui differ 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/papers/zsm-format.md b/papers/zsm-format.md index 739281254..e0fd2c02d 100644 --- a/papers/zsm-format.md +++ b/papers/zsm-format.md @@ -2,9 +2,11 @@ #### Zsound Repo -ZSM is part of the Zsound suite of Commander X16 audio tools found at:
+ZSM is part of the Zsound suite of Commander X16 audio tools found at: https://github.com/ZeroByteOrg/zsound/ +An alternative library with PCM support, ZSMKit, is avalable at: +https://github.com/mooinglemur/ZSMKit #### Current ZSM Revision: 1 @@ -69,14 +71,35 @@ The EXTCMD byte is formatted as `ccnnnnnn` where `c`=channel and `n`=number of b ### PCM Header -The size and contents of the PCM header table is not yet decided. This will depend largely on the strucure of EXTCMD channel 0, and be covered in detail in that specification. +If the PCM header exists in the ZSM file, it will immediately follow the 0x80 end-of-data marker. The PCM header exists only if at least one PCM instrument exists. -Any offset values contained in the PCM data header block will be relative to the beginning of the PCM header, not the ZSM header. The intention is to present the digital audio portion as a set of digi clips ("samples" in tracker terminology) whose playback can be triggered by EXTCMD channel zero. +Since each instrument defined is 16 bytes, the size of the PCM header can be calculated +as 4+(16*(last_instrument_index+1)). + +Offset|Type|Value +--|--|-- +0x00-0x02|String|"PCM" +0x03|Byte|The last PCM instrument index +0x04-0x13|Mixed|Instrument definition for instrument 0x00 +0x14-0x23|Mixed|(optional) Instrument definition for instrument 0x01 +... + +### Instrument definition +Offset|Type|Value +--|--|-- +0x00|Byte|This instrument's index +0x01|Bitmask|AUDIO_CTRL: 00**DC**0000: D is set for 16-bit, and clear for 8-bit. C is set for stereo and clear for mono +0x02-0x04|24-bit int|Little-endian offset into the PCM data block +0x05-0x07|24-bit int|Little-endian length of PCM data +0x08|Bitmask|Features: **L**xxxxxxx: L is set if the sample is looped +0x09-0x0b|24-bit int|Little-endian loop point offset (relative, 0 is the beginning of this instrument's sample) +0x0c-0x0f|...|Reserved for expansion + +Any offset values contained in the PCM data header block are relative to the beginning of the PCM sample data section, not to the PCM header or ZSM header. The intention is to present the digital audio portion as a set of digi clips ("samples" in tracker terminology) whose playback can be triggered by EXTCMD channel zero. ### PCM Sample Data -This will be a blob of PCM data with no internal formatting. Indeces / format information / loop points / etc regarding this blob will be provided via the PCM header. The end of this blob will be the end of the ZSM file. - +This is a blob of PCM data with no internal formatting. Offsets into this blob are provided via the PCM header. The end of this blob will be the end of the ZSM file. ## EXTCMD Channel Scifications @@ -108,7 +131,13 @@ The Custom channel data may take whatever format is desired for any particular p ### EXTCMD Channel: #### 0: PCM audio -The structure of data within this channel is not yet defined. +This EXTCMD stream can contain one or more command + argument pairs. + +command|meaning|argument|description +---|---|---|--- +0x00|AUDIO_CTRL byte|byte|This byte sets PCM channel volume and/or clears the FIFO +0x01|AUDIO_RATE byte|byte|A value from 0x00-0x80 to set the sample rate (playback speed) +0x02|Instrument trigger|byte|Triggers the PCM instrument specified by this byte index #### 1: Expansion Sound Devices @@ -120,7 +149,7 @@ Players implementing this channel should implement detection routines during ini An expansion HW write will contain the following data: -Chip ID|Nuber of writes (`N`)| `N` tuples of data +Chip ID|Number of writes (`N`)| `N` tuples of data --|--|-- one byte|one byte|N * tuple_size bytes @@ -133,7 +162,14 @@ There are currently no supported expansion HW IDs assigned. The purpose of this channel is to provide for music synchronization cues that applications may use to perform operations in sync with the music (such as when the Goombas jump in New Super Mario Bros in time with the BOP! BOP! notes in the music). It is intended for the reference player to provide a sync channel callback, passing the data bytes to the callback function, and then to proceed with playback. -The data structure within this channel is not yet defined. It is our intention to work with the community in order to collaborate on a useful structure. +The synchronization format currently defines this one event type: + +Event Type|Description|Message Format +--|--|-- +`0x00`|Generic sync message|`xx` (any value from `0x00`-`0xff`) + +An example of an EXTCMD containing one sync event might look as follows: `0x40 0x82 0x00 0x05` + #### 3: Custom 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 c19e7d955..767db912d 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -236,6 +236,8 @@ enum DivDispatchCmds { DIV_CMD_NES_LINEAR_LENGTH, + DIV_CMD_EXTERNAL, // (value) + DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol DIV_CMD_MAX @@ -286,10 +288,15 @@ 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; unsigned int val; + DivRegWrite(): + addr(0), val(0) {} DivRegWrite(unsigned int a, unsigned int v): addr(a), val(v) {} }; diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index c30d7f231..56c3625d7 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -78,6 +78,7 @@ #include "platform/ga20.h" #include "platform/sm8521.h" #include "platform/pv1000.h" +#include "platform/k053260.h" #include "platform/pcmdac.h" #include "platform/dummy.h" #include "../ta-log.h" @@ -172,6 +173,7 @@ void DivDispatchContainer::fillBuf(size_t runtotal, size_t offset, size_t size) if (bbIn[i]==NULL) continue; if (bb[i]==NULL) continue; for (size_t j=0; jsetFP(eng->getConfInt("c64Core",1)==1); + ((DivPlatformC64*)dispatch)->setCore(eng->getConfInt("c64Core",0)); ((DivPlatformC64*)dispatch)->setChipModel(true); break; case DIV_SYSTEM_C64_8580: dispatch=new DivPlatformC64; - ((DivPlatformC64*)dispatch)->setFP(eng->getConfInt("c64Core",1)==1); + ((DivPlatformC64*)dispatch)->setCore(eng->getConfInt("c64Core",0)); ((DivPlatformC64*)dispatch)->setChipModel(false); break; case DIV_SYSTEM_YM2151: @@ -501,6 +504,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_PV1000: dispatch=new DivPlatformPV1000; break; + case DIV_SYSTEM_K053260: + dispatch=new DivPlatformK053260; + break; case DIV_SYSTEM_PCM_DAC: dispatch=new DivPlatformPCMDAC; break; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 5afc54078..c32b53112 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1678,6 +1678,51 @@ int DivEngine::addSubSong() { return song.subsong.size()-1; } +int DivEngine::duplicateSubSong(int index) { + if (song.subsong.size()>=127) return -1; + BUSY_BEGIN; + saveLock.lock(); + DivSubSong* theCopy=new DivSubSong; + DivSubSong* theOrig=song.subsong[index]; + + theCopy->name=theOrig->name; + theCopy->notes=theOrig->notes; + theCopy->hilightA=theOrig->hilightA; + theCopy->hilightB=theOrig->hilightB; + theCopy->timeBase=theOrig->timeBase; + theCopy->arpLen=theOrig->arpLen; + theCopy->speeds=theOrig->speeds; + theCopy->virtualTempoN=theOrig->virtualTempoN; + theCopy->virtualTempoD=theOrig->virtualTempoD; + theCopy->hz=theOrig->hz; + theCopy->patLen=theOrig->patLen; + theCopy->ordersLen=theOrig->ordersLen; + theCopy->orders=theOrig->orders; + + memcpy(theCopy->chanShow,theOrig->chanShow,DIV_MAX_CHANS*sizeof(bool)); + memcpy(theCopy->chanCollapse,theOrig->chanCollapse,DIV_MAX_CHANS); + + for (int i=0; ichanName[i]=theOrig->chanName[i]; + theCopy->chanShortName[i]=theOrig->chanShortName[i]; + + theCopy->pat[i].effectCols=theOrig->pat[i].effectCols; + + for (int j=0; jpat[i].data[j]==NULL) continue; + DivPattern* origPat=theOrig->pat[i].getPattern(j,false); + DivPattern* copyPat=theCopy->pat[i].getPattern(j,true); + origPat->copyOn(copyPat); + } + } + + song.subsong.push_back(theCopy); + + saveLock.unlock(); + BUSY_END; + return song.subsong.size()-1; +} + bool DivEngine::removeSubSong(int index) { if (song.subsong.size()<=1) return false; stop(); @@ -2553,6 +2598,8 @@ void DivEngine::stepOne(int row) { } stepPlay=2; ticks=1; + prevOrder=curOrder; + prevRow=curRow; BUSY_END; } @@ -4275,9 +4322,7 @@ void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) { void DivEngine::autoNoteOff(int ch, int note, int vol) { if (!playing) { - reset(); - freelance=true; - playing=true; + return; } //if (ch<0 || ch>=chans) return; for (int i=0; i2.0f) metroVol=2.0f; diff --git a/src/engine/engine.h b/src/engine/engine.h index 85e8f6415..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 "dev159" -#define DIV_ENGINE_VERSION 159 +#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(); @@ -1052,6 +1055,9 @@ class DivEngine { // add subsong int addSubSong(); + // duplicate subsong + int duplicateSubSong(int index); + // remove subsong bool removeSubSong(int index); @@ -1249,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/fileOps.cpp b/src/engine/fileOps.cpp index ed0c0ce95..77f3f3dc5 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1047,6 +1047,11 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.systemFlags[0].set("dpcmMode",false); } + // C64 no key priority + if (ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) { + ds.systemFlags[0].set("keyPriority",false); + } + ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0)); if (active) quitDispatch(); @@ -2927,6 +2932,15 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } } + // C64 key priority compat + if (ds.version<160) { + for (int i=0; i +#include "../ta-log.h" + +template struct FixedQueue { + size_t readPos, writePos; + T data[items]; + + T& front(); + T& back(); + bool pop(); + bool push(const T& item); + + bool pop_front(); + bool pop_back(); + bool push_front(const T& item); + bool push_back(const T& item); + void clear(); + bool empty(); + size_t size(); + FixedQueue(): + readPos(0), + writePos(0) {} +}; + +template T& FixedQueue::front() { + return data[readPos]; +} + +template T& FixedQueue::back() { + if (writePos==0) return data[items-1]; + return data[writePos-1]; +} + +template bool FixedQueue::pop() { + if (readPos==writePos) return false; + if (++readPos>=items) readPos=0; + return true; +} + +template bool FixedQueue::push(const T& item) { + if (writePos==(readPos-1)) { + logW("queue overflow!"); + return false; + } + if (writePos==items-1 && readPos==0) { + logW("queue overflow!"); + return false; + } + data[writePos]=item; + if (++writePos>=items) writePos=0; + return true; +} + +template bool FixedQueue::pop_front() { + if (readPos==writePos) return false; + if (++readPos>=items) readPos=0; + return true; +} + +template bool FixedQueue::push_back(const T& item) { + if (writePos==(readPos-1)) { + logW("queue overflow!"); + return false; + } + if (writePos==items-1 && readPos==0) { + logW("queue overflow!"); + return false; + } + data[writePos]=item; + if (++writePos>=items) writePos=0; + return true; +} + +template bool FixedQueue::pop_back() { + if (readPos==writePos) return false; + if (writePos>0) { + writePos--; + } else { + writePos=items-1; + } + return true; +} + +template bool FixedQueue::push_front(const T& item) { + if (readPos==(writePos+1)) { + logW("stack overflow!"); + return false; + } + if (readPos==0 && writePos==items-1) { + logW("stack overflow!"); + return false; + } + if (readPos>0) { + readPos--; + } else { + readPos=items-1; + } + data[readPos]=item; + return true; +} + +template void FixedQueue::clear() { + readPos=0; + writePos=0; +} + +template bool FixedQueue::empty() { + return (readPos==writePos); +} + +template size_t FixedQueue::size() { + if (readPos>writePos) { + return items+writePos-readPos; + } + return writePos-readPos; +} + +#endif diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 97fd65cc1..76c410f65 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -929,6 +929,10 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song) { break; case DIV_INS_PV1000: break; + case DIV_INS_K053260: + featureSM=true; + featureSL=true; + break; case DIV_INS_MAX: break; diff --git a/src/engine/instrument.h b/src/engine/instrument.h index c2e4a7313..ab2bc5e05 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -80,6 +80,7 @@ enum DivInstrumentType: unsigned short { DIV_INS_POKEMINI=47, DIV_INS_SM8521=48, DIV_INS_PV1000=49, + DIV_INS_K053260=50, DIV_INS_MAX, DIV_INS_NULL }; diff --git a/src/engine/platform/amiga.h b/src/engine/platform/amiga.h index 1c793296f..a05a5994d 100644 --- a/src/engine/platform/amiga.h +++ b/src/engine/platform/amiga.h @@ -21,7 +21,6 @@ #define _AMIGA_H #include "../dispatch.h" -#include #include "../waveSynth.h" class DivPlatformAmiga: public DivDispatch { diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 0e38bf960..20110b9db 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -878,7 +878,7 @@ void DivPlatformArcade::poke(std::vector& wlist) { } void DivPlatformArcade::reset() { - while (!writes.empty()) writes.pop_front(); + writes.clear(); memset(regPool,0,256); if (useYMFM) { fm_ymfm->reset(); diff --git a/src/engine/platform/arcade.h b/src/engine/platform/arcade.h index edcdd8d1c..b5720f197 100644 --- a/src/engine/platform/arcade.h +++ b/src/engine/platform/arcade.h @@ -20,7 +20,6 @@ #ifndef _ARCADE_H #define _ARCADE_H #include "fmshared_OPM.h" -#include #include "../../../extern/opm/opm.h" #include "sound/ymfm/ymfm_opm.h" diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index c32a4ee25..5d9a55932 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -25,7 +25,7 @@ #include #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} -#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(regRemap(a),v); if (dumpWrites) {addWrite(regRemap(a),v);} } +#define immWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(regRemap(a),v)); if (dumpWrites) {addWrite(regRemap(a),v);} } #define CHIP_DIVIDER (extMode?extDiv:((sunsoft||clockSel)?16:8)) diff --git a/src/engine/platform/ay.h b/src/engine/platform/ay.h index 04e3aed12..999db1e57 100644 --- a/src/engine/platform/ay.h +++ b/src/engine/platform/ay.h @@ -20,7 +20,7 @@ #ifndef _AY_H #define _AY_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" #include "sound/ay8910.h" class DivPlatformAY8910: public DivDispatch { @@ -89,9 +89,10 @@ class DivPlatformAY8910: public DivDispatch { unsigned short addr; unsigned char val; bool addrOrVal; + QueuedWrite(): addr(0), val(0), addrOrVal(false) {} QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} }; - std::queue writes; + FixedQueue writes; ay8910_device* ay; DivDispatchOscBuffer* oscBuf[3]; unsigned char regPool[16]; diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 8561548d0..10421942a 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -25,7 +25,7 @@ #include #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} -#define immWrite2(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define immWrite2(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } #define CHIP_DIVIDER (clockSel?8:4) diff --git a/src/engine/platform/ay8930.h b/src/engine/platform/ay8930.h index 3b47cf0d1..113aed917 100644 --- a/src/engine/platform/ay8930.h +++ b/src/engine/platform/ay8930.h @@ -20,7 +20,7 @@ #ifndef _AY8930_H #define _AY8930_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" #include "sound/ay8910.h" class DivPlatformAY8930: public DivDispatch { @@ -99,9 +99,10 @@ class DivPlatformAY8930: public DivDispatch { unsigned short addr; unsigned char val; bool addrOrVal; + QueuedWrite(): addr(0), val(0), addrOrVal(false) {} QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} }; - std::queue writes; + FixedQueue writes; ay8930_device* ay; DivDispatchOscBuffer* oscBuf[3]; unsigned char regPool[32]; diff --git a/src/engine/platform/bubsyswsg.h b/src/engine/platform/bubsyswsg.h index 784396bfd..c3891bf69 100644 --- a/src/engine/platform/bubsyswsg.h +++ b/src/engine/platform/bubsyswsg.h @@ -21,7 +21,6 @@ #define _K005289_H #include "../dispatch.h" -#include #include "../waveSynth.h" #include "vgsound_emu/src/k005289/k005289.hpp" diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 977e39511..6ca85ffe0 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -21,8 +21,9 @@ #include "../engine.h" #include "sound/c64_fp/siddefs-fp.h" #include +#include "../../ta-log.h" -#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } #define CHIP_FREQBASE 524288 @@ -63,35 +64,81 @@ 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=isFP?0:sid.get_dc(0); + int dcOff=(sidCore)?0:sid->get_dc(0); for (size_t i=0; iwrite(w.addr,w.val); } else { - sid.write(w.addr,w.val); - }; + sid->write(w.addr,w.val); + } regPool[w.addr&0x1f]=w.val; writes.pop(); } - if (isFP) { - sid_fp.clock(4,&buf[0][i]); + if (sidCore==2) { + double o=dSID_render(sid_d); + buf[0][i]=32767*CLAMP(o,-1.0,1.0); 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++]=sid_d->lastOut[0]; + oscBuf[1]->data[oscBuf[1]->needle++]=sid_d->lastOut[1]; + oscBuf[2]->data[oscBuf[2]->needle++]=sid_d->lastOut[2]; + } + } else if (sidCore==1) { + sid_fp->clock(4,&buf[0][i]); + if (++writeOscBuf>=4) { + writeOscBuf=0; + 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(); + 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); } } } @@ -106,7 +153,9 @@ void DivPlatformC64::updateFilter() { void DivPlatformC64::tick(bool sysTick) { bool willUpdateFilter=false; - for (int i=0; i<3; i++) { + for (int _i=0; _i<3; _i++) { + int i=chanOrder[_i]; + chan[i].std.next(); if (chan[i].std.vol.had) { DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_C64); @@ -179,8 +228,8 @@ void DivPlatformC64::tick(bool sysTick) { if (--chan[i].testWhen<1) { if (!chan[i].resetMask && !chan[i].inPorta) { DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_C64); - rWrite(i*7+5,0); - rWrite(i*7+6,0); + rWrite(i*7+5,testAD); + rWrite(i*7+6,testSR); rWrite(i*7+4,(chan[i].wave<<4)|(ins->c64.noTest?0:8)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1)); } } @@ -212,6 +261,7 @@ void DivPlatformC64::tick(bool sysTick) { } int DivPlatformC64::dispatch(DivCommand c) { + if (c.chan>2) return 0; switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_C64); @@ -249,6 +299,16 @@ int DivPlatformC64::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { chan[c.chan].insChanged=false; } + if (keyPriority) { + if (chanOrder[1]==c.chan) { + chanOrder[1]=chanOrder[2]; + chanOrder[2]=c.chan; + } else if (chanOrder[0]==c.chan) { + chanOrder[0]=chanOrder[1]; + chanOrder[1]=chanOrder[2]; + chanOrder[2]=c.chan; + } + } chan[c.chan].macroInit(ins); break; } @@ -352,7 +412,7 @@ int DivPlatformC64::dispatch(DivCommand c) { break; case DIV_CMD_C64_CUTOFF: if (c.value>100) c.value=100; - filtCut=c.value*2047/100; + filtCut=(c.value+2)*2047/102; updateFilter(); break; case DIV_CMD_C64_FINE_CUTOFF: @@ -438,10 +498,17 @@ int DivPlatformC64::dispatch(DivCommand c) { void DivPlatformC64::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - if (isFP) { - sid_fp.mute(ch,mute); + if (sidCore==2) { + dSID_setMuteMask( + sid_d, + (isMuted[0]?0:1)| + (isMuted[1]?0:2)| + (isMuted[2]?0:4) + ); + } else if (sidCore==1) { + sid_fp->mute(ch,mute); } else { - sid.set_is_muted(ch,mute); + sid->set_is_muted(ch,mute); } } @@ -500,7 +567,7 @@ bool DivPlatformC64::getWantPreNote() { } float DivPlatformC64::getPostAmp() { - return isFP?3.0f:1.0f; + return (sidCore==1)?3.0f:1.0f; } void DivPlatformC64::reset() { @@ -508,12 +575,24 @@ 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 (isFP) { - sid_fp.reset(); + if (sidCore==2) { + dSID_init(sid_d,chipClock,rate,sidIs6581?6581:8580,needInitTables); + dSID_setMuteMask( + sid_d, + (isMuted[0]?0:1)| + (isMuted[1]?0:2)| + (isMuted[2]?0:4) + ); + needInitTables=false; + } else if (sidCore==1) { + sid_fp->reset(); + sid_fp->clockSilent(16000); } else { - sid.reset(); + sid->reset(); } memset(regPool,0,32); @@ -524,6 +603,10 @@ void DivPlatformC64::reset() { filtCut=2047; resetTime=1; vol=15; + + chanOrder[0]=0; + chanOrder[1]=1; + chanOrder[2]=2; } void DivPlatformC64::poke(unsigned int addr, unsigned short val) { @@ -535,23 +618,11 @@ void DivPlatformC64::poke(std::vector& wlist) { } void DivPlatformC64::setChipModel(bool is6581) { - if (is6581) { - if (isFP) { - sid_fp.setChipModel(reSIDfp::MOS6581); - } else { - sid.set_chip_model(MOS6581); - } - } else { - if (isFP) { - sid_fp.setChipModel(reSIDfp::MOS8580); - } else { - sid.set_chip_model(MOS8580); - } - } + sidIs6581=is6581; } -void DivPlatformC64::setFP(bool fp) { - isFP=fp; +void DivPlatformC64::setCore(unsigned char which) { + sidCore=which; } void DivPlatformC64::setFlags(const DivConfig& flags) { @@ -572,9 +643,30 @@ void DivPlatformC64::setFlags(const DivConfig& flags) { for (int i=0; i<3; i++) { oscBuf[i]->rate=rate/16; } - if (isFP) { + if (sidCore>0) { rate/=4; - sid_fp.setSamplingParameters(chipClock,reSIDfp::DECIMATE,rate,0); + if (sidCore==1) sid_fp->setSamplingParameters(chipClock,reSIDfp::DECIMATE,rate,0); + } + 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; } } @@ -582,11 +674,45 @@ int DivPlatformC64::init(DivEngine* p, int channels, int sugRate, const DivConfi parent=p; dumpWrites=false; skipRegisterWrites=false; + needInitTables=true; writeOscBuf=0; for (int i=0; i<3; i++) { isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } + + if (sidCore==2) { + sid=NULL; + sid_fp=NULL; + sid_d=new struct SID_chip; + } else if (sidCore==1) { + sid=NULL; + sid_fp=new reSIDfp::SID; + sid_d=NULL; + } else { + sid=new SID; + sid_fp=NULL; + sid_d=NULL; + } + + if (sidIs6581) { + if (sidCore==2) { + // do nothing + } else if (sidCore==1) { + sid_fp->setChipModel(reSIDfp::MOS6581); + } else { + sid->set_chip_model(MOS6581); + } + } else { + if (sidCore==2) { + // do nothing + } else if (sidCore==1) { + sid_fp->setChipModel(reSIDfp::MOS8580); + } else { + sid->set_chip_model(MOS8580); + } + } + setFlags(flags); reset(); @@ -598,6 +724,9 @@ void DivPlatformC64::quit() { for (int i=0; i<3; i++) { delete oscBuf[i]; } + if (sid!=NULL) delete sid; + if (sid_fp!=NULL) delete sid_fp; + if (sid_d!=NULL) delete sid_d; } DivPlatformC64::~DivPlatformC64() { diff --git a/src/engine/platform/c64.h b/src/engine/platform/c64.h index be78c2d01..0da59fc83 100644 --- a/src/engine/platform/c64.h +++ b/src/engine/platform/c64.h @@ -21,9 +21,10 @@ #define _C64_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" #include "sound/c64/sid.h" #include "sound/c64_fp/SID.h" +#include "sound/c64_d/dsid.h" class DivPlatformC64: public DivDispatch { struct Channel: public SharedChannel { @@ -55,25 +56,36 @@ 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; + QueuedWrite(): addr(0), val(0) {} QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} }; - std::queue writes; + FixedQueue writes; unsigned char filtControl, filtRes, vol; unsigned char writeOscBuf; + unsigned char sidCore; int filtCut, resetTime; - bool isFP; - SID sid; - reSIDfp::SID sid_fp; + bool keyPriority, sidIs6581, needInitTables; + unsigned char chanOrder[3]; + unsigned char testAD, testSR; + + SID* sid; + reSIDfp::SID* sid_fp; + struct SID_chip* sid_d; unsigned char regPool[32]; 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); @@ -101,7 +113,7 @@ class DivPlatformC64: public DivDispatch { const char** getRegisterSheet(); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void setChipModel(bool is6581); - void setFP(bool fp); + void setCore(unsigned char which); void quit(); ~DivPlatformC64(); }; diff --git a/src/engine/platform/es5506.cpp b/src/engine/platform/es5506.cpp index f388e860c..adb342f38 100644 --- a/src/engine/platform/es5506.cpp +++ b/src/engine/platform/es5506.cpp @@ -26,9 +26,8 @@ #define PITCH_OFFSET ((double)(16*2048*(chanMax+1))) #define NOTE_ES5506(c,note) (parent->calcBaseFreq(chipClock,chan[c].pcm.freqOffs,note,false)) -#define rWrite(a,...) {if(!skipRegisterWrites) {hostIntf32.emplace(4,(a),__VA_ARGS__); }} -//#define rRead(a,st,...) {hostIntf32.emplace(st,4,(a),__VA_ARGS__);} -#define immWrite(a,...) {hostIntf32.emplace(4,(a),__VA_ARGS__);} +#define rWrite(a,...) {if(!skipRegisterWrites) {hostIntf32.push_back(QueuedHostIntf(4,(a),__VA_ARGS__)); }} +#define immWrite(a,...) {hostIntf32.push_back(QueuedHostIntf(4,(a),__VA_ARGS__));} #define pageWrite(p,a,...) \ if (!skipRegisterWrites) { \ if (curPage!=(p)) { \ @@ -118,15 +117,15 @@ void DivPlatformES5506::acquire(short** buf, size_t len) { while (!hostIntf32.empty()) { QueuedHostIntf w=hostIntf32.front(); if (w.isRead && (w.read!=NULL)) { - hostIntf8.emplace(w.state,0,w.addr,w.read,w.mask); - hostIntf8.emplace(w.state,1,w.addr,w.read,w.mask); - hostIntf8.emplace(w.state,2,w.addr,w.read,w.mask); - hostIntf8.emplace(w.state,3,w.addr,w.read,w.mask,w.delay); + hostIntf8.push(QueuedHostIntf(w.state,0,w.addr,w.read,w.mask)); + hostIntf8.push(QueuedHostIntf(w.state,1,w.addr,w.read,w.mask)); + hostIntf8.push(QueuedHostIntf(w.state,2,w.addr,w.read,w.mask)); + hostIntf8.push(QueuedHostIntf(w.state,3,w.addr,w.read,w.mask,w.delay)); } else { - hostIntf8.emplace(0,w.addr,w.val,w.mask); - hostIntf8.emplace(1,w.addr,w.val,w.mask); - hostIntf8.emplace(2,w.addr,w.val,w.mask); - hostIntf8.emplace(3,w.addr,w.val,w.mask,w.delay); + hostIntf8.push(QueuedHostIntf(0,w.addr,w.val,w.mask)); + hostIntf8.push(QueuedHostIntf(1,w.addr,w.val,w.mask)); + hostIntf8.push(QueuedHostIntf(2,w.addr,w.val,w.mask)); + hostIntf8.push(QueuedHostIntf(3,w.addr,w.val,w.mask,w.delay)); } hostIntf32.pop(); } @@ -1095,8 +1094,6 @@ DivMacroInt* DivPlatformES5506::getChanMacroInt(int ch) { void DivPlatformES5506::reset() { while (!hostIntf32.empty()) hostIntf32.pop(); while (!hostIntf8.empty()) hostIntf8.pop(); - while (!queuedRead.empty()) queuedRead.pop(); - while (!queuedReadState.empty()) queuedReadState.pop(); for (int i=0; i<32; i++) { chan[i]=DivPlatformES5506::Channel(); chan[i].std.setEngine(parent); diff --git a/src/engine/platform/es5506.h b/src/engine/platform/es5506.h index 1502d03e2..b7658c52f 100644 --- a/src/engine/platform/es5506.h +++ b/src/engine/platform/es5506.h @@ -22,7 +22,7 @@ #include "../dispatch.h" #include "../engine.h" -#include +#include "../fixedQueue.h" #include "../macroInt.h" #include "../sample.h" #include "vgsound_emu/src/es550x/es5506.hpp" @@ -238,6 +238,15 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf { unsigned int* read; unsigned short delay; bool isRead; + QueuedHostIntf(): + state(0), + step(0), + addr(0), + val(0), + mask(0), + read(NULL), + delay(0), + isRead(false) {} QueuedHostIntf(unsigned char s, unsigned char a, unsigned int v, unsigned int m=(unsigned int)(~0), unsigned short d=0): state(0), step(s), @@ -257,17 +266,8 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf { delay(d), isRead(true) {} }; - struct QueuedReadState { - unsigned int* read; - unsigned char state; - QueuedReadState(unsigned int* r, unsigned char s): - read(r), - state(s) {} - }; - std::queue hostIntf32; - std::queue hostIntf8; - std::queue queuedRead; - std::queue queuedReadState; + FixedQueue hostIntf32; + FixedQueue hostIntf8; int cycle, curPage, volScale; unsigned char maskedVal; unsigned int irqv; 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/fmsharedbase.h b/src/engine/platform/fmsharedbase.h index b59b419d8..c0fb7dd2f 100644 --- a/src/engine/platform/fmsharedbase.h +++ b/src/engine/platform/fmsharedbase.h @@ -22,7 +22,7 @@ #include "../dispatch.h" #include "../instrument.h" -#include +#include "../fixedQueue.h" #define KVS(x,y) ((chan[x].state.op[y].kvs==2 && isOutput[chan[x].state.alg][y]) || chan[x].state.op[y].kvs==1) @@ -79,9 +79,10 @@ class DivPlatformFMBase: public DivDispatch { unsigned short addr; unsigned char val; bool addrOrVal; + QueuedWrite(): addr(0), val(0), addrOrVal(false) {} QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} }; - std::deque writes; + FixedQueue writes; unsigned char lastBusy; int delay; diff --git a/src/engine/platform/ga20.cpp b/src/engine/platform/ga20.cpp index 7794d61af..901927dd7 100644 --- a/src/engine/platform/ga20.cpp +++ b/src/engine/platform/ga20.cpp @@ -22,7 +22,7 @@ #include "../../ta-log.h" #include -#define rWrite(a,v) {if(!skipRegisterWrites) {writes.emplace(a,v); if(dumpWrites) addWrite(a,v);}} +#define rWrite(a,v) {if(!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if(dumpWrites) addWrite(a,v);}} #define CHIP_DIVIDER 64 @@ -68,7 +68,7 @@ void DivPlatformGA20::acquire(short** buf, size_t len) { ga20.write(w.addr,w.val); regPool[w.addr]=w.val; writes.pop(); - delay=w.delay; + delay=1; } } short *buffer[4]={ @@ -361,9 +361,7 @@ DivDispatchOscBuffer* DivPlatformGA20::getOscBuffer(int ch) { } void DivPlatformGA20::reset() { - while (!writes.empty()) { - writes.pop(); - } + writes.clear(); memset(regPool,0,32); ga20.device_reset(); delay=0; diff --git a/src/engine/platform/ga20.h b/src/engine/platform/ga20.h index 1e06378f1..691b68f0a 100644 --- a/src/engine/platform/ga20.h +++ b/src/engine/platform/ga20.h @@ -21,7 +21,7 @@ #define _GA20_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" #include "../macroInt.h" #include "sound/ga20/iremga20.h" @@ -47,15 +47,14 @@ class DivPlatformGA20: public DivDispatch, public iremga20_intf { DivDispatchOscBuffer* oscBuf[4]; bool isMuted[4]; struct QueuedWrite { - unsigned short addr; + unsigned char addr; unsigned char val; - unsigned short delay; - QueuedWrite(unsigned short a, unsigned char v, unsigned short d=1): + QueuedWrite(): addr(0), val(0) {} + QueuedWrite(unsigned char a, unsigned char v): addr(a), - val(v), - delay(d) {} + val(v) {} }; - std::queue writes; + FixedQueue writes; unsigned int sampleOffGA20[256]; bool sampleLoaded[256]; diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 9a5d6d7d4..8d61b3348 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -19,10 +19,11 @@ #include "gb.h" #include "../engine.h" +#include "../../ta-log.h" #include -#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } -#define immWrite(a,v) {writes.emplace(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } +#define immWrite(a,v) {writes.push(QueuedWrite(a,v)); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } #define CHIP_DIVIDER 16 @@ -80,6 +81,7 @@ void DivPlatformGB::acquire(short** buf, size_t len) { } void DivPlatformGB::updateWave() { + logV("WAVE UPDATE"); rWrite(0x1a,0); for (int i=0; i<16; i++) { int nibble1=ws.output[((i<<1)+antiClickWavePos)&31]; @@ -299,6 +301,7 @@ void DivPlatformGB::tick(bool sysTick) { } if (chan[i].keyOn) { if (i==2) { // wave + rWrite(16+i*5,0x00); rWrite(16+i*5,0x80); rWrite(16+i*5+2,gbVolMap[chan[i].outVol]); } else { @@ -319,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))); @@ -394,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; @@ -463,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); @@ -664,22 +677,27 @@ void DivPlatformGB::setFlags(const DivConfig& flags) { } invertWave=flags.getBool("invertWave",true); enoughAlready=flags.getBool("enoughAlready",false); -} -int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { chipClock=4194304; CHECK_CUSTOM_CLOCK; rate=chipClock/16; for (int i=0; i<4; i++) { - isMuted[i]=false; - oscBuf[i]=new DivDispatchOscBuffer; oscBuf[i]->rate=rate; } +} + +int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { parent=p; dumpWrites=false; 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/gb.h b/src/engine/platform/gb.h index 8ba70a913..e68a94f82 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -23,7 +23,7 @@ #include "../dispatch.h" #include "../waveSynth.h" #include "sound/gb/gb.h" -#include +#include "../fixedQueue.h" class DivPlatformGB: public DivDispatch { struct Channel: public SharedChannel { @@ -62,11 +62,12 @@ class DivPlatformGB: public DivDispatch { unsigned char lastPan; DivWaveSynth ws; struct QueuedWrite { - unsigned char addr; - unsigned char val; - QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + unsigned char addr; + unsigned char val; + QueuedWrite(): addr(0), val(0) {} + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} }; - std::queue writes; + FixedQueue writes; int antiClickPeriodCount, antiClickWavePos; diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index e7f1fa153..20e87d618 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)) { @@ -286,7 +284,7 @@ void DivPlatformGenesis::acquire(short** buf, size_t len) { } void DivPlatformGenesis::fillStream(std::vector& stream, int sRate, size_t len) { - while (!writes.empty()) writes.pop_front(); + writes.clear(); for (size_t i=0; i6) 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); @@ -1273,7 +1282,7 @@ float DivPlatformGenesis::getPostAmp() { } void DivPlatformGenesis::reset() { - while (!writes.empty()) writes.pop_front(); + writes.clear(); memset(regPool,0,512); if (useYMFM) { fm_ymfm->reset(); 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..d4ac27603 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; @@ -449,6 +452,9 @@ static int opChanOffsH[4]={ }; void DivPlatformGenesisExt::tick(bool sysTick) { + int hardResetElapsed=0; + bool mustHardReset=false; + if (extMode) { bool writeSomething=false; unsigned char writeMask=2; @@ -459,6 +465,12 @@ void DivPlatformGenesisExt::tick(bool sysTick) { writeMask&=~(1<<(4+i)); opChan[i].keyOff=false; } + if (opChan[i].hardReset && opChan[i].keyOn) { + mustHardReset=true; + unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[i]; + immWrite(baseAddr+ADDR_SL_RR,0x0f); + hardResetElapsed++; + } } if (writeSomething) { if (chan[csmChan].active) { // CSM @@ -627,6 +639,22 @@ void DivPlatformGenesisExt::tick(bool sysTick) { (writeMask&0x80)?'4':'-' );*/ immWrite(0x28,writeMask); + + // hard reset handling + if (mustHardReset) { + for (unsigned int i=hardResetElapsed; i -#define rWrite(a,v) {if(!skipRegisterWrites) {writes.emplace(a,v); if(dumpWrites) addWrite(a,v);}} +#define rWrite(a,v) {if(!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if(dumpWrites) addWrite(a,v);}} #define CHIP_DIVIDER 64 diff --git a/src/engine/platform/k007232.h b/src/engine/platform/k007232.h index 842310da5..b1025f574 100644 --- a/src/engine/platform/k007232.h +++ b/src/engine/platform/k007232.h @@ -21,7 +21,7 @@ #define _K007232_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" #include "../macroInt.h" #include "vgsound_emu/src/k007232/k007232.hpp" @@ -57,12 +57,13 @@ class DivPlatformK007232: public DivDispatch, public k007232_intf { unsigned short addr; unsigned char val; unsigned short delay; + QueuedWrite(): addr(0), val(0), delay(1) {} QueuedWrite(unsigned short a, unsigned char v, unsigned short d=1): addr(a), val(v), delay(d) {} }; - std::queue writes; + FixedQueue writes; unsigned int sampleOffK007232[256]; bool sampleLoaded[256]; diff --git a/src/engine/platform/k053260.cpp b/src/engine/platform/k053260.cpp new file mode 100644 index 000000000..7ba1cc2ca --- /dev/null +++ b/src/engine/platform/k053260.cpp @@ -0,0 +1,513 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "k053260.h" +#include "../engine.h" +#include "../../ta-log.h" +#include + +#define rWrite(a,v) {if(!skipRegisterWrites) {k053260.write(a,v); regPool[a]=v; if(dumpWrites) addWrite(a,v);}} + +#define CHIP_DIVIDER 16 +#define TICK_DIVIDER 64 // for match to YM3012 output rate + +const char* regCheatSheetK053260[]={ + "MainToSub0", "00", + "MainToSub1", "01", + "SubToMain0", "02", + "SubToMain1", "03", + "CHx_FreqL", "08+x*8", + "CHx_FreqH", "09+x*8", + "CHx_LengthL", "0A+x*8", + "CHx_LengthH", "0B+x*8", + "CHx_StartL", "0C+x*8", + "CHx_StartM", "0D+x*8", + "CHx_StartH", "0E+x*8", + "CHx_Volume", "0F+x*8", + "KeyOn", "28", + "Status", "29", + "LoopFormat", "2A", + "Test", "2B", + "CH01_Pan", "2C", + "CH23_Pan", "2D", + "ROMReadback", "2E", + "Control", "2F", + NULL +}; + +const char** DivPlatformK053260::getRegisterSheet() { + return regCheatSheetK053260; +} + +inline void DivPlatformK053260::chWrite(unsigned char ch, unsigned int addr, unsigned char val) { + if (!skipRegisterWrites) { + rWrite(8+((ch<<3)|(addr&7)),val); + } +} + +u8 DivPlatformK053260::read_sample(u32 address) { + if ((sampleMem!=NULL) && (address32767) lout=32767; + if (lout<-32768) lout=-32768; + if (rout>32767) rout=32767; + if (rout<-32768) rout=-32768; + buf[0][i]=lout; + buf[1][i]=rout; + + for (int i=0; i<4; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(k053260.voice_out(i,0)+k053260.voice_out(i,1))>>2; + } + } +} + +void DivPlatformK053260::tick(bool sysTick) { + unsigned char panMask=0; + for (int i=0; i<4; i++) { + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=((chan[i].vol&0x7f)*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul; + chWrite(i,7,chan[i].outVol); + } + if (NEW_ARP_STRAT) { + chan[i].handleArp(); + } else if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val)); + } + chan[i].freqChanged=true; + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-32768,32767); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + if (chan[i].std.panL.had) { // panning + chan[i].panning=4+chan[i].std.panL.val; + if (!isMuted[i]) { + panMask|=1<=0 && samplesong.sampleLen) { + DivSample* s=parent->getSample(sample); + if (s->centerRate<1) { + off=1.0; + } else { + off=8363.0/s->centerRate; + } + } + DivSample* s=parent->getSample(chan[i].sample); + chan[i].freq=0x1000-(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)); + if (chan[i].freq>4095) chan[i].freq=4095; + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].keyOn) { + unsigned int start=0; + unsigned int length=0; + if (chan[i].sample>=0 && chan[i].samplesong.sampleLen) { + start=sampleOffK053260[chan[i].sample]; + length=s->length8; + if (chan[i].reverse) { + start+=length; + keyon|=(16<0) { + if (chan[i].reverse) { + start=start-MIN(chan[i].audPos,s->length8); + } + else { + start=start+MIN(chan[i].audPos,s->length8); + } + length=MAX(1,length-chan[i].audPos); + } + start=MIN(start,getSampleMemCapacity()); + length=MIN(65535,MIN(length,getSampleMemCapacity())); + rWrite(0x28,keyoff); // force keyoff first + rWrite(0x2a,loopoff); + chWrite(i,2,length&0xff); + chWrite(i,3,length>>8); + chWrite(i,4,start&0xff); + chWrite(i,5,start>>8); + chWrite(i,6,start>>16); + if (!chan[i].std.vol.had) { + chan[i].outVol=chan[i].vol; + chWrite(i,7,chan[i].outVol); + } + rWrite(0x28,keyon); + if (s->isLoopable()) { + rWrite(0x2a,loopon); + } + chan[i].keyOn=false; + } + if (chan[i].keyOff) { + rWrite(0x28,keyoff); + rWrite(0x2a,loopoff); + chan[i].keyOff=false; + } + if (chan[i].freqChanged) { + chWrite(i,0,chan[i].freq&0xff); + chWrite(i,1,chan[i].freq>>8); + chan[i].freqChanged=false; + } + } + } + if (panMask) { + updatePanning(panMask); + } +} + +void DivPlatformK053260::updatePanning(unsigned char mask) { + if (mask&3) { + rWrite(0x2c, + (isMuted[0]?0:chan[0].panning)| + (isMuted[1]?0:chan[1].panning<<3)); + } + if (mask&0xc) { + rWrite(0x2d, + (isMuted[2]?0:chan[2].panning)| + (isMuted[3]?0:chan[3].panning<<3)); + } +} + +int DivPlatformK053260::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); + chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:127; + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + } + if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { + chan[c.chan].sample=-1; + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].macroInit(ins); + if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].sample=-1; + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + chWrite(c.chan,7,chan[c.chan].outVol); + } + } + break; + case DIV_CMD_GET_VOLUME: + if (chan[c.chan].std.vol.has) { + return chan[c.chan].vol; + } + return chan[c.chan].outVol; + break; + case DIV_CMD_PANNING: + chan[c.chan].panning=MIN(parent->convertPanSplitToLinearLR(c.value,c.value2,7)+1,7); + if (!isMuted[c.chan]) { + updatePanning(1<chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val-12):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + } + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA)); + } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_SAMPLE_POS: + chan[c.chan].audPos=c.value; + chan[c.chan].setPos=true; + break; + case DIV_CMD_SAMPLE_DIR: { + if (chan[c.chan].reverse!=(bool)(c.value&1)) { + chan[c.chan].reverse=c.value&1; + } + break; + } + case DIV_CMD_GET_VOLMAX: + return 127; + break; + case DIV_CMD_MACRO_OFF: + chan[c.chan].std.mask(c.value,true); + break; + case DIV_CMD_MACRO_ON: + chan[c.chan].std.mask(c.value,false); + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformK053260::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + updatePanning(1<rate=rate; + } +} + +void DivPlatformK053260::poke(unsigned int addr, unsigned short val) { + rWrite(addr&0x3f,val); +} + +void DivPlatformK053260::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr&0x3f,i.val); +} + +unsigned char* DivPlatformK053260::getRegisterPool() { + regPool[0x29]=k053260.read(0x29); // dynamically updated + return regPool; +} + +int DivPlatformK053260::getRegisterPoolSize() { + return 64; +} + +const void* DivPlatformK053260::getSampleMem(int index) { + return index == 0 ? sampleMem : NULL; +} + +size_t DivPlatformK053260::getSampleMemCapacity(int index) { + return index == 0 ? 2097152 : 0; +} + +size_t DivPlatformK053260::getSampleMemUsage(int index) { + return index == 0 ? sampleMemLen : 0; +} + +bool DivPlatformK053260::isSampleLoaded(int index, int sample) { + if (index!=0) return false; + if (sample<0 || sample>255) return false; + return sampleLoaded[sample]; +} + +void DivPlatformK053260::renderSamples(int sysID) { + memset(sampleMem,0,getSampleMemCapacity()); + memset(sampleOffK053260,0,256*sizeof(unsigned int)); + memset(sampleLoaded,0,256*sizeof(bool)); + + size_t memPos=1; // for avoid silence + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + if (!s->renderOn[0][sysID]) { + sampleOffK053260[i]=0; + continue; + } + + int length=MIN(65535,s->getEndPosition(DIV_SAMPLE_DEPTH_8BIT)); + int actualLength=MIN((int)(getSampleMemCapacity()-memPos-1),length); + if (actualLength>0) { + sampleOffK053260[i]=memPos-1; + for (int j=0; jdata8[j]; + } + sampleMem[memPos++]=0; // Silence for avoid popping noise + } + if (actualLength +#include "vgsound_emu/src/k053260/k053260.hpp" + +class DivPlatformK053260: public DivDispatch, public k053260_intf { + struct Channel: public SharedChannel { + unsigned int audPos; + int sample, wave; + int panning; + bool setPos, reverse; + int macroVolMul; + Channel(): + SharedChannel(127), + audPos(0), + sample(-1), + wave(-1), + panning(4), + setPos(false), + reverse(false), + macroVolMul(64) {} + }; + Channel chan[4]; + DivDispatchOscBuffer* oscBuf[4]; + bool isMuted[4]; + int chipType; + unsigned char curChan; + unsigned int sampleOffK053260[256]; + bool sampleLoaded[256]; + + unsigned char* sampleMem; + size_t sampleMemLen; + k053260_core k053260; + unsigned char regPool[64]; + void updatePanning(unsigned char mask); + + friend void putDispatchChip(void*,int); + friend void putDispatchChan(void*,int,int); + + public: + virtual u8 read_sample(u32 address) override; + virtual void acquire(short** buf, size_t len) override; + virtual int dispatch(DivCommand c) override; + virtual void* getChanState(int chan) override; + virtual DivMacroInt* getChanMacroInt(int ch) override; + virtual DivDispatchOscBuffer* getOscBuffer(int chan) override; + virtual unsigned char* getRegisterPool() override; + virtual int getRegisterPoolSize() override; + virtual void reset() override; + virtual void forceIns() override; + virtual void tick(bool sysTick=true) override; + virtual void muteChannel(int ch, bool mute) override; + virtual int getOutputCount() override; + virtual void notifyInsChange(int ins) override; + virtual void notifyWaveChange(int wave) override; + virtual void notifyInsDeletion(void* ins) override; + virtual void setFlags(const DivConfig& flags) override; + virtual void poke(unsigned int addr, unsigned short val) override; + virtual void poke(std::vector& wlist) override; + virtual const char** getRegisterSheet() override; + virtual const void* getSampleMem(int index = 0) override; + virtual size_t getSampleMemCapacity(int index = 0) override; + virtual size_t getSampleMemUsage(int index = 0) override; + virtual bool isSampleLoaded(int index, int sample) override; + virtual void renderSamples(int chipID) override; + virtual int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags) override; + virtual void quit() override; + DivPlatformK053260(): + DivDispatch(), + k053260_intf(), + k053260(*this) {} + private: + void chWrite(unsigned char ch, unsigned int addr, unsigned char val); +}; + +#endif diff --git a/src/engine/platform/msm5232.cpp b/src/engine/platform/msm5232.cpp index 55df61281..4a8107289 100644 --- a/src/engine/platform/msm5232.cpp +++ b/src/engine/platform/msm5232.cpp @@ -23,7 +23,7 @@ #include //#define rWrite(a,v) pendingWrites[a]=v; -#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } #define NOTE_LINEAR(x) ((x)<<7) diff --git a/src/engine/platform/msm5232.h b/src/engine/platform/msm5232.h index b1d83cf01..abdb72f06 100644 --- a/src/engine/platform/msm5232.h +++ b/src/engine/platform/msm5232.h @@ -21,7 +21,7 @@ #define _MSM5232_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" #include "sound/oki/msm5232.h" class DivPlatformMSM5232: public DivDispatch { @@ -46,11 +46,12 @@ class DivPlatformMSM5232: public DivDispatch { unsigned char groupAR[2]; unsigned char groupDR[2]; struct QueuedWrite { - unsigned char addr; - unsigned char val; - QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + unsigned char addr; + unsigned char val; + QueuedWrite(): addr(0), val(0) {} + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} }; - std::queue writes; + FixedQueue writes; int cycles, curChan, delay, detune, clockDriftAccum; unsigned int clockDriftLFOPos, clockDriftLFOSpeed; diff --git a/src/engine/platform/msm6258.cpp b/src/engine/platform/msm6258.cpp index 31002a9c4..7f4d2e635 100644 --- a/src/engine/platform/msm6258.cpp +++ b/src/engine/platform/msm6258.cpp @@ -24,7 +24,7 @@ #include #include -#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } const char** DivPlatformMSM6258::getRegisterSheet() { return NULL; diff --git a/src/engine/platform/msm6258.h b/src/engine/platform/msm6258.h index 0c19d9763..2c18d90c4 100644 --- a/src/engine/platform/msm6258.h +++ b/src/engine/platform/msm6258.h @@ -21,7 +21,7 @@ #define _MSM6258_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" #include "sound/oki/okim6258.h" class DivPlatformMSM6258: public DivDispatch { @@ -42,9 +42,10 @@ class DivPlatformMSM6258: public DivDispatch { struct QueuedWrite { unsigned short addr; unsigned char val; + QueuedWrite(): addr(0), val(0) {} QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {} }; - std::queue writes; + FixedQueue writes; okim6258_device* msm; unsigned char lastBusy; diff --git a/src/engine/platform/msm6295.cpp b/src/engine/platform/msm6295.cpp index 2aff0006c..3d823d16f 100644 --- a/src/engine/platform/msm6295.cpp +++ b/src/engine/platform/msm6295.cpp @@ -23,8 +23,8 @@ #include #include -#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } -#define rWriteDelay(a,v,d) if (!skipRegisterWrites) {writes.emplace(a,v,d); if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } +#define rWriteDelay(a,v,d) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v,d)); if (dumpWrites) {addWrite(a,v);} } const char** DivPlatformMSM6295::getRegisterSheet() { return NULL; diff --git a/src/engine/platform/msm6295.h b/src/engine/platform/msm6295.h index df1406933..dfa59a0c1 100644 --- a/src/engine/platform/msm6295.h +++ b/src/engine/platform/msm6295.h @@ -21,7 +21,7 @@ #define _MSM6295_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" #include "vgsound_emu/src/msm6295/msm6295.hpp" class DivPlatformMSM6295: public DivDispatch, public vgsound_emu_mem_intf { @@ -41,12 +41,13 @@ class DivPlatformMSM6295: public DivDispatch, public vgsound_emu_mem_intf { unsigned short addr; unsigned char val; unsigned short delay; + QueuedWrite(): addr(0), val(0), delay(96) {} QueuedWrite(unsigned short a, unsigned char v, unsigned short d=96): addr(a), val(v), delay(d) {} }; - std::queue writes; + FixedQueue writes; msm6295_core msm; unsigned char lastBusy; diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index ead44cc1e..7e10a01ed 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -23,8 +23,8 @@ #include #define rRead(a,v) n163.addr_w(a); n163.data_r(v); -#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } -#define rWriteMask(a,v,m) if (!skipRegisterWrites) {writes.emplace(a,v,m); if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } +#define rWriteMask(a,v,m) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v,m)); if (dumpWrites) {addWrite(a,v);} } #define chWrite(c,a,v) \ if (c<=chanMax) { \ rWrite(0x78-(c<<3)+(a&7),v) \ @@ -207,7 +207,7 @@ void DivPlatformN163::tick(bool sysTick) { } } if (chan[i].std.wave.had) { - if (chan[i].wave!=chan[i].std.wave.val) { + if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { chan[i].wave=chan[i].std.wave.val; chan[i].ws.changeWave1(chan[i].wave); if (chan[i].waveMode&0x2) { diff --git a/src/engine/platform/n163.h b/src/engine/platform/n163.h index 49c0ff05e..0c5e5913f 100644 --- a/src/engine/platform/n163.h +++ b/src/engine/platform/n163.h @@ -21,7 +21,7 @@ #define _N163_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" #include "../waveSynth.h" #include "vgsound_emu/src/n163/n163.hpp" @@ -54,12 +54,13 @@ class DivPlatformN163: public DivDispatch { DivDispatchOscBuffer* oscBuf[8]; bool isMuted[8]; struct QueuedWrite { - unsigned char addr; - unsigned char val; - unsigned char mask; - QueuedWrite(unsigned char a, unsigned char v, unsigned char m=~0): addr(a), val(v), mask(m) {} + unsigned char addr; + unsigned char val; + unsigned char mask; + QueuedWrite(): addr(0), val(0), mask(~0) {} + QueuedWrite(unsigned char a, unsigned char v, unsigned char m=~0): addr(a), val(v), mask(m) {} }; - std::queue writes; + FixedQueue writes; unsigned char initChanMax; unsigned char chanMax; short loadWave, loadPos, loadLen; diff --git a/src/engine/platform/namcowsg.cpp b/src/engine/platform/namcowsg.cpp index 088f1e639..5b804b23f 100644 --- a/src/engine/platform/namcowsg.cpp +++ b/src/engine/platform/namcowsg.cpp @@ -22,7 +22,7 @@ #include //#define rWrite(a,v) pendingWrites[a]=v; -#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } #define CHIP_FREQBASE 4194304 diff --git a/src/engine/platform/namcowsg.h b/src/engine/platform/namcowsg.h index 9d418a9d2..6aaef0952 100644 --- a/src/engine/platform/namcowsg.h +++ b/src/engine/platform/namcowsg.h @@ -21,7 +21,7 @@ #define _NAMCOWSG_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" #include "../waveSynth.h" #include "sound/namco.h" @@ -41,11 +41,12 @@ class DivPlatformNamcoWSG: public DivDispatch { DivDispatchOscBuffer* oscBuf[8]; bool isMuted[8]; struct QueuedWrite { - unsigned short addr; - unsigned char val; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {} + unsigned short addr; + unsigned char val; + QueuedWrite(): addr(0), val(0) {} + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {} }; - std::queue writes; + FixedQueue writes; namco_audio_device* namco; int devType, chans; diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index d340aa310..3578dbd9c 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -24,7 +24,7 @@ #include #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} -#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define immWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } #define KVSL(x,y) ((chan[x].state.op[orderedOpsL1[ops==4][y]].kvs==2 && isOutputL[ops==4][chan[x].state.alg][y]) || chan[x].state.op[orderedOpsL1[ops==4][y]].kvs==1) diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index aae0e8008..f4881c29d 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -21,7 +21,7 @@ #define _OPL_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" #include "../../../extern/opl/opl3.h" #include "sound/ymfm/ymfm_adpcm.h" @@ -64,9 +64,10 @@ class DivPlatformOPL: public DivDispatch { unsigned short addr; unsigned char val; bool addrOrVal; + QueuedWrite(): addr(0), val(0), addrOrVal(false) {} QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} }; - std::queue writes; + FixedQueue writes; opl3_chip fm; unsigned char* adpcmBMem; size_t adpcmBMemLen; diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 38b892e80..d081fabc3 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -24,7 +24,7 @@ #include #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} -#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define immWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } #define CHIP_FREQBASE 1180068 diff --git a/src/engine/platform/opll.h b/src/engine/platform/opll.h index 4e72936da..7333f7298 100644 --- a/src/engine/platform/opll.h +++ b/src/engine/platform/opll.h @@ -21,7 +21,7 @@ #define _OPLL_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" extern "C" { #include "../../../extern/Nuked-OPLL/opll.h" @@ -50,9 +50,10 @@ class DivPlatformOPLL: public DivDispatch { unsigned short addr; unsigned char val; bool addrOrVal; + QueuedWrite(): addr(0), val(0), addrOrVal(false) {} QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} }; - std::queue writes; + FixedQueue writes; opll_t fm; int delay, lastCustomMemory; unsigned char lastBusy; diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index 47e5bbcdd..b6f43e2da 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -22,7 +22,7 @@ #include //#define rWrite(a,v) pendingWrites[a]=v; -#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } #define chWrite(c,a,v) \ if (!skipRegisterWrites) { \ if (curChan!=c) { \ @@ -531,7 +531,7 @@ int DivPlatformPCE::getRegisterPoolSize() { } void DivPlatformPCE::reset() { - while (!writes.empty()) writes.pop(); + writes.clear(); memset(regPool,0,128); for (int i=0; i<6; i++) { chan[i]=DivPlatformPCE::Channel(); diff --git a/src/engine/platform/pce.h b/src/engine/platform/pce.h index 9b8c610c8..baca77701 100644 --- a/src/engine/platform/pce.h +++ b/src/engine/platform/pce.h @@ -21,7 +21,7 @@ #define _PCE_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" #include "../waveSynth.h" #include "sound/pce_psg.h" @@ -60,11 +60,12 @@ class DivPlatformPCE: public DivDispatch { bool antiClickEnabled; bool updateLFO; struct QueuedWrite { - unsigned char addr; - unsigned char val; - QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + unsigned char addr; + unsigned char val; + QueuedWrite(): addr(0), val(9) {} + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} }; - std::queue writes; + FixedQueue writes; unsigned char lastPan; int cycles, curChan, delay; diff --git a/src/engine/platform/pcmdac.h b/src/engine/platform/pcmdac.h index f9435e3e0..8ef10149c 100644 --- a/src/engine/platform/pcmdac.h +++ b/src/engine/platform/pcmdac.h @@ -21,7 +21,6 @@ #define _PCM_DAC_H #include "../dispatch.h" -#include #include "../waveSynth.h" class DivPlatformPCMDAC: public DivDispatch { diff --git a/src/engine/platform/pcspkr.h b/src/engine/platform/pcspkr.h index 23b3c0b49..0437a90c4 100644 --- a/src/engine/platform/pcspkr.h +++ b/src/engine/platform/pcspkr.h @@ -21,7 +21,7 @@ #define _PCSPKR_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" #include #include #include @@ -40,12 +40,16 @@ class DivPlatformPCSpeaker: public DivDispatch { struct RealQueueVal { int tv_sec, tv_nsec; unsigned short val; + RealQueueVal(): + tv_sec(0), + tv_nsec(0), + val(0) {} RealQueueVal(int sec, int nsec, unsigned short v): tv_sec(sec), tv_nsec(nsec), val(v) {} }; - std::queue realQueue; + FixedQueue realQueue; std::mutex realQueueLock; bool isMuted[1]; bool on, flip, lastOn, realOutEnabled; diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index 790346e1c..08b6da9f0 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -21,7 +21,7 @@ #include "../engine.h" #include "../../ta-log.h" -#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } #define CHIP_DIVIDER 1 diff --git a/src/engine/platform/pokey.h b/src/engine/platform/pokey.h index b50875177..979f60754 100644 --- a/src/engine/platform/pokey.h +++ b/src/engine/platform/pokey.h @@ -21,7 +21,7 @@ #define _POKEY_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" extern "C" { #include "sound/pokey/mzpokeysnd.h" @@ -43,11 +43,12 @@ class DivPlatformPOKEY: public DivDispatch { DivDispatchOscBuffer* oscBuf[4]; bool isMuted[4]; struct QueuedWrite { - unsigned char addr; - unsigned char val; - QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + unsigned char addr; + unsigned char val; + QueuedWrite(): addr(0), val(0) {} + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} }; - std::queue writes; + FixedQueue writes; unsigned char audctl, skctl; bool audctlChanged, skctlChanged; unsigned char oscBufDelay; 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/pv1000.h b/src/engine/platform/pv1000.h index 852bf1204..740f16c4b 100644 --- a/src/engine/platform/pv1000.h +++ b/src/engine/platform/pv1000.h @@ -22,7 +22,6 @@ #include "../dispatch.h" #include "sound/d65modified.h" -#include class DivPlatformPV1000: public DivDispatch { struct Channel: public SharedChannel { diff --git a/src/engine/platform/qsound.h b/src/engine/platform/qsound.h index 9c090f19c..aff53f679 100644 --- a/src/engine/platform/qsound.h +++ b/src/engine/platform/qsound.h @@ -21,7 +21,6 @@ #define _QSOUND_H #include "../dispatch.h" -#include #include "sound/qsound.h" class DivPlatformQSound: public DivDispatch { diff --git a/src/engine/platform/rf5c68.h b/src/engine/platform/rf5c68.h index 94ced515e..4ba40318c 100644 --- a/src/engine/platform/rf5c68.h +++ b/src/engine/platform/rf5c68.h @@ -21,7 +21,6 @@ #define _RF5C68_H #include "../dispatch.h" -#include #include "sound/rf5c68.h" class DivPlatformRF5C68: public DivDispatch { diff --git a/src/engine/platform/saa.cpp b/src/engine/platform/saa.cpp index 7682d3188..803a6cad3 100644 --- a/src/engine/platform/saa.cpp +++ b/src/engine/platform/saa.cpp @@ -23,7 +23,7 @@ #include #include -#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } #define CHIP_DIVIDER 2 diff --git a/src/engine/platform/saa.h b/src/engine/platform/saa.h index 43e3cc875..ffd79db72 100644 --- a/src/engine/platform/saa.h +++ b/src/engine/platform/saa.h @@ -21,7 +21,7 @@ #define _SAA_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" #include "../../../extern/SAASound/src/SAASound.h" class DivPlatformSAA1099: public DivDispatch { @@ -44,9 +44,10 @@ class DivPlatformSAA1099: public DivDispatch { unsigned short addr; unsigned char val; bool addrOrVal; + QueuedWrite(): addr(0), val(0), addrOrVal(false) {} QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} }; - std::queue writes; + FixedQueue writes; CSAASound* saa_saaSound; unsigned char regPool[32]; unsigned char lastBusy; diff --git a/src/engine/platform/scc.h b/src/engine/platform/scc.h index b8b892af3..f075753aa 100644 --- a/src/engine/platform/scc.h +++ b/src/engine/platform/scc.h @@ -21,7 +21,6 @@ #define _SCC_H #include "../dispatch.h" -#include #include "../waveSynth.h" #include "vgsound_emu/src/scc/scc.hpp" diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 47411496a..b9af8a912 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -23,7 +23,7 @@ #include #include -#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } #define chWrite(c,a,v) rWrite(((c)<<3)+(a),v) void DivPlatformSegaPCM::acquire(short** buf, size_t len) { @@ -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 { @@ -423,7 +430,7 @@ const void* DivPlatformSegaPCM::getSampleMem(int index) { } size_t DivPlatformSegaPCM::getSampleMemCapacity(int index) { - return index == 0 ? 16777216 : 0; + return index == 0 ? 2097152 : 0; } size_t DivPlatformSegaPCM::getSampleMemUsage(int index) { @@ -465,7 +472,7 @@ void DivPlatformSegaPCM::reset() { void DivPlatformSegaPCM::renderSamples(int sysID) { size_t memPos=0; - memset(sampleMem,0,16777216); + memset(sampleMem,0,2097152); memset(sampleLoaded,0,256*sizeof(bool)); memset(sampleOffSegaPCM,0,256*sizeof(unsigned int)); memset(sampleEndSegaPCM,0,256); @@ -482,7 +489,7 @@ void DivPlatformSegaPCM::renderSamples(int sysID) { } logV("- sample %d will be at %x with length %x",i,memPos,alignedSize); sampleLoaded[i]=true; - if (memPos>=16777216) break; + if (memPos>=2097152) break; sampleOffSegaPCM[i]=memPos; for (unsigned int j=0; j=sample->samples) { @@ -491,10 +498,10 @@ void DivPlatformSegaPCM::renderSamples(int sysID) { sampleMem[memPos++]=((unsigned char)sample->data8[j]+0x80); } sampleEndSegaPCM[i]=((memPos+0xff)>>8)-1; - if (memPos>=16777216) break; + if (memPos>=2097152) break; } logV(" and it ends in %d",sampleEndSegaPCM[i]); - if (memPos>=16777216) break; + if (memPos>=2097152) break; } sampleMemLen=memPos; } @@ -522,10 +529,10 @@ int DivPlatformSegaPCM::init(DivEngine* p, int channels, int sugRate, const DivC isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - sampleMem=new unsigned char[16777216]; + sampleMem=new unsigned char[2097152]; pcm.set_bank(segapcm_device::BANK_12M|segapcm_device::BANK_MASKF8); pcm.set_read([this](unsigned int addr) -> unsigned char { - return sampleMem[addr&0xffffff]; + return sampleMem[addr&0x1fffff]; }); setFlags(flags); reset(); diff --git a/src/engine/platform/segapcm.h b/src/engine/platform/segapcm.h index b818306b1..067054fe1 100644 --- a/src/engine/platform/segapcm.h +++ b/src/engine/platform/segapcm.h @@ -23,7 +23,7 @@ #include "../dispatch.h" #include "../instrument.h" #include "sound/segapcm.h" -#include +#include "../fixedQueue.h" class DivPlatformSegaPCM: public DivDispatch { protected: @@ -59,9 +59,10 @@ class DivPlatformSegaPCM: public DivDispatch { unsigned short addr; unsigned char val; bool addrOrVal; + QueuedWrite(): addr(0), val(0), addrOrVal(false) {} QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} }; - std::queue writes; + FixedQueue writes; segapcm_device pcm; int delay; int pcmL, pcmR, pcmCycles; diff --git a/src/engine/platform/sm8521.cpp b/src/engine/platform/sm8521.cpp index e72616bdf..52c3442ce 100644 --- a/src/engine/platform/sm8521.cpp +++ b/src/engine/platform/sm8521.cpp @@ -22,7 +22,7 @@ #include //#define rWrite(a,v) pendingWrites[a]=v; -#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } #define CHIP_DIVIDER 64 diff --git a/src/engine/platform/sm8521.h b/src/engine/platform/sm8521.h index b0a119fb5..02e2f458e 100644 --- a/src/engine/platform/sm8521.h +++ b/src/engine/platform/sm8521.h @@ -21,7 +21,7 @@ #define _SM8521_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" #include "../waveSynth.h" #include "sound/sm8521.h" @@ -46,11 +46,12 @@ class DivPlatformSM8521: public DivDispatch { DivDispatchOscBuffer* oscBuf[3]; bool isMuted[3]; struct QueuedWrite { - unsigned short addr; - unsigned char val; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {} + unsigned short addr; + unsigned char val; + QueuedWrite(): addr(0), val(0) {} + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {} }; - std::queue writes; + FixedQueue writes; bool antiClickEnabled; struct sm8521_t sm8521; diff --git a/src/engine/platform/sms.cpp b/src/engine/platform/sms.cpp index f8858aab6..640f364ea 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -22,7 +22,7 @@ #include "../../ta-log.h" #include -#define rWrite(a,v) {if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);}}} +#define rWrite(a,v) {if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);}}} const char* regCheatSheetSN[]={ "DATA", "0", diff --git a/src/engine/platform/sms.h b/src/engine/platform/sms.h index efdfff443..692c0a42c 100644 --- a/src/engine/platform/sms.h +++ b/src/engine/platform/sms.h @@ -25,7 +25,7 @@ extern "C" { #include "../../../extern/Nuked-PSG/ympsg.h" } -#include +#include "../fixedQueue.h" class DivPlatformSMS: public DivDispatch { struct Channel: public SharedChannel { @@ -59,9 +59,10 @@ class DivPlatformSMS: public DivDispatch { unsigned short addr; unsigned char val; bool addrOrVal; + QueuedWrite(): addr(0), val(0), addrOrVal(false) {} QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} }; - std::queue writes; + FixedQueue writes; friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); diff --git a/src/engine/platform/snes.h b/src/engine/platform/snes.h index 68637be82..cec51c0c1 100644 --- a/src/engine/platform/snes.h +++ b/src/engine/platform/snes.h @@ -22,7 +22,7 @@ #include "../dispatch.h" #include "../waveSynth.h" -#include +#include "../fixedQueue.h" #include "sound/snes/SPC_DSP.h" class DivPlatformSNES: public DivDispatch { @@ -81,9 +81,10 @@ class DivPlatformSNES: public DivDispatch { struct QueuedWrite { unsigned char addr; unsigned char val; + QueuedWrite(): addr(0), val(0) {} QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} }; - std::queue writes; + FixedQueue writes; signed char sampleMem[65536]; signed char copyOfSampleMem[65536]; diff --git a/src/engine/platform/sound/c64_d/LICENSE b/src/engine/platform/sound/c64_d/LICENSE new file mode 100644 index 000000000..2127f8a3b --- /dev/null +++ b/src/engine/platform/sound/c64_d/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2021 DefleMask Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/engine/platform/sound/c64_d/README.md b/src/engine/platform/sound/c64_d/README.md new file mode 100644 index 000000000..63b7fa533 --- /dev/null +++ b/src/engine/platform/sound/c64_d/README.md @@ -0,0 +1,11 @@ +dSID +=== + +This is the SID core used in DefleMask. + +The project started as a very careful port from jsSID, comparing the wave +output from both, ensuring they were exactly the same. + +## License + +MIT License diff --git a/src/engine/platform/sound/c64_d/dsid.c b/src/engine/platform/sound/c64_d/dsid.c new file mode 100644 index 000000000..2ff2dacf8 --- /dev/null +++ b/src/engine/platform/sound/c64_d/dsid.c @@ -0,0 +1,370 @@ +#include "dsid.h" +#include +#include // INFINITY +#include +#include // memset, memcpy + +#define SID_OUT_SCALE (0x10000 * 3 * 16) + +// CONTROL +#define GAT 0x01 +#define SYN 0x02 +#define RNG 0x04 +#define TST 0x08 +#define TRI 0x10 +#define SAW 0x20 +#define PUL 0x40 +#define NOI 0x80 + +#define _HZ 0x10 +#define DECSUS 0x40 +#define ATK 0x80 + +// filter mode (high) +#define LP 0x10 +#define BP 0x20 +#define HP 0x40 +#define OFF3 0x80 + +#define waveforms_add_sample(_id,_s) \ + sid->lastOut[_id]=(_s); + +const int Aexp[256] = { + 1, 30, 30, 30, 30, 30, 30, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + +double cmbWF(int chn, int *wfa, int index, int differ6581, struct SID_globals *g) { + if (differ6581 && g->model == 6581) + index &= 0x7FF; + + return wfa[index]; +} + +void cCmbWF(int *wfa, double bitmul, double bstr, double trh) { + for (int i = 0; i < 4096; i++) { + wfa[i] = 0; + for (int j = 0; j < 12; j++) { + double blvl = 0; + for (int k = 0; k < 12; k++) { + blvl += (bitmul / pow(bstr, abs(k - j))) * (((i >> k) & 1) - 0.5); + } + wfa[i] += (blvl >= trh) ? pow(2, j) : 0; + } + wfa[i] *= 12; + } +} + +void dSID_init(struct SID_chip* sid, double clockRate, double samplingRate, int model, unsigned char init_wf) { + if (model == 6581) { + sid->g.model = 6581; + } else { + sid->g.model = 8580; + } + + memset(sid->M,0,MemLen); + memset(sid->SIDct, 0, sizeof(sid->SIDct)); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + sid->SIDct[i].ch[j].Ast = _HZ; + sid->SIDct[i].ch[j].nLFSR = 0x7FFFF8; + sid->SIDct[i].ch[j].prevwfout = 0; + } + sid->SIDct[i].ch[0].FSW = 1; + sid->SIDct[i].ch[1].FSW = 2; + sid->SIDct[i].ch[2].FSW = 4; + } + + sid->g.ctfr = -2.0 * 3.14 * (12500.0 / 256.0) / samplingRate, + sid->g.ctf_ratio_6581 = -2.0 * 3.14 * (samplingRate / 44100.0) * (20000.0 / 256.0) / samplingRate; + sid->g.ckr = clockRate / samplingRate; + + const double bAprd[16] = {9, 32 * 1, 63 * 1, 95 * 1, 149 * 1, 220 * 1, + 267 * 1, 313 * 1, 392 * 1, 977 * 1, 1954 * 1, 3126 * 1, + 3907 * 1, 11720 * 1, 19532 * 1, 31251 * 1}; + const int bAstp[16] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + memcpy(&sid->g.Aprd, &bAprd, sizeof(bAprd)); + memcpy(&sid->g.Astp, &bAstp, sizeof(bAstp)); + if (init_wf) { + cCmbWF(sid->g.trsaw, 0.8, 2.4, 0.64); + cCmbWF(sid->g.pusaw, 1.4, 1.9, 0.68); + cCmbWF(sid->g.Pulsetrsaw, 0.8, 2.5, 0.64); + + for (int i = 0; i < 2048; i++) { + double ctf = (double) i / 8.0 + 0.2; + if (model == 8580) { + ctf = 1 - exp(ctf * sid->g.ctfr); + } else { + if (ctf < 24) { + ctf = 2.0 * sin(771.78 / samplingRate); + } else { + ctf = (44100.0 / samplingRate) - 1.263 * (44100.0 / samplingRate) * exp(ctf * sid->g.ctf_ratio_6581); + } + } + sid->g.ctf_table[i] = ctf; + } + } + + 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); + uint8_t ctrl = voic->control; + uint8_t wf = ctrl & 0xF0; + uint8_t test = ctrl & TST; + uint8_t SR = voic->susres; + double tmp = 0; + if (pgt != (ctrl & GAT)) { + if (pgt) { + sid->SIDct->ch[chn].Ast &= 0xFF - (GAT | ATK | DECSUS); + } else { + sid->SIDct->ch[chn].Ast = (GAT | ATK | DECSUS); + if ((SR & 0xF) > (sid->SIDct->ch[chn].pSR & 0xF)) + tmp = 1; + } + } + sid->SIDct->ch[chn].pSR = SR; + sid->SIDct->ch[chn].rcnt += sid->g.ckr; + if (sid->SIDct->ch[chn].rcnt >= 0x8000) + sid->SIDct->ch[chn].rcnt -= 0x8000; + + double prd; + + if (sid->SIDct->ch[chn].Ast & ATK) { + step = voic->attack; + prd = sid->g.Aprd[(int) step]; + } else if (sid->SIDct->ch[chn].Ast & DECSUS) { + step = voic->decay; + prd = sid->g.Aprd[(int) step]; + } else { + step = SR & 0xF; + prd = sid->g.Aprd[(int) step]; + } + step = sid->g.Astp[(int) step]; + if (sid->SIDct->ch[chn].rcnt >= prd && sid->SIDct->ch[chn].rcnt < prd + sid->g.ckr && + tmp == 0) { + sid->SIDct->ch[chn].rcnt -= prd; + if ((sid->SIDct->ch[chn].Ast & ATK) || + ++sid->SIDct->ch[chn].expcnt == Aexp[(int) sid->SIDct->ch[chn].envcnt]) { + if (!(sid->SIDct->ch[chn].Ast & _HZ)) { + if (sid->SIDct->ch[chn].Ast & ATK) { + sid->SIDct->ch[chn].envcnt += step; + if (sid->SIDct->ch[chn].envcnt >= 0xFF) { + sid->SIDct->ch[chn].envcnt = 0xFF; + sid->SIDct->ch[chn].Ast &= 0xFF - ATK; + } + } else if (!(sid->SIDct->ch[chn].Ast & DECSUS) || + sid->SIDct->ch[chn].envcnt > (SR >> 4) + (SR & 0xF0)) { + sid->SIDct->ch[chn].envcnt -= step; + if (sid->SIDct->ch[chn].envcnt <= 0 && + sid->SIDct->ch[chn].envcnt + step != 0) { + sid->SIDct->ch[chn].envcnt = 0; + sid->SIDct->ch[chn].Ast |= _HZ; + } + } + } + sid->SIDct->ch[chn].expcnt = 0; + } else { + } + } + sid->SIDct->ch[chn].envcnt = (int) sid->SIDct->ch[chn].envcnt & 0xFF; + double aAdd = (voic->freq_low + voic->freq_high * 256) * sid->g.ckr; + if (test || ((ctrl & SYN) && sid->SIDct->sMSBrise)) { + sid->SIDct->ch[chn].pacc = 0; + } else { + sid->SIDct->ch[chn].pacc += aAdd; + if (sid->SIDct->ch[chn].pacc > 0xFFFFFF) + sid->SIDct->ch[chn].pacc -= 0x1000000; + } + double MSB = (int) sid->SIDct->ch[chn].pacc & 0x800000; + sid->SIDct->sMSBrise = (MSB > ((int) sid->SIDct->ch[chn].pracc & 0x800000)) ? 1 : 0; + + if (wf & NOI) { + tmp = sid->SIDct->ch[chn].nLFSR; + if ((((int) sid->SIDct->ch[chn].pacc & 0x100000) != + ((int) sid->SIDct->ch[chn].pracc & 0x100000)) || + aAdd >= 0x100000) { + step = ((int) tmp & 0x400000) ^ (((int) tmp & 0x20000) << 5); + tmp = (((int) tmp << 1) + (step > 0 || test)) & 0x7FFFFF; + sid->SIDct->ch[chn].nLFSR = tmp; + } + wfout = (wf & 0x70) ? 0 + : (((int) tmp & 0x100000) >> 5) + (((int) tmp & 0x40000) >> 4) + + (((int) tmp & 0x4000) >> 1) + (((int) tmp & 0x800) << 1) + + (((int) tmp & 0x200) << 2) + (((int) tmp & 0x20) << 5) + + (((int) tmp & 0x04) << 7) + (((int) tmp & 0x01) << 8); + } else if (wf & PUL) { + double pw = (voic->pw_low + (voic->pw_high) * 256) * 16; + tmp = (int) aAdd >> 9; + if (0 < pw && pw < tmp) + pw = tmp; + tmp = (int) tmp ^ 0xFFFF; + if (pw > tmp) + pw = tmp; + tmp = (int) sid->SIDct->ch[chn].pacc >> 8; + if (wf == PUL) { + int lel = ((int) aAdd >> 16); + if (lel > 0) { + step = 256.0 / (double) lel; + } else { + step = INFINITY; + } + if (test) + wfout = 0xFFFF; + else if (tmp < pw) { + double lim = (0xFFFF - pw) * step; + if (lim > 0xFFFF) + lim = 0xFFFF; + wfout = lim - (pw - tmp) * step; + if (wfout < 0) + wfout = 0; + } else { + double lim = pw * step; + if (lim > 0xFFFF) + lim = 0xFFFF; + wfout = (0xFFFF - tmp) * step - lim; + if (wfout >= 0) + wfout = 0xFFFF; + wfout = (int) wfout & 0xFFFF; + } + } else { + wfout = (tmp >= pw || test) ? 0xFFFF : 0; + if (wf & TRI) { + if (wf & SAW) { + wfout = + (wfout) ? cmbWF(chn, sid->g.Pulsetrsaw, (int) tmp >> 4, 1, &sid->g) : 0; + } else { + tmp = (int) sid->SIDct->ch[chn].pacc ^ (ctrl & RNG ? sid->SIDct->sMSB : 0); + wfout = + (wfout) + ? cmbWF(chn, sid->g.pusaw, + ((int) tmp ^ ((int) tmp & 0x800000 ? 0xFFFFFF : 0)) >> 11, + 0, &sid->g) + : 0; + } + } else if (wf & SAW) + wfout = (wfout) ? cmbWF(chn, sid->g.pusaw, (int) tmp >> 4, 1, &sid->g) : 0; + } + } else if (wf & SAW) { + wfout = (int) sid->SIDct->ch[chn].pacc >> 8; + if (wf & TRI) + wfout = cmbWF(chn, sid->g.trsaw, (int) wfout >> 4, 1, &sid->g); + else { + step = aAdd / 0x1200000; + wfout += wfout * step; + if (wfout > 0xFFFF) + wfout = 0xFFFF - (wfout - 0x10000) / step; + } + } else if (wf & TRI) { + tmp = (int) sid->SIDct->ch[chn].pacc ^ (ctrl & RNG ? sid->SIDct->sMSB : 0); + wfout = ((int) tmp ^ ((int) tmp & 0x800000 ? 0xFFFFFF : 0)) >> 7; + } + if (wf) + sid->SIDct->ch[chn].prevwfout = wfout; + else { + wfout = sid->SIDct->ch[chn].prevwfout; + } + sid->SIDct->ch[chn].pracc = sid->SIDct->ch[chn].pacc; + sid->SIDct->sMSB = MSB; + // double preflin = flin; + if ((sid->mute_mask & (1 << chn))) { + if (sid->M[0x17] & sid->SIDct->ch[chn].FSW) { + double chnout = (wfout - 0x8000) * (sid->SIDct->ch[chn].envcnt / 256); + flin += chnout; + + // fake filter for solo waveform ahead + // mostly copypasted from below + double fakeflin = chnout; + double fakeflout = 0; + double ctf = sid->g.ctf_table[((sid->M[0x15]&7)|(sid->M[0x16]<<3))&0x7ff]; + double reso; + if (sid->g.model == 8580) { + reso = pow(2, ((double) (4 - (double) (sid->M[0x17] >> 4)) / 8)); + } else { + reso = (sid->M[0x17] > 0x5F) ? 8.0 / (double) (sid->M[0x17] >> 4) : 1.41; + } + double tmp = fakeflin + sid->fakepbp[chn] * reso + sid->fakeplp[chn]; + if (sid->M[0x18] & HP) + fakeflout -= tmp; + tmp = sid->fakepbp[chn] - tmp * ctf; + sid->fakepbp[chn] = tmp; + if (sid->M[0x18] & BP) + fakeflout -= tmp; + tmp = sid->fakeplp[chn] + tmp * ctf; + sid->fakeplp[chn] = tmp; + if (sid->M[0x18] & LP) + fakeflout += tmp; + + double wf_out = (fakeflout / SID_OUT_SCALE) * (sid->M[0x18] & 0xF) * 65535; + waveforms_add_sample(chn, wf_out); + } else if ((chn % 3) != 2 || !(sid->M[0x18] & OFF3)) { + double chnout = (wfout - 0x8000) * (sid->SIDct->ch[chn].envcnt / 256); + output += chnout; + + double wf_out = (chnout / SID_OUT_SCALE) * (sid->M[0x18] & 0xF) * 65535; + waveforms_add_sample(chn, wf_out); + } + } else { + waveforms_add_sample(chn, 0); + } + } + int M1 = 0; + if (M1 & 3) + sid->M[0x1B] = (int) wfout >> 8; + sid->M[0x1C] = sid->SIDct->ch[2].envcnt; + + double ctf = sid->g.ctf_table[((sid->M[0x15]&7)|(sid->M[0x16]<<3))&0x7ff]; + double reso; + if (sid->g.model == 8580) { + reso = pow(2, ((double) (4 - (double) (sid->M[0x17] >> 4)) / 8)); + } else { + reso = (sid->M[0x17] > 0x5F) ? 8.0 / (double) (sid->M[0x17] >> 4) : 1.41; + } + + double tmp = flin + sid->SIDct->pbp * reso + sid->SIDct->plp; + if (sid->M[0x18] & HP) + output -= tmp; + tmp = sid->SIDct->pbp - tmp * ctf; + sid->SIDct->pbp = tmp; + if (sid->M[0x18] & BP) + output -= tmp; + tmp = sid->SIDct->plp + tmp * ctf; + sid->SIDct->plp = tmp; + if (sid->M[0x18] & LP) + output += tmp; + return (output / SID_OUT_SCALE) * (sid->M[0x18] & 0xF); +} + +void dSID_setMuteMask(struct SID_chip* sid, int mute_mask) { + sid->mute_mask = mute_mask; +} + +float dSID_getVolume(struct SID_chip* sid, int channel) { + if ((sid->M[0x18] & 0xF) == 0) + return 0; + return sid->SIDct[0].ch[channel].envcnt / 256.0f; +} + +void dSID_write(struct SID_chip* sid, unsigned char addr, unsigned char val) { + sid->M[addr&0x1f]=val; +} diff --git a/src/engine/platform/sound/c64_d/dsid.h b/src/engine/platform/sound/c64_d/dsid.h new file mode 100644 index 000000000..b17a51f55 --- /dev/null +++ b/src/engine/platform/sound/c64_d/dsid.h @@ -0,0 +1,99 @@ +#ifndef DSID_H +#define DSID_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct SID_ctx_chan { + double rcnt; + double envcnt; + double expcnt; + double pacc; + double pracc; + int FSW; + int nLFSR; + double prevwfout; + uint8_t pSR; + int Ast; +}; + +struct SID_ctx { + int sMSBrise; + int sMSB; + double plp; + double pbp; + struct SID_ctx_chan ch[3]; +}; + +struct SIDVOICE { + uint8_t freq_low; + uint8_t freq_high; + uint8_t pw_low; + uint8_t pw_high : 4; + uint8_t UNUSED : 4; + uint8_t control; + uint8_t decay : 4; + uint8_t attack : 4; + uint8_t susres; + // uint8_t release : 4; + // uint8_t sustain : 4; +}; + +struct SIDMEM { + struct SIDVOICE v[3]; + uint8_t UNUSED : 4; + uint8_t cutoff_low : 4; + uint8_t cutoff_high; + uint8_t reso_rt : 4; + uint8_t reso : 4; + uint8_t volume : 4; + uint8_t filter_mode : 4; + uint8_t paddlex; + uint8_t paddley; + uint8_t osc3; + uint8_t env3; +}; + +struct SID_globals { + double ckr; + double ctfr; + double ctf_ratio_6581; + + double ctf_table[2048]; + + int trsaw[4096]; + int pusaw[4096]; + int Pulsetrsaw[4096]; + + double Aprd[16]; + int Astp[16]; + int model; +}; + +#define MemLen 65536 + +struct SID_chip { + struct SID_globals g; + struct SID_ctx SIDct[3]; + uint8_t M[MemLen]; + int16_t lastOut[3]; + int mute_mask; + double fakeplp[3]; + double fakepbp[3]; +}; + +double dSID_render(struct SID_chip* sid); +void dSID_init(struct SID_chip* sid, double clockRate, double samplingRate, int model, unsigned char init_wf); +float dSID_getVolume(struct SID_chip* sid, int channel); +void dSID_setMuteMask(struct SID_chip* sid, int mute_mask); + +void dSID_write(struct SID_chip* sid, unsigned char addr, unsigned char val); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp b/src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp index af636ac7f..d92f68d60 100644 --- a/src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp +++ b/src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp @@ -61,7 +61,6 @@ const unsigned int EnvelopeGenerator::adsrtable[16] = void EnvelopeGenerator::reset() { - // counter is not changed on reset envelope_pipeline = 0; state_pipeline = 0; @@ -73,7 +72,7 @@ void EnvelopeGenerator::reset() gate = false; - resetLfsr = true; + resetLfsr = false; exponential_counter = 0; exponential_counter_period = 1; @@ -81,7 +80,11 @@ void EnvelopeGenerator::reset() state = RELEASE; counter_enabled = true; - rate = adsrtable[release]; + rate = 0; + + envelope_counter = 0; + env3 = 0; + lfsr = 0x7fff; } void EnvelopeGenerator::writeCONTROL_REG(unsigned char control) diff --git a/src/engine/platform/sound/c64_fp/EnvelopeGenerator.h b/src/engine/platform/sound/c64_fp/EnvelopeGenerator.h index f2aab3874..1014cf82c 100644 --- a/src/engine/platform/sound/c64_fp/EnvelopeGenerator.h +++ b/src/engine/platform/sound/c64_fp/EnvelopeGenerator.h @@ -146,7 +146,7 @@ public: counter_enabled(true), gate(false), resetLfsr(false), - envelope_counter(0xaa), + envelope_counter(0), attack(0), decay(0), sustain(0), diff --git a/src/engine/platform/sound/c64_fp/SID.cpp b/src/engine/platform/sound/c64_fp/SID.cpp index a996d2230..03c778c90 100644 --- a/src/engine/platform/sound/c64_fp/SID.cpp +++ b/src/engine/platform/sound/c64_fp/SID.cpp @@ -351,7 +351,7 @@ void SID::write(int offset, unsigned char value) break; case 0x04: // Voice #1 control register - voice[0]->writeCONTROL_REG(muted[0] ? 0 : value); + voice[0]->writeCONTROL_REG(value); break; case 0x05: // Voice #1 Attack and Decay length @@ -379,7 +379,7 @@ void SID::write(int offset, unsigned char value) break; case 0x0b: // Voice #2 control register - voice[1]->writeCONTROL_REG(muted[1] ? 0 : value); + voice[1]->writeCONTROL_REG(value); break; case 0x0c: // Voice #2 Attack and Decay length @@ -407,7 +407,7 @@ void SID::write(int offset, unsigned char value) break; case 0x12: // Voice #3 control register - voice[2]->writeCONTROL_REG(muted[2] ? 0 : value); + voice[2]->writeCONTROL_REG(value); break; case 0x13: // Voice #3 Attack and Decay length diff --git a/src/engine/platform/sound/c64_fp/SID.h b/src/engine/platform/sound/c64_fp/SID.h index 85b6a4e4d..77d7706d4 100644 --- a/src/engine/platform/sound/c64_fp/SID.h +++ b/src/engine/platform/sound/c64_fp/SID.h @@ -320,11 +320,11 @@ int SID::output() const int v2 = voice[1]->output(voice[0]->wave()); const int v3 = voice[2]->output(voice[1]->wave()); - lastChanOut[0]=v1; - lastChanOut[1]=v2; - lastChanOut[2]=v3; + lastChanOut[0]=muted[0]?0:v1; + lastChanOut[1]=muted[1]?0:v2; + lastChanOut[2]=muted[2]?0:v3; - return externalFilter->clock(filter->clock(v1, v2, v3)); + return externalFilter->clock(filter->clock(muted[0]?0:v1, muted[1]?0:v2, muted[2]?0:v3)); } diff --git a/src/engine/platform/sound/oki/okim6258.cpp b/src/engine/platform/sound/oki/okim6258.cpp index 728507dfb..5bf6714d6 100644 --- a/src/engine/platform/sound/oki/okim6258.cpp +++ b/src/engine/platform/sound/oki/okim6258.cpp @@ -137,7 +137,7 @@ void okim6258_device::device_reset() void okim6258_device::sound_stream_update(short** outputs, int len) { - auto &buffer = outputs[0]; + short* buffer = outputs[0]; if (m_status & STATUS_PLAYING) { 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/su.cpp b/src/engine/platform/su.cpp index 728d7a91a..b38b9f860 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -23,7 +23,7 @@ #include //#define rWrite(a,v) pendingWrites[a]=v; -#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } #define chWrite(c,a,v) rWrite(((c)<<5)|(a),v); #define CHIP_DIVIDER 2 diff --git a/src/engine/platform/su.h b/src/engine/platform/su.h index de67c2faf..d83ae4777 100644 --- a/src/engine/platform/su.h +++ b/src/engine/platform/su.h @@ -21,7 +21,7 @@ #define _SU_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" #include "sound/su.h" class DivPlatformSoundUnit: public DivDispatch { @@ -72,11 +72,12 @@ class DivPlatformSoundUnit: public DivDispatch { DivDispatchOscBuffer* oscBuf[8]; bool isMuted[8]; struct QueuedWrite { - unsigned char addr; - unsigned char val; - QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + unsigned char addr; + unsigned char val; + QueuedWrite(): addr(0), val(0) {} + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} }; - std::queue writes; + FixedQueue writes; unsigned char lastPan; bool sampleMemSize; unsigned char ilCtrl, ilSize, fil1; diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 209e2fc2c..04039e6e5 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -21,8 +21,8 @@ #include "../engine.h" #include -#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);}} -#define postWrite(a,v) postDACWrites.emplace(a,v); +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);}} +#define postWrite(a,v) postDACWrites.push(DivRegWrite(a,v)); #define CHIP_DIVIDER 32 @@ -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..72ddae394 100644 --- a/src/engine/platform/swan.h +++ b/src/engine/platform/swan.h @@ -23,7 +23,7 @@ #include "../dispatch.h" #include "../waveSynth.h" #include "sound/swan.h" -#include +#include "../fixedQueue.h" class DivPlatformSwan: public DivDispatch { struct Channel: public SharedChannel { @@ -46,12 +46,13 @@ class DivPlatformSwan: public DivDispatch { unsigned char regPool[0x80]; struct QueuedWrite { - unsigned char addr; - unsigned char val; - QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + unsigned char addr; + unsigned char val; + QueuedWrite(): addr(0), val(0) {} + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} }; - std::queue writes; - std::queue postDACWrites; + FixedQueue writes; + FixedQueue postDACWrites; WSwan* ws; void updateWave(int ch); friend void putDispatchChip(void*,int); @@ -68,6 +69,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/t6w28.cpp b/src/engine/platform/t6w28.cpp index 5d21e1ad4..90140da99 100644 --- a/src/engine/platform/t6w28.cpp +++ b/src/engine/platform/t6w28.cpp @@ -23,7 +23,7 @@ #include //#define rWrite(a,v) pendingWrites[a]=v; -#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } const char* regCheatSheetT6W28[]={ "Data0", "0", diff --git a/src/engine/platform/t6w28.h b/src/engine/platform/t6w28.h index d324a09c8..33c03a886 100644 --- a/src/engine/platform/t6w28.h +++ b/src/engine/platform/t6w28.h @@ -21,7 +21,7 @@ #define _T6W28_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" #include "sound/t6w28/T6W28_Apu.h" class DivPlatformT6W28: public DivDispatch { @@ -38,11 +38,12 @@ class DivPlatformT6W28: public DivDispatch { bool isMuted[4]; bool easyNoise; struct QueuedWrite { - unsigned char addr; - unsigned char val; - QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + unsigned char addr; + unsigned char val; + QueuedWrite(): addr(0), val(0) {} + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} }; - std::queue writes; + FixedQueue writes; unsigned char lastPan; int cycles, curChan, delay; diff --git a/src/engine/platform/tia.h b/src/engine/platform/tia.h index 95ae99173..4e7420b79 100644 --- a/src/engine/platform/tia.h +++ b/src/engine/platform/tia.h @@ -21,7 +21,6 @@ #define _TIA_H #include "../dispatch.h" -#include #include "sound/tia/Audio.h" class DivPlatformTIA: public DivDispatch { diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index 86128340a..6e70895b2 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -986,7 +986,7 @@ void DivPlatformTX81Z::poke(std::vector& wlist) { } void DivPlatformTX81Z::reset() { - while (!writes.empty()) writes.pop_front(); + writes.clear(); memset(regPool,0,330); fm_ymfm->reset(); if (dumpWrites) { diff --git a/src/engine/platform/tx81z.h b/src/engine/platform/tx81z.h index d1ebd543e..d0bc759c1 100644 --- a/src/engine/platform/tx81z.h +++ b/src/engine/platform/tx81z.h @@ -21,7 +21,7 @@ #define _TX81Z_H #include "fmshared_OPM.h" -#include +#include "../fixedQueue.h" #include "sound/ymfm/ymfm_opz.h" class DivTXInterface: public ymfm::ymfm_interface { diff --git a/src/engine/platform/vb.cpp b/src/engine/platform/vb.cpp index 3701b5aa4..9edaf2db4 100644 --- a/src/engine/platform/vb.cpp +++ b/src/engine/platform/vb.cpp @@ -22,7 +22,7 @@ #include //#define rWrite(a,v) pendingWrites[a]=v; -#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } #define chWrite(c,a,v) rWrite(0x400+((c)<<6)+((a)<<2),v); #define CHIP_DIVIDER 16 diff --git a/src/engine/platform/vb.h b/src/engine/platform/vb.h index 09193f25b..2efcdd1b8 100644 --- a/src/engine/platform/vb.h +++ b/src/engine/platform/vb.h @@ -21,7 +21,7 @@ #define _PLATFORM_VB_H #include "../dispatch.h" -#include +#include "../fixedQueue.h" #include "../waveSynth.h" #include "sound/vsu.h" @@ -44,11 +44,12 @@ class DivPlatformVB: public DivDispatch { DivDispatchOscBuffer* oscBuf[6]; bool isMuted[6]; struct QueuedWrite { - unsigned short addr; - unsigned char val; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {} + unsigned short addr; + unsigned char val; + QueuedWrite(): addr(0), val(0) {} + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {} }; - std::queue writes; + FixedQueue writes; unsigned char lastPan; int cycles, curChan, delay; diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 8357f7569..fa0446ca6 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -32,19 +32,22 @@ extern "C" { #define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d); psg_writereg(psg,((c)*4+(a)),(d));if (dumpWrites) {addWrite(((c)*4+(a)),(d));}} #define rWriteLo(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0x3f))|((d)&0x3f)) #define rWriteHi(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0xc0))|(((d)<<6)&0xc0)) -#define rWritePCMCtrl(d) {regPool[64]=(d); pcm_write_ctrl(pcm,d);} -#define rWritePCMRate(d) {regPool[65]=(d); pcm_write_rate(pcm,d);} +#define rWritePCMCtrl(d) {regPool[64]=(d); pcm_write_ctrl(pcm,d);if (dumpWrites) addWrite(64,(d));} +#define rWritePCMRate(d) {regPool[65]=(d); pcm_write_rate(pcm,d);if (dumpWrites) addWrite(65,(d));} #define rWritePCMData(d) {regPool[66]=(d); pcm_write_fifo(pcm,d);} #define rWritePCMVol(d) rWritePCMCtrl((regPool[64]&(~0x8f))|((d)&15)) +#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", + "AUDIO_CTRL", "40", + "AUDIO_RATE", "41", + "AUDIO_DATA", "42", + "ZSM_PCM_LOOP_POINT", "43", + "ZSM_SYNC", "44", NULL }; @@ -226,6 +229,57 @@ void DivPlatformVERA::tick(bool sysTick) { rWritePCMRate(chan[16].freq&0xff); 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; + if (!isMuted[16]) { + if (chan[16].pcm.depth16) { + tmp_l=s->data16[chan[16].pcm.pos]; + tmp_r=tmp_l; + } else { + tmp_l=s->data8[chan[16].pcm.pos]; + tmp_r=tmp_l; + } + if (!(chan[16].pan&1)) tmp_l=0; + if (!(chan[16].pan&2)) tmp_r=0; + } + if (chan[16].pcm.depth16) { + addWrite(66,tmp_l&0xff); + addWrite(66,(tmp_l>>8)&0xff); + addWrite(66,tmp_r&0xff); + addWrite(66,(tmp_r>>8)&0xff); + } else { + addWrite(66,tmp_l&0xff); + addWrite(66,tmp_r&0xff); + } + chan[16].pcm.pos++; + if (s->isLoopable() && chan[16].pcm.pos>=(unsigned int)s->loopEnd) { + chan[16].pcm.sample=-1; + break; + } + if (chan[16].pcm.pos>=s->samples) { + chan[16].pcm.sample=-1; + break; + } + } + } else { + chan[16].pcm.sample=-1; + } + } } int DivPlatformVERA::dispatch(DivCommand c) { @@ -370,6 +424,9 @@ int DivPlatformVERA::dispatch(DivCommand c) { case DIV_CMD_MACRO_ON: chan[c.chan].std.mask(c.value,false); break; + case DIV_CMD_EXTERNAL: + rWriteZSMSync(c.value); + break; case DIV_ALWAYS_SET_VOLUME: return 0; break; @@ -441,6 +498,15 @@ void DivPlatformVERA::poke(std::vector& wlist) { for (auto &i: wlist) poke(i.addr,i.val); } +void DivPlatformVERA::setFlags(const DivConfig& flags) { + chipClock=25000000; + CHECK_CUSTOM_CLOCK; + rate=chipClock/512; + for (int i=0; i<17; i++) { + oscBuf[i]->rate=rate; + } +} + int DivPlatformVERA::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { for (int i=0; i<17; i++) { isMuted[i]=false; @@ -451,12 +517,7 @@ int DivPlatformVERA::init(DivEngine* p, int channels, int sugRate, const DivConf pcm=new struct VERA_PCM; dumpWrites=false; skipRegisterWrites=false; - chipClock=25000000; - CHECK_CUSTOM_CLOCK; - rate=chipClock/512; - for (int i=0; i<17; i++) { - oscBuf[i]->rate=rate; - } + setFlags(flags); reset(); return 17; } diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h index 8781dc953..227512a73 100644 --- a/src/engine/platform/vera.h +++ b/src/engine/platform/vera.h @@ -51,7 +51,7 @@ class DivPlatformVERA: public DivDispatch { Channel chan[17]; DivDispatchOscBuffer* oscBuf[17]; bool isMuted[17]; - unsigned char regPool[67]; + unsigned char regPool[69]; struct VERA_PSG* psg; struct VERA_PCM* pcm; @@ -70,6 +70,7 @@ class DivPlatformVERA: public DivDispatch { void reset(); void tick(bool sysTick=true); void muteChannel(int ch, bool mute); + void setFlags(const DivConfig& flags); void notifyInsDeletion(void* ins); float getPostAmp(); int getOutputCount(); diff --git a/src/engine/platform/vic20.h b/src/engine/platform/vic20.h index 5125bd96a..e233d9844 100644 --- a/src/engine/platform/vic20.h +++ b/src/engine/platform/vic20.h @@ -22,7 +22,6 @@ #include "../dispatch.h" #include "sound/vic20sound.h" -#include class DivPlatformVIC20: public DivDispatch { struct Channel: public SharedChannel { diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index b52bc1069..96088d937 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -22,7 +22,7 @@ #include #include -#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } #define chWrite(c,a,v) rWrite(0x9000+(c<<12)+(a&3),v) const char* regCheatSheetVRC6[]={ diff --git a/src/engine/platform/vrc6.h b/src/engine/platform/vrc6.h index 5a2416101..df0aa92e5 100644 --- a/src/engine/platform/vrc6.h +++ b/src/engine/platform/vrc6.h @@ -20,7 +20,7 @@ #ifndef _VRC6_H #define _VRC6_H -#include +#include "../fixedQueue.h" #include "../dispatch.h" #include "vgsound_emu/src/vrcvi/vrcvi.hpp" @@ -47,11 +47,12 @@ class DivPlatformVRC6: public DivDispatch, public vrcvi_intf { DivDispatchOscBuffer* oscBuf[3]; bool isMuted[3]; struct QueuedWrite { - unsigned short addr; - unsigned char val; - QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {} + unsigned short addr; + unsigned char val; + QueuedWrite(): addr(0), val(0) {} + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {} }; - std::queue writes; + FixedQueue writes; unsigned char sampleBank; unsigned char writeOscBuf; vrcvi_core vrc6; diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 135ad1409..68cccca45 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -974,7 +974,7 @@ void DivPlatformYM2203::poke(std::vector& wlist) { } void DivPlatformYM2203::reset() { - while (!writes.empty()) writes.pop_front(); + writes.clear(); memset(regPool,0,256); if (dumpWrites) { addWrite(0xffffffff,0); diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 589d90d38..02a5ffa08 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -1481,7 +1481,7 @@ void DivPlatformYM2608::poke(std::vector& wlist) { } void DivPlatformYM2608::reset() { - while (!writes.empty()) writes.pop_front(); + writes.clear(); memset(regPool,0,512); if (dumpWrites) { addWrite(0xffffffff,0); diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 66c2f4110..4fbf78595 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -1441,7 +1441,7 @@ void DivPlatformYM2610::poke(std::vector& wlist) { } void DivPlatformYM2610::reset() { - while (!writes.empty()) writes.pop_front(); + writes.clear(); memset(regPool,0,512); if (dumpWrites) { addWrite(0xffffffff,0); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index d38ad525d..4fff47279 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -1508,7 +1508,7 @@ void DivPlatformYM2610B::poke(std::vector& wlist) { } void DivPlatformYM2610B::reset() { - while (!writes.empty()) writes.pop_front(); + writes.clear(); memset(regPool,0,512); if (dumpWrites) { addWrite(0xffffffff,0); diff --git a/src/engine/platform/ymz280b.h b/src/engine/platform/ymz280b.h index 3fb4a7dc6..14a67dc98 100644 --- a/src/engine/platform/ymz280b.h +++ b/src/engine/platform/ymz280b.h @@ -21,7 +21,6 @@ #define _YMZ280B_H #include "../dispatch.h" -#include #include "sound/ymz280b.h" class DivPlatformYMZ280B: public DivDispatch { diff --git a/src/engine/platform/zxbeeper.cpp b/src/engine/platform/zxbeeper.cpp index 047dc2cb5..3592c049b 100644 --- a/src/engine/platform/zxbeeper.cpp +++ b/src/engine/platform/zxbeeper.cpp @@ -260,7 +260,6 @@ int DivPlatformZXBeeper::getRegisterPoolSize() { } void DivPlatformZXBeeper::reset() { - while (!writes.empty()) writes.pop(); memset(regPool,0,128); for (int i=0; i<6; i++) { chan[i]=DivPlatformZXBeeper::Channel(); diff --git a/src/engine/platform/zxbeeper.h b/src/engine/platform/zxbeeper.h index 3e120354b..9bd3678a8 100644 --- a/src/engine/platform/zxbeeper.h +++ b/src/engine/platform/zxbeeper.h @@ -21,7 +21,6 @@ #define _ZXBEEPER_H #include "../dispatch.h" -#include class DivPlatformZXBeeper: public DivDispatch { struct Channel: public SharedChannel { @@ -35,12 +34,6 @@ class DivPlatformZXBeeper: public DivDispatch { Channel chan[6]; DivDispatchOscBuffer* oscBuf[6]; bool isMuted[6]; - struct QueuedWrite { - unsigned char addr; - unsigned char val; - QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} - }; - std::queue writes; unsigned char lastPan, ulaOut; int cycles, curChan, sOffTimer, delay, curSample, curSamplePeriod; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index ce94b883b..aabce5c6a 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -236,6 +236,8 @@ const char* cmdName[]={ "NES_LINEAR_LENGTH", + "EXTERNAL", + "ALWAYS_SET_VOLUME" }; @@ -913,6 +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,i,effectVal)); break; case 0xef: // global pitch globalPitch+=(signed char)(effectVal-0x80); @@ -1127,8 +1130,10 @@ void DivEngine::nextRow() { } } - prevOrder=curOrder; - prevRow=curRow; + if (!stepPlay) { + prevOrder=curOrder; + prevRow=curRow; + } for (int i=0; iwriteC(0xff); break; case DIV_SYSTEM_GA20: - for (int i=0; i<3; i++) { + for (int i=0; i<4; i++) { w->writeC(0xbf); // mute w->writeC((baseAddr2|5)+(i*8)); w->writeC(0); @@ -573,6 +573,16 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(0); } break; + case DIV_SYSTEM_K053260: + for (int i=0; i<4; i++) { + w->writeC(0xba); // mute + w->writeC(baseAddr2|0x2f); + w->writeC(0); + w->writeC(0xba); // keyoff + w->writeC(baseAddr2|0x28); + w->writeC(0); + } + break; default: break; } @@ -610,15 +620,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 +662,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 +705,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; @@ -954,6 +1039,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(baseAddr2|(write.addr&0x7f)); w->writeC(write.val); break; + case DIV_SYSTEM_K053260: + w->writeC(0xba); + w->writeC(baseAddr2|(write.addr&0x3f)); + w->writeC(write.val&0xff); + break; default: logW("write not handled!"); break; @@ -1067,6 +1157,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 +1178,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 +1198,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; } @@ -1123,6 +1216,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p DivDispatch* writeRF5C68[2]={NULL,NULL}; DivDispatch* writeMSM6295[2]={NULL,NULL}; DivDispatch* writeGA20[2]={NULL,NULL}; + DivDispatch* writeK053260[2]={NULL,NULL}; DivDispatch* writeNES[2]={NULL,NULL}; int writeNESIndex[2]={0,0}; @@ -1363,7 +1457,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; @@ -1648,6 +1741,21 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p howManyChips++; } break; + case DIV_SYSTEM_K053260: + if (!hasK053260) { + hasK053260=disCont[i].dispatch->chipClock; + CHIP_VOL(29,0.4); + willExport[i]=true; + writeK053260[0]=disCont[i].dispatch; + } else if (!(hasK053260&0x40000000)) { + isSecond[i]=true; + CHIP_VOL_SECOND(29,0.4); + willExport[i]=true; + writeK053260[1]=disCont[i].dispatch; + hasK053260|=0x40000000; + howManyChips++; + } + break; case DIV_SYSTEM_T6W28: if (!hasSN) { hasSN=0xc0000000|disCont[i].dispatch->chipClock; @@ -1842,6 +1950,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 +1959,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 +2095,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) { @@ -2008,6 +2117,15 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p w->writeI(0); w->write(writeGA20[i]->getSampleMem(),writeGA20[i]->getSampleMemUsage()); } + if (writeK053260[i]!=NULL && writeK053260[i]->getSampleMemUsage()>0) { + w->writeC(0x67); + w->writeC(0x66); + w->writeC(0x8e); + w->writeI((writeK053260[i]->getSampleMemUsage()+8)|(i*0x80000000)); + w->writeI(writeK053260[i]->getSampleMemCapacity()); + w->writeI(0); + w->write(writeK053260[i]->getSampleMem(),writeK053260[i]->getSampleMemUsage()); + } if (writeNES[i]!=NULL && writeNES[i]->getSampleMemUsage()>0) { size_t howMuchWillBeWritten=writeNES[i]->getSampleMemUsage(); w->writeC(0x67); @@ -2241,7 +2359,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 +2399,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/waveSynth.cpp b/src/engine/waveSynth.cpp index 25f68eac2..cdb696881 100644 --- a/src/engine/waveSynth.cpp +++ b/src/engine/waveSynth.cpp @@ -27,6 +27,7 @@ bool DivWaveSynth::activeChanged() { activeChangedB=false; return true; } + if (first) return true; return false; } diff --git a/src/engine/zsm.cpp b/src/engine/zsm.cpp index b65bbcbbb..04bf68b91 100644 --- a/src/engine/zsm.cpp +++ b/src/engine/zsm.cpp @@ -53,9 +53,19 @@ void DivZSM::init(unsigned int rate) { tickRate=rate; loopOffset=-1; numWrites=0; + ticks=0; + // Initialize YM/PSG states memset(&ymState,-1,sizeof(ymState)); memset(&psgState,-1,sizeof(psgState)); - ticks=0; + // Initialize PCM states + pcmRateCache=-1; + pcmCtrlRVCache=-1; + pcmCtrlDCCache=-1; + pcmIsLooped=false; + pcmLoopPointCache=0; + // Channel masks + ymMask=0; + psgMask=0; } int DivZSM::getoffset() { @@ -108,9 +118,17 @@ void DivZSM::writeYM(unsigned char a, unsigned char v) { void DivZSM::writePSG(unsigned char a, unsigned char v) { // TODO: suppress writes to PSG voice that is not audible (volume=0) - if (a>=64) { - logD ("ZSM: ignoring VERA PSG write a=%02x v=%02x",a,v); + // ^ Let's leave these alone, ZSMKit has a feature that can benefit + // from silent channels. + if (a>=69) { + logD("ZSM: ignoring VERA PSG write a=%02x v=%02x",a,v); return; + } else if (a==68) { + // Sync event + numWrites++; + return syncCache.push_back(v); + } else if (a>=64) { + return writePCM(a-64,v); } if (psgState[psg_PREV][a]==v) { if (psgState[psg_NEW][a]!=v) { @@ -131,7 +149,29 @@ void DivZSM::writePSG(unsigned char a, unsigned char v) { } void DivZSM::writePCM(unsigned char a, unsigned char v) { - // ZSM standard for PCM playback has not been established yet. + if (a==0) { // PCM Ctrl + // cache the depth and channels but don't write it to the + // register queue + pcmCtrlDCCache=v&0x30; + // save only the reset bit and volume (if it isn't a dupe) + if (pcmCtrlRVCache!=(v&0x8f)) { + pcmMeta.push_back(DivRegWrite(a,(v&0x8f))); + pcmCtrlRVCache=v&0x8f; + numWrites++; + } + } else if (a==1) { // PCM Rate + if (pcmRateCache!=v) { + pcmMeta.push_back(DivRegWrite(a,v)); + pcmRateCache=v; + numWrites++; + } + } 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; + } } void DivZSM::tick(int numticks) { @@ -151,6 +191,9 @@ void DivZSM::setLoopPoint() { w->seek(loopOffset,SEEK_SET); // reset the PSG shadow and write cache memset(&psgState,-1,sizeof(psgState)); + // reset the PCM caches that would inhibit dupes + pcmRateCache=-1; + pcmCtrlRVCache=-1; // reset the YM shadow.... memset(&ymState[ym_PREV],-1,sizeof(ymState[ym_PREV])); // ... and cache (except for unused channels) @@ -170,16 +213,63 @@ SafeWriter* DivZSM::finish() { tick(0); // flush any pending writes / ticks flushTicks(); // flush ticks in case there were no writes pending w->writeC(ZSM_EOF); + if (pcmInsts.size()>256) { + logE("ZSM: more than the maximum number of PCM instruments exist. Skipping PCM export entirely."); + pcmData.clear(); + pcmInsts.clear(); + } else if (pcmData.size()) { // if exists, write PCM instruments and blob to the end of file + unsigned int pcmOff=w->tell(); + w->writeC('P'); + w->writeC('C'); + w->writeC('M'); + w->writeC((unsigned char)pcmInsts.size()-1); + int i=0; + for (S_pcmInst& inst: pcmInsts) { + // write out the instruments + // PCM playback location follows: + // + // + // of PCM data offset + // of length + w->writeC((unsigned char)i&0xff); + w->writeC((unsigned char)inst.geometry&0x30); + w->writeC((unsigned char)inst.offset&0xff); + w->writeC((unsigned char)(inst.offset>>8)&0xff); + w->writeC((unsigned char)(inst.offset>>16)&0xff); + w->writeC((unsigned char)inst.length&0xff); + w->writeC((unsigned char)(inst.length>>8)&0xff); + w->writeC((unsigned char)(inst.length>>16)&0xff); + // Feature mask: Lxxxxxxx + // L = Loop enabled + 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); + i++; + } + for (unsigned char& c: pcmData) { + w->writeC(c); + } + pcmData.clear(); + // update PCM offset in file + w->seek(0x06,SEEK_SET); + w->writeC((unsigned char)pcmOff&0xff); + w->writeC((unsigned char)(pcmOff>>8)&0xff); + w->writeC((unsigned char)(pcmOff>>16)&0xff); + } // update channel use masks. w->seek(0x09,SEEK_SET); w->writeC((unsigned char)(ymMask&0xff)); w->writeS((short)(psgMask&0xffff)); - // todo: put PCM offset/data writes here once defined in ZSM standard. return w; } void DivZSM::flushWrites() { - logD("ZSM: flushWrites.... numwrites=%d ticks=%d ymwrites=%d",numWrites,ticks,ymwrites.size()); + logD("ZSM: flushWrites.... numwrites=%d ticks=%d ymwrites=%d pcmMeta=%d pcmCache=%d pcmData=%d syncCache=%d",numWrites,ticks,ymwrites.size(),pcmMeta.size(),pcmCache.size(),pcmData.size(),syncCache.size()); if (numWrites==0) return; flushTicks(); // only flush ticks if there are writes pending. for (unsigned char i=0; i<64; i++) { @@ -204,6 +294,113 @@ void DivZSM::flushWrites() { w->writeC(write.val); } ymwrites.clear(); + unsigned int pcmInst=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 + // which cuts PCM data size in half for center-panned PCM events + if (pcmCtrlDCCache&0x10) { // stereo bit is on + unsigned int e; + if (pcmCtrlDCCache&0x20) { // 16-bit + // for 16-bit PCM data, the size must be a multiple of 4 + if (pcmCache.size()%4==0) { + // check for identical L+R channels + for (e=0; e>1; e+=2) { + pcmCache[e]=pcmCache[e<<1]; + pcmCache[e+1]=pcmCache[(e<<1)+1]; + } + pcmCache.resize(pcmCache.size()>>1); + pcmCtrlDCCache&=(unsigned char)~0x10; // clear stereo bit + pcmLoopPointCache>>=1; // halve the loop point + } + } + } else { // 8-bit + // for 8-bit PCM data, the size must be a multiple of 2 + if (pcmCache.size()%2==0) { + // check for identical L+R channels + for (e=0; e>1; e++) { + pcmCache[e]=pcmCache[e<<1]; + } + pcmCache.resize(pcmCache.size()>>1); + pcmCtrlDCCache&=(unsigned char)~0x10; // clear stereo bit + pcmLoopPointCache>>=1; // halve the loop point + } + } + } + } + // check to see if the most recent received blob matches any of the previous data + // and reuse it if there is a match, otherwise append the cache to the rest of + // the PCM data + std::vector::iterator it; + it=std::search(pcmData.begin(),pcmData.end(),pcmCache.begin(),pcmCache.end()); + pcmOff=std::distance(pcmData.begin(),it); + pcmLen=pcmCache.size(); + logD("ZSM: pcmOff: %d pcmLen: %d",pcmOff,pcmLen); + if (it==pcmData.end()) { + pcmData.insert(pcmData.end(),pcmCache.begin(),pcmCache.end()); + } + pcmCache.clear(); + extCmd0Len+=2; + // search for a matching PCM instrument definition + for (S_pcmInst& inst: pcmInsts) { + if (inst.offset==pcmOff && inst.length==pcmLen && inst.geometry==pcmCtrlDCCache && inst.isLooped==pcmIsLooped && inst.loopPoint==pcmLoopPointCache) + break; + pcmInst++; + } + if (pcmInst==pcmInsts.size()) { + S_pcmInst inst; + 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); + extCmd0Len=0; + pcmMeta.clear(); + } + if (extCmd0Len) { // we have some PCM events to write + w->writeC(ZSM_EXT); + w->writeC(ZSM_EXT_PCM|(unsigned char)extCmd0Len); + for (DivRegWrite& write: pcmMeta) { + w->writeC(write.addr); + w->writeC(write.val); + } + pcmMeta.clear(); + if (pcmLen) { + w->writeC(0x02); // 0x02 = Instrument trigger + w->writeC((unsigned char)pcmInst&0xff); + } + } + n=0; + while (n<(long)syncCache.size()) { // we have one or more sync events to write + int writes=syncCache.size()-n; + w->writeC(ZSM_EXT); + if (writes>ZSM_SYNC_MAX_WRITES) writes=ZSM_SYNC_MAX_WRITES; + w->writeC(ZSM_EXT_SYNC|(writes<<1)); + for (; writes>0; writes--) { + w->writeC(0x00); // 0x00 = Arbitrary sync message + w->writeC(syncCache[n++]); + } + } + syncCache.clear(); numWrites=0; } diff --git a/src/engine/zsm.h b/src/engine/zsm.h index b452171f8..ff497693f 100644 --- a/src/engine/zsm.h +++ b/src/engine/zsm.h @@ -30,24 +30,46 @@ #define ZSM_YM_CMD 0x40 #define ZSM_DELAY_CMD 0x80 #define ZSM_YM_MAX_WRITES 63 +#define ZSM_SYNC_MAX_WRITES 31 #define ZSM_DELAY_MAX 127 #define ZSM_EOF ZSM_DELAY_CMD +#define ZSM_EXT ZSM_YM_CMD +#define ZSM_EXT_PCM 0x00 +#define ZSM_EXT_CHIP 0x40 +#define ZSM_EXT_SYNC 0x80 +#define ZSM_EXT_CUSTOM 0xC0 + enum YM_STATE { ym_PREV, ym_NEW, ym_STATES }; enum PSG_STATE { psg_PREV, psg_NEW, psg_STATES }; class DivZSM { private: + struct S_pcmInst { + int geometry; + unsigned int offset, length, loopPoint; + bool isLooped; + }; SafeWriter* w; int ymState[ym_STATES][256]; int psgState[psg_STATES][64]; + int pcmRateCache; + int pcmCtrlRVCache; + int pcmCtrlDCCache; + unsigned int pcmLoopPointCache; + bool pcmIsLooped; std::vector ymwrites; + std::vector pcmMeta; + std::vector pcmData; + std::vector pcmCache; + std::vector pcmInsts; + std::vector syncCache; int loopOffset; int numWrites; int ticks; int tickRate; - int ymMask = 0; - int psgMask = 0; + int ymMask; + int psgMask; public: DivZSM(); ~DivZSM(); diff --git a/src/engine/zsmOps.cpp b/src/engine/zsmOps.cpp index deddd4eca..85ddb5d47 100644 --- a/src/engine/zsmOps.cpp +++ b/src/engine/zsmOps.cpp @@ -150,7 +150,10 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) { logD("zsmOps: Writing %d messages to chip %d",writes.size(),i); for (DivRegWrite& write: writes) { if (i==YM) zsm.writeYM(write.addr&0xff,write.val); - if (i==VERA) zsm.writePSG(write.addr&0xff,write.val); + if (i==VERA) { + if (done && write.addr>=64) continue; // don't process any PCM or sync events on the loop lookahead + zsm.writePSG(write.addr&0xff,write.val); + } } writes.clear(); } @@ -160,7 +163,7 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) { fracWait+=cycles&MASTER_CLOCK_MASK; totalWait+=fracWait>>MASTER_CLOCK_PREC; fracWait&=MASTER_CLOCK_MASK; - if (totalWait>0) { + if (totalWait>0 && !done) { zsm.tick(totalWait); //tickCount+=totalWait; } diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 4076dbc6a..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", @@ -67,6 +70,7 @@ const char* aboutLine[]={ "AmigaX", "AURORA*FIELDS", "battybeats", + "Bernie", "BlueElectric05", "breakthetargets", "brickblock369", @@ -78,6 +82,7 @@ const char* aboutLine[]={ "Dippy", "djtuBIG-MaliceX", "dumbut", + "Eknous-P", "ElectricKeet", "EpicTyphlosion", "FΛDE", @@ -133,6 +138,7 @@ const char* aboutLine[]={ "fd", "GENATARi", "host12prog", + "jvsTSX", "Lumigado", "Lunathir", "plane", @@ -172,6 +178,7 @@ const char* aboutLine[]={ "reSID by Dag Lem", "reSIDfp by Dag Lem, Antti Lankila", "and Leandro Nini", + "dSID by DefleMask Team based on jsSID", "Stella by Stella Team", "QSound emulator by superctr and Valley Bell", "VICE VIC-20 sound core by Rami Rasanen and viznut", 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/dataList.cpp b/src/gui/dataList.cpp index b9c1e11a7..fa5b46471 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -279,6 +279,10 @@ void FurnaceGUI::insListItem(int i, int dir, int asset) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PV1000]); name=fmt::sprintf(ICON_FA_GAMEPAD "##_INS%d",i); break; + case DIV_INS_K053260: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_K053260]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; default: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); name=fmt::sprintf(ICON_FA_QUESTION "##_INS%d",i); diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index 49d568b3c..e4e0907da 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -53,6 +53,7 @@ #include "../engine/platform/ga20.h" #include "../engine/platform/sm8521.h" #include "../engine/platform/pv1000.h" +#include "../engine/platform/k053260.h" #include "../engine/platform/dummy.h" #define COMMON_CHIP_DEBUG \ @@ -356,7 +357,6 @@ void putDispatchChip(void* data, int type) { ImGui::Text("- filtCut: %d",ch->filtCut); ImGui::Text("- resetTime: %d",ch->resetTime); COMMON_CHIP_DEBUG_BOOL; - ImGui::TextColored(ch->isFP?colorOn:colorOff,">> IsFP"); break; } case DIV_SYSTEM_ARCADE: @@ -545,6 +545,13 @@ void putDispatchChip(void* data, int type) { COMMON_CHIP_DEBUG_BOOL; break; } + case DIV_SYSTEM_K053260: { + DivPlatformK053260* ch=(DivPlatformK053260*)data; + ImGui::Text("> K053260"); + COMMON_CHIP_DEBUG; + COMMON_CHIP_DEBUG_BOOL; + break; + } default: ImGui::Text("Unimplemented chip! Help!"); break; @@ -1083,6 +1090,19 @@ void putDispatchChan(void* data, int chanNum, int type) { COMMON_CHAN_DEBUG_BOOL; break; } + case DIV_SYSTEM_K053260: { + DivPlatformK053260::Channel* ch=(DivPlatformK053260::Channel*)data; + ImGui::Text("> K053260"); + COMMON_CHAN_DEBUG; + ImGui::Text("* Sample: %d",ch->sample); + ImGui::Text(" - pos: %d",ch->audPos); + ImGui::Text("- panning: %d",ch->panning); + ImGui::Text("- macroVolMul: %.2x",ch->macroVolMul); + COMMON_CHAN_DEBUG_BOOL; + ImGui::TextColored(ch->setPos?colorOn:colorOff,">> SetPos"); + ImGui::TextColored(ch->reverse?colorOn:colorOff,">> Reverse"); + break; + } default: ImGui::Text("Unimplemented chip! Help!"); break; diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index e440b5891..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); @@ -536,6 +564,9 @@ void FurnaceGUI::drawDebug() { if (ImGui::Button("Spoiler")) { spoilerOpen=!spoilerOpen; } + if (ImGui::Button("Kill Graphics")) { + killGraphics=true; + } ImGui::TreePop(); } if (ImGui::TreeNode("Performance")) { diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 87d74e6cf..60d713d32 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -111,6 +111,7 @@ void FurnaceGUI::doAction(int what) { break; case GUI_ACTION_STEP_ONE: e->stepOne(cursor.y); + pendingStepUpdate=1; break; case GUI_ACTION_OCTAVE_UP: if (++curOctave>7) { @@ -1377,7 +1378,8 @@ void FurnaceGUI::doAction(int what) { i==DIV_INS_SNES || i==DIV_INS_ES5506 || i==DIV_INS_K007232 || - i==DIV_INS_GA20) { + i==DIV_INS_GA20 || + i==DIV_INS_K053260) { makeInsTypeList.push_back(i); } } diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index e3e843481..099ff7e25 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -374,7 +374,7 @@ void FurnaceGUI::drawMobileControls() { if (portrait) ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne",buttonSize)) { e->stepOne(cursor.y); - pendingStepUpdate=true; + pendingStepUpdate=1; } bool repeatPattern=e->getRepeatPattern(); @@ -730,7 +730,7 @@ void FurnaceGUI::drawEditControls() { ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { e->stepOne(cursor.y); - pendingStepUpdate=true; + pendingStepUpdate=1; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Step one row"); @@ -770,7 +770,7 @@ void FurnaceGUI::drawEditControls() { ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { e->stepOne(cursor.y); - pendingStepUpdate=true; + pendingStepUpdate=1; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Step one row"); @@ -875,7 +875,7 @@ void FurnaceGUI::drawEditControls() { } if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne",buttonSize)) { e->stepOne(cursor.y); - pendingStepUpdate=true; + pendingStepUpdate=1; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Step one row"); @@ -1009,7 +1009,7 @@ void FurnaceGUI::drawEditControls() { ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { e->stepOne(cursor.y); - pendingStepUpdate=true; + pendingStepUpdate=1; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Step one row"); diff --git a/src/gui/findReplace.cpp b/src/gui/findReplace.cpp index 0755e791e..b6fcd14ce 100644 --- a/src/gui/findReplace.cpp +++ b/src/gui/findReplace.cpp @@ -39,7 +39,7 @@ const char* queryReplaceModes[GUI_QUERY_REPLACE_MAX]={ "set", "add", "add (overflow)", - "scale", + "scale %", "clear" }; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index eb8b9943c..cd92c003f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -497,6 +497,13 @@ bool FurnaceGUI::InvCheckbox(const char* label, bool* value) { return false; } +void FurnaceGUI::sameLineMaybe(float width) { + if (width<0.0f) width=ImGui::GetFrameHeight(); + + ImGui::SameLine(); + if (ImGui::GetContentRegionAvail().x=0 && orderCursorgetTotalChannelCount()) { + prepareUndo(GUI_UNDO_CHANGE_ORDER); e->lockSave([this,num]() { e->curOrders->ord[orderCursor][curOrder]=((e->curOrders->ord[orderCursor][curOrder]<<4)|num); }); @@ -1429,6 +1437,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { } } e->walkSong(loopOrder,loopRow,loopEnd); + makeUndo(GUI_UNDO_CHANGE_ORDER); } } catch (std::out_of_range& e) { } @@ -2412,7 +2421,7 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { if (y>waveDragMax) y=waveDragMax; if (ynotifyWaveChange(curWave); + notifyWaveChange=true; MARK_MODIFIED; } } @@ -3455,6 +3464,11 @@ bool FurnaceGUI::loop() { break; } break; +#if SDL_VERSION_ATLEAST(2,0,4) + case SDL_RENDER_DEVICE_RESET: + killGraphics=true; + break; +#endif #if SDL_VERSION_ATLEAST(2,0,17) case SDL_DISPLAYEVENT: { switch (ev.display.event) { @@ -3734,6 +3748,11 @@ bool FurnaceGUI::loop() { midiLock.unlock(); } + if (notifyWaveChange) { + notifyWaveChange=false; + e->notifyWaveChange(curWave); + } + eventTimeEnd=SDL_GetPerformanceCounter(); if (SDL_GetWindowFlags(sdlWin)&SDL_WINDOW_MINIMIZED) { @@ -3767,6 +3786,80 @@ bool FurnaceGUI::loop() { }); } + // recover from dead graphics + if (rend->isDead() || killGraphics) { + killGraphics=false; + + logW("graphics are dead! restarting..."); + + if (sampleTex!=NULL) { + rend->destroyTexture(sampleTex); + sampleTex=NULL; + } + + if (chanOscGradTex!=NULL) { + rend->destroyTexture(chanOscGradTex); + chanOscGradTex=NULL; + } + + for (auto& i: images) { + if (i.second->tex!=NULL) { + rend->destroyTexture(i.second->tex); + i.second->tex=NULL; + } + } + + commitState(); + rend->quitGUI(); + rend->quit(); + ImGui_ImplSDL2_Shutdown(); + + int initAttempts=0; + + SDL_Delay(500); + + logD("starting render backend..."); + while (++initAttempts<=5) { + if (rend->init(sdlWin)) { + break; + } + SDL_Delay(1000); + logV("trying again..."); + } + + if (initAttempts>5) { + reportError("can't keep going without graphics! Furnace will quit now."); + quit=true; + break; + } + + rend->clear(ImVec4(0.0,0.0,0.0,1.0)); + rend->present(); + + logD("preparing user interface..."); + rend->initGUI(sdlWin); + + logD("building font..."); + if (!ImGui::GetIO().Fonts->Build()) { + logE("error while building font atlas!"); + showError("error while loading fonts! please check your settings."); + 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!"); + } + } + + firstFrame=true; + mustClear=2; + initialScreenWipe=1.0f; + + continue; + } + bool fontsFailed=false; layoutTimeBegin=SDL_GetPerformanceCounter(); @@ -4757,6 +4850,7 @@ bool FurnaceGUI::loop() { case GUI_FILE_SAMPLE_OPEN_RAW: case GUI_FILE_SAMPLE_OPEN_REPLACE_RAW: pendingRawSample=copyOfName; + pendingRawSampleReplace=(curFileDialog==GUI_FILE_SAMPLE_OPEN_REPLACE_RAW); displayPendingRawSample=true; break; case GUI_FILE_SAMPLE_SAVE: @@ -5166,7 +5260,7 @@ bool FurnaceGUI::loop() { quit=true; } ImGui::SameLine(); - if (ImGui::Button("Cancel")) { + if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) { ImGui::CloseCurrentPopup(); } break; @@ -5190,7 +5284,7 @@ bool FurnaceGUI::loop() { displayNew=true; } ImGui::SameLine(); - if (ImGui::Button("Cancel")) { + if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) { ImGui::CloseCurrentPopup(); } break; @@ -5214,7 +5308,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; @@ -5238,7 +5332,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; @@ -5269,7 +5363,7 @@ bool FurnaceGUI::loop() { nextFile=""; } ImGui::SameLine(); - if (ImGui::Button("Cancel")) { + if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) { ImGui::CloseCurrentPopup(); nextFile=""; } @@ -5323,7 +5417,7 @@ bool FurnaceGUI::loop() { syncSettings(); } ImGui::SameLine(); - if (ImGui::Button("Cancel")) { + if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) { ImGui::CloseCurrentPopup(); } break; @@ -5410,7 +5504,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; @@ -5561,7 +5655,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; } @@ -5624,16 +5718,32 @@ bool FurnaceGUI::loop() { if (s==NULL) { showError(e->getLastError()); } else { - if (e->addSamplePtr(s)==-1) { - showError(e->getLastError()); + if (pendingRawSampleReplace) { + if (curSample>=0 && curSample<(int)e->song.sample.size()) { + e->lockEngine([this,s]() { + // if it crashes here please tell me... + DivSample* oldSample=e->song.sample[curSample]; + e->song.sample[curSample]=s; + delete oldSample; + e->renderSamples(); + MARK_MODIFIED; + }); + } else { + showError("...but you haven't selected a sample!"); + delete s; + } } else { - MARK_MODIFIED; + if (e->addSamplePtr(s)==-1) { + showError(e->getLastError()); + } else { + MARK_MODIFIED; + } } } ImGui::CloseCurrentPopup(); } ImGui::SameLine(); - if (ImGui::Button("Cancel")) { + if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); @@ -5674,6 +5784,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 @@ -5752,13 +5873,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); @@ -5882,6 +6002,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!"); @@ -6176,18 +6297,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; @@ -6244,6 +6365,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.empty()) { - settings.renderBackend=""; - //e->setConf("renderBackend",""); - //e->saveConf(); - //lastError=fmt::sprintf("\r\nthe render backend has been set to a safe value. please restart Furnace."); + if (settings.renderBackend!="SDL") { + settings.renderBackend="SDL"; + 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; @@ -6310,6 +6444,8 @@ bool FurnaceGUI::init() { rend->present(); logD("preparing user interface..."); + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); rend->initGUI(sdlWin); applyUISettings(); @@ -6321,6 +6457,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!"); @@ -6547,8 +6684,8 @@ bool FurnaceGUI::finish() { commitState(); rend->quitGUI(); ImGui_ImplSDL2_Shutdown(); - ImGui::DestroyContext(); quitRender(); + ImGui::DestroyContext(); SDL_DestroyWindow(sdlWin); if (vibrator) { @@ -6566,6 +6703,10 @@ bool FurnaceGUI::finish() { return true; } +void FurnaceGUI::requestQuit() { + quit=true; +} + FurnaceGUI::FurnaceGUI(): e(NULL), renderBackend(GUI_BACKEND_SDL), @@ -6601,6 +6742,7 @@ FurnaceGUI::FurnaceGUI(): preserveChanPos(false), wantScrollList(false), noteInputPoly(true), + notifyWaveChange(false), displayPendingIns(false), pendingInsSingle(false), displayPendingRawSample(false), @@ -6608,6 +6750,7 @@ FurnaceGUI::FurnaceGUI(): modTableHex(false), displayEditString(false), mobileEdit(false), + killGraphics(false), vgmExportVersion(0x171), vgmExportTrailingTicks(-1), drawHalt(10), @@ -6632,6 +6775,7 @@ FurnaceGUI::FurnaceGUI(): pendingRawSampleUnsigned(false), pendingRawSampleBigEndian(false), pendingRawSampleSwapNibbles(false), + pendingRawSampleReplace(false), globalWinFlags(0), curFileDialog(GUI_FILE_OPEN), warnAction(GUI_WARN_OPEN), @@ -6897,7 +7041,7 @@ FurnaceGUI::FurnaceGUI(): fadeMode(false), randomMode(false), haveHitBounds(false), - pendingStepUpdate(false), + pendingStepUpdate(0), oldOrdersLen(0), sampleZoom(1.0), prevSampleZoom(1.0), diff --git a/src/gui/gui.h b/src/gui/gui.h index 8eae6a531..8b3460fa1 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -75,16 +75,20 @@ enum FurnaceGUIRenderBackend { GUI_BACKEND_DX11 }; -#ifdef HAVE_RENDER_SDL -#define GUI_BACKEND_DEFAULT GUI_BACKEND_SDL -#define GUI_BACKEND_DEFAULT_NAME "SDL" -#else #ifdef HAVE_RENDER_DX11 #define GUI_BACKEND_DEFAULT GUI_BACKEND_DX11 #define GUI_BACKEND_DEFAULT_NAME "DirectX 11" #else +#ifdef HAVE_RENDER_GL #define GUI_BACKEND_DEFAULT GUI_BACKEND_GL #define GUI_BACKEND_DEFAULT_NAME "OpenGL" +#else +#ifdef HAVE_RENDER_SDL +#define GUI_BACKEND_DEFAULT GUI_BACKEND_SDL +#define GUI_BACKEND_DEFAULT_NAME "SDL" +#else +#error how did you manage to do that? +#endif #endif #endif @@ -235,6 +239,7 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_POKEMINI, GUI_COLOR_INSTR_SM8521, GUI_COLOR_INSTR_PV1000, + GUI_COLOR_INSTR_K053260, GUI_COLOR_INSTR_UNKNOWN, GUI_COLOR_CHANNEL_BG, @@ -1210,9 +1215,12 @@ struct FurnaceGUIQueryResult { } }; +class FurnaceGUITexture { +}; + struct FurnaceGUIImage { unsigned char* data; - void* tex; + FurnaceGUITexture* tex; int width, height, ch; FurnaceGUIImage(): @@ -1243,13 +1251,13 @@ enum FurnaceGUIBlendMode { class FurnaceGUIRender { public: - virtual ImTextureID getTextureID(void* which); - virtual bool lockTexture(void* which, void** data, int* pitch); - virtual bool unlockTexture(void* which); - virtual bool updateTexture(void* which, void* data, int pitch); - virtual void* createTexture(bool dynamic, int width, int height); - virtual bool destroyTexture(void* which); - virtual void setTextureBlendMode(void* which, FurnaceGUIBlendMode mode); + virtual ImTextureID getTextureID(FurnaceGUITexture* which); + virtual bool lockTexture(FurnaceGUITexture* which, void** data, int* pitch); + virtual bool unlockTexture(FurnaceGUITexture* which); + virtual bool updateTexture(FurnaceGUITexture* which, void* data, int pitch); + virtual FurnaceGUITexture* createTexture(bool dynamic, int width, int height); + virtual bool destroyTexture(FurnaceGUITexture* which); + virtual void setTextureBlendMode(FurnaceGUITexture* which, FurnaceGUIBlendMode mode); virtual void setBlendMode(FurnaceGUIBlendMode mode); virtual void resized(const SDL_Event& ev); virtual void clear(ImVec4 color); @@ -1266,6 +1274,7 @@ class FurnaceGUIRender { virtual void initGUI(SDL_Window* win); virtual void quitGUI(); virtual bool quit(); + virtual bool isDead(); virtual ~FurnaceGUIRender(); }; @@ -1279,7 +1288,7 @@ class FurnaceGUI { SDL_Haptic* vibrator; bool vibratorAvailable; - void* sampleTex; + FurnaceGUITexture* sampleTex; int sampleTexW, sampleTexH; bool updateSampleTex; @@ -1296,14 +1305,16 @@ 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; bool portrait, injectBackUp, mobileMenuOpen, warnColorPushed; bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; - bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly; + bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly, notifyWaveChange; bool displayPendingIns, pendingInsSingle, displayPendingRawSample, snesFilterHex, modTableHex, displayEditString; bool mobileEdit; + bool killGraphics; bool willExport[DIV_MAX_CHIPS]; int vgmExportVersion; int vgmExportTrailingTicks; @@ -1324,7 +1335,7 @@ class FurnaceGUI { String pendingRawSample; int pendingRawSampleDepth, pendingRawSampleChannels; - bool pendingRawSampleUnsigned, pendingRawSampleBigEndian, pendingRawSampleSwapNibbles; + bool pendingRawSampleUnsigned, pendingRawSampleBigEndian, pendingRawSampleSwapNibbles, pendingRawSampleReplace; ImGuiWindowFlags globalWinFlags; @@ -1509,6 +1520,7 @@ class FurnaceGUI { int insertBehavior; int pullDeleteRow; int newSongBehavior; + int memUsageUnit; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -1524,6 +1536,7 @@ class FurnaceGUI { String macroRelLabel; String emptyLabel; String emptyLabel2; + String sdlAudioDriver; DivConfig initialSys; Settings(): @@ -1538,7 +1551,7 @@ class FurnaceGUI { snCore(0), nesCore(0), fdsCore(0), - c64Core(1), + c64Core(0), pokeyCore(1), opnCore(1), pcSpeakerOutMethod(0), @@ -1661,6 +1674,7 @@ class FurnaceGUI { insertBehavior(1), pullDeleteRow(1), newSongBehavior(0), + memUsageUnit(1), maxUndoSteps(100), mainFontPath(""), patFontPath(""), @@ -1675,7 +1689,8 @@ class FurnaceGUI { noteRelLabel("==="), macroRelLabel("REL"), emptyLabel("..."), - emptyLabel2("..") {} + emptyLabel2(".."), + sdlAudioDriver("") {} } settings; struct Tutorial { @@ -1899,7 +1914,8 @@ class FurnaceGUI { int dummyRows, demandX; int transposeAmount, randomizeMin, randomizeMax, fadeMin, fadeMax, collapseAmount; float scaleMax; - bool fadeMode, randomMode, haveHitBounds, pendingStepUpdate; + bool fadeMode, randomMode, haveHitBounds; + signed char pendingStepUpdate; int oldOrdersLen; DivOrders oldOrders; @@ -1957,7 +1973,7 @@ class FurnaceGUI { String chanOscTextFormat; ImVec4 chanOscColor, chanOscTextColor; Gradient2D chanOscGrad; - void* chanOscGradTex; + FurnaceGUITexture* chanOscGradTex; float chanOscLP0[DIV_MAX_CHANS]; float chanOscLP1[DIV_MAX_CHANS]; float chanOscVol[DIV_MAX_CHANS]; @@ -2091,6 +2107,8 @@ class FurnaceGUI { void pushWarningColor(bool warnCond, bool errorCond=false); void popWarningColor(); + void sameLineMaybe(float width=-1.0f); + float calcBPM(const DivGroovePattern& speeds, float hz, int vN, int vD); void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache, bool inhibitSel); @@ -2116,7 +2134,7 @@ class FurnaceGUI { void highlightWindow(const char* winName); FurnaceGUIImage* getImage(FurnaceGUIImages image); - void* getTexture(FurnaceGUIImages image, FurnaceGUIBlendMode blendMode=GUI_BLEND_MODE_BLEND); + FurnaceGUITexture* getTexture(FurnaceGUIImages image, FurnaceGUIBlendMode blendMode=GUI_BLEND_MODE_BLEND); void drawImage(ImDrawList* dl, FurnaceGUIImages image, const ImVec2& pos, const ImVec2& scale, double rotate, const ImVec2& uvMin, const ImVec2& uvMax, const ImVec4& imgColor); void drawMobileControls(); @@ -2290,6 +2308,7 @@ class FurnaceGUI { bool loop(); bool finish(); bool init(); + void requestQuit(); FurnaceGUI(); }; diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 25364d2d9..f7dcd40b8 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -131,6 +131,7 @@ const char* insTypes[DIV_INS_MAX+1]={ "Pokémon Mini/QuadTone", "SM8521", "PV-1000", + "K053260", NULL }; @@ -851,6 +852,7 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_INSTR_POKEMINI,"",ImVec4(1.0f,1.0f,0.3f,1.0f)), D(GUI_COLOR_INSTR_SM8521,"",ImVec4(0.5f,0.55f,0.6f,1.0f)), D(GUI_COLOR_INSTR_PV1000,"",ImVec4(0.4f,0.6f,0.7f,1.0f)), + D(GUI_COLOR_INSTR_K053260,"",ImVec4(1.0f,0.8f,0.1f,1.0f)), D(GUI_COLOR_INSTR_UNKNOWN,"",ImVec4(0.3f,0.3f,0.3f,1.0f)), D(GUI_COLOR_CHANNEL_BG,"",ImVec4(0.4f,0.6f,0.8f,1.0f)), @@ -1034,6 +1036,7 @@ const int availableSystems[]={ DIV_SYSTEM_GA20, DIV_SYSTEM_SM8521, DIV_SYSTEM_PV1000, + DIV_SYSTEM_K053260, DIV_SYSTEM_PCM_DAC, DIV_SYSTEM_PONG, 0 // don't remove this last one! @@ -1142,6 +1145,7 @@ const int chipsSample[]={ DIV_SYSTEM_GA20, DIV_SYSTEM_PCM_DAC, DIV_SYSTEM_ES5506, + DIV_SYSTEM_K053260, 0 // don't remove this last one! }; diff --git a/src/gui/image.cpp b/src/gui/image.cpp index c45284455..493b5a57f 100644 --- a/src/gui/image.cpp +++ b/src/gui/image.cpp @@ -46,7 +46,7 @@ const unsigned int imageLen[GUI_IMAGE_MAX]={ image_pat_size }; -void* FurnaceGUI::getTexture(FurnaceGUIImages image, FurnaceGUIBlendMode blendMode) { +FurnaceGUITexture* FurnaceGUI::getTexture(FurnaceGUIImages image, FurnaceGUIBlendMode blendMode) { FurnaceGUIImage* img=getImage(image); if (img==NULL) return NULL; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index ca254d2c8..6715e56b6 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -59,79 +59,79 @@ const char* opllVariants[4]={ const char* opllInsNames[4][17]={ /* YM2413 */ { "User", - "Violin", - "Guitar", - "Piano", - "Flute", - "Clarinet", - "Oboe", - "Trumpet", - "Organ", - "Horn", - "Synth", - "Harpsichord", - "Vibraphone", - "Synth Bass", - "Acoustic Bass", - "Electric Guitar", + "1. Violin", + "2. Guitar", + "3. Piano", + "4. Flute", + "5. Clarinet", + "6. Oboe", + "7. Trumpet", + "8. Organ", + "9. Horn", + "10. Synth", + "11. Harpsichord", + "12. Vibraphone", + "13. Synth Bass", + "14. Acoustic Bass", + "15. Electric Guitar", "Drums" }, /* YMF281 */ { "User", - "Electric String", - "Bow wow", - "Electric Guitar", - "Organ", - "Clarinet", - "Saxophone", - "Trumpet", - "Street Organ", - "Synth Brass", - "Electric Piano", - "Bass", - "Vibraphone", - "Chime", - "Tom Tom II", - "Noise", + "1. Electric String", + "2. Bow wow", + "3. Electric Guitar", + "4. Organ", + "5. Clarinet", + "6. Saxophone", + "7. Trumpet", + "8. Street Organ", + "9. Synth Brass", + "10. Electric Piano", + "11. Bass", + "12. Vibraphone", + "13. Chime", + "14. Tom Tom II", + "15. Noise", "Drums" }, /* YM2423 */ { "User", - "Strings", - "Guitar", - "Electric Guitar", - "Electric Piano", - "Flute", - "Marimba", - "Trumpet", - "Harmonica", - "Tuba", - "Synth Brass", - "Short Saw", - "Vibraphone", - "Electric Guitar 2", - "Synth Bass", - "Sitar", + "1. Strings", + "2. Guitar", + "3. Electric Guitar", + "4. Electric Piano", + "5. Flute", + "6. Marimba", + "7. Trumpet", + "8. Harmonica", + "9. Tuba", + "10. Synth Brass", + "11. Short Saw", + "12. Vibraphone", + "13. Electric Guitar 2", + "14. Synth Bass", + "15. Sitar", "Drums" }, // stolen from FamiTracker /* VRC7 */ { "User", - "Bell", - "Guitar", - "Piano", - "Flute", - "Clarinet", - "Rattling Bell", - "Trumpet", - "Reed Organ", - "Soft Bell", - "Xylophone", - "Vibraphone", - "Brass", - "Bass Guitar", - "Synth", - "Chorus", + "1. Bell", + "2. Guitar", + "3. Piano", + "4. Flute", + "5. Clarinet", + "6. Rattling Bell", + "7. Trumpet", + "8. Reed Organ", + "9. Soft Bell", + "10. Xylophone", + "11. Vibraphone", + "12. Brass", + "13. Bass Guitar", + "14. Synth", + "15. Chorus", "Drums" } }; @@ -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; @@ -4393,7 +4409,8 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_SNES || ins->type==DIV_INS_ES5506 || ins->type==DIV_INS_K007232 || - ins->type==DIV_INS_GA20) { + ins->type==DIV_INS_GA20 || + ins->type==DIV_INS_K053260) { if (ImGui::BeginTabItem((ins->type==DIV_INS_SU)?"Sound Unit":"Sample")) { String sName; bool wannaOpenSMPopup=false; @@ -5300,7 +5317,8 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_SEGAPCM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU || ins->type==DIV_INS_OPZ || - ins->type==DIV_INS_OPM || ins->type==DIV_INS_SNES || ins->type==DIV_INS_MSM5232) { + ins->type==DIV_INS_OPM || ins->type==DIV_INS_SNES || ins->type==DIV_INS_MSM5232 || + ins->type==DIV_INS_K053260) { volMax=127; } if (ins->type==DIV_INS_GB) { @@ -5389,7 +5407,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC || ins->type==DIV_INS_PET || ins->type==DIV_INS_SEGAPCM || ins->type==DIV_INS_FM || ins->type==DIV_INS_K007232 || ins->type==DIV_INS_GA20 || - ins->type==DIV_INS_SM8521 || ins->type==DIV_INS_PV1000) { + ins->type==DIV_INS_SM8521 || ins->type==DIV_INS_PV1000 || ins->type==DIV_INS_K053260) { dutyMax=0; } if (ins->type==DIV_INS_VBOY) { @@ -5489,6 +5507,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_SEGAPCM) waveMax=0; if (ins->type==DIV_INS_K007232) waveMax=0; if (ins->type==DIV_INS_GA20) waveMax=0; + if (ins->type==DIV_INS_K053260) waveMax=0; if (ins->type==DIV_INS_POKEMINI) waveMax=0; if (ins->type==DIV_INS_SU || ins->type==DIV_INS_POKEY) waveMax=7; if (ins->type==DIV_INS_PET) { @@ -5607,6 +5626,11 @@ void FurnaceGUI::drawInsEdit() { panMax=7; panSingleNoBit=true; } + if (ins->type==DIV_INS_K053260) { + panMin=-3; + panMax=3; + panSingleNoBit=true; + } if (ins->type==DIV_INS_SU) { panMin=-127; panMax=127; @@ -5697,7 +5721,8 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_VBOY || (ins->type==DIV_INS_X1_010 && ins->amiga.useSample) || ins->type==DIV_INS_K007232 || - ins->type==DIV_INS_GA20) { + ins->type==DIV_INS_GA20 || + ins->type==DIV_INS_K053260) { macroList.push_back(FurnaceGUIMacroDesc("Phase Reset",&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); } if (ex1Max>0) { diff --git a/src/gui/intro.cpp b/src/gui/intro.cpp index 8ebbe020b..bace14396 100644 --- a/src/gui/intro.cpp +++ b/src/gui/intro.cpp @@ -25,7 +25,7 @@ void FurnaceGUI::drawImage(ImDrawList* dl, FurnaceGUIImages image, const ImVec2& pos, const ImVec2& scale, double rotate, const ImVec2& uvMin, const ImVec2& uvMax, const ImVec4& imgColor) { FurnaceGUIImage* imgI=getImage(image); - void* img=getTexture(image); + FurnaceGUITexture* img=getTexture(image); float squareSize=MAX(introMax.x-introMin.x,introMax.y-introMin.y); float uDiff=uvMax.x-uvMin.x; diff --git a/src/gui/newSong.cpp b/src/gui/newSong.cpp index 735f8bfb0..170abfa30 100644 --- a/src/gui/newSong.cpp +++ b/src/gui/newSong.cpp @@ -113,7 +113,33 @@ void FurnaceGUI::drawNewSong() { nextDescName=i.name; accepted=true; } + if (ImGui::IsItemHovered()) { + if (ImGui::BeginTooltip()) { + std::map chipCounts; + std::vector chips; + for (FurnaceGUISysDefChip chip: i.orig) { + if (chipCounts.find(chip.sys)==chipCounts.end()) { + chipCounts[chip.sys]=1; + chips.push_back(chip.sys); + } else { + chipCounts[chip.sys]+=1; + } + } + for (size_t chipIndex=0; chipIndexgetSystemDef(chip); + ImGui::Text("%s (x%d): ",sysDef->name,chipCounts[chip]); + ImGui::TextWrapped("%s",sysDef->description); + if (chipIndex+1